Vertebrae framework

built with Backbone.js and RequireJS using AMD

Dependencies Managed With Require() and Define()

Modular Code Dependencies

RequireJS and AMD (Asynchronous Module Definition) provide a pattern for authoring modular code and also managing dependencies without complex namespacing or even adding any properties to the (head) window object. When we write modules in JavaScript, we want to handle the reuse of code intelligently and have the option to build an entire application into a single script or load modules as they are required to execute the business requirements of the application.

Writing AMD modules with RequireJS

Note - This section “Writing AMD modules with RequireJS” is an excerpt from Backbone Fundamentals

The overall goal for the AMD format is to provide a solution for modular JavaScript that developers can use today. The two key concepts you need to be aware of when using it with a script-loader are a define() method for facilitating module definition and a require() method for handling dependency loading. define() is used to define named or unnamed modules based on the proposal using the following signature:

1
2
3
4
5
define(
    module_id /*optional*/,
    [dependencies] /*optional*/,
    definition function /*function for instantiating the module or object*/
);

As you can tell by the inline comments, the module_id is an optional argument which is typically only required when non-AMD concatenation tools are being used (there may be some other edge cases where it’s useful too). When this argument is left out, we call the module ‘anonymous’. When working with anonymous modules, the idea of a module’s identity is DRY, making it trivial to avoid duplication of filenames and code.

Back to the define signature, the dependencies argument represents an array of dependencies which are required by the module you are defining and the third argument (‘definition function’) is a function that’s executed to instantiate your module. A barebone module (compatible with RequireJS) could be defined using define() as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// A module ID has been omitted here to make the module anonymous

define(['foo', 'bar'],
    // module definition function
    // dependencies (foo and bar) are mapped to function parameters
    function ( foo, bar ) {
        // return a value that defines the module export
        // (i.e the functionality we want to expose for consumption)

        // create your module here
        var myModule = {
            doStuff:function(){
                console.log('Yay! Stuff');
            }
        }

        return myModule;
});

The main.js file bootstraps the Web application

(main.js) download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
// main.js
// ------- 
// See require.js, underscore.js, backbone.js
// Requires `require`, `define`

if (!window.HL) { var HL = {}; }
if (!HL.buildVersion) {
    HL.buildVersion = '';
}

