Custom view helper in Ember.js, "You can't use appendChild outside of the rendering process" - ember.js

I want to bind my custom view's class to a controller property.
[javascript]
App.IndexController = Ember.Controller.extend({
headerClass: "a"
});
App.TestHeaderView = Ember.View.extend({
classNames: ["test-header"],
classNameBindings: ["headerClass"],
headerClass: null,
templateName: "views/test-header"
});
[templates]
<script type="text/x-handlebars" data-template-name="index">
{{view App.TestHeaderView text="view helper" headerClass=controller.headerClass }}
<hr />
{{input value=headerClass}}
</script>
<script type="text/x-handlebars" data-template-name="views/test-header">
<small>{{view.text}}</small>
</script>
The result is predictable: everything works. I can enter the class name in the text box and see it reflected in the view.
So now I want to extend this and add my own helper that wraps the {{view}} call.
[javascript]
Ember.Handlebars.helper("test-header", function (options) {
return Ember.Handlebars.helpers.view.call(this, App.TestHeaderView, options);
});
[templates]
<script type="text/x-handlebars" data-template-name="index">
{{test-header text="custom helper" headerClass=controller.headerClass}}
</script>
Nothing special right? Except, I keep getting this:
Uncaught Error: You can't use appendChild outside of the rendering process
For full working jsbin, click here.
It seems this should work. I'm just wrapping the ember's view helper pretty much exactly. What am I missing?

I figured it out.
The trick is in the contexts array in the options hash.
When you call {{view App.MyView}} from handlebars, Ember's view helper gets in its options.contexts array the "context" in which it should search for "App.MyView" property - usually the current controller. In this case, "App.MyView" will be resolved regardless of the context, but I guess Ember keeps the context around and uses it to resolve bound properties.
When I called:
{{test-header text="custom helper" headerClass=controller.headerClass}}
there was no first argument from which to draw the context. Therefore, when I passed the call along to the view helper:
return Ember.Handlebars.helpers.view.call(this, App.TestHeaderView, options);
... there was no context passed along in the options.contexts array.
The way I fixed this is:
Ember.Handlebars.helper("test-header", function (options) {
options.contexts = [this].concat(options.contexts);
return Ember.Handlebars.helpers.view.call(this, App.TestHeaderView, options);
});
IMO Ember should do a better job here. They should either figure out a context from reference, or throw an error (a preferred option).

Related

Using Ember, how do I dynamically bind a property in a view helper contained in an each block?

