ObjectController and ArrayController - ember.js

I am learning emberjs form trek.github.com. That tutorial used both Em.ObjectController and Em.ArrayController. And There is also Em.Controller.
I am confused when to use them, I guess Em.ObjectController is for single object, Em.ArrayController is for array and Em.Controller is just for ApplicationController.
Is there any blessed rule for when to use which?

Usually, if your Controller represent a list of items, you would use the Ember.ArrayController, and if the controller represents a single item, you would use the Ember.ObjectController. Something like the following:
MyApp.ContactsController = Ember.ArrayController.extend({
content: [],
selectedContact: null
});
MyApp.SelectedContactController = Ember.ObjectController.extend({
contentBinding: 'contactsController.selectedContact',
contactsController: null
});
Then in your Ember.Router (if you use them), you would connect the two inside the connectOutlets function:
connectOutlets: function(router) {
router.get('selectedContactController').connectControllers('contacts');
}
Edit: I have never used the Ember.Controller. Looking at the source code, it seems like you might want to use this if you are building a custom controller that doesn't fit in with the two other controllers.

The general rule is that it depends on model from route.
If model is an array then you should use ArrayController. It will allow you to implement in easy way sorting or filtering in future. ArrayController is connecting usually ObjectControllers.
When your model is an instance of Ember Object then you should use ObjectController. It takes place when you are using for instance ember data. With Objectcontroller you can access model properties directly. You don't have to write model.property each time.
App.ApplicationRoute = Ember.Route.extend({
model: function() {
return Ember.Object.create({name: 'Mathew'});
}
});
My name is {{name}}
Finally, when one doesn't have model there is an ideal situation to use just Ember.Controller. It is not going to allow direct access to model properties as ObjectController.

Related

How is Sorting achieved in an Ember model without using Array Controller?

