Backbone Marionette and RequireJS Modules
Asked Answered
O

2

16

I'm beginning a large scale javascript application with Marionette. A Marionette application has a concept of application Modules and RequireJS is also used to break code into modules,

currently I have this for the start of my application:

require([ "jquery", "underscore", "backbone", "marionette" ],
function ($, _, Backbone, Marionette) {
    $(function() {

        App = new Marionette.Application();
        App.addInitializer(function(options) {
            App.addRegions({
                mainArea: "#mainArea"
            });
        });

        App.on("start", function() {
            // done starting up, do stuff here
        });

        App.start();
    });
});

If I wanted to add a view would I do something like the following in a file?

require([ "jquery", "underscore", "backbone", "marionette" ],
function($, _, Backbone, Marionette) {

    App.module("FirstView", function(FirstView, App, Backbone, Marionette, $, _) {
        return Marionette.ItemView.extend({
            //define view stuff in here
         });
    });

});

I'm not sure how I'd get this code to actually run, any help is much appreciated

Oof answered 6/7, 2012 at 14:29 Comment(0)
S
23

Marionette's modules are meant to be a simple alternative to RequireJS (and other) module formats. I would not recommend using them together, as noted in the wiki:

https://github.com/marionettejs/backbone.marionette/wiki/AMD-Modules-vs-Marionette's-Modules

Subtile answered 6/7, 2012 at 14:57 Comment(9)
makes sense. Thanks! I'm going to use RequireJS, because I'm assuming if I used Marionette modules I'd have to stick all my application files in script tags in the head like in BBCloneMail, I'm trying to avoid doing that.Oof
I don't recommend using multiple script tags. BBCloneMail isn't an example of doing that right. :) Real projects have build steps that concat & minify. r.js does for requirejs modules, or it can be done with any of a number of other tools like the Rails asset pipeline or grunt.js, or many many other tools.Subtile
Here's an update link for using Marionette with RequireJS. (The github repository moved.) github.com/marionettejs/backbone.marionette/wiki/…Sinclare
Now I can use this answer as an argument since it is from MarionetteJS creator. :)Kalb
@DerickBailey isn't there still a benefit for using Marionette modules + RequireJS? i.e. the ability to stop modules means we can free up memory.Christal
Does the wiki article on AMD Modules vs Marionette Modules still exist anywhere? I've seen numerous references to it online, but the links provide no longer work.Marvismarwin
I am not able to open the above mentioned link. Will you provide a new link which describe Backbone Marionette and RequireJS Modules modules.Genny
Is this still applicable today (2017)?Outwit
@Outwit - no. marionett's modules were removed a while back (thankfully). you should look at JavaScript's native module format instead of RequireJS, though. much better, if you don't mind having BabelJS or Webpack involved in your build process.Subtile
C
4

IMHO I like to differ from the view point stated above "Marionette's modules are meant to be a simple alternative to RequireJS (and other) module formats."

I like to draw a comparison between Require.js modules and Marionette.js modules with C#'s assembly and namespace concepts. Marionette.js's modules help us group definitions of various building blocks based on functionality, while Require.js could be used to load / inject dependencies.

Again, this is my view / understanding (based on discussions with David Sulc on his book 'Structuring Backbone Code with RequireJS and Marionette Modules'), which has helped in my implementation. In a way we can use Marionette.js and Require.js together as described below.

The example below is a small Library Manager app (sample) which could be found online @ https://github.com/srihari-sridharan/LibraryManagement. The code below (omitting insignificant bits and pieces) creates the application object and renders the list of books after initialization. Please find it here - https://github.com/srihari-sridharan/LibraryManagement/blob/master/app/js/app.js

