Set controller for template rendering - ember.js

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.

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: {{link-to}} helper error when in {{each}} helper

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}}

Is "basic" a reserved route name in Ember?

I have a route in an Ember app called "basic" corresponding to the name of an API endpoint.
This route doesn't work - links to it don't render its template.
Here's a JSBin demonstrating the failure: http://emberjs.jsbin.com/hisoxadi/1
JS:
App = Ember.Application.create();
App.Router.map(function() {
this.route('basic');
this.route('test');
});
App.IndexRoute = Ember.Route.extend({
model: function() {
return ['red', 'yellow', 'blue'];
}
});
Templates:
<script type="text/x-handlebars">
<h2> Welcome to Ember.js</h2>
{{#link-to 'index'}}Index{{/link-to}}
{{#link-to 'basic'}}Basic Route{{/link-to}}
{{#link-to 'test'}}Test Route{{/link-to}}
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="basic">
basic route is here
</script>
<script type="text/x-handlebars" data-template-name="test">
test route is here
</script>
<script type="text/x-handlebars" data-template-name="index">
<ul>
{{#each item in model}}
<li>{{item}}</li>
{{/each}}
</ul>
</script>
To expand on your comment a bit, basic is indeed a reserved word. Specifically, it's a reserved word for the resolver. You can see the source here.
useRouterNaming: function(parsedName) {
parsedName.name = parsedName.name.replace(/\./g, '_');
if (parsedName.name === 'basic') {
parsedName.name = '';
}
},
And because of the way that Ember.js sometimes looks up routes and controllers in the container, it's a fair bet to say that there's no way around this without major code changes. There should probably be a documentation issue filed for this.
EDIT: I created an issue for this here.

Ember.js models not being rendered in nested route

I have an Ember app setup, and when visiting /playlist/1, it renders the templates as expected. However, the model data isn't being displayed. I have a playlist.hbs file with an outlet, and a playlist folder with index.hbs inside of that folder with html and handlebars to display data. I have both App.PlaylistIndexController & App.PlaylistIndexRoute defined.
App.Router.map(function() {
this.resource('account', {path: '/account/:accountId'}, function() {
this.route('login');
});
this.resource('playlist', { path: '/playlist/:playlist_id'}, function() {
this.route('edit');
});
});
FWIW, everything was working properly prior to adding the nested route (with my controller and route defined as App.PlaylistController and App.PlaylistRoute respectively)
"playlist":{"id":1,"name":"playlistname"}
Any ideas how to get the playlist data to display properly?
UPDATE:
App.PlaylistIndexRoute = App.AuthenticatedRoute.extend({
setupController: function(controller, model) {
this._super(controller, model);
var online = this.get('store').find('account');
this.controllerFor('playlistViewers').set('model', online);
},
});
<div id="main">
<div id="primary">
<section id="playlist">
<header class="playlist-header">
<h2>Playlist</h2>
<h1>{{name}}</h1>
</header><!--.playlist-header-->
<div class="playlist-content">
<ul>
{{#each song in songs}}
<li {{action 'play' song}} class="show-for-mobile">
<button {{bind-attr class="song.isPlaying:icon-volume-up:icon-play song.isStreaming:icon-adjust"}} ></button>
<div class="song-meta">
<span class="song-name">{{song.name}}</span>
<span class="song-artist">{{song.artist}}</span>
</div><!--.song-meta-->
</li>
{{/each}}
</ul>
</div><!--.playlist-content-->
</section><!--#playlist-->
</div><!--#primary-->
{{partial "sidebar"}}
That's my current playlist/index.hbs file, but even {{name}} displays nothing
Your PlaylistIndexRoute needs to have a model. This can be the model that was loaded by the playlist resource.
App.PlaylistIndexRoute = App.AuthenticatedRoute.extend({
model: function() {
return this.modelFor('playlist');
}
});

Ember.js Router: Embedded resources

I'm creating an Ember application to display twitter feeds but I am having trouble with displaying individual tweets through embedded resources.
The code is as follows:
Templates
<script type="text/x-handlebars" data-template-name="tweets">
<div id="stream">
{{#each tweet in controller}}
<div class="tweet">
<p class="tweet_text">{{tweet.text}}</p>
<p> {{#linkTo "tweet" tweet}} {{tweet.id}} {{/linkTo}}</p>
</div>
{{/each}}
</div>
</script>
<script type="text/x-handlebars" data-template-name="tweet">
<div id="detail">
{{text}}
</div>
</script>
Router
window.App = Ember.Application.create({
LOG_TRANSITIONS: true
});
App.Router.map(function(){
this.resource('tweets',function(){
this.resource('tweet',{path: ':tweet_id'})
});
});
// (1) App.Router.map(function(){
// this.resource('tweets')
// this.resource('tweet',{path: ':tweet_id'})
// });
App.IndexRoute = Ember.Route.extend({
redirect: function() {
this.transitionTo('tweets');
}
});
App.TweetsRoute = Ember.Route.extend({
model: function(){
var me = [];
$.getJSON("http://search.twitter.com/search.json?q=emberjs&rpp=200&count=200&callback=?",
{},
function (data) {
$.each(data.results,function(k,tweet){
var tweet = App.Tweet.create({
created_at: tweet.created_at,
from_user: tweet.from_user,
profile_image_url: tweet.profile_image_url,
text: tweet.text,
id: tweet.id
});
me.pushObject( tweet );
});
});
return me;
}
});
Objects & Controllers
App.TweetsController = Ember.ArrayController.extend({});
App.Tweet = Ember.Object.extend({
created_at: "",
from_user: "",
profile_image_url: "",
text: "",
id: 0
})
As you can see, I have a commented our router (1) which works in finding the correct tweet, and rendering it in the tweet template. However, I would like this route to be nested so that I can implement it as a Master-Detail application.
Using the LOG_TRANSITIONS, I can see that the correct routes are initialised, but I cannot get the nested resource path to render.
Any ideas would be hugely appreciated, thanks in advance.
I got this working. For anyone stuck on something similar, this is how I did it:
Templates - Changed the {{#linkTo}} "tweet"... to {{#linkTo}} "tweets.tweet"... AND added an {{outlet}}
<script type="text/x-handlebars" data-template-name="tweets">
<div id="stream">
{{#each tweet in controller}}
<div class="tweet">
<p class="tweet_text">{{tweet.text}}</p>
<p> {{#linkTo "tweets.tweet" tweet}} {{tweet.id}} {{/linkTo}}</p>
</div>
{{/each}}
</div>
{{ outlet }}
</script>
Router - Changed 'this.resource' to 'this.route'
App.Router.map(function(){
this.resource('tweets',function(){
this.route('tweet',{path: ':tweet_id'})
});
});
Caveat
I think this is a workaround and that the nested resource was the correct approach in this context. I understand that a nested route should be "a verb" or action route. I would still be grateful if anyone knows the correct approach to the question but hope the above helps others where relevant.