John Ashenden
John Ashenden

Reputation: 23

Ember.js problems binding content of array controller to content of a different array controller

We're having trouble linking the content of one array controller with the content of another array controller in Ember.js. We've scoured Stackoverflow, but no solution seems to work for us.

The scenario is simple. We have a model called "Campaign". The model is pretty basic and looks like this...

Campaign Model:

App.Campaign = DS.Model.extend({

    name: DS.attr('string'),
    description: DS.attr('string'),
    opts: DS.attr('string'),
    starred: DS.attr('boolean'),
    keywords: DS.hasMany('App.Keyword')

});

// Create dummy campaign array

App.Campaign.FIXTURES = new Array();

// Populate dummy campaign array

...

The Campaign controller is also basic. It's a simple ArrayController with nothing going on inside it ...

Campaign Index Controller:

App.CampaignsIndexController = Em.ArrayController.extend({

});

For the sake of this question, we've stripped our application template down to the essentials. As you can see below, the application template has a sidebar. In the sidebar are two views. One view is for the site's navigation, the other view is for "Starred Campaigns". This view will contain a subset of the overall collection of campaigns.

Application Template:

<!-- ================================================== -->
<!-- ======================= APP ====================== -->
<!-- ================================================== -->

<div id="app" class="">

    <!-- ================================================== -->
    <!-- ==================== SIDEBAR ===================== -->
    <!-- ================================================== -->

    <div id="sidebar">

        {{ view App.NavigationView }}

        {{ view App.StarredCampaignsView controller="App.StarredCampaignsController" }}

    </div>

    <!-- ================================================== -->
    <!-- ====================== PAGE ====================== -->
    <!-- ================================================== -->

    <div id="page" class="">

        {{ outlet page }}

    </div>

</div>

We have a controller for Starred Campaigns. This is where things start to get confusing. In our controller, we bind the content of the ArrayController to the content of the CampaignsIndexController, which holds the array of all Campaign models.

Starred Campaigns Controller:

App.StarredCampaignsController = Em.ArrayController.extend({

    content: [],

    contentBinding: Ember.Binding.oneWay('App.CampaignsIndexController.content')

});

The view of for Starred Campaigns is also very simple...

Starred Campaigns View:

App.StarredCampaignsView = Em.View.extend({

    templateName: 'templates/layout/starred-campaigns',

});

And this is what the template for Starred Campaigns looks like. It is attempting to loop over the items in the controller.content and pass them off to another view.

Starred Campaigns Template:

<h5 class="sidebar-title">Starred Campaigns</h5>

<ul class="starred-campaigns clearfix">

    {{# each item in controller.content }}

        {{ view App.StarredCampaignItemView itemBinding="item" }}

    {{/ each }}

</ul>

However, despite all of this, the content in StarredCampaignsController remains empty, even when we populate the App.Campaign array that is represent by App.CampaignsIndexController. See any problems with our approach? I feel like we're missing something really simple here.

Upvotes: 1

Views: 2835

Answers (1)

Wildhoney
Wildhoney

Reputation: 4969

In Ember, for controllers to require other controllers, you need to use the needs property. This is always changing in Ember: in previous versions it was the responsibility of the router with this.controllerFor, and with even older versions, the approach was more akin to the approach you appear to be attempting to take. However, the final approach has long since been deprecated, and instances of controllers/models/views are no longer held on App directly, only their object representations.

To dive straight in with the needs, you need to tell your App.StarredCampaignsController controller that it requires the App.CampaignsIndexController like so:

App.StarredCampaignsController = Em.ArrayController.extend({
    content: [],
    needs: ['campaigns_index']
});

You now have access to the App.CampaignsIndexController controller in your starred campaigns view: {{controllers.campaigns_index}}.

See simple JSFiddle: http://jsfiddle.net/AGJfB/

The reason why yours isn't working is because you've reference the object of the controller, as opposed to its instance:

Ember.Binding.oneWay('App.CampaignsIndexController.content')

Whilst you have an App.CampaignsIndexController on App, that's its object. Ember will create the instance for you when you access it through any of the Ember ways (this.controllerFor, needs, navigating to it via the URL, etc...).

In days gone by we could use:

Ember.Binding.oneWay('App.campaignsIndexController.content')

However, that's such a bad way to do things, because you're referencing an absolute path to the controller. Ever since Ember pre 1, I've never had to use Ember.Binding.oneWay, but there still will be cases where you need it. When you find yourself using it though, it's probably wise to ask yourself if it's necessary, and whether there's a better way to do it.

I hope I've explained myself well. Please feel free to ask any questions!

Upvotes: 1

Related Questions