define([
    'marionette',
    'modules/config/marionette/regions/dialog'], function (Marionette) {

    // Create the application object
    var LibraryManager = new Marionette.Application();

    // Add regions to the application object
    LibraryManager.addRegions({
        //Header
        headerRegion: "#header-region",
        //Main
        mainRegion: "#main-region",
        //Footer
        footerRegion: "footer-region",
        //Overlay Dialog
        dialogRegion: Marionette.Region.Dialog.extend({
            el:"#dialog-region"
        })
    });

    // Subscribe to Initialize After event.
    LibraryManager.on('initialize:after', function() {
        if(Backbone.history){
            require(['modules/books/booksModule', 'modules/about/aboutModule'], function (){
                Backbone.history.start();    
                if(LibraryManager.getCurrentRoute() === ''){
                    LibraryManager.trigger("books:list");
                }                    
            });
        }
    });

    // Return the application object.
    return LibraryManager;
});

Next we define the module / sub-modules based on the functionality. This will also have a module specific router and will wire controllers and handle routes. Note the require call to controllers. This code is present in https://github.com/srihari-sridharan/LibraryManagement/blob/master/app/js/modules/books/booksModule.js

define(['app'], function (LibraryManager) {
    // Define a new module for Books - BooksModule
    LibraryManager.module('BooksModule', function (BooksModule, LibraryManager, Backbone, Marionette, $, _) {

        BooksModule.startWithParent = false;

        BooksModule.onStart = function () {
            console.log('Starting BooksModule.');
        };

        BooksModule.onStop = function () {
            console.log('Stopping BooksModule.');
        };

    });

    // Define a new module for a Router specific to BooksModule
    LibraryManager.module('Routers.BooksModule', function (BooksModuleRouter, LibraryManager, Backbone, Marionette, $, _) {

        BooksModuleRouter.Router = Marionette.AppRouter.extend({
            appRoutes: {
                'books': 'listBooks',
                'books(?filter:=criterion)': 'listBooks',
                'books/:id': 'showBook',
                'books/:id/edit': 'editBook'
            }
        });

        var executeAction = function (action, arg) {
            LibraryManager.startSubModule('BooksModule');
            action(arg);
            LibraryManager.execute('set:active:header', 'books');
        };

        var API = {
            // This is where we are using / referring to our controller
            listBooks: function (criterion) {
                require(['modules/books/list/listController'], function (ListController) {
                    executeAction(ListController.listBooks, criterion);
                });
            },

            showBook: function (id) {
                require(['modules/books/show/showController'], function (ShowController){
                    executeAction(ShowController.showBook, id);
                });
            },

            editBook: function (id) {
                require(['modules/books/edit/editController'], function (EditController) {
                    executeAction(EditController.editBook, id);
                });
            }

        };

        // Navigating routes.
        LibraryManager.on('books:list', function () {
            LibraryManager.navigate('books');
            API.listBooks();
        });

        LibraryManager.on('books:filter', function(criterion) {
            if(criterion){
                LibraryManager.navigate('books?filter=' + criterion);
            }
            else{
                LibraryManager.navigate('books');
            }
        });

        LibraryManager.on('book:show', function (id) {
            LibraryManager.navigate('books/' + id);
            API.showBook(id);
        });

        LibraryManager.on("book:edit", function(id){
            LibraryManager.navigate('books/' + id + '/edit');
            API.editBook(id);
        });

        LibraryManager.addInitializer(function () {
            new BooksModuleRouter.Router({
                controller: API
            });
        });
    });

    return LibraryManager.BooksModuleRouter;
});

Finally we have the definitions for our views, models and controllers. These definitions will be tied to module / sub module objects.

The view code is shown below. Look at the .extend() methods. They are assigned to variables attached to the BooksModule.List.View sub module. https://github.com/srihari-sridharan/LibraryManagement/blob/master/app/js/modules/books/list/listView.js

