Standards (proposed)

Code Files

Should put all code, other than $(document).ready() blocks, within self-executing closures with important namespaces passed in:

// Example Widget
function(app, ns, data, utils){
    // Create Widget short name for local reference    exWdgt = {};

    // Use closures for private code    var testFunc();

    // Add all widget code to widget namespace.
    ns.WidgetModel = can.Model({...});
    ns.WidgetControl = can.Control({...});

    // Add an initialization function if the widget should be initialized externally
    ns.initWidget = function(/*params*/){...};

// Pass in external values that will be used by this code
}(
raft.app,              // The app namespace
raft.widgets.ExWidget, // The specific namespace to which the resulting objects and functions from this file will be added.
raft.data.ExWidget,    // The specific data namespace for this widget/page/object. Cached and local data  should go here
raft.utils             // The namespace containing app framework utilities
));

$(document).ready(function()){
    // Code to run when document is finished loading
}
Standard Page files and Naming

pages/PageName/PageNameDef.js: Creates namespace for page and data and optionally dynamically loads files required.

pages/PageName/PageName.css: Styles specific to this page

pages/PageName/PageNameModel.js: Models used by this page

pages/PageName/PageNameCtrl.js: Controllers used by this page

pages/PageName/PageNameView.ejs: Main page view template

pages/PageName/PageNameViews.ejs: Optional second/third/... view templates

Standard Widget files and Naming

widgets/WidgetName/WidgetNameDef.js: Creates namespace for widget and data and optionally dynamically loads files required.

widgets/WidgetName/WidgetName.css: Styles specific to this widget

widgets/WidgetName/WidgetNameModel.js: Models used by this widget

widgets/WidgetName/WidgetNameCtrl.js: Controllers used by this widget

widgets/WidgetName/WidgetNameView.ejs: Main widget view template

widgets/WidgetName/WidgetNameViews.ejs: Optional second/third/... view templates

Sample Widget/Page Definition File
// Example Widget Definition
function(app, ns, data, utils){
    // Create Widget short name for local reference
    ns.ExampleWidget = {
        SomeWidgetFunctionObjectOrEnum : {...}
    };

    // Create Widget data namespace
    data.ExampleWidget = {
        WidgetDataVal1 : "foo",
        WidgetDataVal2 : {...}
    };

    // It's common to always defer loading of css and data since they aren't debugged
    utils.requireCSS("widgets/ExampleWidget/ExampleWidget.CSS");
    utils.requireScript("widgets/ExampleWidget/widget_test_data.js", false);

    // Only defer load these if it's turned on
    if (app.config.isDeferLoad) {
        utils.requireScript("widgets/ExampleWidget/ExampleWidgetModel.js", true);
        utils.requireScript("widgets/ExampleWidget/ExampleWidgetCtrl.js", true);
    }
// Pass in external values that will be used by this code
}(
raft.app,     // The app namespace
raft.widgets, // The widget or page namespace
raft.data,    // The data namespace
raft.utils    // Application framework utilities namespace
));

$(document).ready(function()){
    // Code to run when document is finished loading
}

Documentation

PhpStorm has a shortcut for creating object and function documentation. Just place the cursor on the line just prior to the function/object you want documented and type /** then hit enter. It will create a documentation block matching in jsDoc/javaDoc syntax. Then just add your documentation to the block. It even uses introspection to do it’s best to determine the types of the params and return value.

1
2 function foo(param1, param2) {
3     return param1 + param2
4 }

While in PhpStorm place the cursor on line 1 and type /** and hit enter. It will generate the following block:

1 /**
2 *
3 * @param param1
4 * @param param2
5 * @return *
6 */
7 function foo(param1, param2) {
8     return param1 + param2
9 }

Utilities

Helper Functions
$.isDefined(obj) // Returns true if object is not undefined and not null

String.isString(obj) // Returns true if object is a string literal or String object

String.isEmpty(obj) // Returns true if object is a zero-length string

String.isNonEmpty(obj) // Returns true if object is a non-zero-length string

Number.isNumber(obj) // Returns true if object is a primitive number or a Number object
Dynamic Loader Functions

requireScript(): Loads the specified script, synchronously by default. If the file has already been loaded it will not be loaded again and a comment will be logged to that effect. The return value is a promise.

raft.utils.requireScript(path).always(...).then(...);

requireCSS(): Adds a tag to the document.head element which loads the specified CSS file.

raft.utils.requiresCSS(path);

Misc:

- Always include both params when calling can.Model()

- Discovered that calling can.route.link() returns an escaped string. This means that using it from within a view to create an <a href> does not work and instead adds "<a href>" to the page. The only way to do this that I have discovered so far is to use the can.route.link() call from within an append() call in the controller after the view finishes rendering. There may be other ways to do this but I haven't looked for them.

Can.Route Bugs

Incorrect Route Called

The problem is that can routing is kinda broken and the Canjs guys know it as far as what I heard from Eugene. There are two main causes. The biggest one is that the routing code does not match routes based on the route string (ie no regex matching is performed). Instead matching is based on the number of matching parameters (using param names for matching and including default params) in the requested route and the route list. The matcher returns the route with the most matching params that does not have any params that DON'T match. This is fine except for in the case of two routes that have all the same params but one having params the other does not. The second cause (which would not matter if not for the first cause) is that the Route object is a simple can.Observe Object and sends out events to bound functions whenever an attribute is changed, NOT when setRoute is called. The route value is an attribute of the Route Object, as are all of the route parameters from the current route. This means that when the route is changed any attributes that existed for the previous route are still there. This is really bad when we are matching routes based on the number of route parameters that match.

A good example of the above is visible with our Workset and Cost Object Routes. The Workset Route has the parameters worksetId, contextId and navId. The Cost Object route has these plus costobjectId. All is fine when going from any page to Cost Object page, even from Workset page. The problem comes when we try to go from the Cost Object page to the Workset page. The route object still contains a costobjectId parameter (we had to set it to get to the Cost Object page). So when setting the route to the Workset route, due to the feature allowing us to only change params that have changed, we end up with a route object which contains all of the params required by the Workset route  as well as the Cost Object route. Since the Cost Object route has one more matching parameter than the Workset Route the matcher returns that route, even though we specifically set the Workset route in the call to can.route().

A small bit of good news:

Removing params that are not relevant to a route from the router object seems to solve this problem. It is not clear if there are side effects to this but so far it has been working. For example if we call

can.route.removeAttr(“costobjectId”);

before calling

*can.route(

Unknown macro: {routedata…}

);*

Eugene added a function to the router changeRoute(newRoute) to automatically remove the properties from a routing call that are not needed. You pass the same information into the function that you would into can.route() but you call it instead and you must specify all params that you wish the function to retain (meaning we cannot take advantage of can.route()'s ability to handle just params that have changed, in fact this is exactly the feature that is causing this bug). This is a workaround to the real issue above. Mike, can you try calling that function from your routing calls instead of can.route().

  • No labels