Brian Clifton
Brian Clifton

Reputation: 701

Ember app working intermittently- code modularized with requirejs

this post has a lot of detail so please bear with me

Most of my Ember.js development experience is pre-release (0.9.8.1) and I'm fairly new to routes. I've put in a lot of time understanding them and have created a working app:

http://mbregistry.info/

Problem is, my code intermittently doesn't work (mostly noticeable in Chrome). At first, I was having problems with ember not finding the templates. I was overriding the view for a route and specifying the template like so:

scripts/users.js

define(['scripts/app','text!scripts/templates/UserIndex.html'],
function (app, templateUserIndex) {
    //route is called app.UserIndexRoute
    app.UserIndexView = Ember.View.extend({
        template: Ember.Handlebars.compile(templateUserIndex),
        Created: function () {
            return app.ParseDate(this.get('controller.model.created')).toLocaleDateString();
        }.property('controller.model.created'),
        MailTo: function () {
            return 'mailto:' + this.get('controller.model.email');
        }.property('controller.model.email')
    });
});

I verified requirejs loads the html using the text plugin... but then found that other folks on StackOverflow had problems with using "template" with views in routed objects. So instead of fighting that, I removed the" template" line and updated my App class to explicitly set the templates for all my views in the ready() event (since I know the route will default the template name)

scripts/app.js

define(['jquery',
    'handlebars',
    'ember',
    'text!scripts/templates/Application.html',
    'text!scripts/templates/Loading.html',
    'text!scripts/templates/Index.html',
    'text!scripts/templates/OwnedCarsIndex.html',
    'text!scripts/templates/OwnedCar.html',
    'text!scripts/templates/UserIndex.html'],
function (jQuery, handlebars, ember, templateApplication, templateLoading, templateIndex, templateOwnedCarsIndex, templateOwnedCar, templateUserIndex) {
    App = Ember.Application.create({
        ready: function () {
            Ember.TEMPLATES['application'] = Ember.Handlebars.compile(templateApplication);
            Ember.TEMPLATES['loading'] = Ember.Handlebars.compile(templateLoading);
            Ember.TEMPLATES['index'] = Ember.Handlebars.compile(templateIndex);
            Ember.TEMPLATES['ownedCars/index'] = Ember.Handlebars.compile(templateOwnedCarsIndex);
            Ember.TEMPLATES['ownedCar'] = Ember.Handlebars.compile(templateOwnedCar);
            Ember.TEMPLATES['user/index'] = Ember.Handlebars.compile(templateUserIndex);
        },
        LOG_TRANSITIONS: true,
        LOG_TRANSITIONS_INTERNAL: true,
        LOG_VIEW_LOOKUPS: true,
        LOG_ACTIVE_GENERATION: true
    });

    App.Router.map(function () {
        this.resource('users', function () {
            this.resource('user', { path: ':user_id' }, function () {
                this.resource('ownedCars', { path: 'cars' }, function () {
                    this.resource('ownedCar', { path: ':ownership_id' }, function () {
                        this.resource('expenses');
                    });
                });
            });
        });

        this.resource('cars', function () {
            this.resource('car', { path: ':car_id' });
        });
    });

    return App;
});

This solved the templates intermittently not loading. But now, again intermittently, I am running into an issue with the route. I have a link-to helper and here is some of the console output for when the page experiences this issue:

DEBUG: -------------------------------
DEBUG: Ember      : 1.3.0
DEBUG: Handlebars : 1.1.2
DEBUG: jQuery     : 1.10.2
DEBUG: -------------------------------
Attempting URL transition to / 
generated -> route:application Object {fullName: "route:application"}
generated -> route:index Object {fullName: "route:index"}
Transition #1: Beginning validation for transition to index
Transition #1: application: calling beforeModel hook
Transition #1: application: resolving model
Transition #1: application: calling afterModel hook
Transition #1: application: validation succeeded, proceeding
Transition #1: index: calling beforeModel hook
Transition #1: index: resolving model
Transition #1: index: calling afterModel hook
Transition #1: index: validation succeeded, proceeding
Transition #1: Validation succeeded, finalizing transition;
generated -> controller:application Object {fullName: "controller:application"}
Rendering application with default view <(subclass of Ember.View):ember209> Object {fullName: "view:application"}
generated -> controller:index Object {fullName: "controller:index"}
Rendering index with default view <Ember._MetamorphView:ember225> Object {fullName: "view:index"}
Transitioned into 'index'
Transition #1: TRANSITION COMPLETE.
generated -> route:cars Object {fullName: "route:cars"}
generated -> route:cars.index Object {fullName: "route:cars.index"}
generated -> route:users Object {fullName: "route:users"}
generated -> route:user Object {fullName: "route:user"}
generated -> route:user.index Object {fullName: "route:user.index"}

