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.
Related
I'm working through Ryan LaBouve's YouTube tutorial on building the TodoMVC app with Ember CLI. I'm about half way through, now adding a conditional within the template. When a (todo list) item is double-clicked, it is supposed to trigger a function editTodo that sets a property "isEditing" to true and replaces the text with an input box.
The doubleClick function is not working at all. It throws the following error in the console:
Uncaught Error: Nothing handled the action 'editTodo'. If you did handle the action, this error can be caused by returning true from an action handler in a controller, causing the action to bubble.
This is the relevant template section (todos.hbs):
<section id="main">
<ul id="todo-list">
{{#each todo in model itemComtroller="todo"}}
<li {{bind-attr class="todo.isCompleted:completed todo.isEditing:editing"}}>
{{#if todo.isEditing}}
<input class="edit">
{{else}}
{{input type="checkbox" class="toggle" checked=todo.isCompleted}}
<label {{action "editTodo" on="doubleClick"}}>{{todo.title}}</label><button class="destroy"></button>
{{/if}}
</li>
{{/each}}
</ul>
<input type="checkbox" id="toggle-all">
</section>
Here is the controller with the "editTodo" function (todo.js):
import Ember from 'ember';
export default Ember.ObjectController.extend({
actions: {
editTodo: function() {
this.set('isEditing', true);
}
},
isEditing: false,
isCompleted: function(key, value) {
var model = this.get('model');
if (value === undefined ) {
return model.get('isCompleted');
} else {
model.set('isCompleted', value);
model.save();
return value;
}
}.property('model.isCompleted')
});
I've cross-referenced my code with the video and the associated Github repo and still can't find the cause of the problem. There are also related issues on SO, but I've not seen one quite like this. Obviously I am new to Ember.js and can use all the help I can get.
THANKS!
It seems that you've run into a bug that was introduced in Ember 1.13.4. I'm not sure what it is, but it has to do with using an item controller and having an action in the loop. For now, you can downgrade to Ember 1.13.3 or lower to fix the issue. I will file a bug report so that this can be fixed in later versions of Ember.
Long term you won't be writing code like this. Item controllers have been completely removed in Ember 2.0, so you'll likely switch to using components for this type of situation when using Ember 2.0. For now you can practice using Ember 1.13.3.
Almost forgot:
Code that works in 1.13.3
Same code broken in 1.13.4
EDIT: Link to GitHub issue
EDIT2: It seems that some behavior has changed in Ember (possibly another bug?) between 1.12.0 and 1.13.0. For some reason the actions are no longer being caught in the item controller and instead are being sent directly to the route controller. I'm not 100% sure why that is, but as a fix you can either downgrade to Ember 1.12 or move your editTodo action to your todos controller instead.
EDIT3: Link to second Github issue
EDIT4: As suggested in the second bug ticket, you can also change the target of the action:
<label {{action "editTodo" on="doubleClick" target="todo"}}>{{todo.title}}</label>
I have a simple blog in which I learn Ember.
Now I have a controller
App.IndexController = Ember.ArrayController.extend({
sortProperties: ['originalId:desc']
sortedPosts: Ember.computed.sort('model', 'sortProperties'),
})
So in template I show all posts. For this I want to show edit link near all of my posts.
So question is how to do this in handlebars.
I want to do something like this:
{{#each post in sortedPosts}}
<h1>{{link-to post.title 'post' post }}</h1>
{{#if session.isAuthenticated and post.ownedBy(session.user)}} <!--This place doesn't work-->
{{link-to 'Edit' 'post.edit' post }}
{{/if}}
{{{post.text}}}
<hr/>
{{/each}}
I found this question Logical operator in a handlebars.js {{#if}} conditional but I hope that there is better solution.
P.S.: There is one more question about sorting with SortableMixin. It doesn't reload templates when sortProperties is changed. So I have to create one more property sortedPosts. Maybe someone knows why it doesn't work?
You will need to create a computed property in the controller and then use it in the view. If you have 2 computed properties and want to combine them into a 3rd - you can use computed.and (see here)
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've been working through the Discover meteor book, currently at chapter 6. I'm having great difficulty in understanding the relationship between templates, and how they work.
For example, I have a template called posts_lists.html and a javascript file called posts_lists.js
Within posts_lists.js I have the following:
Template.postsList.helpers({
posts: postsData
});
And within posts_lists.html I have:
<template name="postsList">
<div class="posts">
{{#each posts}}
{{> postItem}}
{{/each}}
</div>
</template>
So, am I right in thinking that Template.postsList.helpers({ is a block that contains all the variables accessed by the postsList template. The bock returns postsData in the form of posts.
posts is looped over - calling the postItem template. This is where I get lost... as I don't have a postItem.js file, or postItem.html file
I do have post_item.html and post_item.js - but these aren't named the same as postItem...
....but it works???? I don't get it. I know its not magic... but cant figure it out. In terms of my level / skillset I'm a designer / jquery user trying to learn more.
Thanks,
Rob
The file names have little significance. It's just a convention.
Your post_item.html should contain a template named postItem.
I am currently rendering a list of views:
<ul>
{{#each newsItem in controller}}
{{view App.NewsItemView contentBinding="newsItem" class="news-item" }}
{{/each}}
</ul>
But I would like to inject a NewsItemController into each view.
I've tried using render, but this only seems to support a single view, giving the exception:
Uncaught Error: Handlebars error: Could not find property 'control'
on object .
I've found a brief mention of using control instead, but this no longer seems to be included.
So how can I render multiple versions of the same view, injecting a separate controller into each one?
{{render}} should be fixed in current master (if you build it from Github). You should be able to use it multiple times if you pass a model:
<ul>
{{#each controller}}
{{render "newsItem" this}}
{{/each}}
</ul>
{{control}} is still there but hidden behind a flag (because it's still experimental). To use it you need to do : ENV.EXPERIMENTAL_CONTROL_HELPER = true before including the ember.js file. If you can avoid using it, it would be better.
However I think the simplest approach would be to use itemController:
<ul>
{{#each controller itemController="newsItem"}}
{{view App.NewsItemView class="news-item" }}
{{/each}}
</ul>
I think you can combine them to make it simpler (I haven't tried it yet):
<ul>
{{each controller itemController="newsItem" itemViewClass="App.NewsItemView"}}
</ul>