scoping of an action helper within a handlebars #each loop - javascript

I've become confused about the scoping within an {{#each}} block.
If I have the handlebars script
<script type="text/x-handlebars">
{{#each vars}}
<button {{action foo}}> {{name}} </button>
{{/each}}
</script>
and I set my application controller as
App.ApplicationController = Ember.Controller.extend({
vars: Ember.ArrayController.create({
content: [
{ name: "Cow",
foo: function(){console.log("moo");}
},
{ name: "Cat",
foo: function(){console.log("meow");}
}
]
})
});
The script can see {{name}} just fine and puts it in as the title of the button as you'd expect, but action does not get bound to the foo functions defined within the array members.
Is there are way to do this that I'm missing, or do I need to refactor to make foo be defined directly within ApplicationController?

You can set up an event on ApplicationController and pass in the individual object and call the stored foo(). Inside of the {{#each vars}}...{{/each}} block you can use this to pass the actual object to the event handler.
JS:
App.ApplicationController = Ember.Controller.extend({
vars: Ember.ArrayController.create({
content: [
{ name: "Cow",
foo: function(){console.log("moo");}
},
{ name: "Cat",
foo: function(){console.log("meow");}
}
]
}),
doFoo: function(obj) {
obj.foo();
}
});
Handlebars:
{{#each vars}}
<button {{action doFoo this}}> {{name}} </button>
{{/each}}
JSBin example

Action in handlebars template not firing
This may be due to the root element, check this posting here for more information

Related

How do I change between templates to display different media in Meteor/Jscript

I am currently trying to have 3 separate templates that the user can switch between by clicking on one of 3 buttons. By using a session variable ('currentContent'), 3 buttons and 3 templates I cannot see what's going wrong with my current code.
In my javascript:
Template.priority.helpers({
expensesbtn:function(){
return Session.get('currentContent') ==='expenses'?true:false;
},
custombtn:function(){
return Session.get('currentContent') ==='cexpenses'?true:false;
},
incomebtn:function(){
return Session.get('currentContent') ==='earning'?true:false;
},
});
Template.priority.events({
"click #expensesbtn":function(event, template){
Session.set('currentContent', 'expenses')
},
"click #custombtn":function(event, template){
Session.set('currentContent', 'cexpenses')
},
"click #incomebtn":function(event, template){
Session.set('currentContent', 'earning')
}
});
and then in my html:
{{>priority}}
{{#if cexpenses}}
{{> cexpenses}}
{{/if}}
{{#if expenses}}
{{> expenses}}
{{/if}}
{{#if earning}}
{{> earning}}
{{/if}}
Any help with this would be greatly appreciated. Thanks!
#Sindis is right, your helper names must match what you are calling in your templates. But another problem is that while the conditionals aren't inside a template named priority, your helpers are attached to the priority template, so they wouldn't be recognized even if the names match. You can fix this by putting everything inside the priority template and separating the buttons out into a separate sub-template if that is your intention.
You can also make your code much more elegant and DRY by using only one session variable chosenTemplate and only one helper and avoiding repeating all those conditionals. Then use Meteor's Template.dynamic feature to display the correct template. Here is an example solution below. Make a new template called templateControl and place the buttons inside it. Then place everything inside the priority template.
<template name="priority">
{{> templateControl }}
{{> Template.dynamic template=chosenTemplate }}
</template>
<template name="templateControl">
{{#each buttons}}
<button id="{{ id }}" class="chose-template">{{ label }}</button>
{{/each}}
</template>
Template.templateControl.helpers
buttons: [
{ id: 'incomebtn', label: 'Income' },
{ id: 'expensesbtn', label: 'Expenses' },
{ id: 'custombtn', label: 'Custom' }
]
Template.templateControl.events
'click .chose-template': function(e, t) {
Session.set('chosenTemplate', this.id);
}
Template.priority.helpers
chosenTemplate: function(e, t) {
return Session.get('chosenTemplate');
}
Then be sure to give the three templates you switch between names corresponding to the template ids set in the chosenTemplate Session variable!
Hope this helps.
Your helpers have to be the same when you use them in HTML, so your js should look like:
Template.priority.helpers({
expenses: function(){
return Session.get('currentContent') === 'expenses';
},
cexpenses: function(){
return Session.get('currentContent') === 'cexpenses';
},
earning: function(){
return Session.get('currentContent') === 'earning';
}
});

Update Variable in Mustache Ractive Template

I am trying to get Ractive templates to go through a loop and compare the last accessed value to the current value.
My attempt at this was to create a helper function that updates a "lastValue" variable with the value the template loop has encountered.
You can see my jsfiddle here:
http://jsfiddle.net/k6hj6q46/3/
<script id='template' type='text/ractive'>
<ul>
{{#each names}}
<li>value: {{lastValue}}</li>
<li>{{name}}</li>
{{update(name)}}
{{/each}}
</ul>
</script>
<div id='container'></div>
var ractive = new Ractive({
// The `el` option can be a node, an ID, or a CSS selector.
el: '#container',
// We could pass in a string, but for the sake of convenience
// we're passing the ID of the <script> tag above.
template: '#template',
// Here, we're passing in some initial data
data: {
lastValue: 'oldValue',
names: [{
name: 'value1'
}, {
name: 'value2'
}],
update: function (newValue) {
console.log(newValue);
this.lastValue = newValue;
}
}
});
What about:
{{#each names:i}}
<li>last value: {{names[i-1]}}
<li>current value: {{this}}
{{/each}}

Ember.js use itemController if not following naming Conventions

According to official documentation, way to create itemcontroller is:
App.PostsController = Ember.ArrayController.extend({
itemController: 'post'
});
App.PostController = Ember.ObjectController.extend({
// the `title` property will be proxied to the underlying post.
titleLength: function() {
return this.get('title').length;
}.property('title')
});
But I'm not setting my ArrayController to App. It is set to a local variable behind a function scope. And the itemController property can only be string (according to documentation). So how do I set the itemController property?
My code looks like this:
var Channels=Ember.Object.extend({
list:Ember.ArrayController.create(
{
"model":[
{
"id":"display",
"label":"Display",
},{
"id":"social",
"label":"Social",
},{
"id":"email",
"label":"Email",
}
]
}
)
});
App.ChannelController=Ember.Controller.extend({
channels:Channels,
}));
<script type="text/x-handlebars" data-template-name='channel'>
<div>
{{#each channel in channels.list}}
{{channel.label}}
{{/each}}
</div>
</script>
I don't want to pollute App namespace with itemControllers that is to be used locally.
Update
Suppose my channels is like this:
var Channels=Ember.Object.extend({
list:Ember.ArrayController.create(
{
"model":[
{
"id":"display",
"label":"Display",
},{
"id":"social",
"label":"Social",
},{
"id":"email",
"label":"Email",
}
]
}
),
selected:"display"
});
and I want to something like this in template:
<script type="text/x-handlebars" data-template-name='channel'>
<h1>{{channels.selected}}</h1>
<div>
{{#each channel in channels.list}}
<div {{bind-attr class="channel.isselected:active:inactive"}}>{{channel.label}}</div>
{{/each}}
</div>
</script>
so that it outputs:
<h1>display</h1>
<div>
<div class="active">Display</div>
<div class="inactive">Social</div>
<div class="inactive">Email</div>
</div>
How do I do it with components?
You'll likely want to read the guide of components to get the full picture, but the gist of it is that you want to replace all item controllers with components. However, components will also replace the template inside of the each block as well. I don't entirely understand what's going on in your code, but here's an example roughly based on your code.
// Component
App.ChannelDisplayComponent = Ember.Component.extend({
channel: null,
isSelected: function() {
// Compute this however you want
// Maybe you need to pass in another property
}.property('channel')
});
{{! Component Template }}
<div {{bind-attr class="channel.isSelected:active:inactive"}}>
{{channel.label}}
</div>
{{!Channels Template}}
{{#each channel in channels.list}}
{{channel-component channel=channel}}
{{/each}}
The component is essentially your item controller, only it gets its own template as well.
You really shouldn't be worried about polluting the app namespace (unless you're having naming collisions, but that's a different issue). And as Kitler said, you should move to components instead of item controllers. But if you want to do this, the best way I can think of is overridding the (private) controllerAt hook.
var ItemController = Ember.Controller.extend({});
App.PostsController = Ember.ArrayController.extend({
controllerAt: function(idx, object, controllerClass) {
var subControllers = this._subControllers;
if (subControllers.length > idx) {
if (subControllers[idx]) {
return subControllers[idx];
}
}
var parentController = (this._isVirtual ? this.get('parentController') : this);
var controller = ItemController.create({
target: parentController,
parentController: parentController,
model: object
});
subControllers[idx] = controller;
return controller;
}
})

Emberjs itemController / controller property binding

I've made a jsbin to illustrate my issue.
the binding seems KO with lastname property defined inside the itemController and the fullname value is not updated in my items loop.
What am I doing wrong ?
Controller for item in list is different than one you edit property lastname for, so it will never get updated. Propery lastname has to be specified as Model's property (if using Ember Data you simply don't use DS.attr for it and it won't be persisted). If you use custom library for data persistence you have to manually remove lastname property. You can use Ember Inspector extension to see that there are 5 controllers when you click on item. 4 for each item in list and one is being generated when you click. You edit property lastname for this fifth controller. To solve this you can use:
JavaScript:
App = Ember.Application.create();
App.Router.map(function() {
this.resource('items', function() {
this.resource('item', {path: '/:item_id'});
});
});
App.Model = Ember.Object.extend({
firstname: 'foo',
lastname: 'bar',
fullname: function() {
return this.get('firstname') + ' ' + this.get('lastname');
}.property('firstname', 'lastname')
});
App.ItemsRoute = Ember.Route.extend({
model: function() {
return [App.Model.create({id: 1}), App.Model.create({id: 2}), App.Model.create({id: 3}), App.Model.create({id: 4})];
}
});
App.ItemRoute = Ember.Route.extend({
model: function(params) {
return this.modelFor('items').findBy('id', +params.item_id);
}
});
Templates:
<script type="text/x-handlebars">
<h2>Welcome to Ember.js</h2>
{{link-to "items" "items"}}
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="items">
<ul>
{{#each item in model}}
<li>
{{#link-to 'item' item.id}}
{{item.fullname}} {{item.id}}
{{/link-to}}
</li>
{{/each}}
</ul>
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="item">
{{input value=model.firstname}}
{{input value=model.lastname}}
{{model.fullname}}
</script>
Please keep in mind that ArrayController and ObjectController aren't recommended to use, because they will be deprecated in future. Demo.

Ember Data: preload relationships

What I'm trying to do is very basic but I'm having very little luck...
Simply enough, I don't want to display a chunk of HTML until a certain Ember Data model property is fully loaded.
As you can see from the jsfiddle, the parent model: App.Person gets loaded into the DOM and it also loads the 3 placeholders for its hasMany property belts.
It then executes the request to populate App.Belt and fills in the placeholders.
While this is usually ok, it makes a big mess of things when trying to build an SVG, for example. Since the surrounding <svg> tags will get appended to the DOM immediately and then some time down the track (once the asynchronous request returns data), the inner svg components will be added between the tags. This usually creates browser rendering errors.
TL;DR
In the example, how do I defer the <h3>...</h3> section of the template from being added to the DOM until the model data and its relationships (belts) are fully loaded? This way everything gets visually and physically added to the DOM at once.
The JS:
// Create Ember App
App = Ember.Application.create();
// Create Ember Data Store
App.store = DS.Store.create({
revision: 11,
//Exagerate latency to demonstrate problem with relationships being loaded sequentially.
adapter: DS.FixtureAdapter.create({latency: 5000})
});
// Create parent model with hasMany relationship
App.Person = DS.Model.extend({
name: DS.attr( 'string' ),
belts: DS.hasMany( 'App.Belt' )
});
// Create child model with belongsTo relationship
App.Belt = DS.Model.extend({
type: DS.attr( 'string' ),
parent: DS.belongsTo( 'App.Person' )
});
// Add Parent fixtures
App.Person.FIXTURES = [{
"id" : 1,
"name" : "Trevor",
"belts" : [1, 2, 3]
}];
// Add Child fixtures
App.Belt.FIXTURES = [{
"id" : 1,
"type" : "leather"
}, {
"id" : 2,
"type" : "rock"
}, {
"id" : 3,
"type" : "party-time"
}];
// Set route behaviour
App.IndexRoute = Ember.Route.extend({
model: function() {
return App.Person.find();
},
renderTemplate: function() {
this.render('people');
}
});
The HTML/HBS:
<script type="text/x-handlebars">
<h1>Application</h1>
{{outlet}}
</script>
<script type="text/x-handlebars" id="people">
<h3>Don't load this header until every belt defined in App.Person.belts is loaded</h3>
<ul>
{{#each controller}}
{{debugger}}
<li>Id: {{id}}</li>
<li>Name: {{name}}</li>
<li>Belt types:
<ul>
{{#each belts}}
<li>{{type}}</li>
{{/each}}
</ul>
</li>
{{/each}}
</ul>
</script>
The fiddle: http://jsfiddle.net/zfkNp/4/
Check for the controller.content.length and belts.isLoaded, See the jsfiddle for a solution.
<script type="text/x-handlebars" id="people">
{{#if controller.ready}}
<h3>Don't load this header until every belt defined in App.Person.belts is loaded</h3>
{{/if}}
<ul>
{{#each controller}}
{{debugger}}
{{#if belts.isLoaded}}
<li>Id: {{id}}</li>
<li>Name: {{name}}</li>
<li>Belt types:
<ul>
{{#each belts}}
<li>{{type}}</li>
{{/each}}
</ul>
</li>
{{/if}}
{{/each}}
</ul>
</script>
App.IndexController = Ember.ArrayController.extend({
content: null,
ready:function() {
return this.get('content.length')>0
}.property('content.length')
});

Categories