I have a template with code similar to:
{{#each item in items}}
<label>{{item.name}}</label>
{{view Ember.Select content=selectableValues value={{item.name}}}}
{{/each}}
I understand the code above won't work, but it illustrates my problem and what I want to achieve. I don't want to bind what's selected in the Ember.Select to the property of the iterated item, but rather to a controller property of the name of the iterated item's name property.
How might I do this?
You're going to have to dynamically create a computed property, since you are binding the value of the value, unfortunately, this presents problems if you then decide to change the value of the value, but, I'll leave that up to you to play with.
<script type="text/x-handlebars" data-template-name="index">
{{input value=foo}}
{{view App.BlahView host=controller propName=chacha}}
</script>
<script type="text/x-handlebars" data-template-name="blah">
{{view.propValue}}
</script>
Code
App.IndexController = Em.Controller.extend({
chacha:'foo',
foo:'bar'
});
App.BlahView = Em.View.extend({
templateName:'blah',
setupDyna: function(){
var propName = this.get('propName');
Ember.defineProperty(this, 'propValue', Ember.computed(function() {
return this.get('host').get(propName);
}).property("host." + propName));
}.on('init')
});
http://emberjs.jsbin.com/vunureno/1/edit

Ember.JS - Return & Display Parameter from URL

I'm building my first Ember app and I'm trying to get it to pass and display a parameter in the view, grabbing it from the URL...
So basically if someone went to index.html#/quotes/5
I want the view to display "TEST: 5"
App.js:
App.Router.map(function(){
this.resource('quote', function(){
this.resource('quoteNumber', {path: ':quote_id'});
});
});
App.quoteNumberRoute = Ember.Route.extend({
model: function(params){
return(params.quote_id);
}
});
HTML:
<script type="text/x-handlebars" id="quote">
Test: {{outlet}}
</script>
<script type="text/x-handlebars" id="quoteNumber">
{{quote_id}}
</script>
so if I go to "example.com/index.html#/quote/3"
I'd simply want the view to display "TEST: 3",
if I went to "example.com/index.html#/quote/10"
I'd want the view to display "TEST: 10"
but right now it displays nothing, and I can't find what's missing.
The main issue is the use of quote_id in the template. Inside the template you are telling handlebars to grab the property quote_id off the model provided for that template.
Dissecting the model associated with that route you'll see that the model itself is the quote_id value, 5 or 7 etc. The model that would fit your template would be { quote_id: 5 } that way Ember/Handlebars can search for the property quote_id on the model and bind to that value.
Here's an example of what you're trying to do (note, I could have just returned the params object itself).
http://emberjs.jsbin.com/OxIDiVU/309/edit
I also added a convenient link, but you could type in any url and get what you were desiring, http://emberjs.jsbin.com/OxIDiVU/309#/quote/123123
PS. You didn't show, nor mention, but you also need an application template. This is the root of your application, if you plan on having nothing in it, you can just put a {{outlet}}
Ex.
<script type="text/x-handlebars" data-template-name="application">
{{outlet}}
</script>
or for short
<script type="text/x-handlebars">
{{outlet}}
</script>

Evaluating controller property everytime the template is rendered in Ember

I have a template called sample and I am changing the property show in the controller based on a button press. Now I want the controller to reset the property every time the template is rendered. Currently even if I go to 'next' template and come back, the property show remains true if the button on sample was pressed. I want to change this behaviour and the property show should be false by default. I know I can do this by defining a view and using the didInsertElement hook but is that the only way to do this?? Ember.js website says that Views in Ember.js are typically only created for the following reasons:
When you need sophisticated handling of user events
When you want to create a re-usable component
and I am doing none of the above. Here is some sample code:
<script type="text/x-handlebars" data-template-name="sample">
{{#if show}}
Showing stuff
{{/if}}
<button {{action changeShow}}>Change</button>
{{#link-to 'next'}} Next {{/link-to}}
</script>
<script type="text/x-handlebars" data-template-name="next">
Hello
{{#link-to 'sample'}}Back{{/link-to}}
</script>
App.SampleController=Ember.Controllers.Extend{(
show:false,
actions:{
changeShow:function(){
this.controllerFor('sample').set('show',true);
}
}
)};
you can use didTransition action which will trigger automatically once the transition happened. didTransition action
App.SampleController=Ember.Controllers.Extend{(
show:false,
actions:{
didTransition:function(){
this.controllerFor('sample').set('show',false);
},
changeShow:function(){
this.controllerFor('sample').set('show',true);
}
}
)};
You can use the renderTemplate hook for the route you're doing this in, and change the controller variable in there.
http://emberjs.com/api/classes/Ember.Route.html#method_renderTemplate
I'd do something like this:
App.PostsRoute = Ember.Route.extend({
renderTemplate: function(controller, model) {
var favController = this.controllerFor('favoritePost');
favController.set("toggle", false)
this._super()
}
});

Dynamic value in application template

I tried to implement user name displaying after log in. It displays in top menu. But top menu is getting displayed before log in, so it user name is getting cached.
I tried many approaches, and using volatile() is seems the best option, but it doesn't work. In this simple example currentTime calculates only once:
<script type="text/x-handlebars" data-template-name="application">
{{currentTime}}
</script>
App.ApplicationController = Ember.Controller.extend({
currentTime: function() {
console.log('computing value');
var time = new Date();
return time;
}.property().volatile()
});
Ember version 1.3
P.S. I prepared the gist to illustrate this issue: http://jsbin.com/OPUSoTaF/1
Actually, I can't find ANY way do display dynamic value in Ember's application template. Tried to display value from another controller using {{render}} helper, value still gets cached.
It seems that I just need to update value on ApplicationController from some other controller, and to do it in a proper way. Like this:
App.LoginController = Ember.Controller.extend({
needs: 'application',
setTime: function() {
this.get('controllers.application').set('currentTime', new Date());
}
});
The application to illustrate: http://jsbin.com/OPUSoTaF/4/edit
You can change ember properties and thus views using Handlebars {{action 'actionName'}} helper. You can add action helper to almost any UI element in your handlebars template an it is usually triggered on click. When triggered it calls actionName method on the controller.
Example:
Handlebars template:
<script type="text/x-handlebars" data-template-name="application">
<button {{action 'login'}}>Login</button>
{{loginTime}}
</script>
Controller:
App.ApplicationController = Ember.Controller.extend({
loginTime: 'User not logged in yet',
actions: {
login: function() {
// ... Do some login stuff ...
this.set('loginTime', new Date());
}
}
});
Working jsbin example is here: http://jsbin.com/udUyOXaL/1/edit

using emberjs new-router-V3 and #with helper to access events defined in a different route

I have this working jsfiddle. The EmBlog.PostsEditRoute has a destroyPost event which I want to call with an action helper in the 'post/show.hbs' which is the template for the EmBlog.PostsShowRoute.
I am using #with helper to change scope in the template as suggested here. It doesn't destroy the object and throws no error.
<script type="text/x-handlebars" data-template-name="posts/show">
{{#with EmBlog.PostsEditController}}
<a href='#' {{action destroyPost this}}> Destroy</a>
{{/with}}
</script>
EmBlog.PostsShowRoute = Ember.Route.extend({
});
EmBlog.PostsEditRoute = Ember.Route.extend({
events: {
destroyPost: function(context) {
var post = context.get('content');
post.deleteRecord();
post.get('store').commit();
this.transitionTo('posts');
}
}
});
I think this is basically because you have to define your event handler in the EmBlog.PostsShowRoute or in the PostsRoute if you want it to be accessible in an other PostsXXX view. see http://emberjs.com/guides/templates/actions/ for details.
(the use of the #with helper here seems wrong here BTW, as your reference is about something quite old). I would simply do
<script type="text/x-handlebars" data-template-name="posts/show">
<a {{action destroyPost content}}> Destroy</a>
</script>
Here is the modified fiddle: http://jsfiddle.net/Qn3ry/4/
Note that when you try to destroy a post which is in the fixtures, it reappears when you transition to posts/index. This is simply because the post is not destroyed in the fixtures, and when entering to the PostIndexRoute, App.Post.find() will reload it again.