Calling a handlebars block helper from another helper - ember.js

How can I call a handlebars block helper from another helper in ember.
I am interested in converting the following in a bound helper.
{{#each link in sideMenuLinks}}
<li class="navigation page">
{{#linkTo ROUTE_VARIABLE link.linkToRouteContext}}
{{{link.iconTag}}}<i class="icon-right-open"></i>{{link.linkText}}</a>
{{/linkTo}}
</li>
{{/each}}
Note that I will have to call a {{#linkTo}} block inside the helper and also change ROUTE_VARIABLE with a value from the property link.linkToRoute.

The Ember handlebars helpers are placed at Ember.Handlebars.helpers. You can call them with Ember.Handlebars.helpers.{helperName}.call.
However, The above looks like a dynamic partial/view style helper. I would suggest creating a Handlebars View helper for it. The syntax is similar but you pass in a View class to the helper.
Ember.Handlebars.helper('sideMenuLinks', App.SideMenuLinksView);
The corresponding view can use a template similar to yours by giving a templateName
App.SideMenuLinksView = Ember.View.extend({
templateName: 'sideMenuLinksTemplate'
});
You template would be something like,
<script type='text/x-handlebars' data-template-name='sideMenuLinksTemplate'>
{{#each link in view.links}}
<li class="navigation page">
{{#linkFor parentView.routeVariable link.linkToRouteContext}}
{{{link.iconTag}}}<i class="icon-right-open"></i>{{link.linkText}}</a>
{{/linkFor}}
</li>
{{/each}}
</script>
The default Ember linkTo is static, In that you cannot pass the named route from a variable. You will need a dynamic linkTo helper like the linkFor that looks up the variable path before
applying linkTo internally.
Ember.Handlebars.registerHelper('linkFor', function(path, params, options) {
var view = options.data.view;
var name = view.get(path);
var args = [name, params, options];
return Ember.Handlebars.helpers.linkTo.apply(this, args);
});
Finally you can use this helper like below. The links property will be bound to the content of the controller in this example.
<script type='text/x-handlebars' data-template-name='application'>
{{sideMenuLinks links=content routeVariable='page'}}
</script>
Here's a working jsbin.

Related

EmberJS registerHelper to dynamically render a template in Ember 1.8

I'd like to dynamically render a template, like this:
{{#each layout in layouts}}
{{render layout.get('type') layout}}
{{/each}}
The problem is that render doesn't expect a variable as its first arguments, so in the older EmberJS-versions, a workaround was possible by registering a helper:
Ember.Handlebars.registerBoundHelper('render_layout',
function(callingContext, layout, options) {
return Ember.Handlebars.helpers.render.call(callingContext,
layout.get('type'), 'layout', options);
});
and in the template:
{{#each layout in layouts}}
{{render_layout this layout}}
{{/each}}
Unfortunately the thing doesn't work in newer ember versions.
Ember.Handlebars.helpers.render.call expects 3 arguments but I can not get them right. Any ideas?
I've tried:
(this, layout.get('type'), options)
and in the template:
{{#each layout in layouts}}
{{render_layout layout}}
{{/each}}
I get Uncaught TypeError: Cannot read property 'pushChildView' of null
... and many others.
Any suggestions would be greatly appreciated.
Components allow you to specify layoutName. And you can dynamically compute the layout name based on a parameter passed into the component.
App.XRenderComponent = Ember.Component.extend({
tagName: "",
layoutName: function(){
return this.get('displayLayout.type');
}.property('displayLayout'),
});
Then you can just do
<script type="text/x-handlebars" id="index">
{{#each layout in model}}
{{ x-render displayLayout=layout }}
{{/each}}
</script>
See working example here

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

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).

Calling a controller method whenever the value of a {{input}} Ember Handlebars helper changes

Ember 1.0.0 RC3 provides a Handlebars helper named {{input}}.
Instead of merely updating a String property in the underlying controller, I would like to additionally call a method on the underlying controller whenever the input has changed.
Is there a way to call a controller method after changes to an {{input}} textfield?
Use the .observes("propertyChangedByInputHelper") on the controller method that shall react on changes.
<--Handlebars template -->
<script type="text/x-handlebars" id="autocomplete">
{{input type="text" value=searchText placeholder="Search users..."}}
<table>
{{#each searchResults}}
<tr><td>
{{#linkTo "user" this}}
{{firstName}} {{lastName}}
{{/linkTo}}
</td></tr>
{{/each}}
</table>
</script>
.
//inside Ember application / app.js
App.AutocompleteController = Ember.ObjectController.extend({
searchText: null, // mutated by "autocomplete" Handlebars template
searchResults: Ember.A(), //initialize to empty array
searchForUsers: function() {
this.set("searchResults", Ember.A() ); // clear possibly non-empty array
var searchResultsBuilder = Ember.A();
//...
//... making modifications to searchResultsBuilder
//...
this.get("searchResults").addObjects(searchResultsBuilder);
}.observes("searchText")
});
N.B.: When searchText changes, a .property("searchText") wouldn't be enough to trigger the searchForUsers method: .property(..) makes the method act only lazily on demand, while .observes(..) makes the method act eagerly.

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.

In templates in Ember.js, how do you refer to a value in the parent context when you are inside an #each block?

I have a situation in a template where I want to use an if block on a value in the parent context while inside an each block.
The code:
App = Ember.Application.create({});
App.view = Ember.View.extend({
foo: [1, 2, 3],
bar: true
});
The template:
<script type="text/x-handlebars">
{{#view App.view}}
{{#each foo}}
{{#if bar}}
{{this}}
{{/if}}
{{/each}}
{{/view}}
</script>
This does not work because names referenced inside an each loop are scoped to the element of iteration. How do you refer to things in the parent context?
Demo: http://jsfiddle.net/hekevintran/sMeyC/1/
I found a better solution.
From the Ember.js View Layer guide (http://emberjs.com/guides/understanding-ember/the-view-layer/):
Handlebars helpers in Ember may also specify variables. For example, the {{#with controller.person as tom}} form specifies a tom variable that descendent scopes can access. Even if a child context has a tom property, the tom variable will supersede it.
This form has one major benefit: it allows you to shorten long paths without losing access to the parent scope.
It is especially important in the {{#each}} helper, which provides a {{#each person in people}} form. In this form, descendent context have access to the person variable, but remain in the same scope as where the template invoked the each.
The template:
<script type="text/x-handlebars" >
{{#view App.view}}
{{#each number in view.foo}}
{{#if view.bar}}
{{number}}
{{/if}}
{{/each}}
{{/view}}
</script>​
Demo: http://jsfiddle.net/hekevintran/hpcJv/1/
What hekevintran's answer means is that you can rename any variable using #with. We have a similar problem in JavaScript with this. In JavaScript, sometimes you'll see code like this to work around it.
var self = this;
doSomething(function() {
// Here, `this` has changed.
if (self.bar) {
console.log(this);
}
});
In Ember flavored Handlebars, something similar is happening with view. Say you have App.MyOuterView and another view inside it. You can work around it like this.
{{#with view as myOuterView}}
{{#each foo}}
{{#if myOuterView.bar}}
{{this}}
{{/if}}
{{/each}}
{{/with}}
Similar to the JavaScript, you can essentially rename view to something else so it doesn't get shadowed by the inner view. {{#each person in people}} is just a special case of that. But renaming using {{#with view as myView}} is the more general solution/workaround to this problem that also works with nested calls to the view helper.
I was also stumped on this. This thread and this other thread (Using a container view in ember.js - how to access parent variables from child view) helped me with the solution. I used Jonathan's suggestion to do {#with} and also figured out that I should access my variable by calling the controller. Mine worked like this:
// I use the #which command to preserve access to the outer context once inside the #each
{{#with view as myOuterView}}
{{#each myInnerArray}}
//here, i get my 'name' property from the *controller* of myOuterView
{{myOuterView.controller.name}}
// stuff i did in inner array
{{/each}
{{/with}
No need to place the if inside each in the first place:
<script type="text/x-handlebars">
{{#view App.view}}
{{#if view.bar}}
{{#each view.foo}}
{{this}}
{{/each}}
{{/if}}
{{/view}}
</script>
Demo: http://jsfiddle.net/ppanagi/NQKvy/35/