Every google result is about an ArrayController sorting. Need a sorting mechanism without using ArrayController.
There is a model where there are sort params. Like say 'sortOrder' as one of the properties in the model (which will be from a back end).
Will be rendering this model using #each but this should do the iteration based on the sortOrder property and not the model's ID property.
In Ember 2.0 SortableMixin is deprecated and is on its way out too.
In the Controller (not the ArrayController) you may define a new computed property like SortedUsers1,2,3 below:
export default Ember.Controller.extend({
sortProps: ['lastName'],
sortedUsers1: Ember.computed.sort('model', 'sortProps'),
sortedUsers2: Ember.computed.sort('content', 'sortProps'),
sortedUsers3: Ember.computed('content', function(){
return this.get('content').sortBy('lastName');
})
});
The assumption above is that the model itself is an array of users with lastName as one of user properties. Dependency on 'model' and 'content' look equivalent to me. All three computed properties above produce the same sorted list.
Note that you cannot replace 'sortProps' argument with 'lastName' in sortedUsers1,2 - it won't work.
To change sorting order modify sortProps to
sortProps: ['lastName:desc']
Also if your template is in users/index folder then your controller must be there as well. The controller in users/ would not do, even if the route loading model is in users/.
In the template the usage is as expected:
<ul>
{{#each sortedUsers1 as |user|}}
<li>{{user.lastName}}</li>
{{/each}}
</ul>
Here is how I manually sort (using ember compare)
import Ember from "ember";
import { attr, Model } from "ember-cli-simple-store/model";
var compare = Ember.compare, get = Ember.get;
var Foo = Model.extend({
orderedThings: function() {
var things = this.get("things");
return things.toArray().sort(function(a, b) {
return compare(get(a, "something"), get(b, "something"));
});
}.property("things.#each.something")
});
You just need to include a SortableMixin to either controller or component and then specify the sortAscending and sortProperties property.
Em.Controller.extend(Em.SortableMixin, {
sortAscending: true,
sortProperties: ['val']
});
Here is a working demo.
In situations like that, I use Ember.ArrayProxy with a Ember.SortableMixin directly.
An ArrayProxy wraps any other object that implements Ember.Array
and/or Ember.MutableArray, forwarding all requests. This makes it very
useful for a number of binding use cases or other cases where being
able to swap out the underlying array is useful.
So for example, I may have a controller property as such:
sortedItems: function(){
var items = Ember.ArrayProxy.extend(Ember.SortableMixin).create({content: this.get('someCollection')});
items.set('sortProperties', ['propNameToSortOn']);
return items;
}.property()
Like so: JSBin

Sorting parent's model the ember way

In a simple, standard ember.js route ...
this.resource('foo', function () {
this.route('show');
this.route('new');
});
I have a foo template that shows a new button (link to foo/new route) and the foo.bar data of the foo objects (with links to foo/show in a sorted way using the foo controller)
import Ember from 'ember';
export default Ember.Controller.extend({
sortProperties: [ 'bar:desc' ],
sortedList: Ember.computed.sort('model', 'sortProperties')
});
The routes foo/show and foo/new do the obvious, nothing special.
I don't need or want a "real" foo/index route, since all my navigation needs are catered for in the foo route and template. Therefore I want to transitionTo from foo/index whenever I enter that route to the first (as in given by the sorting above) foo/show route (or foo\new if there is nothing to show).
I've learned that "the ember way" is to transitionTo out of the route, but never ever out of the controller.
Now, my problem is, that I get access to the unsorted data very easily (it's the model of my parent route, that I can access via modelFor) and I can sort this "manually", but I'd rather use the already sorted data from my parent's controller. Now, I'm at a bit of a loss which hook to use in the route to be sure that my parent route's controller's computed properties are ready - and it doesn't feel right. So, what would be the ember way to do this?
Edit: Non-working example at http://emberjs.jsbin.com/hujoxigami/2/edit?html,js,output ...
Use the afterModel hook as you mention in one of your comments.
App.FooIndexRoute = Ember.Route.extend({
afterModel: function(posts, transition) {
if (posts.get('length') > 0) {
this.transitionTo('foo.show', posts.get('firstObject'));
}
}
And there is nothing wrong with sorting your model before you return it from the FooRoute. (Even better if you can receive it already sorted from your API/data storage)
App.FooRoute = Ember.Route.extend({
model: function() {
// not fully functional, just illustrative
return [{id:1, name:"Hans", detail:"german"},
{id:3, name:"Henri", detail:"french"},
{id:2, name:"Harry", detail:"english"}].sortBy('name');
}
});
The sorting properties in the controller remain relevant if you want to provide sorting controls for the user.

How to structure a multi-record Ember app with named outlets?

I'm trying to build a Tweetdeck-like UI to arrange items from a central library into categories. I really need help wrapping my head around the canonical way of using Ember's router.
Essentially, I have a search UI, which allows the user to open zero or more categories simultaneously. The categories show a list of items, which the user can add to from a central library on the right. By completely ignoring the router and the URL, I have managed to hack together a semi-working proof of concept. Now I want to go back and try to do it the Ember way. Below is a high level sketch of what I am trying to accomplish:
If I understand correctly, the desired URL scheme would be a comma-separate list of model IDs that are currently open. I got a good idea of how to approach that from another question, How to design a router so URLs can load multiple models?.
Unfortunately, there are a few concepts I do not understand:
How do I construct my templates and router, such that the library is displayed with its own model and controller? I assume a named {{outlet}} is the way to go, but I am completely lost when it comes to the renderTemplate configuration. Or perhaps I should use {{render}} instead? In either case, I do not understand the router's role in this situation.
EDIT 1/28: I've added an updated fiddle that includes a standalone library route/template and documents my attempts to render it into the categories template. How does Ember expect me to give the library template its model when I try to embed it into another route? I've tried both {{outlet}} with renderTemplate and {{render}}, but in both cases, I am stuck when it comes to specifying the model.
Using renderTemplate:
App.CategoriesRoute = Ember.Route.extend({
renderTemplate: function() {
this.render('categories');
this.render("library", {
into: "categories",
outlet: "library",
controller: "library",
});
},
});
When my controller receives a request to open a category, how do I communicate that to the router? How is the hash path updated? Who is responsible for loading the appropriate model(s)? I assume I should start with transitionTo or transitionToRoute, but I do not understand the router's role here either. Specific questions:
How do I de-serialize multiple, comma-separated models from the URL? Do I just split on the comma or is there a better way?
Once I get the IDs from the URL, how do I make my model hook return multiple records? Do I just shove them all into an Ember array?
When the controller gets the ID of a new record to open, how do I communicate that to the router?
I've tried to work this out on my own and have read the Ember documentation many times, but I am afraid it is simply over my head. I put together a minimal (currently non-functional) fiddle to outline my thoughts and point out where I am stuck. I would appreciate any help anyone could offer.
this.render does not accept a model parameter, but you could pass the model through the controller property instead, this makes sense to do since the Controller is really a proxy for the model at any rate
App.IndexRoute = Ember.Route.extend({
var self = this,
notesController = self.controllerFor('notes').set('content', self.store.find('notes'));
renderTemplate: function() {
this.render('notes', {
controller: notesController,
into: 'index',
outlet: 'notes'
});
}
});
You could also try something like this from this link.
App.IndexRoute = Ember.Route.extend({
model: function() {
return Ember.RSVP.hash({
books: this.store.findAll('book'),
genres: this.store.findAll('genre')
});
},
setupController: function(controller, model) {
controller.set('books', model.books);
controller.set('genres', model.genres);
}
});
Here, they load multiple models into one route using Ember.RSVP.hash and then using setupController they set each model (Rails: instance variable?) individually.
I'm assuming using this method that you could load as many models as you needed.

Why does my non-model backed template persist state when I reload it?

I have a route that loads all my models, and a nested route that allows the user to add a new model.
App.Router.map(function() {
this.resource("foo", {path: "/foo"}, function() {
this.route("add", {path: "/add"});
});
});
My add template looks like this (very basic)
{{input value=wat}}
Here is the linkTo from my index template
{{#linkTo 'foo.add'}}Add A New Model{{/linkTo}}
When I click the add button I simply create the model using $.ajax and transition back to the list route. All works great, until I click the "add" link again.
When the add route loads up the template from above the 2nd time it still shows the "wat" value I entered previously. I was hoping it would not persist any state as each time I "add" a new model it should be unaware of any previous model data.
How can I achieve this with ember 1.1.2+
Update
The approach I took was to reset each element in the setupController method of the route (as this is invoked each time you load the controller).
App.FooAddRoute = Ember.Route.extend({
model: function(params) {
var parentId = 1;
return Ember.Object.create({'bar': parentId});
},
setupController: function(controller, model) {
this._super(controller, model);
controller.set('bazz', '');
}
});
The quick and dirty answer is you want to use a model on the route. If you didn't, you'd have to manually blank out the values on the controller. Ember builds up singleton controllers. This generally is super convenient and very performant.
Singleton controllers keep state. The best way to keep them stateless is to have them backed by a model (return an empty object from the model hook, and don't have the values defined on the controller). By returning something from the model hook it will use an ObjectController (or you'll need to update your code to use an ObjectController on your controller). Then all values will be proxied to the model instead of being stored on the controller.
http://emberjs.jsbin.com/OPaguRU/1/edit

Data sharing in ember

I'm trying to understand how to share data between my controllers/routes.
I have an application that's displaying data about companies. Here are the routes I want:
/ summary info
/companies list of all companies with some more detail
/companies/:id details about a single company
Now, the data required for all three routes is contained in a single array of company data. So, I want that data to load when the app starts up, and then be used for each route. There are also additional methods I will need on the controller that should be shared.
It is clear that the second and third routes are nested, so I can share the data from the CompaniesController when I link to a specific company, by passing in that company's data:
{{#linkTo 'company' company}}{{ company.name }}{{/linkTo}}
But the summary route is where I'm getting stuck. The two options I've come up with:
Create the CompaniesController with any additional methods I need, and create the IndexController by extending it
App.IndexController = App.CompaniesController.extend({});
Then, as far as I can tell, both routes will need to find the models:
App.Router.map(function() {
this.resource('companies');
});
App.CompaniesRoute = Ember.Route.extend({
model: function() {
return App.Company.find();
}
});
App.IndexRoute = Ember.Route.extend({
model: function() {
return App.Company.find();
}
});
Seems like there should be a better way, since I'll have to repeat this for each new route I add (e.g. /revenue).
Nest the summary route within the companies resource, and give it a path of '/'. What I don't like about this is that the 'nesting' of my UI doesn't match the data. It also seems like I'll have to redefine the model property for each route.
Is there another option that's better?
tl;dr: How should I share data across controllers?
To share data beetwen controllers the correct way would be to use the needs API.
Assuming your CompaniesController has all the data that you want to make available to other controllers you should define it via needs, this can be a simple string, or an array of strings if you define more then one.
App.MyController = Ember.ObjectController.extend({
needs: ['companies'],
myFunction: function() {
// now you can access your companies controller like this
this.get('controllers.companies');
}
});
To make things more easy accessible you could additionally define a binding, for example:
App.MyController = Ember.ObjectController.extend({
needs: ['companies'],
companiesBinding: 'controllers.companies',
myFunction: function() {
// now you can access your companies controller like this
this.get('companies');
}
});
Hope it helps.