Backbone Marionette: Marionette.Application causing Require.js module load error, "'Error: Module name 'App' has not been loaded yet for context: _"
Asked Answered
L

4

0

I'm trying to include the App instance to use it's event aggregator as shown here

I get an error when I include the instance in a view.

Kicking things off in the Requirejs config file, from App.Bootloader.js:

require(['App'], function (App){
      App.start();
      });

from App.js:

define(function (require){

  //...requisite includes $, _, Backbone, Marionette ...

var Layout = require('Layout');

  var App = new Marionette.Application();

        App.addRegions({
            main: '#view_content'
        });

        App.addInitializer(function (){

                App.main.show(new Layout());
                //... adding router etc ...    

                Backbone.Marionette.TemplateCache.loadTemplate = function (template, callback){
                   callback.call(this, Handlebars.compile(template));
                };
                Backbone.history.start();

        });

    return App;
});

From Layout.js:

define(function(require){
   var View = require('folder/folder/View');
   //template contains #sub div
   var template = require('text!template.html');

   return Marionette.Layout.extend({
      template: template,
      regions: {
         sub: '#sub'
      },
      initialize: function(){
         //wait till template is rendered in dom
         _.defer(function(region){
             region.sub.show(new View());
          }, this)
      }

   });

});

From /folder/folder/View.js:

define(function (require){

      //...requisite includes $, _, Backbone, Marionette ...

     var App = require('App');
     return Marionette.ItemView.extend({});
});

Where I get the error "'Error: Module name 'App' has not been loaded yet for context: _"

Any ideas? Lemme know if you need more information.

Loriannlorianna answered 15/6, 2012 at 19:2 Comment(0)
K
5

I'm also looking for a good way to handle with this kind of situations using marionette and require.js

I have worked on a solution but I don't know if it is the best way, here are my thoughts:

  • the application has actions attached to events without knowing about the views
  • inside the view we use triggers to attach actions to events
  • the connection of actions between the view and the application is made inside the view

This is a possible solution:

app.js

define( [ 'underscore', 'jquery', 'backbone', 'marionette' , 'view'], 
    function( _, $, Backbone, Marionette, View  ) {

    var app = new Marionette.Application();
    app.addRegions({ content: '#content'})

    app.on( "initialize:after", function(){

        console.log( 'after init', app)

        var view = new View();
        app.content.show( view );

    });

    // this is the action that we would like to call from the view      
    app.vent.on( 'viewClick', function(){ console.log( 'clicked on view' )})

    return app;
});

view.js

define( [ 'underscore', 'jquery', 'backbone', 'marionette' ], 
    function( _, $, Backbone, Marionette ) {

    var View = Marionette.ItemView.extend({
        template: '#view',

        triggers: {
            'click': 'clicked'
        },

        initialize: function(){

            // thisView will be referring to the view instance
            var thisView = this;

            // we require the app because we need access to the event aggregator 
            require(['app'], function( app ){

                // when our event is triggered on our view
                thisView.on( 'clicked', function(){ 
                    // we trigger an event on the application event aggregator
                    app.vent.trigger( 'viewClick' )
                });
            });
       }
    })

    return View
}); 

it is important to remember that require is asynchronous, so when we use it like this it will not be executed immediately:

require( ['some-module'], function( someModule ){ 
   // do something, but only as soon as someModule is loaded 
});

we can prepare objects wich point to the external context, like this:

var thisName = this;
require( ['some-module'], function( someModule ){ 
   // access to external this using thisName
});
Kwangchow answered 7/7, 2012 at 2:4 Comment(2)
This solution was mentioned in the Marionette docs here, but without an example. This example worked for me. Thanks!Suzannsuzanna
This looks like exactly what i needed too — sadly i've found this whole process annoying enough to stick with the asset pipeline for now... hahaErnaernald
T
3

I guess you have a problem with circular dependencies. App needs View and View needs App. Hmm… But why View requires App? I can't figure it from your code. In the end, are you sure View needs App? By the way, I think you mistyped. The first From /folder/folder/View.js probably should be From Layout.js.

Trautman answered 16/6, 2012 at 0:25 Comment(3)
Yes your are right about the typo, it's fixed. I don't see the circular dependency now: App needs Layout, Layout displays View, View calls App because in Marionette the app has the invaluable Event Aggregator (ie App.vent.trigger or App.vent.bind) used for cross viewmodule observation aka pub sub.Loriannlorianna
Temporary hack was to export App to window.App for others to access it, but that shouldn't be necessary and breaks philosophy of contained modules.Loriannlorianna
Okay, you can use the [special module exports][1]. For example: #4881559 1: github.com/jrburke/requirejs/wiki/…Trautman
A
0

user1248256 is correct. I had the same problem. My App needed a controller and my Controller needed App.

By passing in the controller (view for your code) as part of options I don't have to add it to the require.js definition.

//data-main:
define(function(require) {
    var   $                 = require("jquery"),
        _                   = require("underscore"),
        App                 = require("app/App"),
        PublicRouter        = require("routers/DesktopRouter"),
        PublicController    = require("routers/publicController");

    var options = {
        publicController  :   PublicController,
        publicRouter      :   PublicRouter

    }

    App.start(options);
});

Now in App I don't have to "require" the PublicController

//App:
define(['jquery', 'backbone', 'marionette', 'underscore'],
function ($, Backbone, Marionette, _) {
    var App = new Marionette.Application();
    ...snip...
        console.log("Creating Routers");
        App.Routers = {};
        // Connect controllers to its router via options
        // init router's router/controller
        App.Routers.publicRouter = new options.publicRouter.Router({
            controller: options.publicController
        });
    });

Hope that helps.

Andrew

Alienate answered 5/6, 2013 at 15:48 Comment(0)
A
0

I generally think it's bad practice to use the app's EventAggregator when using requireJS, if for no other reason than it's really easy to wind up with a circular reference.

Just define a separate EventAggregator module that can be required by App, View, and Layout, then add it to the dependencies of any module that needs it.

Allover answered 21/8, 2014 at 20:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.