define(['app',
        'tpl!modules/books/list/templates/layout.html',
        'tpl!modules/books/list/templates/panel.html',
        'tpl!modules/books/list/templates/none.html',
        'tpl!modules/books/list/templates/list.html',
        'tpl!modules/books/list/templates/listItem.html'], 
    function (LibraryManager, layoutTemplate, panelTemplate, noneTemplate, listTemplate, listItemTemplate) {

        LibraryManager.module('BooksModule.List.View', function(View, LibraryManager, Backbone, Marionette, $, _) {

            View.Layout = Marionette.Layout.extend({

                template: layoutTemplate,

                regions:{
                    panelRegion: '#panel-region',
                    booksRegion: '#books-region'
                }

            });

            View.Panel = Marionette.ItemView.extend({
                // More code here!
            });

            View.Book = Marionette.ItemView.extend({                
                // More code here!
            });

            var NoBooksView = Marionette.ItemView.extend({
                template: noneTemplate,
                tagName: "tr",
                className: "alert"
            });

            View.Books = Marionette.CompositeView.extend({
                // More code here!
            });
        });
    return LibraryManager.BooksModule.List.View; // Return the definition.
});

The controller code is shown below. This gets called from the code in booksModule.js. The controller definition is attached to BooksModule.List sub module.

define(['app', 'modules/books/list/listView'], function (LibraryManager, View) {

    LibraryManager.module('BooksModule.List', function (List, LibraryManager, Backbone, Marionette, $, _) {

        List.Controller = {

            listBooks: function (criterion) {

                require(['common/views', 'entities/book'], function (CommonViews) {

                    var loadingView = new CommonViews.Loading();
                    LibraryManager.mainRegion.show(loadingView);

                    var fetchingBooks = LibraryManager.request('book:entities');
                    var booksListLayout = new View.Layout();
                    var booksListPanel = new View.Panel();

                    require(['entities/common'], function (FilteredCollection) {

                        $.when(fetchingBooks).done(function (books) {
                            // More code here!
                            });

                            if(criterion){
                                filteredBooks.filter(criterion);
                                booksListPanel.once('show', function () {
                                    booksListPanel.triggerMethod("set:filter:criterion", criterion);
                                });
                            }

                            var booksListView = new View.Books({
                                collection: filteredBooks
                            });

                            booksListPanel.on('books:filter', function (filterCriterion) {
                                filteredBooks.filter(filterCriterion);
                                LibraryManager.trigger("books:filter", filterCriterion);
                            });

                            booksListLayout.on("show", function(){
                                booksListLayout.panelRegion.show(booksListPanel);
                                booksListLayout.booksRegion.show(booksListView);
                            });

                            booksListPanel.on('book:new', function () {

                                require(["modules/books/new/newView"], function (NewView) {
                                        // More code here!
                                    });

                                    LibraryManager.dialogRegion.show(view);
                                });
                            });

                            booksListView.on('itemview:book:show', function (childView, model) {
                                LibraryManager.trigger("book:show", model.get('id'));
                            });

                            booksListView.on('itemview:book:edit', function(childView, model) {
                                require(['modules/books/edit/editView'], function (EditView) {
                                    // More code here!
                                    LibraryManager.dialogRegion.show(view);
                                });
                            });

                            booksListView.on("itemview:book:delete", function (childView, model) {
                                model.destroy();
                            });

                            LibraryManager.mainRegion.show(booksListLayout);

                        });

                    });

                });

            }

        }

    });

    return LibraryManager.BooksModule.List.Controller; // Return the definition.
});

Thus require.js modules and marionette modules can coexist. The following are the advantages.

  • Much cleaner organization of source code and clearer separation of concerns.
  • Module start and stop methods provide provision to initialize and cleanup objects.
  • When you model functionalities and sub-functionalities as modules and sub modules, we have more granular control over what resides in memory and what should not.
  • Also, module definition can be split across multiple files.

Please post your thoughts. Thanks for reading.

PS: Based on the above view point, please find the changes to your example below:

require([ "jquery", "underscore", "backbone", "marionette" ],
function($, _, Backbone, Marionette) {
    App.module("FirstView", function(FirstView, App, Backbone, Marionette, $, _) {
        FirstView.View = Marionette.ItemView.extend({
            //define view stuff in here
        });

        return FirstView.View;
    });
});
Cesium answered 19/2, 2015 at 9:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.