How to load child object with parent in ember - ember.js

I am using Ember RC2 and Ember-Data 12 have the following relationship (simplified):
App.GlobalData = DS.Model.extend({
lat: DS.attr('number'),
lon: DS.attr('number'),
});
App.System = DS.Model.extend({
title: DS.attr('string'),
data: DS.belongsTo('App.GlobalData'),
});
In my (System) view I now want to access the child's data like this: {{ data.lat }}
It seems like Ember (currently I am using the FixtureAdapter, but I would also like to make this work with the RESTadapter in the future) does not automatically load the child element data. While {{ data.id }} returned the value of the id (as specified in the App.GlobalData.FIXTURES), {{ data.lat }} returned undefined.
I somewhat got around this issue by creating an array controller:
App.GlobalDatasController = Ember.ArrayController.extend({});
App.globalDatasController = App.GlobalDatasController.create({});
and preloading all GlobalData in my ApplicationRoute
App.ApplicationRoute = Ember.Route.extend({
setupController: function() {
App.globalDatasController.set('content', App.GlobalData.find());
}
});
However, this does not seem like a good solution, because it requires me to load all GlobalData, even though I may only need one.
I am sure there is a best practice on how to handle this, however, despite my best research efforts I have not been able to find it.
To summarize my question:
How and where do I tell ember to load child data with the parent (without sideloading it)?
If sideloading is the only option, how would I implement that in the FixtureAdapter?
Thanks!

I found the problem: In my FIXTURES, I specified IDs as integers instead of strings. Not sure why that would make a difference, but once I changed that, it worked.

Related

Nested routes and array

