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

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.

Related

Having issues adding components within a ContainerView dynamically

I have a container view in application level and I wanted to add some components to this container view via some action. The way I do this is :
1) I push some objects to the application controller
2) ContainerView is within the context of application controller, so I observe the length of the array holding the objects mentioned in step 1
3) and then I create the components and append them to the container view
One weird thing I noticed was initially the observer defined in the container view was not triggering, but then I added initialize hook where I log the controller, and then the observer started working for me. This tells me maybe I am not doing something right. Here is my container view:
App.MyContainerView = Ember.ContainerView.extend({
initialize: function(){
//IF YOU WERE TO COMMENT THIS CONSOLE.LOG THEN THE OBSERVER NEVER GETS HIT WHEN YOU CLICK ON ADD BUTTON. MAYBE CONTROLLER IS NOT SET UP AND IT NEEDS SOME TIME???
console.log(this.get('controller').toString());
}.on('init'),
appendMyComponent: function(){
console.log("inside the observer");
this.get('controller.myComponents').forEach(function(comp){
console.log(this.toString());
this.pushOject(App.MyTestComponent.create({}));
}, this);
}.observes('controller.myComponents.length')
});
Do I have to use named outlet and then render the container view within the outlet? Not sure what I am doing wrong here.
Here is a jsbin: http://emberjs.jsbin.com/xamoxese/2/
Your feedback is much appreciated. Thanks good ppl of stackoverflow.
USING :
Ember : 1.5.0
Handlebars : 1.3.0
jQuery : 1.10.2
Allright some more updates, initially the solution provided by #bmeyers was not working for me although it was working in jsbin. So in my real app mu application.hbs looked like this:
{{outlet navbar}}
{{outlet}}
{{outlet modal}}
{{view App.MyContainerView}}
but then I re-ordered it as :
{{view App.MyContainerView}}
{{outlet navbar}}
{{outlet}}
{{outlet modal}}
and then the solution worked. Now only if I could get rid of that console.log thing :)
Here is updated jsbin that I have added on top of #bmeyers solution http://emberjs.jsbin.com/xamoxese/10/
I have fixed this for you and shown an example here: http://emberjs.jsbin.com/xamoxese/3/edit?html,css,js,output
I didn't fix your issue where you are adding the same components over and over again and never clearing the childView array, but maybe that was your intention.
Basically you do not push to the container view, you need to push to the childViews property.
Hope this helped!
UPDATE FIXED
http://emberjs.jsbin.com/xamoxese/6/edit
Ok so looks like you just needed a reference outside your foreach function and that for some reason allowed using it the way it is documented
UPDATE 2:: Change the containerView code to....
App.MyContainerView = Ember.ContainerView.extend({
init: function() {
this._super();
this.appendMyComponent();
},
appendMyComponent: function(){
this.get('controller.myComponents').forEach(function(comp){
this.pushObject(App.MyTestComponent.create({}));
}, this);
}.observes('controller.myComponents.length')
});
This just feels a bit cleaner than creating an empty view that you will never use. Picky I know but it was bugging me.
Finally figured out how to get rid of that console.log thing. So I actually have 2 solutions for this:
1) pass the controller to the container view
{{view 'myContainer' controller=this}}
2) set an empty view to the ContainerView
App.MyContainerView = Ember.ContainerView.extend({
childViews: [Ember.View.create()],
appendMyComponent: function(){
this.get('controller.myComponents').forEach(function(comp){
this.pushOject(App.MyTestComponent.create({}));
}, this);
}.observes('controller.myComponents.length')
});
Actually the best solution is as #bmeyers suggested, is to have init function for container view like this:
init: function() {
this._super();
/*
THIS IS THE OBSERVER METHOD THAT WE HAVE IN THE VIEW, THIS CALL DOES NOT EVEN
NEED TO ADD A CHILD VIEW, BUT STILL NEEDS TO BE CALLED - STRANGE I KNOW BUT WHY
I AM NOT SURE. ALSO FACED ISSUE DURING TEST FOR ABOVE 2 APPROACHES, THIS ONE WORKED WELL
*/
this.appendMyComponent();
}
Here is the working jsbin: http://jsbin.com/xamoxese/13/

Ember.js ArrayController not Binding to correct controller?

