filtering hasMany association in Ember - ember.js

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

Related

Passing associated models to actions in EmberJS

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.

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.

Mixing ember-data and non ember-data models

In my API and on my server I have a model hierarchy like this:
WorkoutPlan->workouts->exercises
I can successfully load that structure with DS.hasMany relationships.
However, my client side view breaks up the plan into weeks so it would be nice to have a client side model structure of
WorkoutPlan->weeks->workouts->exercises
Is it possible to do this if weeks isn't an ember-data model?
If not, should I just transform my JSON so that I can has a hasMany on a Week model (workouts have a week column I could use as a quasi id for the week model).
Or would it be best to keep the current model structure and just somehow filter workouts somehow by week.
Your ember model doesn't have to mimic your server model. There're usually good reasons to de-normalize, minimize the amount of data and simplify the model. For example, the server deals with multiple users, your ember app is likely just concerned with one.
I see two options here. I don't know enough about your model to suggested what's the best.
Add WeekPlan as a model. You could change the serialization logic in your server (if you have an app specific API) or change this during the serialization client side (if this change won't make sense for other API consumers).
Add a filter in your workout router. Also you could have an ArrayController with weeks that simply aggregates the weeks from the workouts in a workout plan.
In general I would lead towards 1, but as I said I don't know enough about your model to make a strong case for either.
Update. Expand on 2
There're two parts to this. The first one is the WeekPlanRoute. That might look something like the following. It's basically responsible to create an array of Weeks and uses that to pass it to pass the workouts to a WorkoutRoute/Controller
App.WeekPlaneRoute = Ember.Route.extend({
model: function(){
// assuming we already have a WorkoutPlan
return workoutPlan.workouts.mapBy('week');
},
);
Then you can navigate to the workouts by using a link-to that passes the week as a parameter:
{{#each}}
{{#link-to 'workouts.index' this}}{{/link-to}}
{{/each}}
In your WorkoutRoute you will filter using that parameter:
Todos.WorkoutRoute = Ember.Route.extend({
model: function(params){
// assuming we already have a WorkoutPlan
return workout.filterBy(params.weekNumber);
}
);
You will also have to change your route to add that dynamic segment for the weekNumber (it has to match that param used above).
this.resource('workouts', {path: '/workouts/:weekNumber'});

Heterogeneous objects in a single URL: are undefined properties set to null?

I am trying to add a "profile management" functionality to my Ember application, using ember-data. For that, my backend / frontend play together in the following way:
The backend returns a profile array with several "profile objects", with well-known IDs, for the logged-in user, in a fixed URL: /api/profile. Currently I have a me object (settings related to currently logged in user) and a my-company object (settings related to the company belonging to the current logged-in user). It is straightforward to add more objects. The objects are not of the same kind, and have different properties. There is only one object of each kind.
I can view and edit each of the objects using different router/controller/template. Most of the functionality is reusable. For the routers and controllers the only thing I need to adapt is the id of the object in the profile array, and the route to the edit controller.
By using this approach, I can easily add objects to the profile, and add templates to display and edit the properties of each object. The only thing I need to do is:
Define the model for the new object
Setup standard routers and controllers based on the already coded mixins
Create the templates
So this is very easy and flexible, which was my motivation to implement this, but I have one worry. Since I am putting together all properties in the Profile model:
App.User = Ember.Mixin.create({
email : DS.attr('string')
});
App.Company = Ember.Mixin.create({
vat : DS.attr('string')
});
// To do this, the profile model includes the User and Company mixins,
// as well as some common properties.
// TODO: Any other way to do this?
App.Profile = DS.Model.extend(App.User, App.Company, {
name : DS.attr('string'),
type : DS.attr('string')
});
I am worried that when writing back to the server (currently I have only tried with FIXTURES) the properties from the Company object will leak to the User object, and vice versa.
So, the question is: if a model has undefined properties (that is, the data coming from the server does not have those properties) will those properties be sent back to server with null value, when serializing the object, or will they not be set at all in the JSON? Mind you that the properties were not there to start with, but the model has "all possible properties, for all different profile objects".
Maybe there is another way of defining the Profile model, without including all properties of all different objects? I have not been able to do this: if a property is not declared in the model, it can not be used in the template/controller (which is the whole point of defining them in the model, I suppose).
I would also like to hear some generic feedback on the chosen approach. I feel I am bending Ember a bit too much by having all this "bunch of different objects" in a common URL, but I also have the impression that this can reduce the complexity of this part of my application.
A demo can be seen here.
First and foremost i have not worked with ember-data, but i am pretty sure that properties that are not defined are also sent to the server. I also think, that this is not a good approach on data modeling. I pretty sure you should rather have subclasses of your App.Profile. I would go for something like this:
App.Profile = DS.Model.extend({
name : DS.attr('string'),
type : DS.attr('string')
});
App.User = App.Profile.extend({
email : DS.attr('string'),
type : DS.attr('string', {defaultValue : 'user'})
});
App.Company = App.Profile.extend({
vat : DS.attr('string', {defaultValue : 'company'})
});
I guess you can could have one API-Point for all with this approach also, by just defining an API-Endpoint on the level of App.Profile.

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.