Dynamically setting controllers in {{#each}} with Ember - ember.js

In Ember you can dynamically lookup controllers with {{#each}} doing something like this according to the guides:
Template:
{{#each controller}}
{{name}}
{{/each}}
Array controller:
App.DocumentListController = Ember.ArrayController.extend
lookupItemController: ((object) ->
if #get('name') == 'something' then 'someController'
else 'someOtherController'
I have a situation where I want to do the same thing but I need to use an ObjectController instead of an ArrayController.
So my template would look more like this:
{{#each controller in controller.documents itemController=lookupItemController}}
{{name}}
{{/each}}
Object controller:
App.DocumentListController = Ember.ObjectController.extend
lookupItemController: (object) ->
if #get('name') == 'something' then 'someController'
else 'someOtherController'
This second example is just made up and doesn't work. Is there a way to dynamically set controllers like this in an {{#each}}? Is there a better way to accomplish this?

What you might do in this instance is something like:
App.ListController = Ember.ObjectController.extend
needs: ['documents']
App.DocumentsController = Ember.ArrayController.extend
needs: ['list']
contentBinding: 'controllers.list.documents'
lookupItemController: (object) ->
if #get('name') == 'something' then 'someController'
else 'someOtherController'
And then in your template (assuming 'ListController' is the controller for the view):
{{#each document in controllers.documents}}
{{document.name}}
{{/each}}

Related

How do I render a collection of models each with variable view and controller?

I have a collection of models in my Ember.js app, which I would like to render. The catch is that I want to be able to specify a specialized view and controller for each of the models.
The controller part seems to be easy: I would just wrap the array in an ArrayController and implement itemController method. The view part is where it gets tricky. I don't see an obvious idiomatic way of doing this.
The best way we came up with is the combination of ArrayController and CollectionView with an overridden createChildView. For instance:
createChildView: function(viewClass, attrs) {
var viewInstance,
widgetType = attrs.content.get('type');
// lookup view, if found, use it, if not, pass empty view
var viewDefined = this.container.lookup('view:' + widgetType);
var createWidgetType = viewDefined ? widgetType : 'empty';
// create view instance from widgetType name
// it causes lookup in controller
viewInstance = this._super(createWidgetType, attrs);
// if `attrs.content` is controller (item of `ComponentsController`)
// set it as controller of newly created view
if(attrs.content.get('isController')) {
viewInstance.set('controller', attrs.content);
}
return viewInstance;
}
This feels unnecessarily convoluted, I don't like that I have to connect the view with the controller manually like that. Is there a cleaner way?
You can create a component, which will act as controller and have a view associated with it:
App.XItemComponent = Ember.Component.extend({
controllerProperty: '!',
tagName: 'li'
});
Then, you can just do:
<script type="text/x-handlebars" data-template-name="index">
<ul>
{{#each model }}
{{ x-item item=this }}
{{/each}}
</ul>
</script>
http://emberjs.jsbin.com/wehevixolu/1/edit?html,js,output
I'd use the {{render}} helper. It'll create a view and controller for each instance.
{{#each item in model}}
{{render "item" item}}
{{/each}}
Example: http://emberjs.jsbin.com/vuwimu/2/edit?html,js,output
Render helper guide: http://emberjs.com/guides/templates/rendering-with-helpers/#toc_the-code-render-code-helper
Additionally:
In your comment you mentioned you want different controller/view types for particular model types. This could be done like this:
{{#each item in model}}
{{#if item.typeX}}
{{render "itemX" item}}
{{/if}}
{{#if item.typeY}}
{{render "itemY" item}}
{{/if}}
{{/each}}
or if you'd choose to go with components:
{{#each item in model}}
{{#if item.typeX}}
{{component-x item=item}}
{{/if}}
{{#if item.typeY}}
{{component-y item=item}}
{{/if}}
{{/each}}
Without knowing what you are trying to accomplish in more detail it’s hard to tell what the best solution is.

How to access an array object inside handlebars in Ember.js, when the index is dynamic?

I am trying to access an object within an array inside handlebars, on Ember.js. I've tried the following combinations:
1 {{render "input-validation-message" validity.options.[_view.contentIndex]}}
2 {{render "input-validation-message" validity.options._view.contentIndex}}
3 {{render "input-validation-message" validity.options[_view.contentIndex]}}
The array is validity.options and the index is _view.contentIndex, which is the only way I know how to access the index within {{#each}} loops on Ember.js.
None of the three methods are passing the object through. Does anyone have a solution?
EDIT:
After some playing around, I found out that the _view.contentIndex is not being accessed inside of an {{unless}} or {{if}} block. My code looks a little like this:
{{#each modelArray}}
{{#unless _view.contentIndex}}
[code here that needs to access _view.contentIndex]
{{else}}
[more code here that needs to access _view.contentIndex]
{{/unless}}
{{/each}}
Is there a way to get pass this issue?
FIXED!
I modified the correct answer below slightly:
{{#each item in modelArray itemController='foo'}}
{{render "input-validation-message" item.validityOption}}
{{/each}}
App.FooController = Ember.ObjectController.extend({
validityOption: function(){
var model = this.get('model'),
eachController = this.get('target'),
parentController = eachController.get('target'),
idx = eachController.get('content').indexOf(model);
return parentController.get('validity.options').objectAt(idx);
}.property('model')
});
If the array is not an Ember.Array, use [] not objectAt().
You'll want to use an itemController and hook up the association in the controller
{{#each item in modelArray itemController='foo'}}
{{render "input-validation-message" item.validityOption}}
{{/each}}
App.FooController = Ember.ObjectController.extend({
validityOption: function(){
var model = this.get('model'),
parentController = this.parentController,
idx = parentController.indexOf(model);
return parentController.get('validity.options').objectAt(idx);
}.property('model')
});
Example: http://emberjs.jsbin.com/ruzusiya/1/edit

How can I render a block only if a specific route is active?

I wanna render a block in Ember Handlebars only, if a specific route is active.
So, how can I create a 'ifRoute' helper, with the same conditons then the 'active' class on the 'linkTo' helper?
I want this, because I've a two layer navigation. So, I want to show the sub-navigation only, if the head navigation point is active. I dont wanna use the 'active' class, because I use lazy loading and I only want to load the sub navigation when the head navigation point is active.
So, what I want to do is:
<ul>
{{#each assortmentGroups}}
<li>
{{#linkTo "assortmentGroup" this}} {{description}} {{/linkTo}}
{{#ifRoute "assortmentGroup" this}}
<ul>
{{#each itemCategories}}
<li>{{#linkTo "itemCategory" this}} {{description}} {{/linkTo}}</li>
{{/each}}
</ul>
{{/ifRoute}}
</li>
{{/each}}
<ul>
How can I do this or is there a better solution?
Thanks
Just add to the controller:
needs: ['application'],
isCorrectRouteActive: Ember.computed.equal('controllers.application.currentRouteName', 'correctRoute')
Similarly:
isCorrectPathActive: Ember.computed.equal('controllers.application.currentPath', 'correct.path')
isCorrectURLActive: Ember.computed.equal('controllers.application.currentURL', 'correctURL')
I am quite sure latest Ember does the rest
Here are two possible options, although for both you first have to save the currentPath in your ApplicationController to have access to it whenever you need it:
var App = Ember.Application.create({
currentPath: ''
});
App.ApplicationController = Ember.ObjectController.extend({
updateCurrentPath: function() {
App.set('currentPath', this.get('currentPath'));
}.observes('currentPath')
});
Using a computed property
Then in the controller backing up the template, let's say you have a NavigationController you create the computed property and define also the dependency to the ApplicationController with the needs API to gather access, then in the CP you check if the currentPath is the one you want:
App.NavigationController = Ember.Controller.extend({
needs: 'application',
showSubMenu: function(){
var currentPath = this.get('controllers.application.currentPath');
return (currentPath === "assortmentGroup");
}.property('controllers.application.currentPath')
});
So you can use a simple {{#if}} helper in your template:
...
{{#linkTo "assortmentGroup" this}} {{description}} {{/linkTo}}
{{#if showSubMenu}}
<ul>
{{#each itemCategories}}
<li>{{#linkTo "itemCategory" this}} {{description}} {{/linkTo}}</li>
{{/each}}
</ul>
{{/if}}
</li>
...
Using a custom '{{#ifRoute}}' helper
But if your really want a custom helper to deal with your condition then this is how you could do it, note that the currentPath stored on your application is still needed since we need a way to get the value of the current route:
Ember.Handlebars.registerHelper('ifRoute', function(value, options) {
if (value === App.get('currentPath')) {
return options.fn(this);
}
else {
return options.inverse(this);
}
});
And then you could use it like this:
...
{{#linkTo "assortmentGroup" this}} {{description}} {{/linkTo}}
{{#ifRoute "assortmentGroup"}}
<ul>
{{#each itemCategories}}
<li>{{#linkTo "itemCategory" this}} {{description}} {{/linkTo}}</li>
{{/each}}
</ul>
{{/ifRoute}}
</li>
...
See here also a simple Demo of the "custom helper" solution: http://jsbin.com/izurix/7/edit
Note: with the second solution there is a catch! Since bound helpers do not support blocks (in embers handlebars customization) I used a simple helper that does not reevaluate the condition depending on bindings which is may not what you want.
Hope it helps.
After investigating the ember code for the linkTo and if helpers, the answer from intuitivepixel and a blog post about writing my own bound block helper, I've found a solution:
var resolveParams = Ember.Router.resolveParams;
var resolvedPaths = function(options) {
var types = options.options.types.slice(1),
data = options.options.data;
return resolveParams(options.context, options.params, { types: types, data: data });
};
Ember.Handlebars.registerHelper('ifRoute', function(name) {
var options = [].slice.call(arguments, -1)[0];
var params = [].slice.call(arguments, 1, -1);
var theResolvedPaths = resolvedPaths({ context: this, options: options, params: params });
var router = options.data.keywords.controller.container.lookup('router:main');
var self = this;
var evaluateIsCurrentRoute = function() {
self.set('current_route_is_active_bool_for_ifroute', (function() {
return router.isActive.apply(router, [name].concat(theResolvedPaths)) ||
router.isActive.apply(router, [(name + '.index')].concat(theResolvedPaths));
})());
};
evaluateIsCurrentRoute();
router.addObserver('url', evaluateIsCurrentRoute);
options.contexts = null;
return Ember.Handlebars.helpers.boundIf.call(this, 'current_route_is_active_bool_for_ifroute', options);
});
I found an easy way to check if a route is active, but to get this into a computed property may not be so easy.
// Test if you are currently in a route by it's lowercase name
App.isInRoute = function(name) {
return App.Router.router.currentHandlerInfos.mapProperty('name').contains(name);
}
To use:
App.isInRoute('posts.show'); // true if in the route

ember.js: notify entry creation in nested route

I have a problem: I add an object, and i would like it to be appended 'live' to my list of objects, in a context of nested routes. Being in Ember.js context, i don't want to notify this change, it should auto-update.
Is it possible?
given this router:
App.Router.map ->
#resource "raw_materials", ->
#route 'new'
and these templates:
raw_materials.hbl
{{#linkTo raw_materials.new}}CREATE{{/linkTo}}
<ul>
{{#each controller}}
<li>
{{name}} - {{quantity}}
</li>
{{outlet}}
raw_materials/new.hbl
{{view Ember.TextField valueBinding="name"}}
{{view Ember.TextField valueBinding="quantity"}}
<a {{action 'create' this}}>V</a>
controller:
App.RawMaterialsNewController = Ember.ArrayController.extend
create: (params)->
App.RawMaterial.createRecord({
name: params.name,
quantity: params.quantity
})
#get("store").commit()
so that the result is this:
UPDATE:
my current route is:
App.RawMaterialsRoute = Ember.Route.extend
model: -> App.RawMaterial.find({})
if i change it in
App.RawMaterialsRoute = Ember.Route.extend
model: -> App.RawMaterial.find()
the problem is resolved.
The reason why i used .find({}) is that it gives me the isLoaded event, while with .find() the array isLoaded is always true, also when it's not loaded
What can I do?
Thank you

How to access a property in an itemController via the needs API in Ember.js

How can I access the properties of an itemController when I'm accessing the content of a controller via the needs API?
App.PostsIndexController = Ember.ArrayController.extend
itemController: 'post'
someAction: -> console.log("i'm melting!")
App.PostController = Ember.ObjectController.extend
someComputedProperty: (-> true ).property()
App.IndexController = Ember.Controller.extend
needs: ['postsIndex']
<script type="text/x-handlebars" data-template-name="index">
{{#each post in controllers.postsIndex.content}}
{{someComputedProperty}}
<a {{action someAction target="postsIndex"}}>click</a>
{{/each}}
</script>
I tried {{controllers.postsIndex.someComputedProperty}} and {{someComputedProperty}} in the index template.
How can I access the properties of an itemController when I'm accessing the content of a controller via the needs API?
First you should pass the controllers.postsIndex to the {{each}} helper instead of controllers.postsIndex.content. Otherwise the {{each}} helper will be looping over your model objects without going thru the PostsIndexController. So like #MilkyWayJoe suggested, try something like:
<script type="text/x-handlebars" data-template-name="index">
{{#each post in controllers.postsIndex}}
{{someComputedProperty}}
<a {{action someAction target="postsIndex"}}>click</a>
{{/each}}
</script>
I tried that, but it seems that someComputedProperty doesn't update when it changes, although it calculates correctly on page load.
Ember computed properties don't update unless you specify a list of dependencies:
App.PostController = Ember.ObjectController.extend
someComputedProperty: (-> true ).property('title', 'someotherproperty', 'etc')