but when I try to click the "Edit profile" link, which was generated with the link-to helper, I get this:

Attempting transition to user.index
Transition #2: Beginning validation for transition to user.index
Transition #2: application: using context from already-active handler
Transition #2: application: validation succeeded, proceeding
Transition #2: users: calling beforeModel hook
Transition #2: users: resolving model
Transition #2: users: calling afterModel hook
Transition #2: users: validation succeeded, proceeding
Transition #2: user: calling beforeModel hook
Transition #2: user: resolving model 
Assertion failed: You used the dynamic segment user_id in your route user, but App.User did not exist and you did not override your route's `model` hook. 
Transition #2: user.index: transition was aborted

I'm not sure why it's trying to access App.User, instead of App.UserRoute (App.UserRoute has the overloaded model function, which correctly generates a model)

Any help is very much appreciated :)

UPDATE: One problem I was having (which I didn't even mention) I solved- I needed to shim Ember.js using requirejs's config:

require.config({
    paths: {
        'text': 'scripts/references/text',
        'jquery': 'scripts/references/jquery-1.10.2.min',
        'handlebars': 'scripts/references/handlebars-1.1.2',
        'ember': 'scripts/references/ember-1.3.0'
    },
   shim: {
       'ember': {
           deps: ['handlebars', 'jquery'],
           exports: 'Ember'
       }
   }
});

This got rid of intermittent "module jquery not found" or "module handlebars not found" (which only happened when the cache was dumped). The main issue described is still present though. From the debugger console, here is the only difference I noticed when it renders the page...

Working page

Transition #1: TRANSITION COMPLETE.
generated -> route:cars Object {fullName: "route:cars"}
generated -> route:users Object {fullName: "route:users"} 

Not working page (errors when you click link)

Transition #1: TRANSITION COMPLETE.
generated -> route:cars Object {fullName: "route:cars"}
generated -> route:cars.index Object {fullName: "route:cars.index"}
generated -> route:users Object {fullName: "route:users"}
generated -> route:user Object {fullName: "route:user"}
generated -> route:user.index Object {fullName: "route:user.index"} 

If you notice the non-working page output, Ember is generating route objects for routes that aren't being used yet... Why is this only happening sometimes?

Upvotes: 1

Views: 462

Answers (2)

Brian Clifton
Brian Clifton

Reputation: 701

Found it! It should have been more obvious to me when I realized the templates weren't compiling properly and I tried the workaround shown in my question.

The root cause for my issue was that Ember was (intermittently- race condition) generating routes when processing the link-to helper BEFORE the browser loaded the JavaScript that registers the route. I got around this by making my modules return a function and then using it like a constructor.

So final code for scripts/app.js looks like this:

define(['jquery',
        'handlebars',
        'ember',
        'scripts/users'],
function (jQuery, handlebars, ember, usersModule) {
    App = Ember.Application.create({
    });

    usersModule = new usersModule;

    App.Router.map(function () {
        this.resource('users', function () {
            this.resource('user', { path: ':user_id' }, function () {
                //..
            }
        }
    }

    return App;
});

And now my scripts/users.js module looks like this:

define(['text!scripts/templates/UserIndex.html'],
function (gravitar, templateUserIndex) {
    return function () {
        App.UserIndexRoute = Ember.Route.extend({
            model: function (param) {
                return this.modelFor('user');
            }
        });
        App.UserIndexView = Ember.View.extend({
            template: Ember.Handlebars.compile(templateUserIndex),
            Created: function () {
                return App.ParseDate(this.get('controller.model.created')).toLocaleDateString();
            }.property('controller.model.created'),
            MailTo: function () {
                return 'mailto:' + this.get('controller.model.email');
            }.property('controller.model.email')
        });
    }
});

So in conclusion, require.js still serves it's purpose (and I really like it) although, there could be a much better way to integrate it with Ember.js than the hack I used. My problem was that I was an idiot and didn't expect Ember.js to process anything BEFORE all the modules were loaded (some of those modules registering routes). The intermittent behavior I saw was Ember processing the link-to helpers in the application view BEFORE all the routes were registered.

ember-app-kit looks pretty interesting, but it looks like it solves the problem by providing an entire framework around using Ember.js (which I'm not ready for). But definitely appreciate the help Adam :)

Upvotes: 1

Adam
Adam

Reputation: 3158

I'm not sure why you're getting the User model instead of UserRoute when you expect it but I can tell you that Ember doesn't play nice with AMD modules out of the box. It has to do with it's resolver which is responsible for figuring out the correct name of things in the container when they're requested. The ember-app-kit project is working on a new version of the resolver that works with the es6 module transpiler (which transpiles es6 modules down to AMD modules). You could possibly look to that for inspiration for how to override the default resolver to get it to work with require.js.

Upvotes: 1

Related Questions