Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ember: Using itemController in ArrayController doesn't work (TypeError: Cannot call method 'lookup' of null )

Tags:

ember.js

Following the examples on Ember for setting the itemController in my ArrayController definition causes the blocking error message:

TypeError: Cannot call method 'lookup' of null

JSFiddle: http://jsfiddle.net/jgillick/M4BVV/

// Addresses array controller
App.AddressesController = Ember.ArrayController.extend({
    itemController: 'address'
});

// Address object controller
App.AddressController = Ember.ObjectController.extend({
    city: function(){
        return "San Francisco";
    }.property()
});

The only way that I have found to fix this is either...

1) Pass itemController in the #each handler (jsfiddle):

{{#each addresses itemController="address"}}
    <li>{{line1}}, {{city}}</li>
{{/each}}

...or...

2) Add a container property to the ArrayController (jsfiddle):

var addresses = App.AddressesController.create({
    container: indexController.get('container'),
    content: [
        App.Address.create({'line1': '700 Hansen Wy'}),
        App.Address.create({'line1': '900 Hansen Wy'})
    ]
});

Both of these solutions feel hacky and very wrong. What am I doing wrong with setting itemController in the ArrayController itself?

like image 648
Jeremy Gillick Avatar asked Jan 13 '23 16:01

Jeremy Gillick


2 Answers

The itemController from the controller and the each view, don't have relation. Although that do the same thing: wraps an object in a specified controller.

Your first implementation throws Cannot call method 'lookup' of null, because the container instance of the AddressesController is null. The container is setup when you create a controller by other container. This is abstracted for you, if you use:

this.controllerFor('addresses')

instead of

 App.AddressesController.create ...

So this work http://jsfiddle.net/marciojunior/GRaa5/

1) Pass itemController in the #each handler

Like I said before, itemController from controller, and each helper, don't have relation. So you can mix him.

{{#each addresses itemController="address"}}
    <li>{{line1}}, {{city}}</li>
{{/each}}

See this in action http://jsfiddle.net/marciojunior/spA9Q/

2) Add a container property to the ArrayController

This work because of the my previous explanation,

because the container instance of the AddressesController is null

Finishing the @colymba example works, because before of the setupController:

setupController: function(controller, model){
    controller.set('content', model);
}

ember provide a controller in that hook using the controllerFor, then the container is present in the generated controller too.

like image 99
Marcio Junior Avatar answered May 19 '23 09:05

Marcio Junior


Looking at the fiddle, it wasn't really the best way to go at it. Giving the code a quick clean up, I got this fiddle working: http://jsfiddle.net/colymba/kV8LM/

What I did is, add a route AddressesRoute, which IndexRoute redirects to:

App.Router.map(function() { 
  this.route("addresses");
});

App.IndexRoute = Ember.Route.extend({
    redirect: function() {
        this.transitionTo('addresses');
    }
});


App.AddressesRoute = Ember.Route.extend({
    model: function(params){
        return [
            App.Address.create({'line1': '700 Hansen Wy'}),
            App.Address.create({'line1': '900 Hansen Wy'})
        ];
    },
    setupController: function(controller, model){
        controller.set('content', model);
    }
});

Using naming conventions helps, with the AddressesRoute, Ember instantiate automatically the Controller and the View.

Changing the View accordingly, its name to addresses and updating the {{#each}} to loop over content:

<script type="text/x-handlebars" data-template-name="addresses">
     <h2>Index Content:</h2>
    <p>
        Length: {{content.length}}
    </p>
    <ul>
    {{#each addresses in controller}}
        <li>{{line1}}, {{city}}</li>
    {{/each}}
    </ul>
</script>

The {{#each}} loop could also just be written {{#each controller}} since it will look for content or model by default. In both cases, controller tells the View that it should access its Controller to find the content for the {{#each}} loop, otherwise the itemController isn't used (since it doesn't exist on the View). (which relates to what @intuitivepixel said)

The model and controller are the same as you had:

App.Address = Ember.Object.extend();

App.AddressController = Ember.ObjectController.extend({
    city: function(){
        return "San Francisco";
    }.property()
});

App.AddressesController = Ember.ArrayController.extend({
    itemController: 'address'
});

App.IndexController = Ember.ObjectController.extend();
like image 40
colymba Avatar answered May 19 '23 10:05

colymba