matewilk
matewilk

Reputation: 1301

Creating/Accessing Global object with requirejs and backbonejs

This is presented in easy to understand fashion, so enjoy reading :)

I have a backbone app which is initialised with main.js file like this:

require([
    'backbone',
    'app',
    'models/session'
], function (Backbone, Application, SessionModel) {


    //new Application();
    window.App = {
      session: new SessionModel()
    };

    new Application();

    Backbone.history.start();
});

As you can see, requirejs requires 'app' as a second param.

App is just a router which looks something like this (less important parts has been removed)

define(function(require){
    'use strict';

    var Backbone = require('backbone'),
        Header = require('views/header'),
        Login = require('views/login');

    var Router = Backbone.Router.extend({

        initialize: function(){
            this.header = new Header();
            this.header.render();
        },


        routes: {
            'login': 'showLogin'
        },

        showLogin: function() {
            this.showView(new Login(), {requiresAuth: false});
            this.header.model.set('title', 'Login');
        },

        showView: function(view, options) {

            if(this.currentView) this.currentView.remove();
            $('.content').html(view.render().$el);
            this.currentView = view;

            return view;
        }

    });

    return Router;
});

The important bit here is that in the first lines I'm requiring

Header = require('views/header');

Header view requires another view in a standard way:

LogoutView = require('views/logout');

Logout view looks like this and here I come to the essence of the problem :

define(function(require){
    'use strict';

    var Backbone = require('backbone'),
        JST = require('templates')

    return Backbone.View.extend({
        model: App.session,

        template: JST['app/scripts/templates/logout.hbs'],

        events: {
            'submit form': 'logout'
        },

        initialize: function(){
            this.listenTo(this.model, 'change', this.render)
        },

        render: function(){
            this.$el.html(this.template(this.model.toJSON()));

            return this;
        },

        logout: function(e){
            //nothing important here
        }

    });
});

As you can see at the very first line after Backbone.View.extend I'm trying to define the model property of the view:

model: App.session,

which I suppose should be accessible, because I was defining the:

window.App = {
      session: new SessionModel()
    };

in the main.js file.

But it seems that there is a require problem, because as require is trying to get all the files in the first lines of the program:

require([
    'backbone',
    'app', <<--- in this line it's trying to get all the files required in the app.js 
    'models/session'
], function (Backbone, Application, SessionModel) {

Then I'm getting this error :

Uncaught ReferenceError: App is not defined   logout.js:8

Which is exactly the line trying to get access to App global variable:

 model: App.session,

That should be defined after running main.js file, but it doesn't even go that far as require is getting the files and finds not defined App variable in the LogoutView.

I can work around this problem by defining the model inside the initialize function of the view like so:

initialize: function(){
    this.model = App.session;
    this.listenTo(this.model, 'change', this.render)
},

But what I really want is to understand why this error is occurring and what is the best practice of creating global Models using Backbone.js and Require.js.

Upvotes: 3

Views: 782

Answers (2)

matewilk
matewilk

Reputation: 1301

The code below is more or less how I was trying to solve this problem before I've posted this question:

define(function(require) {
    'use strict';

    var SessionModel = require('models/session')

    var session = (function(){
        if(App.session instanceof SessionModel.constructor){
            return App.session;
        } else {
            App.session = new SessionModel()
            return App.session;
        }
    })();

    return session;
});

But I think I've abandoned it for some reason, either because App wasn't defined or maybe it seemed that there is a better solution for this.

The dirty hack was that I had to create the:

window.App = {};

Just before I was running the entire application.

Upvotes: 0

nikoshr
nikoshr

Reputation: 33344

The list of dependencies in a define or require (the first argument in your require or the simplified wrapper you use in your other modules) tells RequireJS that it should load and interpret those modules before interpreting the current file.

Here's what happens:

  1. The first require has backbone, app, models/session as dependencies
  2. before its function is evaluated, it loads its dependencies and in turn looks for their dependencies
  3. Rinse and repeat until you reach views/logout
  4. views/logout is interpreted, you try to assign App.session to your view.model but that does not exist at that point and you get a App is not defined error

One solution would be to create a singleton of your Session object and require it when you need it.

For example, let's say you have globals/session

define(['models/session'], function (SessionModel) {
    return new SessionModel();
});

You would define your app as

require([
    'backbone',
    'app',
    'globals/session'
], function (Backbone, Application, session) {

    //new Application();
    window.App = {
      session: session
    };

    new Application();

    Backbone.history.start();
});

and the same change in your view/logout

define(function(require){
    'use strict';

    var Backbone = require('backbone'),
        JST = require('templates')

    return Backbone.View.extend({
        model: require('globals/session'),
        ...
    });
});

Upvotes: 1

Related Questions