Issue with EmberJS link-to helper - javascript

I'm working on a simple restaurant menu and I have a code like this which works fine:
<script type="text/x-handlebars" data-template-name="application">
<h1> Categories </h1>
{{#each}}
{{#link-to "dish" this.id}} {{name}} {{/link-to}}
</br>
{{/each}}
{{outlet}}
</script>
It works, but I still can't figure out why I should pass this.id to the parameter of a link-to. Inititaly I've passed just this to the helper and it didn't work (WHY?) and only after some time of debugging I made it work with this.id.
So the question is, why it works with this.id and doesn't work with this? All examples I've seen so far always used this so is it a good approach to use this.id? Please explain!
Here is the full code http://jsbin.com/AcoHeNA/44/

It works with this.id because the {{link-to}} helper can take:
The name of a route. In this case it would be "dish".
At most one model for each dynamic segment.
An optional title which will be bound to the a title attribute.
When you pass a model to the route, it doesn't fetch model data from a remote server, it takes "as is" and passes it to the controller.
If you pass an id to the helper, it makes sure to run the route's model hook with the id as an argument, retrieving the model's data.
What happens is that in your code you were passing this which is a Category model. The DishRoute is for Dish models so it just doesn't work.
It works if you pass this.id because it is an integer and Ember will fetch a Dish that happens to have the same id that whatever Category instance this is set to.
In doubt, read the official docs (http://emberjs.com/guides/templates/links/)

Related

Models, naming, and this

While running through the starter tutorial on EmberJS' site, a few things have me a little confused now.
One thing to note immediately is that I decided to use the ember 1.9.0beta4 with handlebars 2.0.0 instead of 1.8.1 / 1.3.0 included in the starter pack.
First the code included in the screencast:
app.js
App.Router.map(function() {
this.resource('about');
this.resource('posts');
this.resource('post', {path: ':post_id'})
});
App.PostsRoute = Ember.Route.extend({
model: function() {
return posts;
}
});
and
index.html
{{#each model}}
<tr><td>
{{#link-to 'post' this}}
{{title}} <small class='muted'>by {{author.name}}</small>
{{/link-to}}
</td></tr>
{{/each}}
This works exactly as expected and the requested post appears when clicked.
However, because I'm using 1.9.0, the preceding code produces a deprecated warning for {{#each}}, telling me to use {{#each foo in bar}} instead. I understand why this appears and agree the verbosity helps show exactly what data is being looped through.
So I change the line {{#each model}} to {{#each post in model}} and every bit of data disappears... I then try to change the code to:
updated index.html
{{#each post in model}}
<tr><td>
{{#link-to 'post' this}}
{{post.title}} <small class='muted'>by {{post.author.name}}</small>
{{/link-to}}
</td></tr>
{{/each}}
Great! The title and author's name once again appear for each post. But clicking either post gives me an undefined id. I change {{#link-to 'post' this}} to {{#link-to 'post' this.id}}. Same result. I change it to {{#link-to 'post' post.id}}. The id is now included but when I click the link I get this error:
Error while processing route: post Assertion Failed: You used the dynamic segment
post_id in your route post, but App.Post did not exist and you did not override
your route's `model` hook.
My questions are:
What happens internally that forces the post. prefix if I simply include the post in code? To me I should be able to use either this or continue to not need any prefix.
After adding post in to the each statement, what happens to this? Why does it no longer refer to the same object?
How can models be named to make it easier to categorize? post in model should really be post in posts but I haven't found a way to name the data container.
What is causing the error now that I'm no longer referring to the model as this? How can it be remedied?
Your frustration and questions are exactly the reason why the first syntax is deprecated and only the each post in ... form will be supported. Hopefully this answers your questions, and please respond if you need clarification.
In your first example where you use each model, the scope of the block changes to a post, meaning this refers to the current post in the loop. When you the form each post in ..., the scope does not change. When it does not change, that means this is actually referring to the previous scope (prior to the loop). In your example, the previous scope is the array controller, not the post object.
This is related to question 1. With the each post in ... format, this refers to whatever it was outside of the each block. It's not that something happens to this, it's that something does not happen to this because the scope doesn't change.
For better naming I usually setup a property as an alias to the content in the array controller:
posts: Ember.computed.alias('content')
In your original example, when you supply the link-to helper with this, you're passing the full post object. From what you've tried, it looks like this is the one thing you didn't do:
{#link-to 'post' post}}
I will try to answer your questions in order:
When you say {{#each model}} you are looping through the posts (array) in the model, so every time through the loop this is referring to the current post. Therefore when you say {{title}} you are really saying {{this.title}} When you are more explicit by saying {{#each post in model}} then each iteration through the loop is no longer referring to this and instead refers to the variable you made called post
Like mentioned in #1 above, this no longer refers to each individual iteration. I understand how you are thinking that perhaps still being able to use this (alongside post) would be convenient, but think about the following scenario. What happens when you have a nested {{#each}}? Would the implicit this refer to the outer array or inner array? If you really don't feel like typing the extra post. you can always use the {{#with post}} handlebars helper that scopes post back to this See the following example here
If you have a property in your model or controller, you can absolutely loop through that property as in {{#each color in colors}} See here for a working example
Finally, the link-to should be {{#link-to 'post' post}}

Ember sort data from another controller from an ember-data model association

I have a jsbin: http://jsbin.com/watab/6/
Which is a simplified version of my end goal. Basically I need sort data that is in the hasMany association.
If a User hasMany Books and I want to sort the books for displaying with {{#each book in books}} then how do I sort them?
What I am doing is in the user route during setupController I set the content for the books controller.
this.controllerFor('books').set('content', model.get('books'));
And now in the Users controller I set the needs of course
needs: ['books']
And set an alias
books: Ember.computed.alias('controllers.books.content')
Now I have books controller
App.BooksController = Ember.ArrayController.extend({
sortProperties: ['name'],
sortAscending: false
});
However when I do my {{#each book in books}} it is never sorted.
The jsbin http://jsbin.com/watab/6/ isn't exactly what I described but it employs the basic ideas I have described here.
This is the same jsbin http://jsbin.com/watab/7/ except I changed the sortAscending to true. And I get the same results...
Thanks!
Ok, here is the thing, taking the example of the JSBin here http://jsbin.com/watab/6/, when you're in the 'index' template, your current controller is 'IndexController', that's fine. In that controller you have a property 'users', that's fine; however, that property's content is not sorted, even if you defined the sorting options in the UsersController, that property won't be sorted, why ?, controllers are decorators for your models, so, that code you put for sorting will work whenever you go to the 'users' route, then, the users will be sorted there, that's why it doesn't work in your 'IndexController', because the data is not sorted. To sort it, there are a few ways, but I like this one, in your 'IndexController', add these properties:
userSortProperties: ['name:desc'],
sortedUsers: Ember.computed.sort('users', 'userSortProperties')
and change your template to this:
{{#each user in sortedUsers}}
<div>{{user.name}}</div>
{{/each}}
I hope you get the idea.

How can I get an instance of a model's controller inside a handlebars template?

I've got a sticky situation that I keep on running into: The need for a new instance of a controller inside a handlebars template.
Here is a brief example of my situation. (Please excuse my use of coffeecript)
In Ember, I have a model:
App.Foo = DS.Model.extend
attr: DS.attr()
...
Which I load from an endpoint etc.. And place into an array controller:
App.FooArray = Ember.ArrayController.extend
###*
* Array of App.Foo
* #type {Array}
*/
content:
method: ->
...
Finally, I have an 'instance' controller for this model, which implements further methods (i.e. this is not a singleton controller as would be found at the router level, but a decorator (or proxy) that augments the model with added methods and event handlers):
App.FooController = Ember.ObjectController.extend
###*
* Content
* #type {App.Foo}
*/
content: null
action: ->
...
In handlebars, I want to iterate over items in an App.FooArray:
{{#each myFooArray}}
Hi! My attr is {{attr}}
{{/each}}
etc.. This works splendidly for parameters and such.
However, the trouble starts when I want to use actions (or other properties which would belong to a FooController)
{{#each myFooArray}}
Hi! My attr is {{attr}} <a {{action 'action'}}>Action me!</a>
{{/each}}
Suddenly my actions are not working. That's because the action helper doesn't apply the action to 'this' but rather to a controller higher up, possibly even at the Route level!
So to work around this, I need to pass a target (i.e. a controller):
{{action 'action' target=**********}}
Well, the controller I want is an instance of App.FooController.
Up until now, I've been instantiating controllers inside the model (yuck!):
App.Foo = DS.Model.extend
attr: DS.attr()
...
attrn: DS.attr()
myController: Ember.computed (->
App.FooController.create
content: this
)
and thus iterating as follows:
{{#each myFooArray}}
Hi! My attr is {{attr}} <a {{action 'action' target=myController}}>Action me!</a>
{{/each}}
I know this is bad, but I can't think of a better way. Somebody, please help me see the light!
You can explicitly set the itemController in your each loop.
{{#each myFooArray itemController="foo"}}
Hi! My attr is {{attr}}
{{/each}}
This question poses an important and longstanding question about ArrayControllers, CollectionViews, Models and ObjectControllers.
At the time of writing, my knowledge of the inner workings of Ember was limited. However, I can rephrase my question more concisely as follows:
Given an ArrayController, CollectionView and instance controllers for a model, how can one leverage the itemControllerClass property of the ArrayController to iterate over its content and wrap each item in a unique (i.e. non-singleton) instance of itemController?
Turns out this problem is longstanding and the solution echoes #jeremy-green but I will expand on things here.
First off though: the Ember support thread that encapsulates the problem: https://github.com/emberjs/ember.js/issues/1637
I think the discussion there points very clearly to the need for non-singleton controllers in certain situations.
As well, here is the documentation for ArrayController that indicates the presence of an 'itemController' property on the ArrayController: http://emberjs.com/api/classes/Ember.ArrayController.html#property_itemController
Looking further into the docs, you will also note the presence of an 'lookupItemController' function: http://emberjs.com/api/classes/Ember.ArrayController.html#method_lookupItemController
These functions are for the express purpose of returning the content as an array of Controllers but how?
Well the first requirement is to use the ArrayController directly as the content in a loop. Unfortunately this is where things start to fall apart.
You may think it would be simply the case that a CollectionView can be used:
{{view myCollectionView controllerBinding=myArrayController}}
or
{{view myCollectionView contentBinding=myArrayController}}
But unfortunately this is not the case. More so in the situation where you are rendering a Controller on another Controller's route. CollectionView does not preserve the relationship between the ArrayController and its 'itemControllerClass`
The only way to make this work, as #jeremy-green points out:
{{#each myFooArray itemController="foo"}}
Hi! My attr is {{attr}}
{{/each}}
A more complete example would be:
<ol class="foo-item-list">
{{#each controllers.foo_items}}
<li>{{ view "foo" }}</li>
{{/each}}
</ol>
Wherein App.FooItemsController has either the property itemController or lookupItemController defined.
Unfortunately in this situation we lose the benefits of using the tagName or emptyView properties of CollectionView. Hopefully if an 'ArrayView' is ever created, it will bring the best of both worlds to this situation!

Ember: transition to route passing the ID instead of obj

I have some route like /ads/:ad_id and from my controller I can do
this.transitionToRoute('ads.ad', adObj)
How can I do the similar thing but this time passing the ID instead of the loaded object?
O course I understand that I can load an obj by ID first, but Ember's power is in doing lost of boilerplate for us.
Update: So, as by default Ember serializes the model to URL params by doing like
mode_instance -> { model_name_id: model_instance.id }
My trivial attempt was doing
this.transitionToRoute('ads.ad', { id: adObjId })
But when passed a model object Ember does not re-fetch it.
So, the question: I have a route (single ad view) that depends on ad ID. I have this ID as number. I want to transition to this route like if I simply entered the url /ads/ID
This can be accomplish by passing the URL to transitionTo. For example,
this.transitionToRoute('/ads/' + adObjId)
The model() method will be called with the params from the URL.
Here is a use case for this:
Transitioning from a list view to a detail view. In the list view, the records don't have any relations tied to them, but the detailed view should side-load relational data. For this reason, the models are not 1:1 between the list view and detailed view. There should be a way to transition simply using the id.
Cp
What's your use case for this? Most cases when you would want to specify an object by id, you already have the object to pass to transitionTo. Can you provide more context about what you're trying to do? I think you can probably accomplish it without using the object id.
In any case, I don't think there's a good way to do this, because when you transition via transitionTo(someRoute, someModel), the route's model hook is not called, and the model you pass in (someModel) is supplied directly to the other route hooks (setupController(controller, model), redirect(model), renderTemplate(controller, model)).
See Ember.JS Route api -- model method for more details.

Ember.js: how to add parameters to a View

I guess this is simple but as an Ember newbie I cannot find a solution...
So let's say I have this very simple view:
MyApp.MyTextView = Ember.View.extend({
template: Ember.Handlebars.compile('{{value}}')
});
And I want to use this view in order to display two different properties of the same object. This sounds like I want to add parameters when I 'call' my view.
In a second view I want to be able to do something like the following (supposing my content object is a Person : {firstName: 'toto', lasName: 'titi'}):
MyApp.AnotherView = Ember.View.extend({
template: Ember.Handlebars.compile('FirstName: {{view MyApp.MyTextView value="content.firstName"}} - LastName: {{view MyApp.MyTextView value="content.lastName"}}')
});
I also tried to use Handlebars helpers as explained here, but It does not work (when I use {{highlight firstName}} or {{highlight content.firstName}} what is displayed is firstName or content.firstName, not the property value...)
Do you guys have any idea? I'm stuck here...
Thanks!
To pass in parameters to your view, you can specify each one like so:
{{view App.MyView firstNameBinding="content.firstName" lastNameBinding="content.lastName"}}
Which would allow you to do:
template: Ember.Handlebars.compile('{{view.firstName view.lastName}}');
However, you can imagine that this will get very long as you add more properties. Therefore you can instead simply pass in your content object as the context (but you don't need to pass it in as context -- although that's for another day):
{{view App.MyView contextBinding="content"}}
(Please note that contextBinding is special, and changes the context in the view.)
That way the view will hold the object you pass in, and so in your view you can now do:
template: Ember.Handlebars.compile('{{firstName lastName}}');
(You can do it like this because content is now your view's context.)
I've knocked you up a quick JSFiddle to elucidate (hopefully!): http://jsfiddle.net/YVCrq/

Categories

Resources