// Param (string) str is a path beginning with '/' (src directory)
if (!HL.prependBuild) {
    HL.prependBuild = function (str) {
        var path = str;
        if (HL.buildVersion !== '') {
            path = '/' + HL.buildVersion + str;
        }
        return path;
    };
}
require.config({
    baseUrl: './',
    locale: 'en-us',
    paths: {

        // ** Libraries **
        'json2'        : HL.prependBuild('/vendor/json2.min'),
        'modernizr'    : HL.prependBuild('/vendor/modernizr.min'),
        'requirejquery': HL.prependBuild('/vendor/require-jquery'),
        'jquery'       : HL.prependBuild('/vendor/jquery-1.7.2.min'),
        'zepto'        : HL.prependBuild('/vendor/zepto'),
        'underscore'   : HL.prependBuild('/vendor/underscore'),
        'mustache'     : HL.prependBuild('/vendor/mustache'),
        'backbone'     : HL.prependBuild('/vendor/backbone'),

        // r.js plugins
        'use'          : HL.prependBuild('/vendor/plugins/use'),
        'domready'     : HL.prependBuild('/vendor/plugins/domReady'),
        'order'        : HL.prependBuild('/vendor/plugins/order'),
        'text'         : HL.prependBuild('/vendor/plugins/text'),

        // jQuery plugins should be loaded in the facade.js file
        'jquerycookie' : HL.prependBuild('/vendor/plugins/jquery.cookie'),

        // Zepto plugins should be loaded in the facade.js file
        // these need the following order! type, callbacks, deferred
        'type'         : HL.prependBuild('/vendor/plugins/type'),
        'callbacks'    : HL.prependBuild('/vendor/plugins/callbacks'),
        'deferred'     : HL.prependBuild('/vendor/plugins/deferred'),

        // Touch events
        'touch'        : HL.prependBuild('/vendor/plugins/touch'),

        // Facade references to vendor / library methods
        'facade'       : HL.prependBuild('/facade'),

        // Vendor libs and plugin, packaged group of common dependencies
        'vendor'       : HL.prependBuild('/vendor'),

        // Utilities, Mixins and HauteLook libraries
        'utils'        : HL.prependBuild('/utils'),

        // (Backbone) Syncs depend on both vendor and utils
        'syncs'        : HL.prependBuild('/syncs'),

        // Should be used as required dependencies with use of `define`, 
        'models'       : HL.prependBuild('/models'),
        'views'        : HL.prependBuild('/views'),
        'collections'  : HL.prependBuild('/collections'),
        'controller'   : HL.prependBuild('/controller'),

        // ** Packages **

        'chrome'       : HL.prependBuild('/packages/chrome'),
        'events'       : HL.prependBuild('/packages/events'),
        'catalog'      : HL.prependBuild('/packages/catalog'),
        'product'      : HL.prependBuild('/packages/product'),
        'hello'        : HL.prependBuild('/packages/hello'),

        // ** Application ** bootstrap for frontend app 
        'application'  : HL.prependBuild('/application')

    },
    use: {
        "underscore": {
            attach: "_"
        },
        "backbone": {
            deps: ["use!underscore", "jquery"],
            attach: function(_, $) {
                return Backbone;
            }
        }
    },
    priority: ['text', 'use', 'order', 'modernizr', 'json2', 'vendor', 'utils'],
    jquery: '1.7.2',
    waitSeconds: 33
});


require(['facade', 'application', 'utils'],
function (facade,   App,           utils) {

    var $ = facade.$,
        Backbone = facade.Backbone,
        Channel = utils.baselib.Channel,
        debug = utils.debug;
        cssArr = [
            HL.prependBuild("/css/base.css"),
            HL.prependBuild("/css/global.css")
        ];

    // load style sheets
    Channel('load:css').publish(cssArr);

    $(function () { // doc ready
        var app;
        // run the application, it all starts here.
        debug.log("initialize application...");
        app = new App();
        Backbone.history.start({pushState: true});
        debug.log("...app router active", Backbone.history);
    });

});

Example of Backbone.js Model definition using AMD

Below is an example of defining a Backbone Model object which returns a constructor rather than an instance. The recommended convention for naming variables is to use Upper CamelCase for constructors and lower camelCase for others.

(event.js) download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
// Event Model
// -----------

// Requires define
// Return {Event} constructor

define(['models/base'], function (BaseModel) {

    var Event;

    Event = BaseModel.extend({
        defaults : {
            // v4 (JSON HAL) Events format...
            "_links": {
                "self": {
                    "href": null
                },
                "http://hautelook.com/rels/catalog": {
                    "href": null
                },
                "http://hautelook.com/rels/availability": {
                    "href": null
                },
                "http://hautelook.com/rels/images/event-hero": {
                    "href": null
                },
                "http://hautelook.com/rels/images/seals/event-hero": {
                    "href": null
                },
                "http://hautelook.com/rels/images/seals/catalog-banner": {
                    "href": null
                },
            },
            "id": null, //"13454",
            "title": null, //"Pour La Victoire",
            "start_date": null, //"2012-02-01 08:00:00",
            "end_date": null, //"2012-02-03 08:00:00",
            "tagline": null, //"Fashionable platform pumps and ballet flats",
            "status": null, //"active",
            "visibility": null, //"public",
            "sale_type": null, //"default",
            "verticals": [ { "vertical": null /* "Women" */ } ],
            "images": {
                "hero": null, // PARSED from _links data
            },
            "seals": {
                "hero": "",
            },
            "nesting": {
                "parents": [],
                "children": [ /*{ "id": "12638" },...*/ ],
                "siblings": []
            }
        },

        urlRoot: "/v4/events",

        parse: function (data) {
            try {
                if (!data.images && data._links) {
                    data.images = {
                        hero: data._links["http://hautelook.com/rels/images/event-hero"].href
                    };
                    data.images.hero = data.images.hero.replace("event-small", "event-large");
                }
            } catch (e) {
                debug.log(e);
            }
            return data;
        },
    });

    return Event;
});

