I'd like to create a page where on the left I have fixed view (some filters) which are applied to results on the right.
For example on the left are filters to filter movies by genre, title, year of creation.... On the right are different charts and tables, which update based on the filters selected.
So I was thinking of having a fixed view on the left, then on the right an outlet which would change based on the route.
Is this the correct way? If it is, I can't figure out how to communicate filter changes to controller on the right.
This JSFiddle shows a similar setup : http://jsfiddle.net/DEHcK/2/
In the ContentRoute -> setupController I get an instance of NavigationController, but then I can't figure out how to get changes to someValue.
In the example above, how can ContentController get the changes to someValue in NavigationController?
Is this the correct way to implement the app I described in ember?
JavaScript:
window.App = Ember.Application.create();
App.Router.map(function () {
this.route('content');
});
App.ContentRoute = Ember.Route.extend({
setupController: function (controller) {
controller.set('navigationController', this.controllerFor('navigation'));
}
});
App.ContentController = Ember.ObjectController.extend({
navigationController: null,
observerOfSomeValue: function () {
this.set('observedValue', this.get('navigationController.someValue'));
}.observes('navigationController', 'navigationController.someValue'),
observedValue: null
});
App.IndexRoute = Ember.Route.extend({
redirect: function () {
this.transitionTo('content');
}
});
App.NavigationView = Ember.View.extend({
init: function () {
this._super();
this.set('controller', this.get('parentView.controller').controllerFor('Navigation'));
},
templateName: "navigation"
});
App.NavigationController = Ember.ObjectController.extend({
someValue: 'xx',
observedValue: null,
observerOfSomeValue: function () {
this.set('observedValue', this.someValue);
}.observes('someValue')
});
HTML:
<script type="text/x-handlebars" data-template-name="application" >
<div>
{{view App.NavigationView}}
</div>
<div>
{{ outlet }}
</div>
</script>
<script type="text/x-handlebars" data-template-name="navigation" >
Change {{view Ember.TextField valueBinding="controller.someValue"}}
<div>observed value in same view: {{controller.observedValue}}</div>
</script>
<script type="text/x-handlebars" data-template-name="content" >
<div style="margin-top: 2em">
observed value in another view: {{observedValue}}
</div>
</script>
I've gone ahead and created you a JSFiddle with a basic implementation of what you're after. I think it's worth running through it all though so that you can get a grip of Ember.
Router
We're just configuring our IndexRoute
at this point where we store all of our songs for the {{outlet}}
.
App.IndexRoute = Ember.Route.extend({
setupController: function(controller) {
controller.set('content', [
Ember.Object.create({ title: 'Stairway to Heaven', genre: 'Metal' }),
Ember.Object.create({ title: 'Ratts of the Capital', genre: 'Post Rock' }),
Ember.Object.create({ title: 'Wonderwall', genre: 'Brit Pop' }),
Ember.Object.create({ title: 'Last Flowers', genre: 'Indie Rock' })
]);
}
});
There's a good chance this code will be replaced with an AJAX call of some sort to your back-end Ruby/PHP. For the time being though, we'll give the IndexRoute
the responsibility of setting up the controller (hence setupController
). This responsibility could just as well lie on the controller itself, and is probably a good idea to abstract out the AJAX call since you'll have many similar AJAX calls.
You may also decide to use Ember's DataStore, which will change the implementation of the controller again.
Index Controller
Next we're going to setup our IndexController
(which is our SongsController
really), we want this controller to have two responsibilities:
For this we create a computed property to filter out the content, since we don't want to actually manipulate the special content
array directly.
App.IndexController = Ember.ArrayController.extend({
content: [],
excludeGenres: [],
filteredContent: function() {
var excludeTheseGenres = this.get('excludeGenres').mapProperty('genre');
return this.get('content').filter(function(model) {
return Boolean(jQuery.inArray(model.get('genre'), excludeTheseGenres) === -1);
});
}.property('excludeGenres.length', 'content.length')
});
The excludeGenres
will take an array of genre objects. For example, if "Post Rock" is contained within excludeGenres
, then we won't show any "Post Rock" related songs, but if it isn't present, then we we'll show them! The IndexController
doesn't have the responsibility of maintaining this array, but it does have the responsibility of filtering its content for when this array is updated.
Genres Controller
Perhaps the most confusing controller in our little application, because it doesn't actually have any content of its own, but rather depends on the IndexController
's content.
App.GenresController = Ember.ObjectController.extend({
needs: ['index'],
genres: function() {
return this.get('controllers.index').mapProperty('genre').uniq();
}.property('controllers.index.length'),
toggle: function(genreName) {
var indexController = this.get('controllers.index'),
genres = indexController.get('excludeGenres'),
genre = indexController.findProperty('genre', genreName);
if (genres.findProperty('genre', genreName)) {
genres.removeObject(genre);
return;
}
genres.pushObject(genre);
}
});
Genre controller's responsibilities can be defined as so:
content
array of the IndexController
and fetch the unique genre names when its length changes;excludeGenres
array when a user clicks on a genre in the list to include/exclude it.To have the toggle
method called, we need to specify an action in our genre view to invoke it when clicked: <a {{action "toggle" genre}}>{{genre}}</a>
. Now whenever a user clicks on a genre, the toggle
method will be invoked, and the genre name passed as the first argument.
Once we're in the toggle
method, it will determine whether the genre is already being excluded. If it is being excluded, then it will be removed, and vice-versa. Once we've added/removed the genre, the filteredContent
computed property will be fired again, and the index view will be updated seamlessly.
The reason the GenresController
doesn't actually have its own content is that it would seem silly to manage two content
arrays when the two have a relationship. Since genres can be determined from the songs present in the application, the controller can gather the information from that list, and just pull out the information it requires -- in our case, genres.
This way, if a song is added/removed, then the genre list can be kept in sync.
Conclusion
I think the answer to your original question is, however, that in order for controllers to be communicating with one another, you need to be specifying what it needs (with needs
).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With