I am experimenting around with integrating Ember.Data into my application and also wanted to find out how to propery use ArrayController. Unfortunately I didn't even get around simple databinding on the controller.
I simply can't figure out where I went wrong, so I fully expect someone to be able to point out: Hey you wrote .extend instead of .create
I am trying something rather similar to what Discouse is doing in their AdminSiteSettings:
The controller in question:
App.UsersController = Ember.ArrayController.extend({
foo: 'bar'
});
route:
App.UsersRoute = Ember.Route.extend({
model: function () {
var users = App.User.find();
return users;
},
setupController: function (controller, model) {
controller.set('model', model);
console.log(controller.get('foo')); // this works correctly => bar
}
});
Only problem: The template is being rendered, but I can't bind to the foo property:
<script type="text/x-handlebars" data-template=name="users">
We render the correct template
{{foo}}
</script>
Only problem is: The Template never renders 'bar'. It's just empty.
Now I found something similar in Discourse where they have a textbox bound to filter:
https://github.com/discourse/discourse/blob/master/app/assets/javascripts/admin/controllers/admin_site_settings_controller.js
I can't really understand why my controller property is not showing up, (and yes I am actually trying to get Ember.data to work, but since this can easily be reproduced without it I figured I'd settle for the simple foo: bar property :(
Versions in use:
Ember.js: v1.0.0-rc.4-23-gbfd3023
Handlebars: 1.0.0-rc.4
Ember.data: 13
Any pointers are welcome. Thanks!
I don't know exactly where you app fails, but I have tried to recreate your use case and in this example jsbin it's working correctly, have a look.
Hope it helps.

Ember.js how to design different representations of Data (with TodoMVC as an example)?

I would like to know what's the best way of designing the display of different representations of the same data model in Ember.js. To ask my question, I'll use the TodoMVC of Ember.JS, which has 3 representations of todo-data:
any todo, i.e. the entire todo list (TodosIndexRoute)
todos that are still active and incomplete (TodosActiveRoute)
todos that have been completed (TodosCompletedRoute)
Currently, you can see each of the 3 by clicking on the words at the bottom of the list, directing to a different URL each time. Since currently each representation has a route, it makes sense that each representation gets its unique URL. The main page displays the entire todo list (1.).
A. What is the best ember.js design to make the main page display all 3 representations (i.e. under just one URL)?
B. How about the best design that displays all 3 on the main page as well as on separate pages?
Currently I only figured out a clumsy way and made this modified TodoMVC app that shows incomplete and completed lists at the bottom of the page.
In index.html, I added new named lists:
{{#each todosactive itemController="todo"}}
{{ title }},
{{/each}}
In the js router, I copied TodosActiveRoute and TodosCompletedRoute into TodoIndexRoute, which is code duplication, very bad.
Todos.TodosIndexRoute = Ember.Route.extend({
setupController: function () {
var todos = Todos.Todo.find();
this.controllerFor('todos').set('filteredTodos', todos);
var todos_active = Todos.Todo.filter(function (todo) {
if (!todo.get('isCompleted')) {
return true;
}
});
this.controllerFor('todos').set('todosactive', todos_active);
...
});
I feel like I'm missing an elegant way of doing this, but my current ember.js knowledge is very limited. Should I use {{partial}}, {{render}}, render, or something else?
I tried {{ partial }} and {{ render }}, but I can't get them to display any data .
Thanks for helping out.
A) Ember tries to work really closely with urls. This is a good thing since if you want to share a url, the view should be consistent. The url is a powerful tool and each unique url should link to the same unique page. Having one url that links to multiple views isn't great, and certainly not shareable. If you have some time listen to some talks by Tom Dale and Yehuda Katz for an interesting overview of ember and what they're trying to do.
B) You can include different views on one page. Have a look at the guides, most notably on rendering templates and using helpers for more information on including different views under one url.
A) To display all 3 representations in one view, we actually just need the basic model in the single route. The key is for the controller to give you flavors of that model. The other important thing is to use data binding in the handlebars template. You can see the running version here.
In the IndexRoute, add a model that gets the plain todos list:
Todos.TodosIndexRoute = Ember.Route.extend({
model: function(params) {
return Todos.Todo.find();
},
...
Make a TodosListView that doesn't need to have anything:
Todos.TodoListView = Ember.View.extend();
In the controller, add 2 computed properties that returns the desired arrays:
Todos.TodosController = Ember.ArrayController.extend({
...
todosActive: function() {
return this.filterProperty('isCompleted', false);
}.property('#each.isCompleted'),
todosCompleted: function() {
return this.filterProperty('isCompleted', true);
}.property('#each.isCompleted'),
...
Finally, in the HTML template:
<script type="text/x-handlebars" data-template-name="todos">
Active:
{{#view Todos.TodoListView lalaBinding="todosActive"}}
{{#each view.lala}}
{{title}},
{{/each}}
{{/view}}
Completed:
{{#view Todos.TodoListView dataBinding="todosCompleted"}}
{{#each view.data}}
{{title}},
{{/each}}
{{/view}}
</script>
note the dataBinding.
Thanks to the folks on #emberjs irc for helping out.

Getting the property inside a controller in Ember.js

I am beginner with Ember.js and I guess there's a really simple answer to my problem so please bear with me.
I try to establish a two-way binding between a controller property and a
"value" property in a Ember.TextField that is in the controller's correspondant template. I already succeded in giving the property in the controller a specific initial value that is reflected in the value property of the Ember.TexField, like:
js code:
App = Ember.Application.create();
App.IndexController = Ember.ObjectController.extend({
aProperty: 1 });
template:
<script type:"text/x-handlebars" data-template-name="index">
{{view Ember.TextField valueBinding="aProperty"}}
</script>
Now the text field shows the value("1") and everything is fine.
If I now try to enter a new value into the textfield, say "2", and try to check the value of "aProperty" in the browser console with App.IndexController.get("aProperty") it doesn't work and throws an error and says that "get()" is not defined.
How could I retrieve the value of "aProperty" from the IndexController and check if it has updated the value to what I entered in the Ember.TextField?
Any hints, comments and advices are greatly appreciated!
Thank you!
Edit:
Both answers below accomplished what I was trying to do. I got it working too: I created a concrete instance of the App.IndexController after having extended it, like this:
App.IndexController = Ember.ObjectController.extend({
aProperty: 1 });
App.indexController = App.IndexController.create();
and could retrieve the value of aProperty from the console with App.cueController.get('aProperty'). However, all of the examples from Ember's documentation never create concrete instances of the controllers so I'm wondering if this is necessary, much less encouraged by Ember for a real-world app. Any ideas?
You should lookup the controller like this in your debugging console:
App.__container__.lookup('controller:index').get('aProperty')
http://jsbin.com/uxojek/9/edit
Since using the __container__ is only for debugging purposes, if you want to log the property change without using the private __container__ you could set an observer on the controller property watching for changes and log the new value to the debugger console.
Example:
App = Ember.Application.create({});
App.IndexController = Ember.ObjectController.extend({
aProperty: 1,
aPropertyDidChange: function() {
console.log(this.get('aProperty'));
}.observes('aProperty')
});
Working jsbin.
Hope it helps

How to load child object with parent in ember

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.