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}}
Related
I am upgrading my
Emberjs => from 1.10.0 to 1.12.0
Ember-cli => from 0.1.12 to 0.2.5
While i am figuring out most of the deprecations there are few which i am not able to understand. PFB the same
DEPRECATION: Ember.required is deprecated as its behavior is inconsistent and unreliable. Where is this used and how to change it?
DEPRECATION: lookupFactory was called on a Registry. The
initializer API no longer receives a container, and you should use
an instanceInitializer to look up objects from the container.
I do understand this issue but my initializer does not use lookup at all. PFB the code of my initializer.
//app/initializer/abc
initialize: function(registry, app) {
app.register('store:main', Store);
// Inject into each route a store property with an instance of store:main
app.inject('route', 'store', 'store:main');
// Inject into each controller a store property with an instance of store:main
app.inject('controller', 'store', 'store:main');
}
//app/initializer/def
initialize: function(registry, app) {
// Register the session object.
app.register('session:main', Session);
// Inject the session object into all controllers.
app.inject('controller', 'session', 'session:main');
}
DEPRECATION: Using the context switching form of {{each}} is
deprecated. Please use the block param form ({{#each bar as
|foo|}}) instead. See
http://emberjs.com/guides/deprecations/#toc_more-consistent-handlebars-scope
for more details.
I understand here that {{#each foo in bar itemController="abc"}} should be changed to {{#each bar itemController="abc" as |foo|}}. But my code is as below and does not have "in", meaning using this context!
{{#each paged itemController="class.adm.man.stop-term"}}
How can i change this?
Following your list:
Seems like it's an ember-data related issue. If not, I'm sure there is another addon which use it, but not you. Thus nothing you can do.
The same thing. I've already introduced an example as a comment. You code looks good for me, so, I believe, nothing you can do there as well.
Ember tries to make more consistent and explicit scopes. in part is not deprecated for now, so the simplest solution is to add dummy in part, for example {{#each page in paged itemController="class.adm.man.stop-term"}}. But in general I'd recommend to use more complex solution - to create an ItemList component and refactor it as:
{{#each paged as |page|}}
{{item-list model=page}}
{{/each}}
How do I use a template-helper to edit the value of the parameter I passed into a route created using the pathFor method of iron-router???
I have this template-helper:
Template.registerHelper('slugify', function(obj){
return _.slugify(obj);
});
in my .html file I have this:
{{#each menuItemsFromDB}}
{{#each arrayOfMenuItems}}
<a class="item category" href="">
{{this}}
</a>
{{/each}}
{{/each}}
The {{this}} in the above code returns a string, the name of the category.
Because I have registered a template helper, I can SLUGIFY this category by:
{{slugify this}}
and then I have this route:
this.route('List',{
path: '/list/:category_slug',
template: 'list',
controller: 'ListController'
});
I can link to this route and pass a parameter to this route by:
{{pathFor 'List' category_slug='the-value-i-passed'}}
But that would be hard-coding it which cannot achieve the desired result I want.
I want it to be dynamic by using the 'slugify' template helper and iron-router's pathFor method and using the value of {{this}}.
What I'm trying to achieve is something like this although this code below doesn't work:
{{pathFor 'List' category_slug={{slugify this}} }}
What's the work around to achieve what I'm 'trying' to achieve with the above line????
I was hoping I can do something like:
{{pathFor 'List' category_slug=slugify(this) }}
or
{{pathFor 'List' category_slug='{{slugify this}}' }}
Long story short, what you're looking for is not yet implemented using the current syntax, although it's part of the Handlebars standard implementation, upon which Meteor Spacebars is based.
For the moment, you have to create a separate helper that slugifies your input and call it within pathFor.
JS
Template.myTemplate.helpers({
slugified: function(){
return _.slugify(this);
}
});
Spacebars
{{pathFor 'List' category_slug=slugified}}
Note that Handlebars sub-expression support is planned in a near future and might even make its way to the next version of Meteor according to this PR : https://github.com/meteor/meteor/pull/4101
My template is rendering data OK without any problem, but if I try to access to it inside my JS script i get null or undefined... Here is my code
iron-router
#route 'responder',
path: '/quesabesde/:_screenName'
yieldTemplates:
responderContent:
to: "mainContent"
responderHeader:
to: "mainHeader"
waitOn: ->
#subscribe 'getPreguntas', #params._screenName
data: ->
Preguntas.findOne({owner: #params._screenName})
Template script with NULL result
Template.responderContent.rendered = ->
console.log(#data)
Template HTML WORKING
{{#each level}}
<h1>{{title}}</h1>
{{/each}}
And I get null on console... but as I said my template is rendering it OK. I want to access data inside my script in order to set sessions and manipulate the data for other purposes
This is a documented (if often undesirable) phenomenon. I would highly recommend reading this, particularly from Nov 19 downwards. Note that the easiest fix is just to have a loading template.
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/)
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!