My backed has a list of objects, at api/vehicles:
{
"vehicles" : [
{
"id" : "car" ,
"nr-doors" : 4,
...
},
{
"id" : "ship",
"nr-sails" : 3
...
},
{
"id" : "train",
"seats-per-wagon" : 30,
...
}
]
}
All vehicles have only the "id" field in common, and they have several specific properties. There is only one vehicle of each kind. The IDs are fixed and already known. I would like to have a template to display/edit the car's properties. How can I relate a specific id (like "car") with a model/template/controller/view?
The problem that I have is that in ember and ember-data, the IDs at a certain URL refer to objects of the same kind, which can be handled by the same route/view/controller/model. In my application, this is not the case: each ID must be handled in a completely different controller/model/view/template.
How can I do this?
each ID must be handled in a completely different controller/model/view/template
Are you sure? This would be a radical departure from not just ember but any model-view-controller architecture.
If you really want to accomplish this, I would suggest the following:
Start by creating the templates and routes you want to have
App.Router.map(function() {
this.route("car");
this.route("ship");
this.route("train");
});
App.CarRoute = Ember.Route.extend({
model: function() {
return App.Vehicle.find('car');
}
});
App.CarController = Ember.ObjectController.extend({
//add any custom fx related to cars here
});
// Now define handlebars template for car and if needed a CarView
Related
Imagine we have two entities, stored in a relational database as:
person(id, name)
friendship(id, personAID, personBID, strength)
Here we can see that two people can be friends and a "strength" (some numerical value) will be given to that friendship.
If persons 1 and 2 are friends, then we have the following entry in the friendship table:
xyz | 1 | 2 | 50
note that the following corresponding entry does not exist:
abc | 2 | 1 | 50
Only one entry is created per "friendship".
What I struggle with is how to model this in the ember app. A "person" can have many friendships, and a "friendship" relates exactly 2 people.
// models/person.js
DS.Model.extend({
name: DS.attr('string'),
friendships: DS.hasMany('friendship', {inverse: 'friends'})
});
The friendship objects are serialized as an array of
{
id: 'abc',
friends: ['1', '2'],
score: '50'
}
so:
// models/friendship.js
DS.Model.extend({
friends: DS.hasMany('person', {inverse: friendships}),
personA: Ember.computed(friends, function() {
return this.get('friends').objectAt('0');
}),
personB: Ember.computed(friends, function() {
return this.get('friends').objectAt('1');
}),
getFriend: function(id) {
if (id === this.get('personA.id')) {
return this.get('personB');
}
if (id === this.get('personB.id')) {
return this.get('personA');
}
return null;
},
score: DS.attr('number')
});
The implementation of the friendship model feels hacky for lots of reasons. First of all, a friendship does not "have many" friends. It has exactly two. However, if I change the implementation to:
DS.Model.extend({
personA: DS.belongsTo('person'),
personB: DS.belongsTo('person'),
...
});
then I'm not sure how to model the "hasMany" relationship of person => friendship. What would the 'inverse' field be set to, for instance?
The "score" field is complicating things. If that didn't exist then this would be a reflexive relation within the "person" model, but the relation has additional data and must be able to be represented as its own entity.
Also, given the situation where I want to list all of the friends of a given person, along with the strength of that friendship, the "getFriend()" function is required. This function smells a bit to me and I can't quite put my finger on why.
So my question is, what do you see as an effective way to model two-way symmetric relationships that contain additional data such as the "score" field? I can't change anything about how the data is stored in the DB, but I do have control over everything else, so I can transform the data into any structure on the way out of the DB and into the Ember client.
Ember and Ember-data 2.x
So it took me a year but I finally came back to this problem and I think I have a fairly good solution for it. I actually wrote two blog posts detailing the solution (part 1 and part 2), but let me try to summarize it here.
The solution has both a front-end and a back-end component to it.
I defined the models (in Rails, but it could be anything really) as follows:
# app/models/friendship.rb
class Friendship < ActiveRecord::Base
belongs_to :friender, class_name: Person
belongs_to :friended, class_name: Person
end
# app/models/person.rb
class Person < ActiveRecord::Base
def friendships
Friendship.where("friender_id = ? OR friended_id = ?", id, id);
end
end
I used a JSON API compliant backend and a gem called jsonapi-resources to help me implement the server response. Whatever your implementation, the important piece is to have an endpoint for each person that returns the friendships for that person (e.g /people/1/friendships).
That endpoint should return the relationship links where data could be fetched to see who the people are on both sides of the relationships:
{
"data": [
{
"id": "1",
"type": "friendships",
(...)
"attributes": {
"strength": 3
},
"relationships": {
"friender": {
"links": {
"self": "http://localhost:4200/friendships/1/relationships/friender",
"related": "http://localhost:4200/friendships/1/friender"
},
"data": {
"type": "people",
"id": "1"
}
},
"friended": {
"links": {
"self": "http://localhost:4200/friendships/1/relationships/friended",
"related": "http://localhost:4200/friendships/1/friended"
},
"data": {
"type": "people",
"id": "4"
}
}
}
},
{
"id": "2",
"type": "friendships",
(...)
}
]
}
The models on the Ember side looks as follows:
// app/models/person.js
import DS from 'ember-data';
import Model from 'ember-data/model';
export default Model.extend({
name: DS.attr(),
friendships: DS.hasMany(),
frienderFriendships: DS.hasMany('friendship', { inverse: 'friender' }),
friendedFriendships: DS.hasMany('friendship', { inverse: 'friended' }),
});
// app/models/friendship.js
import DS from 'ember-data';
import Model from 'ember-data/model';
export default Model.extend({
strength: DS.attr('number'),
friender: DS.belongsTo('person', { inverse: 'frienderFriendships' }),
friended: DS.belongsTo('person', { inverse: 'friendedFriendships' }),
});
In the route I'm fetching the person and just display their friendships in the simplest way:
<h2>Friends of {{model.name}}</h2>
<ul>
{{#each model.friendships as |friendship|}}
<li>{{friendship.friender.name}} - {{friendship.friended.name}} - {{friendship.strength}}</li>
{{/each}}
</ul>
That works, but sends out lots of xhr requests to the backend, one for each end (friender and friended) of the relationship.
You can cut down on the number of these requests by using resource linkage in the response of the friendships endpoint. If you don't use JSON API, there are surely ways to indicate which record is on the ends of a relationship (friendship.friender or friendship.friended) so that no further ajax requests need to be made to fetch them.
I have a single call to the server that returns the whole data of my application
[
{
"id": 1,
"count": 0,
"canGroup": true,
"childs": {
"group": [
{
"id": 0,
"count": 3,
"canGroup": true,
"childs": {
"user": [
{
"id": 0,
"count": 3,
"canGroup": true
}
...
]
}
] ...
}
...
]
How can I do to deserialize this model with Ember Data?
There are two parts to this question. One is how to structure your model to represent this data, the second is how to modify the incoming data so Ember can handle it. The model, let's call it group, would be something like:
// models/group.js
export default DS.Model.extend({
count: DS.attr(),
canGroup: DS.attr(),
childs: DS.hasMany('group')
});
Now we need to tell Ember Data that the children are going to be coming in embedded inside the parent. To do that, we mix in DS.EmbeddedRecordsMixin into the serializer, and also specify an attrs hash, as follows:
// serializers/group.js
export default DS.RESTSerializer(DS.EmbeddedRecordsMixin, {
attrs: {
childs: { embedded: 'always' }
}
However, there is one remaining problem. By default, Ember Data will expect that the childs property contains an array of, well, children. Instead, your data has childs containing a group property which contains the array. We need to fix that up, which we will do by overriding normalize:
normalize(type, hash, prop) {
hash.childs= hash.childs.group;
return this._super(type, hash, prop);
}
});
There are other issues to worry about, including whether you want Ember Data to serialize the embedded children (when writing back to the server), but the above should be enough to get you started.
By the way, I notice you have two different groups with an id of 0. That is going to greatly confuse Ember Data. Try to have unique IDs. If that is not possible, you might consider synthesizing IDs with additional serializer logic.
I have a model and I would like to use it with two different templates on the page. I didn't find anything on how to specify what model to use for a template (other than its name).
For example, I would like to display all the subusers from the model "subusers" in the template named "assignationdd". Right now, I already have a template named "subusers" so it links it with the model automatically, but can I reuse the model in another template?
EDIT :
I have a multi-model ressource because I need both all conversations and subusers at the root of the app. I should have precised that before. So there is no change in the url or route, I just want to display my model in 2 different templates. And yes I read the docs on ember-data (and it shows very few and simpler examples).
Router :
App.Router.map(function(){
//Routing list to raw namespace path
this.resource('conversations', { path : '/' }, function() {
this.resource('conversation', { path : '/:conversation_id'});
});
});
Route :
App.ConversationsRoute = Ember.Route.extend({
subusers: null,
currentUser: null,
model: function(params){
return this.store.find('conversation', { status : params.status});
},
setupController: function(controller, model){
this.controller.set('content', model);
if(!this.get('subusers'))
{
this.set('subusers', this.store.findAll('subuser'));
}
this.controllerFor('subusers').set('content', this.get('subusers'));
},
queryParams: {
status: {
refreshModel: true
}
}
});
If I understand you correctly, the model is assigned by the Route and not the template. You can use one model in multiple routes. I suggest you read the getting started section on Ember and Ember Data.
First of all, don't nest resources. A resource should only be a noun, and a route should be a verb. So your router should be like this:
//Routing list to raw namespace path
this.resource('conversations', { path : '/' }, function() {
this.route('view', { path : '/:conversation_id'});
});
Secondly, try this for multiple models:
model: function (params) {
return Ember.RSVP.hash({
conversation: this.store.find('conversation', { status: params.status}),
subusers: this.store.findAll('subuser')
});
}
I am relatively new to Ember.js, so I am giving myself a project to figure things out.
I believe I understand the very basics. Controllers contain state-logic, while models contain model attribute-logic.
In my example, I have a collection of models. These models contain an attribute that represents an id of another model:
App.Pokeball = DS.Model.extend({
name: DS.attr('string'),
rate: DS.attr('number'),
pokemon: DS.belongsTo('pokemon')
});
I have a Controller that contains selectedPokemonId and selectedPokemon attributes. When selectedPokemonId changes, I want to automatically update all the Pokeball models.
I know its awful, but here is the function I am using to update the Models:
selectedPokemon: function(selectedPokemonId) {
var pokemonId = this.get('selectedPokemonId'),
store = this.store,
id = 1,
max = App.Pokeball.FIXTURES.length;
for (id,max; id<= max;id++) {
store.update('pokeball', {
id: id,
pokemon: pokemonId
});
}
return store.find('pokemon', this.get('selectedPokemonId'));
}.property('selectedPokemonId'),
Technically, this does what I need it to... but I am certain I am not doing this the "ember way", there has to be a cleaner way to bind the relationship between controller state and models.
Github Example Code here
Working example
I like to work directly with models as objects instead of managing record ids. Doing this greatly simplifies your code. Here's how I would accomplish this.
First, your route should return all the models you want to work with using the model hook.
The route's model hook should look something like:
model: function()
{
return Ember.RSVP.hash ({
pokeballs: this.store.find('pokeball'),
pokemon: this.store.find('pokemon')
});
}
In general you want to do store.find calls in the route model hook because they can be asynchronous (return a Promise) and the model hooks waits for promises to resolve before proceeding. This ensures your data will always be ready for your controller to work with it. More here: http://emberjs.com/guides/models/finding-records/. Note that the model we'll be working with is an object with two properties, pokeballs and pokemon, which are both collections representing all the respective objects in the store.
In your controller, instead of a selectedPokemonId, you can reference a selectedPokemon model object directly. You can then observe the change to the selectedPokemon using 'observes' and then simply set the selectedPokemon on each pokeball and save each pokeball model to persist it back to the store. If you're just using fixtures you could get away without even saving each pokeball because 'set'-ing a property on the model object is enough to change it in the store.
selectedPokemonObserver: function()
{
var thePokemonToSet = this.get('selectedPokemon');
this.get('pokeballs').forEach( function( aPokeball ) { // note you can also do this.get('model.pokeballs') since the model is an object with two properties, pokeballs and pokemon
aPokeball.set('pokemon', thePokemonToSet); //note that instead of an id, i'm setting the pokemon model object here to satisfy the belongsTo relationship
aPokeball.save(); // you might not need this if using only fixtures and not persisting to db.
});
}.observes('selectedPokemon')
Anything referencing these model objects in your templates will automatically be updated.
I think the "Ember way" to do what you want to accomplish is to use an observer instead of a property:
...
selectedPokemonObserver: function() {
var pokemonId = this.get('selectedPokemonId'),
store = this.store,
id = 1,
max = App.Pokeball.FIXTURES.length;
for (id, max; id <= max; id++) {
store.update('pokeball', {
id: id,
pokemon: pokemonId
});
}
}.observes('selectedPokemonId'),
selectedPokemon: function() {
return this.store.find('pokemon', selectedPokemonId);
}.property('selectedPokemonId'),
...
I am learning Ember.js 1.0.0-rc.3 and I would like to know the proper way to create a "dynamic MVC" app. What I mean by "Dynamic MVC" is that the only content I have when hitting the index page is a collection of entity names. The rest will be dynamically generated, i.e. model (data), eventually specific actions for the controllers, and specially templates.
Here is what I am trying to achieve:
I have a collection of entities (such as ["User", "Customer", "Invoice", etc]).
I expect the route for each entity to be:
.../#/user/
.../#/customer/
.../#/invoice/
for each entity name
this.resource("entity name lower case");
To make it simple for the explanation, hitting an entity route will display a view with 2 links. One to create a new entity, one to display a list of entities. The routes will then be:
.../#/user/ => will display a "Create" link and "List" link
.../#/user/create/ => sub route to display a form to create a User
.../#/user/list/ => sub route to display a collection of existing Users
.../#/customer/
.../#/customer/create/
.../#/customer/list/
...etc...
Now the templates for the sub routes (".../#/user/create/", ".../#/user/list/", etc.) don't exist but instead will be loaded/created on demand. That's part of a new UI Design Pattern named SSD (Sketch, Schema, Data) where a template is generated based on a Sketch (a skeleton of a view), a Schema (specific to that view). Same for the actions.
1) First question.
Knowing that I may end up with 100 entities+, is it a correct approach to have a Controller, a Model, a View for each route? And as well sub routes... I am a bit concerned about the performances here as all these objects are created when hitting the index page.
In the current sample, I will have the following Ember objects:
UserController, UserRoute, UserView
UserCreateController, UserCreateRoute, UserCreateView
UserListController, UserListRoute, UserListView
CustomerController, CustomerRoute, CustomerView
CustomerCreateController, CustomerCreateRoute, CustomerCreateView
CustomerListController, CustomerListRoute, CustomerListView
etc.
//it becomes easily heavy
I tried to link multiple paths to the same MVC objects but then it's hard to identify the current entity used. Analyzing the current path, if doable, seems really ugly.
For instance:
this.resource('entity', { path: '/' + user });
this.resource('entity', { path: '/' + customer });
this.resource('entity', { path: '/' + invoice });
//all attached to EntityController, EntityView and EntityRoute
//Could be working but hard to identity which entity User, Customer or Invoice is really used
this.resource('entity.create', { path: '/' + user + '/create' });
this.resource('entity.create', { path: '/' + customer + '/create' });
this.resource('entity.create', { path: '/' + invoice + '/create' });
//Same for EntityCreate
I also tried to use this.controllerFor('entity'), this.controllerFor('entity.create'), this.controllerFor('entity.list') but this doesn't reduce the number of objects and it makes the code more complicated than it should be.
2) Second Question:
To be able to work on dynamic templates, I implement setController() with an ajax call to load the necessary SSD which will be used to create the template and also the model.
I save the template to Ember.TEMPLATES[template name], change a data property, and attach an observer to the renderTemplate of the Route.
Is it the proper way of dynamically changing a template?
Here is a sample (not tested, just to see the process of loading/changing a template and model):
App["UserCreateRoute"] = Ember.Route.extend({
templateName: "loading", //display a temporary template while loading the template
renderTemplate: function() {
this.render(this.get('templateName'), {
///...
});
}.observes("templateChanged"),
setupController: function(controller, model) {
var self = this;
$.when(` ajax load `).done(function( response ) {
var templateName = xxx; // based on the entity for instance "user/create"
if (`template doesn't exist`) {
Ember.TEMPLATES[templateName] = CreateMyTemplate( response );
self.set("templateName", templateName);
}
//also change the model
self.set("data", reponse.someData );
//eventually add method to the controller?
//...
//tell renderTemplate to re-render
self.set("templateChanged", new Date());
})
},
model: function(params) {
//simplified for sample
return this.get("data")
}
})
It's a bit long, thanks for reading!