In my app I have the following setup:
Router
this.resource('types');
this.resource('type', {path: 'types/:type_id'})
When a user navigates to types he gets a list with all the different types. When he clicks on a link he navigates to the specific type:page. Here I would like to show an array of products. Each type has an array of products. I tried to get the products doing so:
this.resource('types');
this.resource('type', {path: 'types/:type_id'}, function(){
this.resource('products', {path: '/products'});
});
Router
model: function(){
return this.store.find('product');
}
but that didn't work. I also tried with the params but that doesn't work.
model: function(params){
return this.store.find('product', {id: params_id});
}
Templates
Type-template
<h3>{{name}}</h3>
<hr>
{{outlet}}
Product-template
{{#each}}
{{name}}
{{/each}}
But nothing gets loaded in the template. I don't know if this is the way to go. I had some success with removing the nested route and simply loading the two models in the afterModel hook on the typeController, but that doesn't give me access to some other models, linked to the product.
It's very frustrating that some "easy" things, like loading an array of products that belong to a type, is such a hassle.
Edit
What I try to achief is that when you visit the homepage you can
click a link "types" that takes you to the url ..../types and shows you a list of all the types. No problem there.
When you click on a type it takes you to types/type_id (e.g. types/1) and show you some info about the specific type (that works too).
On that same page I want to show a list of all the products associated with this type. Here is where I'm struggling
Models
type
name: DS.attr('string'),
products: DS.hasMany('product', {async: true})
product
name: DS.attr('string'),
prodcuts: DS.belongsTo('type', {async: true})
I tried this first by passing two models on the same route as suggested in this question. That works fine to some extend, but I want to put an action and computed property on each of the products and because the models are loaded in the afterModel hook, they don't get updated. I thought the way to go was to input a nested resource that returns me all of the products based on the type_id but I can't get that to work somehow.

Ember.js - How do I insert a controller?

So my understanding from the Ember docs is that the pattern for views/controllers/models is as follows:
[view] <- [controller] <- [model]
(with views consuming controllers consuming models)
In my previous experience using Ember, I'd set up a view to consume a model, like so:
{{#with blogpost}}
{{#view MyApp.BlogPostView contentBinding="this"}}
<h1>{{title}}</h1>
<p>{{content}}</p>
{{/view}}
{{/with}}
Now say I create a controller:
MyApp.BlogPostController = Ember.BlogPostController.extend()
Where do I initialize this controller?
Looking at the Ember docs, it seems like this happens automatically if the controller is associated with a route, but what if I just want an ad-hoc controller which ties together a view and a model? This could be for an arbitrary component on my page.
Am I responsible for instanciating the controller? Should I use some kind of controllerBinding attribute? Will it be instantiated automatically with my model, or with my view?
Any advice appreciated; I'm comfortable with the model/view pattern in Ember, but I'm having some difficulty working out where controllers fit in.
Looking at the Ember docs, it seems like this happens automatically if the controller is associated with a route
This is correct, a controller associated with a route will be automatically instantiated by ember when needed.
but what if I just want an ad-hoc controller which ties together a view and a model? This could be for an arbitrary component on my page. Am I responsible for instanciating the controller? Should I use some kind of controllerBinding attribute? Will it be instantiated automatically with my model, or with my view?
There are different way's to get your arbitrary controller instantiated automatically by ember without the needs of doing it yourself.
For the examples, let's assume you have a controller which is not associated with any routes called LonelyController,
App.LonelyController = Ember.ArrayController.extend({
content: ['foo', 'bar', 'baz']
});
Approach 1
Let's assume you have a route and you hook into setupController, if you try here to request you LonelyController with this.controllerFor('lonely'); this will make ember instantiate it for you:
App.IndexRoute = Ember.Route.extend({
setupController: function(controller, model) {
this.controllerFor('lonely').get('content');
// the above line will retrive successfully
// your `LonelyController`'s `content` property
}
});
Approach 2
Another possible way to get your LonelyController automatically instantiated by ember would be by defining a dependence with the needs API in another controller:
App.IndexController = Ember.ObjectController.extend({
needs: 'lonely',
someAction: function() {
this.get('controllers.lonely').get('content');
// the above line will retrive successfully
// your `LonelyController`'s `content` property
}
});
Using the needs API you could also doing something like this:
App.IndexController = Ember.ObjectController.extend({
needs: 'lonely',
lonelysContentBinding: 'controllers.lonely.content',
someAction: function() {
this.get('lonelysContent');
// the above line will retrive successfully
// your `LonelyController`'s `content` property
}
});
There are also some other combinations of the mentioned methods to get your LonelyController automatically instantiated, but I guess this should be more clear by now.
One last tip: to get a clue of what ember creates automatically under the hood you could also enable the generation logging to observe this in your console, which is very helpful, by doing:
var App = Ember.Application.create({
LOG_ACTIVE_GENERATION: true
});
Hope it helps.

How to add multiple selection into a many-to-many model in Ember.js?

I have this small app where I'm trying to add the fruits selections of a multiple Ember.Select into an attribute of a model, "myfruits" of Person Alice. However, things are broken.
Perhaps my model is set up incorrectly.
This is the Ember.Select handlebars in the html:
{{view Ember.Select
multiple="true"
contentBinding="App.fruits"
valueBinding="pickedFruits"
}}
This is the model:
App.Person = DS.Model.extend({
name: DS.attr('string'),
myfruits: DS.hasMany('App.Fruit')
});
App.Fruit = DS.Model.extend({
kind: DS.attr('string'),
likedBy: DS.hasMany('App.Person')
});
This is the function that tries to save the multiple selection:
pickThem: function(){
var input_fruits = this.get('pickedFruits');
// should I create a Fruit object for each input_fruits?
var aperson = App.Person.createRecord({
name: "Alice",
myfruits: input_fruits
});
aperson.save();
}
I feel like the problem might be I'm not creating the Fruit objects. But I'm not sure how to make it work with the many-to-many relationship between Person and Fruit.
I guess what you need to do is as you already mentioned to create a App.Fruit record for every selected fruit and add it to the newly created App.Person.
Basically the important bit is:
App.PersonController = Ember.ArrayController.extend({
pickThem: function(){
var aperson = App.Person.createRecord({name: "Alice", myfruits: []});
this.get('pickedFruits').forEach(function(item){
aperson.get('myfruits').pushObject(App.Fruit.createRecord({kind:item, likedBy:[aperson.get('id')]}));
});
aperson.save();
}
});
Then provide a model for your person template:
App.PersonRoute = Ember.Route.extend({
model: function() {
return App.Person.find();
}
});
and in your template you can loop over the person records and inside that loop over their respective fruits:
{{#each model}}
{{name}} likes are:
{{#each myfruits}}
{{kind}}
{{/each}}
{{/each}}
Have a look at this updated jsbin.
You should however reset your local store adapter's data to avoid multiple entries after each application initialization. I've done it by creating a pseudo random suffix for the namespace of the LSAdapter, but this could be anything you find more convenient.
App.LSAdapter = DS.LSAdapter.create({
namespace: 'app-emberjs-'+Math.floor(Math.random()*1000)
});
Hope it helps.
Edit
After reading your last comment and just to show how it looks like in the chrome debugger tools that the LSAdapter stores the data. Have a look at the below screenshot. Here I've reloaded 2 times the app, and as expected two namespaces are created. If you have the same namespace every time thing are going to overlap resulting in some unexpected behavior.

Reacting on a finished Ember Data hasMany query

I use Ember 1.0.0-RC3 with Ember Data. I have the requirement to react efficiently when a hasMany relationship has been loaded successfully.
My domain model looks conceptually like this:
App.Person = DS.Model.extend({
fullName: DS.attr("string"),
friends: DS.hasMany("App.Person")
});
I use Ember Data's default DS.RESTAdapter. Its default behavior is to load hasMany relationships on demand in one big batch request.
As somePerson can have lots of friends, receiving the friends relationship back from the server may take some time.
I would like to provide users of my web application visual feedback (e.g a spinner animation) during the time a hasMany relationship has been requested and until the server responses with a result.
How can I accomplish this requirement efficiently? Are there hooks that I can use to plug in my custom "startedLoading" and "finishedLoading" callbacks?
Try on a bindAttr in the handlebars view on the friends.isLoaded property:
<span {{bindAttr class="friends.isLoaded:loading:hide"}}></span>
For a more advanced processing yo can do a Ember.observer in the controller:
App.PersonController = Ember.ObjectController.extend({
watchFriends: Ember.observer(function(){
var loaded = this.get('friends.isLoaded')
}, 'friends.isLoaded')
})

Populating Ember.Select from a dependent controller using EmberData lazy-loading

I have spent about 10 days on a simple problem I could have solved in about 10 minutes with Dojo. I hope I am missing something simple - I'm a noob who would like to use Ember.
I am simply trying to populate the content of an Ember.Select with data from another Controller using EmberData. Consider the case: being able to select a FoodGroup for a Raw Ingredient.
It seemed clear to use a 'needs' dependency between RawIngredientController and FoodGroupsController, and bind on the content.
This is the closest I have gotten to success and it does not look sufficient to me - I will explain.
<script type="text/x-handlebars" data-template-name="rawIngredients">
{{#each item in controller itemController="rawIngredient" }}
{{! view Ember.Select
contentBinding="controllers.foodGroups.content"
optionValuePath="content.id"
optionLabelPath="content.name"
prompt="select Food Group" }}
{{/each}}
</script>
Cook.RawIngredientController = Ember.ObjectController.extend({
isEditing: false,
needs: ['foodGroups'],
...
});
Cook.RawIngredient = DS.Model.extend({
name: DS.attr('string'),
nameKey: DS.attr('string'),
foodGroup: DS.belongsTo('Cook.FoodGroup')
});
Cook.FoodGroupsRoute = Ember.Route.extend({
setupController: function(controller) {
controller.set('content', Cook.FoodGroup.all()); // have also tried find()
}
});
Cook.FoodGroup = DS.Model.extend({
name: DS.attr('string'),
nameKey: DS.attr('string')
});
This actually renders all RawIngredients with no errors, but the Selects are empty because the FoodGroups store is empty. I can navigate to the FoodGroups screen which loads the store, come back to RawIngredients, and the selects are populated with FoodGroups.
I've seen a number of posts on this issue but none sufficiently address the issue when EmberData is involved. There are plenty of post-loaded examples, like this clever one from pangratz http://jsfiddle.net/pangratz/hcxrJ/ - but I haven't found any using EmberData to lazy-load the content.
This post is pretty close
Ember.js: Ember.Select valueBinding and lazy loading
and contains a workaround using an observer, which I couldn't get to work. I ended up binding to a binding which never invoked the actual loading of the FoodGroup data. Here's an example, maybe not the best, of one such attempt (of probably 20+):
Cook.FoodGroupsController = Ember.ArrayController.extend({
...
// NOTE: This seems redundant
values: function() {
return this.get('content');
}.property(),
...
});
Cook.RawIngredientController = Ember.ObjectController.extend({
...
// this should be the instance; also tried the object name FoodGroupsController....also tried using needs + 'controllers.foodGroups.values' in the binding
allFoodGroups: Ember.Binding.oneWay('Cook.foodGroupsController.values'), '
/*
// ... or rather ....??
allFoodGroups: function() {
this.get('foodGroupsBinding');
},
foodGroupsBinding: "Cook.foodGroupsController.values",
*/
...
});
{{view Ember.Select
contentBinding="allFoodGroups"
optionValuePath="content.id"
optionLabelPath="content.name"
prompt="select Food Group" }}
but it errors saying 'allFoodGroups' is providing a Binding not an Ember.Array. I think I am lost in a sea of swirling naming conventions. I could show my other attempts at this, but all had errors of undefined content.
Ugh. So backing up a bit... at least using my original solution and pre-loading the FoodGroups store should provide a workaround, however I cannot seem to get EmberData to go out and load the data, programmatically. I tried an init() function in the RawIngredientController like
init: function() {
var item = this.get('controllers.foodGroups.content.firstObject');
},
but I haven't found the right combination there either. And even if I do, it seems like the wrong approach because this will set up a reference for each RawIngredient rather than use a single reference from RawIngredients (or FoodGroups?) - but that seems like a different topic.
This is my attempt at a fiddle describing the problem http://jsfiddle.net/jockor/Xphhg/13/
Has anyone figured out an efficient, effective way to load and use stores defined in other controllers, using EmberData to lazy-load the associated content?
Your basic problem seems to be that you're not accessing the foodGroups route, so its setupController() never gets executed, thus the content of the select controller never gets set.
In your example, when adding a link to the route in question and clicking it, the route gets initialized and the bindings work.
I tried to update your JSFiddle, but it is linking to the "latest" version of ember-data from Github which has not been updated in a while (you're supposed to build it yourself until they make an official release), and it's also using an old version of Ember, so I was getting some weird errors.
So here is a version with the latest Ember and Ember-data: http://jsfiddle.net/tPsp5/
Notice what happens when you click the Countries link. I left behind some debugger; statements that may help you understand what gets invoked when.
As a design note, your "parent" controller should probably not depend on "child" controllers.