Example of Backbone.js Collection definition using AMD

The events collection defined below returns an instance of the Backbone Collection object. In this collection constructor “EventsCollection” the event model is assigned to the model property of the object literal used to extend the base collection object (pseudo class, as in JavaScript everything is an object). Notice that one of the required scripts is “models” which contains the “Event” constructor in an object which is referenced as the variable “models.Event”.

Events Collection (events-example.js) download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// Events Collection  
// -----------------

// @requires `define`  
// @return {BaseCollection} EventsCollection instance

define(['jquery','underscore','models','collections/base','utils/debug'],
function ($,      _,           models,  BaseCollection,    debug) {

    var EventsCollection,
        eventsModel = new models.EventsData(),
        Event = models.Event;

    EventsCollection = BaseCollection.extend({
        model: Event,

        initialize: function (models, options) {
        // ...
        }
        // ...
    });

    return new EventsCollection();

});

In the code above there is a single var statement which lists the local variables in the current scope. This is a best practice and encouraged so the developers have a common place to find the local variables for this module. Also it provide some syntax to how the code dependencies are used at the top of the module. For example the eventsModel is a new instance of the required EventsData constructor and Event is the constructor included in the required models object.

The previous code examples that the objects used to extend when defining the constructors are : BaseModel and BaseCollection not Backbone.Model and Backbone.Collection. We are using base classes so that they may be extended similar to the use of an abstract class.

Grouping objects simplifies dependency management and also facilitates an organized build

The strategy we are using for authoring modular and reusable code includes the use of site-wide or global objects as well as code organized in packages intended for specific components of the application. To provide global objects as dependencies which will be required by various packages we group (or wrap) common types of objects into a single object which can be optimized in the build process with r.js. Grouping objects also helps to keep your list (array) of dependencies in the first argument of the (RequireJS) global define function call much cleaner and concise.

models (models.js) download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// List of models  
// --------------
// Models in this directory are intended for site-wide usage  

// This module is used with the r.js optimizer tool during build  
// See http://requirejs.org/docs/faq-optimization.html

// Requires `define`  See <http://requirejs.org/docs/api.html#define>  
// Returns {Object} model constructor references

define([
        "models/application-state",
        "models/base",
        "models/event",
        "models/member-summary",
        "models/messaging"
        ],
function (
        ApplicationStateModel,
        BaseModel,
        EventModel,
        MemberSummaryModel,
        MessagingModel
        ) {

    // Add models in this same directory to this object, 
    // for use when requiring this module.
    // grouping site-wide models in this module (object)
    // optimizes the performance and keeps dependencies organized
    // when the (build) optimizer is run.
    return {
        "ApplicationStateModel": ApplicationStateModel,
        "BaseModel": BaseModel,
        "EventModel": EventModel,
        "MemberSummaryModel": MemberSummaryModel,
        "MessagingModel": MessagingModel
    };

});

Wrapper: Utils Library

Another example of grouping objects for a base library used to extend our base objects e.g. BaseModel…

utils (utils.js) download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// List of utility libraries
// -------------------------

// this module is used with the r.js optimizer tool during build  
// See http://requirejs.org/docs/faq-optimization.html

// Requires `define` see <http://requirejs.org/docs/api.html#define>  
// Returns {Object} reference to utiltiy modules

