Ember: Idiom for ArrayController to loop through same-named ObjectController - ember.js

I have the following pattern recurring throughout my app, and I was wondering if there is a way to clean this up:
Array Controller:
App.ThingsController = Ember.ArrayController.extend()
Template for Array Controller (using Emblem)
each thing in controller
= render 'thing' thing
Object Controller:
App.ThingController = Ember.ObjectController.extend()
I don't like having that intermediary template that has only those two lines.
I looked into itemController as a potential solution but that doesn't seem to do what I want.

Instead of rendering inside your each loop you could just include the code, and then use itemController. This would consolidate your templates into one, but still allow you to use the ObjectController.
{{each thing in controller itemController="thing"}}
{{thing.name}}.id = {{thing.id}}
{{/each}}

Related

#each over different models same route

I have two JS arrays, one route, I can only get one model per route and regardless of trying endless ways of creating ArrayControllers and using them I can't use a 2nd model in my template.
e.g.,
{{#each}} // uses content model and default controller... works fine
<label>{{name}}</label>
{{/each}
{{#each %%whatever%%}} // trying to use another model/ JS array
<label>{{name}}</label>
{{/each}
Either I get errors or no data shows up.
I've tried specifying the array directly, using an ArrayController and setting its content and/or model to be the JS array. I've tried using in...
For example, suppose I have
var x = [{name : "Jo"}, {name : "Bob"}];
var y = [{name : "Jake"}, {name : "Ben"}];
How can I display them as
<label>Jo</label><label>Bob</label>
<label>Jake</label><label>Ben</label>
?
(No reduction here, have to use two separate arrays. My example is more complex but the page does not require all out complicating things like creating extra views, templates, and routes)
it would be nice if one could do
{{#each x}}
<label>{{name}}</label>
{{/each}
{{#each y}}
<label>{{name}}</label>
{{/each}
but this doesn't work.
Any ideas, this is driving me crazy! (I know I could add a variable to each element that specifies which collection it part of then use #if but this is too much of a hack and not very convenient)
Thanks...
You can use the setupController hook in your Route to set properties on the controller. These controller properties are then rendered by the template.
// define some arrays
App.x = [{name: "Jo"}, {name: "Bob"}];
App.y = [{name: "Jake"}, {name: "Ben"}];
App.ApplicationRoute = Ember.Route.extend({
setupController: function (controller, model) {
// make these available to the controller
controller.set('x', App.x)
controller.set('y', App.y)
}
});
See: http://jsfiddle.net/n724p/

How to use itemControllerClass to achieve a singleton like experience with ember.js

I've got a fairly simple form but it should never carry any state with it. I started reading an older discussion about how you can use itemControllerClass to get a "singleton" class created each time you enter the route.
If I want to use this how would I plug this into the template and wire it up from the parent controller?
Here is what I'm guessing you would do from the javascript side
App.FooController = Ember.Controller.extend({
itemControllerClass: 'someclass_name'
});
The only "must have" is that I need to have access to the parent route params from the child singleton controller.
Any guidance would be excellent -thank you in advance!
Update
Just to be clear about my use case -this is not an ArrayController. I actually just have a Controller (as shown above). I don't need to proxy a model or Array of models. I'm looking for a way to point at a url (with a few params) and generate a new instance when the route is loaded (or the parent controller is shown).
I'm doing this because the template is a simple "blank form" that doesn't and shouldn't carry state with it. when the user saves the form and I transition to the index route everything that happened in that form can die with it (as I've cached the data in ember-data or finished my $.ajax / etc)
Anyone know how to accomplish this stateless behavior with a controller?
I'm betting you're talking about this discussion. It's one of my personal favorite discoveries related to Ember. The outcome of it was the itemController property of an ArrayController; I use it all the time. The basic gist of it is, when you're iterating over an array controller, you can change the backing controller within the loop. So, each iterating of the loop, it will provide a new controller of the type you specify. You can specify the itemController as either a property on the ArrayController, or as an option on the {{#each}} handlebars helper. So, you could do it like this:
App.FooController = Ember.ArrayController.extend({
itemController: 'someclass'
});
App.SomeclassController = Ember.ObjectController.extend({});
Or, like this:
{{#each something in controller itemController='someclass'}}
...
{{/each}}
Within the itemController, you can access the parent controller (FooController, in this case), like:
this.get('parentController');
Or, you can specify the dependency using needs, like you ordinarily would in a controller. So, as long as the params are available to the parentController, you should be able to access them on the child controller as well.
Update
After hearing more about the use case, where a controller's state needs to reset every time a transition happens to a particular route, It sounds like the right approach is to have a backing model for the controller. Then, you can create a new instance of the model on one of the route's hooks; likely either model or setupController.
From http://emberjs.com/api/classes/Ember.ArrayController.html
Sometimes you want to display computed properties within the body of an #each helper that depend on the underlying items in content, but are not present on those items. To do this, set itemController to the name of a controller (probably an ObjectController) that will wrap each individual item.
For example:
{{#each post in controller}}
<li>{{title}} ({{titleLength}} characters)</li>
{{/each}}
App.PostsController = Ember.ArrayController.extend({
itemController: 'post'
});
App.PostController = Ember.ObjectController.extend({
// the `title` property will be proxied to the underlying post.
titleLength: function() {
return this.get('title').length;
}.property('title')
});
In some cases it is helpful to return a different itemController depending on the particular item. Subclasses can do this by overriding lookupItemController.
For example:
App.MyArrayController = Ember.ArrayController.extend({
lookupItemController: function( object ) {
if (object.get('isSpecial')) {
return "special"; // use App.SpecialController
} else {
return "regular"; // use App.RegularController
}
}
});
The itemController instances will have a parentController property set to either the the parentController property of the ArrayController or to the ArrayController instance itself.

Custom Handlebars block helper

How do I define a custom block helper in Handlebars (for use with Ember.js)? I've tried using the method described on the Handlebars site, but it doesn't seem to work. I get this error from Ember.js:
Assertion failed: registerBoundHelper-generated helpers do not support use with Handlebars blocks.
Here's the code for my helper. The idea is that the block will only be rendered if the models that I pass in are the same:
Ember.Handlebars.helper 'sameModel', (model1, model2, options) ->
if model1 is model2
new Handlebars.SafeString(options.fn(this))
else
''
6 months later, it looks like this is possible now, at least to a certain extent. You can view the discussion here. I agree with the pull request that this should usually be handled using computed properties, but this is still very useful in some cases.
I'm going to accept this answer to keep this post updated. If I've broken any SO etiquette, let me know. :)
Assertion is correct. You cannot do that, at least not it RC6 and before.
You may want to create a view with a template and bind some properties to it:
some.hbs
{{#if model1}}
This is model1 {{model1.name}}
{{/if}}
{{#if model2}}
This is model2 {{model2.name}}
{{/if}}
views/some.js
App.SomeView = Ember.View.Extend({
templateName: "some"
})
different template
<h3>{{view App.SomeView model1Binding=someModel1 model2Binding=someModel2}}</h3>

{{outlet}}, {{view}}, {{render}}, and {{control}} helpers

I am trying to put together a simple master-details Ember app. Directory tree on one side and file list on another.
Ember offers few helpers to render context into a view. Which of them I can use for:
Subtrees of the directory tree.
Details list.
In fact, would be very helpful if someone can point me to any docs I can read about the difference between {{render view}}, {{view view}} and {{control view}} helpers and how to use them properly.
Thanks a lot!
{{view "directory"}} renders the view within the context of the current controller.
{{render "directory"}} renders the view App.DirectoryView with template directory within the context of the singleton App.DirectoryController
{{control directory}} behaves the same way as render only it creates a new instance of App.DirectoryController every time it renders (unlike render which uses the same controller instance every time).
Update 18 Feb 2014: {{control}} has been removed.
The last two helpers are relatively new, so there isn't much documentation about them. You can find {{view}} documentation here.
Now looking at your use case, I don't think you need any of these helpers. Just use nested routes and the {{outlet}} helper and it should just work.
App.Router.map(function(){
this.resource('directories', function() {
this.resource('directory', { path: '/:directory_id'}, function() {
this.route('files');
});
});
});
You can build on that following this guide.
UPDATE: {{render}} now creates a new instance every time if you pass a model.
For a very good explanation of the helpers render, partial, outlet and template have a look at this question.
Just as a rough a summary, how one might use those helpers:
{{render "navigation"}} -> Renders the NavigationController and NavigationView at this place. This is helper is good for places, where the Controller and View do not change, e.g. a navigation.
{{outlet "detailsOutlet"}} -> This will provide a stub/hook/point into which you can render Components(Controller + View). One would use this with the render method of routes. In your case you will likely have a details route which could look like this. This would render the DetailsController with DetailsView into the outlet 'detailsOutlet' of the index template.
App.DetailsRoute = Ember.Route.extend({
renderTemplate: function() {
this.render('details', { // the template/view to render -> results in App.DetailsView
into: 'index', // the template to render into -> where the outlet is defined
outlet: 'detailsOutlet', // the name of the outlet in that template -> see above
});
}
});
{{view App.DetailsView}} -> This will render the given view, while preserving the current context/controller. One might change the context, e.g. using your master entity and pass its details to a view like this:
{{view App.DetailsView contextBinding="masterEntity.details"}}
This is helper is useful, when you want to encapsulate certain parts of a component in subviews, that have own custom logic like handling of actions/events.
{{control}} I know that control instantiates a new controller every time it is used, but I cannot see a good fit for your, nor have i a good example for using it.
To Understand the difference between ember {{render}},{{template}},{{view}},{{control}}
you can refer this article
http://darthdeus.github.io/blog/2013/02/10/render-control-partial-view/

How do I setup an Ember View class to be appended to a particular container?

Ember.View has a nice method called .appendTo("#container") which would allow me to specify a container div for the view. However, when I use the router and .connectOutlet method, an instance of my view is created automatically based on convention and is added to the page body element by default. Is there a way to configure the class definition of the view so that upon creation it will be inside my desired #container. Here is my view:
Jimux.BuildsView = Em.View.extend({
templateName: 'builds',
appendTo: '#jimux-header', #this was just a guess and did not work. but does some propery like this exist for the view?
tagName: 'div',
listVisible: true,
...
Another way to ask this question is: how do I tell Ember router to append a view to a particular item in the dom? By default the router appends the view to the body.
And here is the router bit:
# Connect builds controller to builds view
router.get('applicationController').connectOutlet("builds","builds", Jimux.buildsController)
To clarify, I dont want to put my whole Ember app in a container. I have several views in my application, and most of them are fine directly in the body. But there are a couple like the one mentioned in this question, which I want to put inside "#application-header" div.
You can specify the root element for your application object.
window.App = Ember.Application.create({
rootElement: '#ember-app'
});
Edit:
Having re-read your question, I think you should look into named outlets, so you could do something like:
<div id="application-header">
{{outlet builds}}
</div>
{{outlet}}
well..after understanding your question, i remember having same trouble. Also, thing is i didn't find any way to do this even after going through the Ember code. But later i understood that its for good purpose only. I know you already might have come across with handlebars with which we can acheive this. If we give a view a ID to get appended, we are constraining the application and the whole use of ember becomes useless. Ok coming to you question, as far as i know, we can acheive that appending mustache templates in you div element of HTML.
<div id="jimux-header">
{{view Jimux.BuildsView}}
</div>
This way we can use the Jimux.BuildsView where ever you want and as many times possible. The Beauty of Ember you have to say...
Just add rootElement in the application object.
var App = Ember.Application.create({
rootElement: '#container'
});