In my Ember template, I render an arbitrarily-sized array of displayItems like so:
<script type="text/x-handlebars" id="display">
<h1>{{name}}</h1>
<hr>
<div id="display-items">
{{#each displayItem in displayItems itemController="chart"}}
{{render "chart" displayItem}}
{{/each}}
</div>
</script>
However, I need to initialize some properties on the chart controller before the chart view renders. I know that for predefined view and controller structures, you can use the setupController hook on a route, but since these controllers are created dynamically, I thought I could use the chart controller's init property like so:
...other controller code
init: function() {
var self = this;
self._super();
self.get("views")
.then(function(views) {
self.set("currentView", views.objectAt(0));
});
}
...
But although the init hook is called, it is called too late--the view has already rendered with undefined values. Is there a way to setup dynamically-created controllers before their views render?
What you need to do is to create a property in your 'chart' controller, that will be observing all properties you need to be filled int before displaying the view. First off, I would recommend you to change this:
{{#each displayItem in displayItems itemController="chart"}}
{{render "chart" displayItem}}
{{/each}}
to this:
{{#each displayItem in displayItems}}
{{render "chart" displayItem}}
{{/each}}
'render' will instantiate a new charController for you, and its corresponding view. Then, in your 'chartController', add a property listening to all properties you need before rendering the view:
App.ChartController = Ember.ObjectController.extend({
isAllInformationComplete: function() {
return !Ember.isEmpty('property1') && !Ember.isEmpty('property2');
}.property('property1', 'property2')
});
and in your view, wrap your code in an if statement:
<script type="text/x-handlebars" data-template-name="chart">
{{#if isAllInformationComplete}}
All your view html, handlebars, etc...
{{/if}}
</script>
here is a Fiddle for more details.
Related
I'm learning Ember right now and i'm beeing a bit confused because of the Docu of Ember and the getting started example.
In the Documentation it says:
In Ember.js, templates get their properties from controllers, which decorate a model.
And
Templates are always connected to controllers, not models.
But after doing the getting started guide i'm not sure if this is correct.
I've uploaded the finished TodoMVC app here: https://github.com/Yannic92/stackOverflowExamples/tree/master/Ember/TodoMVC
In the Index.html you'll find this template:
<script type="text/x-handlebars" data-template-name="todos/index">
<ul id="todo-list">
{{#each todo in model itemController="todo"}}
<li {{bind-attr class="todo.isCompleted:completed todo.isEditing:editing" }}>
{{#if todo.isEditing}}
{{edit-todo class="edit" value=todo.title focus-out="acceptChanges" insert-newline="acceptChanges"}}
{{else}}
{{input type="checkbox" checked=todo.isCompleted class="toggle"}}
<label {{action "editTodo" on="doubleClick"}}>{{todo.title}}</label>
<button {{action "removeTodo"}} class="destroy"></button>
{{/if}}
</li>
{{/each}}
</ul>
</script>
My question refers to the 3rd Line:
{{#each todo in model itemController="todo"}}
The Controller todo is only needed to provide the actions for the todos. The data is accessable even without this controller.
In my opinion there is the model directly connected with the template isn't it?
Or is there a default Controller like the docu mentioned here?
For convenience, Ember.js provides controllers that proxy properties from their models so that you can say {{name}} in your template rather than {{model.name}}.
As you can see in this line: <script type="text/x-handlebars" data-template-name="todos/index"> this is the template for / because the router has this line: this.route('todos', { path: '/'}). Which will have a controller named TodosController, even if you didn't write one ember will generate one for you. So when you delete it that's what happens.
In this template you loop through the todo's list. Each of these Todo models are decorated with a controller the TodoController. And with this line: {{#each todo in model itemController="todo"}} you tell ember to use this TodoController for every element in the list.
If you leave out the itemController ember assumes the todo's are part of the model for the IndexController provided by the IndexRoute.
By default ember has an empty controller which proxies everything to the underlying model. (Note: I believe this will change in ember 2.0). So it may look like it's directly coupled to the model. But you could write a controller that changes everything without changing the model.
I have a container view which, among other things, displays a list of objects, like so:
{{#each}}
<div {{bind-attr class="author.first_name task"}}></div>
{{/each}}
I would like to hook a Javascript function everytime a DOM element is added to this list. I've tried doing:
didInsertElement: function() { ... }
But this hook apparently runs only the first time the view is initialized. I figured that maybe the hook doesn't run because the view is actually inserted once, and what's inserted more than once are just the nested element.
So should I use a nested view?
I tried something along these lines:
{{#each}}
{{#view App.SingleItemView}}
<div {{bind-attr class="author.first_name task"}}></div>
{{/view}}
{{/each}}
But in this case, though it works somehow, it doesn't get passed the necessary data that would render the properties such as author.first_name.
render will give you a new scope and is really easy to assign the content as well
<ul>
{{#each item in controller}}
{{render 'ind' item}}
{{/each}}
</ul>
http://emberjs.jsbin.com/alAKubo/1/edit
I'm using the {{render}} handlebars helper in one of my templates to (attempt to) render the template of another route on the same page. So for example:
<script type="text/x-handlebars" data-template-name="index">
{{#each model}}
{{name}}<br />
{{/each}}
{{render "people"}}
</script>
<script type="text/x-handlebars" data-template-name="people">
{{#each controller}}
{{name}}<br />
{{/each}}
</script>
In my people controller I set the model as usual:
App.PeopleRoute = Ember.Route.extend({
model: function() {
return App.People.find();
}
});
If I visit my people route directly (/people), I get a list of people. In my main template where the people template is being rendered into another template, the render helper doesn't call the PeopleRoute, so the model is never set (as far as I understand it).
Is there a way for me to set the content property on the PeopleController to the list of people objects?
I can't get any variation of this to work:
App.PeopleController = Ember.ArrayController.extend({
content: function() {
return App.People.find();
}
});
The render helper doesnt make any transitions and hence the model hook will not get called.. It just renders the template in the current context with the same-named controller... You can pass an optional model to the render helper which binds to the content of the same-named controller... Ember API
<script type="text/x-handlebars" data-template-name="index">
{{#each model}}
{{name}}<br />
{{/each}}
{{render "people" model}}
</script>
Sample fiddle here
I'm hoping someone can explain what I'm doing wrong here. I'm using Ember.js with handlebars templates, I've tried a number of ways to get handlebars #if else working for me but it always returns both the contents of if and else.. Here's the code I'm currently using.
App.js:
App = Ember.Application.create({
selectedView: "Home",
IsHome: function() {
this.get('selectedView') === 'Home'
}.property('selectedView')
});
index.html head contains:
<script type="text/x-handlebars" data-template-name="home-view">
Hello, This is the home view.
</script>
<script type="text/x-handlebars" data-template-name="edit-account">
Hello, This is the account edit view.
</script>
and in the body:
<script type="text/x-handlebars">
{{#if App.IsHome}}
{{view HomeView}}
{{else}}
{{view editAccountView}}
{{/if}}
</script>
This ends up showing both views in the body of the page. Any advice is appreciated,
thanks
Steve
The fundamental reason for your problem is the Handlebars view helper ( {{view MyView}} ) expects a path parameter to an Ember “class”, not an instance of a class. To create a view class use the extend method of Ember.View. The Handlebars view helper will instantiate your view class and append it to the document for you.
See this post for more information on the Ember object model and the differences between Ember’s extend and create methods: http://ember-object-model.notlong.com.
Here is your example with a few changes made to correct the points I made above. http://jsfiddle.net/ethan_selzer/kcjzw/217/
Regards,
Ethan
That's because you are appending both views (homeView and editAccountView) manually with appendTo.
Also, avoid defining views within the App create() function. Therefore, create your App:
App = Ember.Application.create({
ready: function() {
App.MainView.create().append();
}
});
where App.MainView is your top-level view defined by:
App.MainView = Ember.View.extend({
templateName: 'main-view',
selectedView: "Home",
isHome: function() {
this.get('selectedView') === 'Home'
},
homeView: Ember.View.extend({
templateName: 'home-view'
}),
editAccountView: Ember.View.extend({
templateName: 'edit-account'
})
});
with the handlebars template:
<script type="text/x-handlebars" data-template-name="main-view">
{{#if view.isHome}}
{{view view.homeView}}
{{else}}
{{view view.editAccountView}}
{{/if}}
</script>
I can't seem to get a button, generated within an #each template loop, to bind its click action to its associated model. Here's a quick demo of the problem...
The Ember.js app setup:
window.Contacts = Ember.Application.create();
Contacts.Person = Ember.Object.extend({
first: '',
last: '',
save: function() {
// send updated information to server.
}
});
Contacts.contactsList = Ember.ArrayController.create({
content:[],
init: function() {
this.pushObject( Contacts.Person.create({
first:'Tom',
last:'Riddle'
}));
}
});
The template:
<script type="text/x-handlebars">
{{#each Contacts.contactsList}}
<li>
{{view Ember.TextField valueBinding="first" viewName="firstname"}}
{{view Ember.TextField valueBinding="last" viewName="lastname"}}
<button {{action "save" on="click"}}>Save</button>
</li>
{{/each}}
</script>
The problem:
So, the idea in this simple demo scenario is that the "Save" button for each record will trigger an action to save the state of its own model. However, clicking the Save button gives an error:
Uncaught TypeError: Cannot call method 'call' of undefined
My assumption would be that specifying "save" as the button's action would bind it to the save method on its model. However, this does not appear to be the case. Some other object appears to be handling click actions, wherein a "save" hander is undefined. Am I missing something here? How could I make each line item's button call a handler on its own model?
Thanks in advance for any help!
You can define a target of an action by setting the - surprise - target property, see http://jsfiddle.net/pangratz666/FukKX/:
<script type="text/x-handlebars" >
{{#each Contacts.contactsList}}
<li>
{{view Ember.TextField valueBinding="first" viewName="firstname"}}
{{view Ember.TextField valueBinding="last" viewName="lastname"}}
{{#with this as model}}
<button {{action "save" target="model"}}>Save</button>
{{/with}}
</li>
{{/each}}
</script>
The {{#with}} helper around the action is needed because somehow the action helper does not accept this as a target.
But a note to your design: actions should be called on views or on a controller. The target is then responsible for executing further actions like saving, ...
So I would implement your example as follows, see http://jsfiddle.net/pangratz666/U2TKJ/:
Handlebars:
<script type="text/x-handlebars" >
{{#each Contacts.contactsList}}
<li>
{{view Ember.TextField valueBinding="first" viewName="firstname"}}
{{view Ember.TextField valueBinding="last" viewName="lastname"}}
<button {{action "save" target="Contacts.contactsController" }}>Save</button>
</li>
{{/each}}
</script>
JavaScript:
Contacts.contactsController = Ember.Object.create({
save: function(event) {
console.log('do something with: ', event.context);
}
});
There´re two possible ways
{{#each answer in effort_reasons itemController="module_answer"}}
{{/each}}
So each Item gets its own controller where model is the each item (in this case answer), this is especially usefull for something like an Input View where valueBinding would result in binding every input view to the same value. Note this is the only time where controllers in Ember are not singletons, and are having different ID´s, if you want to save your values in the original controller you can get a reference via
this.get('controller.parentController')
inside your itemController.
Or you use the mentioned action approach inside the each loop with
{{action "actionname" parameter paramter2...}}