define([
       "utils/ajax-options",
       "utils/baselib",
       "utils/cookies",
       "utils/storage",
       "utils/debug",
       "utils/date",
       "utils/shims"
        ],
function (
        ajaxOptions,
        baselib,
        docCookies,
        storage,
        debug,
        parseDate
        ) {

    return {
        "ajaxOptions": ajaxOptions,
        "baselib": baselib,
        "docCookies": docCookies,
        "storage": storage,
        "debug": debug,
        "parseDate": parseDate
    };
    // the shim only extend JavaScript when needed, e.g. Object.create
});

Events Collection

The events collection object can be extended to further segment the collection for specific lists presented to site visitors. In the example below the required script ‘collections/events’ returns an instance, however the instance’s constructor is easily referenced like so eventsCollection.constructor and extended to create a new constructor which will have methods to filter the collection by segment.

Events Segments Collection

Events Segments Collection (events-segments-example.js) download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// Events Segments Collection  
// -----------------

// @requires `define`  
// @return {Array} `segments` multi-dimensional array[schedule][type] 
// with collection {EventsSegmentCollection} instances

define(['jquery','underscore','collections/events','utils/baselib','utils/debug'],
function ($,      _,           eventsCollection,    baselib,        debug) {

    var EventsSegmentCollection, types, schedules, segments;

    // Subclass for creating specific segments of a collection
    EventsSegmentCollection = eventsCollection.constructor.extend({

        model: eventsCollection.model,

        // ...

        // Methods to filter collection and match schedule and/or type
        selectFilters: function (options) {
            var collection = this, models;

            // ... 

            return models;
        }

    });

    // Create segmented collections from the events collection
    schedules = ['today', 'ending_soon' /*, 'upcoming' */ ];
    types = ['Beauty', 'Getaways', 'Home', 'Kids', 'Men', 'Women', 'All'];

    // segments are combinations of schedules and types, e.g. `today` and `Beauty`
    segments = {};

    // iterate over schedules and types to build new collections by segments
    _.each(schedules, function (schedule) {
        segments[schedule] = {};
        _.each(types, function (type) {
            segments[schedule][type] = new EventsSegmentCollection(null, {
                "type" : type,
                "schedule" : schedule
            });
        });
    });

    return segments;

});

The truncated module above returns a multi-dimensional “array[schedule][type]” using the extended constructor. This module is useful throughout the application where ever segments of the events are listed and presented in various formats whether in a menu, list or as graphic slides the data is common and already segmented as required for the business of the application.

Defining a module with non-JavaScript dependencies like a Mustache html template

The view module below needs an HTML template file written for use with the template library Mustache. The required template 'text!packages/header/templates/nav.html' has the prefix text! which is a plugin to the RequireJS library.

header nav view (nav.js) download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// header nav view 
// ---------------
// @requires define
// @return {NavView} view constructor object

define(['jquery','underscore','views/base','text!packages/header/templates/nav.html','mustache','utils/debug'],
function ($,      _,           BaseView,    navTemplate,                              Mustache,  debug) {

    // global header view
    var NavView;

    NavView = BaseView.extend({

        initialize: function () {
            debug.log("navView init");
            this.template = navTemplate;
            NavView.__super__.initialize.call(this, arguments);
        },

        render: function () {
            // ...
        },

    });

    return NavView;

});

Summary

Functions require() and define() on the head object (window) are created by the RequireJS library and are strategic in authoring modular and reusable code. The function require() is used in the main.js to kick off the application; main.js is referenced in our html file in a script object or could be using in an inline script as well. The call to require() has as the first argument an array of the required scripts which are referenced in the second argument’s anonymous function definition which is used as a callback. When the requirements are ready they are passed to the function which lists the arguments by name giving reference to the returned objects of each required script. The define() function works the same way as require() using an array as the list of dependencies; however define() is called to define a module of code. the RequireJS library also has plugins to load non-JavaScript dependencies, e.g. Mustache template files, to reference the plugin use the prefix text!. All scripts in the Web application framework should use these two methods which provide the closures and scope for authoring modular code in JavaScript; it is not necessary to add any properties to the head (window) object.