Backbone Marionette and RequireJS Modules Confusion – module seems to be undefined
Asked Answered
U

1

5

I just got started using Marionette and I'm reading and following along Marionette - A Gentle Introduction by David Sulc. It's a really good read and it's easy to follow along building the sample application Contact Manager with the companion repository.

However, I had previously set up a project using RequireJS and wanted to translate and integrate the ideas and concepts of that book into this project. I haven't actually gotten that far and I think I might be a little bit confused about the use of Marionette Modules in combination with AMD modules which leads to undefined objects.

To be more specific, let me list app.js, listView.js and listController.js which should be the RequireJS version of this commit of the sample repo.

app.js

/*global define*/
define([
    'marionette'
], function ( Marionette ) {
    'use strict';

    var ContactManager = new Marionette.Application();

    ContactManager.addRegions({
        mainRegion : '#main-region'
    });

    ContactManager.on( 'initialize:after', function() {

        ContactManager.ContactsApp.List.Controller.listContacts();
    });

    return ContactManager;
});

listView.js

/*global define*/
define([
    'app',
    'marionette',
    'handlebars',
    'text!templates/contact.hbs'
], function ( ContactManager, Marionette, Handlebars, contactTemplate ) {
    'use strict';

    var List = ContactManager.module( 'ContactsApp.List' );

    List.Contact = Marionette.ItemView.extend({

        tagName: 'li',
        template : Handlebars.compile( contactTemplate ),

    });

    List.Contacts = Marionette.CollectionView.extend({

        tagName: 'ul',
        itemView: List.Contact
    });

    return List;
});

listController.js

/*global define*/
define([
    'app'
], function ( ContactManager ) {
    'use strict';

    var List = ContactManager.module( 'ContactsApp.List');

    List.Controller = {

        listContacts: function() {

            var contacts = ContactManager.request('contact:entities');

            var contactsListView = new ContactManager.ContactsApp.List.Contacts({
                collection: contacts
            });

            ContactManager.mainRegion.show( contactsListView );
        }
    };

    return List.Controller;
});

So, the error I get is Uncaught TypeError: Cannot read property 'List' of undefined in app.js:15 which refers to this line:

ContactManager.ContactsApp.List.Controller.listContacts();

This means that the ContactsApp module is undefined and this is exactly what I don't understand.

To my understanding I attach the ContactsApp module and the List sub-module to the ContactManager inside either listView.js or listController.js (whichever is called first) with the line:

ContactManager.module( 'ContactsApp.List' );

Shouldn't the ContactsApp be defined inside app.js then?

This is the main.js file which includes the require.config and the entry point to the application:

require.config({
    baseUrl: './scripts',
    paths: {
        jquery     : '../bower_components/jquery/jquery',
        underscore : '../bower_components/underscore/underscore',
        backbone   : '../bower_components/backbone/backbone',
        marionette : '../bower_components/backbone.marionette/lib/backbone.marionette',
        bootstrap  : '../bower_components/sass-bootstrap/dist/js/bootstrap',
        text       : '../bower_components/requirejs-text/text',
        handlebars : '../bower_components/handlebars/handlebars',
        templates  : '../templates'
    },

    shim: {
        underscore : {
            exports : '_'
        },
        backbone : {
            deps : [ 'underscore', 'jquery' ],
            exports : 'Backbone'
        },
        marionette : {
            deps : [ 'backbone' ],
            exports : 'Backbone.Marionette'
        },
        bootstrap : {
            deps : [ 'jquery' ],
        },
        handlebars : {
            exports : 'Handlebars'
        }
    },
    deps : [ 'jquery', 'underscore' ]
});

require([
    'app',
    'bootstrap'
], function ( ContactManager ) {
    'use strict';

    ContactManager.start();
});
Uniformize answered 13/9, 2013 at 10:29 Comment(0)
B
7

RequireJS bascially works like this: declare all the dependencies a given module has, then use them within the call back function.

Here's the issue with your code: in app.js, you require only marionette so as far as RequireJS is concerned, nothing else should be loaded for the module's code to be functional. But then, in that same file, you call ContactManager.ContactsApp.List.Controller.listContacts(). Where does that come from? Nowhere: it isn't defined within the current module, and wasn't declared as a dependency. Therefore, it doesn't exist and you get the undefined issue.

You can't just refer to the module, thinking it's attached to the main application: it only actually gets attached when the Marionette module code gets executed. And for that to happen, it needs to be required as a dependency.

As an aside, you'll have a tough time adapting the book's code to use with RequireJS, because it isn't architected for RequireJS use (in addition to the issue you ran into, you'll have circular dependencies, etc.).

I'd suggest you just read along the book to get a good feel for Marionette on its own, then look into using it with RequireJS. Shameless plug, I also wrote a book on marionette and requirejs.

Bahner answered 13/9, 2013 at 11:46 Comment(4)
Thanks for your quick reply, David. I thought it should be okay not to include the controller in app.js because I'm attaching the controller sub-module to the ContactManager inside listController.js. I assumed the controller would be available on the ContactManager in app.js then. Doesn't seem to be the case?! If I include the the listController in the define block in app.js, I will run into a circular dependency again. Would would be a good and clean way to do it then?Uniformize
Also, I'm aware of your other book and I'm reading the source code as well but since I'm not very familiar with Marionette yet, not everything makes sense to me at this point. I guess I'll follow your advice and finish the book by itself and hope the structuring-backbone-with-requirejs-and-marionette repo will make more sense to me then.Uniformize
Does Structuring Backbone Code with RequireJS and Marionette Modules start with the finished Contact Manager app from Backbone.Marionette.js: A Gentle Introduction and "translates" it to be used with RequireJS or do you start the app from scratch in this book?Uniformize
I've edited my answer to clear up why the module being attached to the main app deosn't mean you can use it right away. The second book starts with the finished application and shows you how to "transform" it to use RequireJS. It also covers a few other topics on the way (e.g. mixing Marionette and RequireJS modules, starting/stoping modules according to routed paths)Bahner

© 2022 - 2024 — McMap. All rights reserved.