EmberJS and Handlerbars helpers in a CollectionView - javascript

I'm trying to use a collection view and in each item view use a handlerbars helper, but I can't get the helper function to expand my path into the value.
Ember.CollectionView.create({content: App.AController,
itemViewClass: App.ItemView
});
Em.Handlebars.registerHelper('editable', function (path, options) {
options.hash.valueBinding = path;
return Em.Handlebars.helpers.view.call(this, App.EditField, options);
});
<script type="text/x-handlebars" data-template-name="edit-field">
{{#if isEditing}}
{{view Ember.TextField valueBinding = "value" propagatesEvents = true}}
{{else}}
{{#if value}}
{{value}}
{{else}}
<span class="no-name">empty</span>
{{/if}}
{{/if}}
</script>
<script type="text/x-handlebars" data-template-name="item-view">
{{view.content.name}}
{{editable view.content.name}}
</script>
http://jsfiddle.net/epigeon/dNqsV/29/ with full code example.

The 'isEditing' property is on the view, but the context for the collectionView itemView is the content for that view. In order to refer to properties on the view in your template, you have to start the property path with 'view,' as in 'view.isEditing.'
I made that change in your fiddle and the example seems to work as I would expect.

Related

EmberJS: How to render a template on select change

I'm new to ember and am trying to figure out how to render a template when a select control changes.
CODE:
App.LocationTypeController = Ember.ArrayController.extend({
selectedLocationType: null,
locationTypeChanged: function() {
//Render template
}.observes('selectedLocationType')
});
{{view Ember.Select
contentBinding="model"
selectionBinding="selectedLocationType"
optionValuePath="content.id"
optionLabelPath="content.name"}}
When the locationType changes the locationTypeChanged function is fired in the controller.
But how do I render some content into the dom from there? (this.render()?)...
Yes you have to use this.render() only, but the key here is into option inside it.
App.LocationTypeController = Ember.ArrayController.extend({
selectedLocationType: null,
locationTypeChanged: function() {
var selectedLocationType = this.get('selectedLocationType');
this.send('changeTemplate',selectedLocationType);
}.observes('selectedLocationType')
});
Have the action in your route as
changeTemplate: function(selection) {
this.render('template'+selection.id,{into:'locationType'});
}
and have an {{outlet}} in your locationType's template.
{{view Ember.Select
contentBinding="model"
selectionBinding="selectedLocationType"
optionValuePath="content.id"
optionLabelPath="content.name"}}
{{outlet}}
Sample JSBin for your requirement
If you need to show only a frament, when exist something selected, you can use the if handlebars helper:
In your template
...
{{#if selectedLocationType}}
Any content here will be visible when selectedLocationType has some value
{{/if}}
...
{{view Ember.Select
contentBinding="model"
selectionBinding="selectedLocationType"
optionValuePath="content.id"
optionLabelPath="content.name"}}
I hope it helps

Nothing handled the event error in Emberjs

I am trying to filter a JSON response by using 'filterProperty' in Emberjs. But I am getting this error, Uncaught Error: Nothing handled the event 'last'
Here's my App.js
App = Ember.Application.create({});
App.IndexRoute = Ember.Route.extend({
renderTemplate : function(controller) {
this.render('MyApp', {
controller : controller
});
},
model : function() {
return App.MyTemplateModel.find();
}
});
App.IndexController = Ember.ArrayController.extend({
last : (function() {
this.get('content').filterProperty('last_name', 'Solow');
}).property('content.#each.type')
});
App.MyTemplateModel = Ember.Model.extend({
id : Ember.attr(),
last_name : Ember.attr(),
first_name : Ember.attr(),
suffix : Ember.attr(),
expiration : Ember.attr()
});
App.SiteController = Ember.ObjectController.extend({
});
App.MyTemplateModel.url = "http://ankur1.local/index.php/api/example/users/";
App.MyTemplateModel.adapter = Ember.RESTAdapter.create();
var existing = App.MyTemplateModel.find();
App.MyTemplateModel.camelizeKeys = true;
Here's my HTML page,
<script type="text/x-handlebars" data-template-name="MyApp">
{{#each item in content }}
<tr><td>
{{id}} <p> {{item.first_name}} {{item.expiration}}</p>
</td></tr>
{{/each}}
<button {{action last}}>filter</button>
</script>
<script type="text/x-handlebars">
<h1>Application Template</h1>
{{outlet}}
</script>
</body>
What I might be doing wrong in my App.js or should I use any other property to filter the JSON response?
You declared the property last on your IndexController as Computed Property, but if you want to use the {{action}} helper, this is not allowed. It has be a plain function. This is why Ember does not find a suitable event anywhere and complains about it.
App.IndexController = Ember.ArrayController.extend({
// for initial filling of this property, will be overridden by last action
filteredContent : Ember.computed.oneWay("content"),
last : function() {
var filtered = this.get('content').filterProperty('last_name', 'Solow');
this.set("filteredContent", filtered);
}
});
<script type="text/x-handlebars" data-template-name="MyApp">
{{#each item in filteredContent }}
<tr><td>
{{id}} <p> {{item.first_name}} {{item.expiration}}</p>
</td></tr>
{{/each}}
<button {{action last}}>filter</button>
</script>
So i did basically two things:
I changed the computed property to a plain function.
The template is iterating over filteredContent instead of content. (Note the initialization i had to do on your Controller.)
Sou the basic mechanism is to have an additional property on your Controller, which holds the filtered content. You have to expand on this, because your usecase is a bit more complex for sure. :-)

How to use a logical OR with #if

I have data handed to me by the server.
I do not want the container <p> to be present if there is no data item. But there could be several data items or 1 data item, if any exist I need a <p> wrapper.
Ideally, I am looking for some kind of #ifor helper which will accept any number of arguments. Is this possible?
{{#ifor firstname||lastname||nickname||age}}
<p>
{{#if firstname}}
<span class="firtname">{{firstname}}</span>
{{/if}}
{{#if lastname}}
{{lastname}}
{{/if}}
{{#if age}}
Age: {{age}}
{{/if}}
{{#if nickname}}
- ( {{type}} )
{{/if}}
</p>
{{/if}}
A simple solution would be to write a helper that accepts the attributes you wish to test, without the OR operator. You then iterate over the arguments and check if they match a truth test :
Handlebars.registerHelper('ifor', function() {
var l, opts;
opts = Array.prototype.pop.call(arguments);
for (l = arguments.length-1; l>=0; l--) {
// test against undefined if you prefer
//if (typeof(arguments[l])!=='undefined')
if (arguments[l])
return opts.fn(this);
}
return opts.inverse(this);
});
And a Fiddle http://jsfiddle.net/G5Vhc/1/

handlebars - is it possible to access parent context in a partial?

I've got a handlebar template that loads a partial for a sub-element.
I would need to access a variable from the parent context in the calling template, from within the partial. .. doesn't seem to resolve to anything inside the partial.
Simplified code goes like this:
the template
{{#each items}}
{{> item-template}}
{{/each}}
the partial
value is {{value}}
(obviously the real code is more complicated but it's the same principle, within the partial .. appears to be undefined.)
To show it's undefined, I've used a very simple helper whatis like this:
Handlebars.registerHelper('whatis', function(param) {
console.log(param);
});
and updated the above code to this:
updated template
{{#each items}}
{{whatis ..}} <-- Console shows the correct parent context
{{> item-template}}
{{/each}}
updated partial
{{whatis ..}} <-- Console shows "undefined"
value is {{value}}
Is there a way to go around that issue? Am I missing something?
EDIT: There's an open issue relating to this question on handlebars' github project
Just in case anyone stumbles across this question. This functionality exists now in Handlebars.
Do this:
{{#each items}}
{{! Will pass the current item in items to your partial }}
{{> item-template this}}
{{/each}}
Working fiddle (inspired by handlebars pull request #385 by AndrewHenderson)
http://jsfiddle.net/QV9em/4/
Handlebars.registerHelper('include', function(options) {
var context = {},
mergeContext = function(obj) {
for(var k in obj)context[k]=obj[k];
};
mergeContext(this);
mergeContext(options.hash);
return options.fn(context);
});
Here's how you'd setup the parent template:
{{#each items}}
{{#include parent=..}}
{{> item-template}}
{{/include}}
{{/each}}
And the partial:
value is {{parent}}
As of 2.0.0 partials now supports passing in values.
{{#each items}}
{{> item-template some_parent_var=../some_parent_var}}
{{/each}}
Took me awhile to find this, hope it's useful for someone else too!
The easiest way to pass the parent context to the partial is to do the loop inside the partial. This way the parent context is passed by default and when you do the loop inside the partial the {{../variable}} convention can access the parent context.
example fiddle here.
The Data
{
color: "#000"
items: [
{ title: "title one" },
{ title: "title two" },
]
}
The Template
<div class="mainTemplate">
Parent Color: {{color}}
{{> partial}}
</div>
The Partial
<div>
{{#each items}}
<div style="color:{{../color}}">
{{title}}
</div>
{{/each}}
</div>
You can use some of the proposed solutions on the comments from the link to github:
https://github.com/wycats/handlebars.js/issues/182#issuecomment-4206666
https://github.com/wycats/handlebars.js/issues/182#issuecomment-4445747
They create helpers to pass the info to the partial.
I created an each Helper function that includes the parent key/values within the subcontext under the key parentContext.
http://jsfiddle.net/AndrewHenderson/kQZpu/1/
Note: Underscore is a dependency.
Handlebars.registerHelper('eachIncludeParent', function ( context, options ) {
var fn = options.fn,
inverse = options.inverse,
ret = "",
_context = [];
$.each(context, function (index, object) {
var _object = $.extend({}, object);
_context.push(_object);
});
if ( _context && _context.length > 0 ) {
for ( var i = 0, j = _context.length; i < j; i++ ) {
_context[i]["parentContext"] = options.hash.parent;
ret = ret + fn(_context[i]);
}
} else {
ret = inverse(this);
}
return ret;
});
To be used as follows:
{{#eachIncludeParent context parent=this}}
{{> yourPartial}}
{{/eachIncludeParent}}
Access parent context values in your partial using {{parentContext.value}}
I needed dynamic form attributes for something like this...
{{#each model.questions}}
<h3>{{text}}</h3>
{{#each answers}}
{{formbuilder ../type id ../id text}}
{{/each}}
{{/each}}
and a helper like so...
Handlebars.registerHelper('formbuilder', function(type, id, qnum, text, options)
{
var q_type = options.contexts[0][type],
a_id = options.contexts[1].id,
q_number = options.contexts[0][qnum],
a_text = options.contexts[1].text;
return new Handlebars.SafeString(
'<input type=' + q_type + ' id=' + a_id + ' name=' + q_number + '>' + a_text + '</input><br/>'
);
});
Which produces...
<input type="checkbox" id="1" name="surveyQ0">First question</input>
My model is a big blob of arrays and objects mixed together. What's noteworthy is that using '../' like so '../type', passes in the parent model as the context, and without it, such as with 'id', it passes in the current model as the context.
To get specifically the parent of the partial (where you may be several partials deep) then follow the other answers like SeanWM.
If you know that the parent is the main template then you can use #root which resolves to the top-most context no matter how deep you are.
e.g. {{#root.rootObject.rootProperty}}
It is a pity that ../../.. does not go up past a partial.

How to pass parameters with the action Helper of Ember.js?

I have a list of items:
<ul>
{{#each applications}}
<li>
<a {{bindAttr href="url"}}
{{action "appClicked" on="click"}}>
{{name}}
</a>
</li>
{{/each}}
</ul>
On click it calls the method appClicked of the view, that this template belongs to. I want to pass some information (for example, the name of the application) to the method appClicked. Something like, {{action "appClicked(name)" on="click"}}.
Is it possible, and how?
Apparently, Ember has evolved now and there is an ability to pass a parameter to an action:
{{action "functionName" parameter}}
In your case, that would be:
<a {{bindAttr href="url"}}
{{action "appClicked" name on='click'}}>
{{name}}
</a>
However, you could pass any attribute from the model (like the id) instead of the name.
See http://emberjs.com/guides/templates/actions/ for more information.
The API says you can pass in multiple parameters.
html and handlebars:
{{officename}}
<button {{action "actionTest" "hello" "goodbye" officename}}>See parameters through action in the console</button>
controller:
actionTest: function(a, b, c){
console.log(a);
console.log(b);
console.log(c);
},
See it in action in this jsbin
I was thinking something more along the lines of this since you'll have access to a bunch more through an actual view. But Zack, if you could explain a bit more what exactly you're trying to do if this isn't what you're looking for?
App = Ember.Application.create();
App.peopleController = Ember.ArrayController.create({
content: [ { name: 'Roy', url: '#' },
{ name: 'Mike', url: '#' },
{ name: 'Lucy', url: '#' } ]
});
App.PersonView = Ember.View.extend({
tagName: 'li',
content: null,
linkClicked: function() {
console.log(this.getPath('content.name'));
}
});
<ul>
{{#each App.peopleController}}
{{#view App.PersonView contentBinding="this"}}
<a {{bindAttr href="content.url"}} {{action "linkClicked" on="click"}}>
{{content.name}}
</a>
{{/view}}
{{/each}}
</ul>
From subviews, you can attach data-attributes and access them in your controller.
For example, in your view, if you have:
{{#view Ember.Button target="App.controller" action="publish" data-publish=false}}Unpublish{{/view}}
Then in your controller,
App.controller = Ember.Object.extend({
publish: function(v){
var status = v['data-publish'];//your additional information is appended to the view.
}
}
An improvement to Veeb's answer, remember you have the jQuery event so you can do:
// template
<ul>
{{#each applications}}
<li>
<a {{bindAttr href="url"}} param="abc" {{action "appClicked" on="click"}}>
{{name}}
</a>
</li>
{{/each}}
</ul>
// In your js code
appClicked: function (event) {
var param = $(event.target).attr('param');
...
}
You can try to make the parameter an attribute of the <li> or <a> tag and then use jQuery to access it.
Maybe something like
// template
<ul>
{{#each applications}}
<li>
<a {{bindAttr href="url"} param="abc"}
{{action "appClicked" on="click"}}>
{{name}}
</a>
</li>
{{/each}}
</ul>
// In your js code
appClicked: function (event) {
// You may have to play around and check which DOM element
// has the the param attribute. From memory it is the parent.
var param = this.$().parent().attr('param');
...
}

Categories

Resources