Ember: {{link-to}} helper error when in {{each}} helper - ember.js

The link-tohelper return the following error:
Uncaught Error: each doesn't match link-to - 5:10
The template:
<script type="text/x-handlebars" id="actions">
<div class='container-fluid'>
<div class="row"> <!-- -->
<div class="col-md-6 col-lg-4"> <!-- -->
{{#each action in model}}
{{link-to 'action' action}}{{action.id}}{{/link-to}}
{{/each}}
{{outlet}}
</div>
</div>
</div>
</script>
The router:
App.Router.map(function() {
this.resource('application', function() {
this.resource('actions', function() {
this.resource('action', { path: '/:action_id'});
});
});
The route:
App.ActionsRoute = Ember.Route.extend({
model: function() {
return this.store.findAll('action');
//return this.modelFor('user').get('actions');
},
setupController: function (controller, model) {
controller.set('model', model);
},
});
I cannot find what is wrong.

It's a quite small error. When using a block helper such as each or link-to you need to invoke them with a # in front, such as you have done with {{#each}}. Since you lack that on your starting link-to, the parser sees the {{/link-to}} and notices that it currently is working with a each-block and those doesn't match. Just add a # in front of your starting 'link-to' and it should work fine.
{{#each action in model}}
{{#link-to 'action' action}}{{action.id}}{{/link-to}}
{{/each}}

Related

Rendering model via nested templates in Ember

I'm building an app with multiple todo lists. The todo list used to correctly show todo model.
But I added function() to stack router to wrap todos router (so that it would correctly render todos template in stack template).
Then todos/index template (which is rendered through the outlet in todos) stopped displaying todo model.
This is my router structure:
Todos.Router.map(function() {
this.resource('stacks', {path: '/stacks'});
this.resource('stack', {path: '/stacks/:stack_id'}, function () {
this.resource('todos', { path: '/todos/:todos_id' }, function () {
// additional child routes will go here later
this.route('active');
this.route('completed');
this.route('new');
});
this.resource('todo', { path: 'todo/:todo_id' });
});
});
Stack template which renders Todo template:
<script type="text/x-handlebars" data-template-name="stack">
<h1>
<label {{action "editStack" on="doubleClick"}}>{{stackTitle}}</label>
{{input
value=stackTitle
action="createStack"}}
<div>{{model.stackTitle}}</div>
</h1>
{{render 'todos' todo}}
</div>
</script>
Then this todo template has an outlet for todo/index:
<script type="text/x-handlebars" data-template-name="todos">
<div class="container-fluid">
<section id="main">
{{outlet 'todos/index'}}
{{outlet modal}}
{{input type="checkbox" id="toggle-all" checked=allAreDone}}
</section>
</div>
</script>
And todo/index template:
<script type="text/x-handlebars" data-template-name="todos/index">
<ul id="todo-list">
<li {{bind-attr class="todo.isCompleted:completed todo.isEditing:editing"}}>
{{#each todo in model itemController="todo"}}
{{#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"}}
{{outlet}}
<button {{action 'openModal' 'modal' model}}>
<label {{action "editTodo" on="doubleClick"}}>{{todo.title}}</label>
</button>
{{/if}}
</li>
{{/each}}
</ul>
</script>
Routers:
Todos.TodosRoute = Ember.Route.extend({
model: function() {
return this.store.find('todo');
},
});
Todos.TodosIndexRoute = Ember.Route.extend({
model: function() {
return this.modelFor('todos');
},
});
I've tried using {{partial}} helper in case that was a problem, but it didn't seem to change much.
Maybe I'm missing something in todos/index router that I need to call data through nested templates?
I appreciate your help!
There's a lot going on here but I think the root of your problem is with the way that you're using render in your templates/stack.
Since your routes/todos route is nested inside your routes/stack route, you're going to want to use an outlet inside your templates/stack if you want any of the templates for the nested routes to be rendered.
When you use render 'todos' todo, you're saying to render templates/todos with the todo property from the controllers/stack as the model. This isn't going to use your routes/todos or routes/todos-index to set the model.
What you probably want is for your templates/stack to have an {{outlet}} that either the routes/todos or routes/todo is rendered into when you visit either of those routes.

Ember.js: How to filter a model by route

I'm trying to adopt the completed/archived-route-behavior from this TodoMVC-Tutorial.
I'm using Ember 1.7.0, Ember Data 1.0.0-beta.9 and Handlebars 1.3.0.
I want to get all active users on my index route (example.com/organization/users) and all archived users on my archived route (example.com/organization/users/archived).
But the filter doesn't work: archived users are shown on my index route and vice versa, but my console.log output is correct.
Update: Please check this JSBin: http://emberjs.jsbin.com/puxeludeviga/3/
That's how I tried it:
Docket.OrganizationUsersRoute = Docket.AuthenticatedRoute.extend({
model: function() {
return this.store.find('user');
},
renderTemplate: function() {
// render all posts
this.render('organization/users', {
into: 'application'
});
// render toolbar
this.render('organization/toolbar', {
into: 'application',
outlet: 'toolbar'
});
}
});
Docket.OrganizationUsersIndexRoute = Docket.OrganizationUsersRoute.extend({
model: function() {
console.log('index');
return this.store.filter('user', function(user) {
return !user.get('archived');
});
}
});
Docket.OrganizationUsersArchivedRoute = Docket.OrganizationUsersRoute.extend({
model: function() {
console.log('archived');
return this.store.filter('user', function(user) {
return user.get('archived');
});
}
});
And that's my template:
<ul class="entries">
{{#each}}
<li>
<div class="actions">
<button {{action "remove" this}} class="ei-icon-close"></button>
</div>
<div class="link" {{action "edit" this}} data-uk-modal="{target:'#user-modal'}">
<span class="before">{{initial}}</span>{{name}}
</div>
</li>
{{else}}
<li>No users</li>
{{/each}}
</ul>
My (shortened) router.js file:
Docket.Router.map(function() {
// organization route
this.resource('organization', function() {
// user routes
this.resource('organization.users', { path: '/users' }, function() {
this.route('archived');
});
});
});
I think I found your issue. The thing here is that template you're rendering the users into, has this name:
organization/users
and turns out that it matches to you organization.users route, so, it is always rendering the data from that route. You need to change the name of you template, let's say to:
organization/users_list
and then, use that in your routes, it will not have any problems because the name is different to the users route.
something like:
<script type="text/x-handlebars" data-template-name="organization/users_list">
<div class="top">
<h1>Users</h1>
{{#link-to 'organization.users.index' class="button-archive"}}active{{/link-to}} - {{#link-to 'organization.users.archived' class="button-archive"}}archived{{/link-to}}
</div>
<ul class="entries">
{{#each}}
<li>{{name}} - Archived: {{archived}}</li>
{{else}}
<li>No users to show</li>
{{/each}}
</ul>
</script>
and in your routes:
renderTemplate: function() {
// render all posts
this.render('organization/users_list', {
into: 'application'
});
// render toolbar
this.render('organization/toolbar', {
into: 'application',
outlet: 'toolbar'
});
}

Set controller for template rendering

I recently decided to look into Ember.js after having spent the last two years with KO. The first thing to notice is that the complexity seems a TAD steeper but I shall prevail :)
Now, I seem to need to hardcode the controller for a certain template which seems weird:
App.IndexRoute = Ember.Route.extend({
renderTemplate: function() {
this.render('todosList', { into: 'application' });
}
});
App.todosController = Ember.ArrayController.create({
content: [App.Todo.create(), App.Todo.create()]
});
<script type="text/x-handlebars" data-template-name="application">
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="todosList">
<ul id="todo-list">
{{#each todo in App.todosController.content}}
<li>
<label {{bindAttr class="todo.isCompleted"}}>{{view Ember.Checkbox checkedBinding="todo.isCompleted"}} {{todo.title}}</label>
<button {{action 'removeTodo' todo target="App.todosController"}}>Ta bort</button>
</li>
{{/each}}
</ul>
{{view Ember.TextField valueBinding="App.todosController.newTodoText"}}
<button {{action 'newTodo' App.todosController.newTodoText target="App.todosController"}}>New todo</button>
</script>
I tried setting controller: 'App.todosController' in the render() call but nothing. The #each in the view accepts nothing else than App.todosController.content which doesn't seem right. Why do I even need to explicitly state that it's the content it should read, isn't that set automatically?
Thankful for any help, Ember seems to have its niceties but in the beginning much is confusing.
Short answer first:
working jsbin: http://jsbin.com/usaluc/8/edit
Longer answer:
You had some misconceptions in your code that I've changed to be more ember-like, this results in this very simple example.
todosList template
<script type="text/x-handlebars" data-template-name="todosList">
<ul id="todo-list">
{{#each todo in controller}}
<li>
<label {{bindAttr class="todo.isCompleted"}}>
{{view Ember.Checkbox checkedBinding="todo.isCompleted"}} {{todo.title}}
</label>
<button {{action 'removeTodo' todo target="controller"}}>Remove toto</button>
</li>
{{/each}}
</ul>
{{view Ember.TextField valueBinding="newTodoText"}}
<button {{action 'newTodo' newTodoText target="controller"}}>New todo</button>
</script>
IndexRoute
When using renderTemplate to make sure the correct controller is used you should define it in the hash you are passing into the render function:
App.IndexRoute = Ember.Route.extend({
renderTemplate: function() {
this.render('todosList', {
into: 'application',
controller: 'todosList'
});
}
});
Route map
Since you dind't post your route map, and furthermore because your are using the IndexRoute renderTemplate hook I assumed your todosList is rendered directly when visiting '/', so just to be concise here a simple router map the renders the todosList template when visiting '/'
App.Router.map(function() {
this.resource('todosList', {path: '/'});
});
TodosListRoute
Now that you have a TodosListRoute in where you want to set to correct controller content, you should hook into the setupController function and do just that:
App.TodosListRoute = Ember.Route.extend({
setupController: function(controller, model) {
var myTodos = [
App.Todo.create({title: 'Drink water', text:'foo'}),
App.Todo.create({title: 'Bring out the trash', text:'bar'})
];
controller.set('content', myTodos);
}
});
TodosListController
The TodosListController looks fairly simple so far only including the two functions newTodo and removeTodo using the title value passed from the action helper in your template:
App.TodosListController = Ember.ArrayController.extend({
newTodo: function(title) {
var todo = App.Todo.create({title: title, text:'foo'});
this.get('content').pushObject(todo);
},
removeTodo: function(todo) {
this.get('content').removeObject(todo);
}
});
Hope it helps.

How do I pass model information to this each statement in a nested template?

I have an index route where I need to populate a menu in one outlet based on a list of models. However I cannot get the models to be represented in the each statement.
Here is the Category Model:
App.Category = DS.Model.extend({
title: DS.attr('string'),
parent: DS.belongsTo('App.Category')
})
And here is the indexRoute where I render it
App.IndexRoute = Em.Route.extend({
renderTemplate: function(){
this.render('index')
this.render('categoryMenu',
{
outlet: 'sidebar',
into: 'index',
model: function() {
return App.Category.find()
},
controller: App.CategoriesController
})
this.render('badgesList',{
outlet: 'badgesList',
into: 'index'
})
}
})
Index Template:
<script type="text/x-handlebars" data-template-name="index">
<div class="span3">
{{outlet sidebar}}
</div>
<div class="span8">
{{outlet badgesList}}
</div>
</script>
Nested Category Template
<script type="text/x-handlebars" data-template-name="categoryMenu">
<ul>
{{#each model}}
{{title}}
{{/each}}
</ul>
</script>
I have tried to change the each statement to several different things like controller or item in model but nothing is displayed.
Thank you for any help!
Make yourself CategoriesController first like this:
App.CategoriesController = Ember.ArrayController.extend({
content: function () {
return App.Category.find()
}.property()
});
rename your template just to categories
<script type="text/x-handlebars" data-template-name="categories">
<ul>
{{#each model}}
{{title}}
{{/each}}
</ul>
</script>
in your index template replace {{outlet}} with {{render 'categoies'}}
<script type="text/x-handlebars" data-template-name="index">
<div class="span3">
{{render 'categoies'}}
</div>
<div class="span8">
{{outlet badgesList}}
</div>
</script>
as last thing remove the call rendering categoryMenu.

Emberjs rendering wrong template

I am working with EmberJs and can't figure out why the following code won't render the 'new' template. When I navigate to /shops, I get a list of shops and a create button linking to '/shops/new', but when I click create, the 'new' template isn't rendered and instead stays the same as 'shops'. I can navigate to each individual shop fine, just not new.
App.js
window.App = Ember.Application.create();
App.Router.reopen({
location: 'history'
});
// Router
App.Router.map(function() {
this.resource('shops', function() {
this.route('new');
});
this.resource('shop', { path: '/shops/:shop_id' });
});
App.ShopsRoute = Ember.Route.extend({
model: function() {
return App.Shop.find();
}
});
App.ShopsNewRoute = App.ShopsRoute.extend({
model: function() {
return App.Shop.createRecord();
},
setupController: function( controller, model ) {
return this._super(),
controller.set( 'content', model )
}
});
App.ShopsNewController = Ember.ObjectController.extend();
// Models
App.Store = DS.Store.extend({
revision: 11,
adapter: DS.RESTAdapter
});
App.Shop = DS.Model.extend({
name: DS.attr('string'),
body: DS.attr('string'),
});
Index.html
<!DOCTYPE html>
<html>
<head>
<title>Ember/Rails App</title>
<%= stylesheet_link_tag "application", :media => "all" %>
<%= javascript_include_tag "application" %>
<%= csrf_meta_tags %>
</head>
<body>
<script type="text/x-handlebars" data-template-name="application">
<div class="row">
<div class="twelve columns">
<h1>Ordr</h1>
<hr>
{{outlet}}
</div>
</div>
</script>
<script type="text/x-handlebars" data-template-name="shops/new">
<h2>New Shop</h2>
</script>
<script type="text/x-handlebars" data-template-name="shops">
{{#each shop in controller}}
{{#linkTo "shop" shop}} {{ shop.id }}{{/linkTo}}
{{/each}}
{{#linkTo 'shops.new'}}Create{{/linkTo}}
</script>
<script type="text/x-handlebars" data-template-name="shop">
<h2>{{name}}</h2>
<p>{{body}}</p>
</script>
</body>
</html>
The way you have set up your routes and templates, you are telling Ember that you want to navigate to shops.new and keep all the list of shops showing even while you are in shops.new.
If that is in fact the scenario that you want, all you need to do is add an {{outlet}} inside the shops template:
<script type="text/x-handlebars" data-template-name="shops">
{{#each shop in controller}}
{{#linkTo "shop" shop}} {{ shop.id }}{{/linkTo}}
{{/each}}
{{#linkTo 'shops.new'}}Create{{/linkTo}}
{{outlet}}
</script>
However, if that is not what you really intend, and you actually want the list of shops to be hidden when you transition to shops.new, then you will need to change a couple of things.
change your App.ShopsRoute to App.ShopsIndexRoute :
App.ShopsIndexRoute = Ember.Route.extend({
model: function() {
return this.store.find('shop');
}
});
and your shops template to shops/index template:
<script type="text/x-handlebars" data-template-name="shops/index">
{{#each shop in controller}}
{{#linkTo "shop" shop}} {{ shop.id }}{{/linkTo}}
{{/each}}
{{#linkTo 'shops.new'}}Create{{/linkTo}}
</script>
Any of those 2 methods should fix your problem, depending on what your intentions are.