Passing associated models to actions in EmberJS - ember.js

I have a hard time understanding the logic behind the way EmberJS deals with passing associated models as action arguments.
In a template I have the following:
{{#each account.people as |person|}}
<p>{{person.name}}</p>
{{#each person.relationships as |relationship|}}
<p {{action "doStuff" person relationship relationship.otherPerson}}>
{{relationship.otherPerson.name}}
</p>
{{/each}}
{{/each}}
The weird thing is that in the doStuff action
person is a Model
relationship is a Model
otherPerson is a Promise
I don't know if it's an expected behavior and how to deal the right way with it if so.
Note: This is a contrived example and passing all those arguments is weird, it's just to illustrate my point.
Note2: I'm aware I could use Promise.resolve(otherPerson) in my action but I don't see why I should use a low-level call like this.
Note3: My question is "Since I'm accessing otherPerson.name why is otherPerson still a promise to be resolved when I'm in the action? Is there a way to always have a resolved model without doing hacky stuff?"
Thanks!

I'm not sure what the actual question here is, but...
relationship.otherPerson
is a promise because Ember Data lazy loads relationships. HTMLBars templates are able to understand and wait for resolution on relationships, but they're still promise-like under the hood.
If you use {async: false} in your model definition
e.g.
export default Model.extend({
name: attr('string')
author: belongsTo('person', {async:false})
});
This is telling Ember Data that the data is already loaded, and that it can treat the relationship as synchronous. No promise is returned when accessing relationship.otherPerson. It does require you to already have the relationship data loaded, either side-loaded in you route or from being confident it's already in the Store.
It is a bit confusing how Ember allows you to happily access relationships with dot syntax in the template, but in code you need to treat it as a promise.

Related

Ember.js: Loading related models directly

I dont understand how ember loads related models.
Lets say thats my model:
export default DS.Model.extend({
title: DS.attr('string'),
description: DS.attr('string'),
states: DS.hasMany('state', {async: true})
})
I load this on of my outer routes. When navigating though an ember-app (into nested routes), model-contexts are often provided for routes not by the model-hook of the route but with the link-to helper (when using dynamic-segments, the model-hook will be ignored). When the target route has something in its template like {{#each model.states as |state|}}, ember will load automatically the related model-entrys from (in that case)the state-model. (How and why? - Just because of the each in the template?
When accesing an dynamic-route directly, the model is not given and the model-hook of the dynamic route will be called. So loading my model is easy: just override the model hook and load record with the url parameter (return this.store.find('item', {title: params.item_title})). But no related models will be loaded. How can I do that manually and what (and when) is the way ember do it by default?
How does Ember know to automatically fetch relationships?
ember-data allows you to define relationships (currently only belongsTo and hasMany) with async option set to true or false. Based on this option, after fetching the model from the API (via find method), ember-data will expect relationships object either directly in the response JSON or not. You have async: true (which is rather a common and supported way of handling relationships), therefore ember-data assumes that in your JSON response it gets the ids of states, but not necesseraily the states themselves.
If you define your hasMany as async: true, it always returns promise. It means, that if you make something like this:
this.get("item").get("states")[0]
Will not work, as get("states") will not return an array, but the promise to fetch this array. However, Handlebars are smart (as the get and set methods of Ember) and it can find out what is a promise and wait for it to resolve before using it content. Therefore if your template contains:
{{#each model.states as |state|}}
Handlebars finds out that states is promise, wait for it to resolve, and after resolve they use its content as an array. Very similar behaviour can be found using belongsTo method. Assuming that your item has one state, if you use the code as follows:
this.get("item.state.somePropertyOfState")
Even if you did not fetched the state and currently you don't know what is the somePropertyOfState value, ember get will find out it's a promise and fetch it for you automatically.
How can I manually fetch relationships?
There are couple of ways to do it.
First one is to explicitly fetch them in ember code:
this.get("item.states").then(function(states) {
# now you have fetched the states that the item has, and moreover
# they are accessible in states variable
});
Second, you can let Ember do it automatically for you as I described formerly (e.g. via template).
Third, you can send the relationships with your response using a mechanism called sideload. This will significantly reduce the number of API requests. When you allow ember fetch your relationships, ember is performing one request per one relationship object, which means that if you have ten states that belongs to item, the API will be hit ten times. However, if you sidelod the states while fetching the item, the request will be sent only once. Take a look here to get more info about that.
Sorry for a long post, but I hope that I clarifed a bit.

filtering hasMany association in Ember

I'm brand new to Ember and stuck on something that seems very basic. So far in my e-commerce application, I have two models, Product, and Style; a Product has many Styles. For each product, I want to list a subset of the styles (e.g., those that are in stock or out of stock). Coming from a Rails background, I thought I would create a model method in Product called stockedStyles, which filters its styles and returns only those with stocked=true. That didn't work, so I tried another approach of using a controller method, but struck out there too.
Here's the code so far: http://jsbin.com/mufumowabi/1/edit?html,js,console,output
While I would definitely like to know the best practices way of doing this, I'm also curious about other approaches people can suggest that would work but are not recommended -- and why they aren't recommended. Especially when I'm learning something new, I like knowing all the different ways you could do something and then choosing the best/cleanest one.
If there's a tutorial that covers this sort of thing, please send it my way. I couldn't find anything that does this sort of thing, even though it seems so basic.
Lastly, I've found debugging Ember to be somewhat of a black box. For example, for the non-working code posted here, the JS console just says "error". Tips on how I would get more information about why what I'm doing is wrong would be most appreciated as well.
TIA,
fana
I feel your pain. I too came from a rails background expecting similarities in the implementation only to get confused initially. Nobody is ever exaggerating when they claim Ember requires a very large learning investment, but trust me if you stick around it's totally worth it.
Real quick let's take care of a simple goof: You can assign an object property to be either Ember.computed, or function() { /***/ }.property('sdf'); You can't have both. So make that computed function either:
unstockedStyles: Ember.computed.filterBy('styles', 'stocked', false);
or
unstockedStyles: function() {
return this.get('styles').filterBy('stocked', false);
}.property('styles.#each.stocked')
but you can't do both at once.
Ember Models vs Rails Models
Next, the difference with Ember, coming from rails perspective, is that models in Ember.js are intended to be extremely light, serving only as a minimal binding between the data source and the framework overall. Rails takes quite literally the opposite approach, encouraging a very heavy model object (and this is still a large source of contention in the rails community).
In ember.js, the model method helpers are intended to be placed in the controller objects (again, counterintuitive coming from rails). Moving that out, you'll want your models to look like this:
App.Product = DS.Model.extend({
title: DS.attr(),
styles: DS.hasMany('style', { async: true })
});
App.Style = DS.Model.extend({
desc: DS.attr(),
stocked: DS.attr("boolean")
});
The reason for this difference from Rails is that the role of controllers in Ember.js is for "decoration" of your object, whereas in Rails its to handle incoming/outgoing data logic. For each page, you may want to render the same data in different ways. Thus, the model will remain the same, and the controller takes on the burden of encapsulating the extra fluff/computation. You can think of decoration in the same way you were taught the inheritance pattern in OO, with a slight twist:
Let's say you want to have a Person base class (your Ember model), but then you extend it to Teacher and Student subclasses (your controllers) in order to add an additional propertiy that may be from the same type but is not modeled in the same way. For example, Teachers and Students have a many-to-many relationship to courses, but you want to model Students as attending their courses, where Teachers are instructing them. The controller acts as a way to provide such a filter.
ArrayController vs ObjectController
As for the controllers, computed properties for individual records should be placed in the ArrayController's associated ObjectController (we'll call it ProductController), so your controllers now look like this:
App.IndexController = Ember.ArrayController.extend();
App.ProductController = Ember.ObjectController.extend({
unstockedStyles: Ember.computed.filterBy('styles', 'stocked', true)
});
Finally, while Ember.js can automatically associate ObjectControllers with their associated ArrayController for resources defined in your router, you're loading a Product array on the IndexController, so you need to tell IndexController to use ProductController for its item behavior:
App.IndexController = Ember.ArrayController.extend({
itemController: 'product'
});
App.ProductController = Ember.ObjectController.extend({
unstockedStyles: Ember.computed.filterBy('styles', 'stocked', true)
});
Handlebars Binding
Not much here except for a small mistake: while you've enabled a computed property correctly in the proper controller, your {{#each}} loop is instead bound to the original styles array. Change it to use the new property:
{{#each unstockedStyles}}
<li>
{{desc}}, in stock: {{stocked}}
</li>
{{/each}}
And now you're good to go!
JSBin of fixes here: http://jsbin.com/sawujorogi/2/edit?html,js,output

Concurrent states with Ember.router

I'd like to implement this statechart in Ember.js v1.5.x using Ember.Router, but I have problems with the concurrency and history mechanism.
Basically when I activate the Summary route/state I'd like to transition to No changes made and Transient state in the same time. How can I achieve this?
P.S. I know that e.g. stativus have these capabilities but don't know how to use it with Ember.js routing. An example would bee good.
(image source: Ian Horrocks: Constructing the User Interface With Statecharts p.153).
:)
Yeah statecharts are lovely and all, but Ember actually affords sub-states through computed properties.
I'm not overly familiar with state charts, and I'd really need to consume the resources (horrocks) you mentioned here (https://github.com/emberjs/ember.js/issues/4767#issuecomment-41458710) before I'd be fully conversant in the nomenclature of that particular example (which I can do if you'd like).
To that end, and having said that, please take my answer with a grain of salt, because I may not fully understand the context. I just hope to help.
So in Ember you have routes. Those routes explain the interface of your application. These will effectively be your states. Routes are not your actions, or events. They provide a URL for your app to present itself to the world.
So, state A seems to be presenting the Students. You have two sub-states in there... 0 students and >0 students. You would handle these with the same Route (call it StudentsRoute), because they're both about the same set of data, just different substates of it. The route would have a path called /students probably. At that point, you'd have a controller gets fed a model by the router (the list of students), so to that end, this controller would be an extension of Em.ArrayController.
This array controller (auto-named StudentsController, extends Em.ArrayController) automatically has a 'model', and that model, once resolved, is the students "array".
In StudentsController, you could easily have a computed property called zeroCount which represents the state of zero or not about the model. Computed properties automatically stay up to date. That'd be defined like this:
App.StudentsController = Em.ArrayController.extend({
zeroCount: function() {
// zeroCount is true if zero, otherwise false
return this.get('model.length') == 0;
}.property('model.length');
});
In your students template, you could conditionally render one of two sub-templates depending on this zeroCount state... you'd do that like this:
{{#if zeroCount}}
{{partial "noStudents"}}
{{else}}
{{partial "someStudents"}}
{{/if}}
Mind you, for this example, that'd be somewhat overkill... you probably don't need to render other templates (partials) like that.. there's an easier simpler way to do it because this is a common pattern in ember (rendering a list, and optionally rendering something else if there are no items in it, without needing the zeroCount property).
{{#each model}}
<p>This renders against each student... <br>
so if your students have a property called name, <br>
then you could just write {{name}} and it'd render the
students name</p>
{{else}}
<p>This renders when there are no students</p>
{{/each}}
You'd put a delete link on each of those items... and the live bound properties handle all the states for you... (thus, when model has zero items in it, the template goes into the else block of the each... otherwise it goes into the main block).
The delete action, handled by something like Delete inside your #each model template (handlebars) directive goes to the controller and looks for an action inside of it called, unsurprisingly, delete... and that'd look like this:
App.StudentsController = Em.ArrayController.extend({
actions: {
delete: function(itemToDelete) {
itemToDelete.delete();
// assuming the model "class" understands delete
}
}
});
The edit state would have its own route... possibly a nested route on the students, called edit, possibly not depending on if you wanted the list to appear on the screen while the edit page appears...
The "changes made" state is effectively handled not on the route, but on the model... as it should be... the model is responsible for persisting the object graph, or telling the view and controller whether or not the model has changed (Ember Data, for example, afford isDirty as a state on each model instance that can tell you whether it has changed or not)...
Hopefully this whets your appetite. I recommend going through some of the examples on the Ember site... they really do help, and following the Ember TODOMVC app if you haven't checked that out...
Ember thrives on these kind of flow-based state driven UIs... check out Tom and Yehuda's keynote at confreaks if you haven't already... they talk about flows in exactly the same way you're talking about these states and sub-states.
Hope that helps.

How to link to nested resources in Ember.js?

Assume you have the following routes in an Ember application.
App.Router.map(function() {
this.resource('series', function() {
this.resource('serie', { path: '/:serie_id' }, function() {
this.resource('seasons', function() {
this.resource('season', { path: '/:season_id' }, function() {
this.resource('episodes', function() {
this.resource('episode', { path: '/:episode_id' });
})
});
});
});
});
});
How would I link to a specific episode using the linkTo helper that Handlebars provides? In other words, how does Ember figure out what the other parameters of the URL should be, that is, the serie_id and episode_id? The documentation states that I should pass an episode model to the episode route as shown below.
{{#linkTo "episode" episode}}
This is to link to the following URL structure.
/series/:serie_id/seasons/:season_id/episodes/:episode_id/
When I use the linkTo helper like that, Ember throws an error telling me that it cannot call get with id on undefined. I assume that it uses the episode model to figure out what the serie_id and episode_id are and my guess is that the model needs to conform to a specific convention (structure or blueprint) for Ember to find these ids.
These are the aspects that I find most difficult about Ember. It isn't very transparent even if you use Ember in debug mode. Any pointers or references are much appreciated.
UPDATE 1: After some digging, I found out that the route's serialize method is a key element in accomplishing this. However, when I use the linkTo helper as illustrated above, the model passed to the route's serialize method is undefined for some reason (even though it is not when passed to the linkTo helper. The question that led to this discovery can be found here.
UPDATE 2: It turns out that the serieSeason route's serialize method receives the wrong model, an episode instead of a season, when the link is generated. It isn't clear, though, why it is receiving the wrong model. Where does the model parameter of the serialize method come from?
UPDATE 3: The linkTo helper works fine if I return static data from the serialize method of each route involved, which means that the linkTo helper isn't involved in the problem.
It turns out that the answer could be found in the properly documented source of Ember ... because that is what one does after searching the web for several days.
The answer is simple. The linkTo helper accepts more than one model. For each dynamic segment of the destination URL, you pass a corresponding model. Each passed model will become the model of the corresponding route in the destination URL. In the example that I describe above, this results in the following.
{{#linkTo "episode" serie season episode}}
The serie model will be passed to the serie route, the season model to the season route, and the episode model to the episode route. What confuses many developers is that the route's model hook isn't triggered when you use the linkTo helper. This isn't too surprising if you realize that the developer provides (or can provide) the model for the corresponding route by passing one or more models (or zero).
Because there isn't much documentation for deeply nested resources, it wasn't trivial to find out how the linkTo helper does its job under the hood. Diving in Ember's source definitely helps getting up to speed with the framework.

accessing the model from the template

Playing around with ember, I found that sometimes the model is stored on the controller's content property, sometimes the model is directly available on the controller as well. I do not understand however, when this is the case.
Let me explain it by an example which I found when assembling my ember MVC.
Setup A - The start
I defined a custom Member object, corresponding MemberRoute, MemberView classes and a template with the name member.
The Member object had some attributes such as id, nickname, etc.
NOTE: no controller of the form MemberController was defined, thus by ember's convention, it provides the controller on its own.
Setup B - The customization
Same as setup A, but now there is a MemberController defined that contains some action methods that are triggered from within the template.
The strange behaviour (resp. what I do not completely understand)
in setup A, I can refer to the Member's attributes directly with {{id}} or {{nickname}}.
in setup B, I have to use {{content.id}} or {{content.nickname}}
As documented in ember's documentation, MemberView does
setupController : function(controller, member) {
controller.set('content', member);
},
So, could somebody help me to understand why the difference and where the difference is? Currently, my guess would be either
that the context of the template is different (possibly there is a code piece missing in the setup of the controller?)
or
the default controller that is provided by ember automatically, has some additional magic that is not directly avaiable for customized controllers.
Any help to understand this is highly appreciated. It already took my quite a while to come as far as this. I first thought it could be the modularization introduced by the project setup with requireJS (well, I still think that could have a influence). Ember is v1.0pre4.
Thanks in advance!
Patrick
So, could somebody help me to understand why the difference and where the difference is? Currently, my guess would be either
that the context of the template is different (possibly there is a code piece missing in the setup of the controller?)
or
the default controller that is provided by ember automatically, has some additional magic that is not directly avaiable for customized controllers.
It's hard to say for sure without seeing your code, but my best guess is that your MemberController extends Ember.Controller. The default provided by ember (in this scenario) would have been an Ember.ObjectController. If that's what you want, change your MemberController definition to:
App.MemberController = Ember.ObjectController.extend({
myProperty: 'value'
});
An objectController acts as a proxy to it's content property, typically that is an ember model. So if things are wired up correctly you should never need to access a model via the 'content` property. If you ever see something like:
{{content.id}} or {{content.nickname}}
it's a sign that you should change to an ObjectController. See EMBER GUIDES: REPRESENTING A SINGLE MODEL! for a more detailed explanation.
an ObjectController acts as proxy to the object set to the controller's content. When no controller is defined, Ember will create a controller for you and set its content by default to whatever object is returned by the model() function, if defined, in the route. The behaviour should be the same whether you define your own controller or let Ember define one for you
The default context in the template is the controller itself i.e. this = an instance of your controller or the generated one. When you try to access nickname in that context, Ember will first try to resolve it against the controller itself and if nothing is found, it resolves it against its content, i.e the object if you already manually set it to the controller's content.
Finally, there is no default implementation of the model() function in the Route except when you're using dynamic urls, say /foo/id that resolves against /foo/:id, Ember uses the id provided to load a Foo object with the id provided, thus providing a default implementation to the model() function. At the end it boils down to the same mechanism, only automated for your convenience.
I suggest you listen to this for more insights on how things are automated for you by Ember. But when it comes to the content being displayed, there is no magic you have to manually wire the content of the controller.