Update Variable in Mustache Ractive Template - javascript

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}}

Related

Can autorun's reactive computation be dependent on part of a collection?

I'd like to create a template in Meteor that has a Tracker.autorun which exclusively runs when part of a document changes --- but not when other parts of the document change.
So here is sample code using a minimongo collection and template.autorun
parent.html
{{#each items}}
{{> child}}
{{/each}}
child.html
<div>{{title}}</div>
<p>{{description}}</p>
Minimongo Collection
LocalProject.findOne() output:
"items": [
{
"title": "hi",
"description": "test"
},
{
"title": "hi 2",
"description": "test 2"
},
],
"otherstuff:{//etc}
child.js
Template.child.onRendered(function(){
this.autorun(function() {
var data = Template.currentData();
doSomething(data.title,data.description)
});
});
addnewitem.js
LocalProject.update(currentEditingProjectID,{ $push: { 'items': newItem }},function(error,result){
if(error){console.log(error)}
});
The problem is, whenever I run addnewitem.js, all of my Template.child autoruns execute even though their reactive data source (Template.currentData()) has not changed unless it was the specific item I updated. Similarly if I want to update an existing item, not just add a new one to the array, all of the autoruns for each item get executed.
So is there a way, using this model, to create a dependency for autorun that is reactively granular to specific portions of a document?
I don't think the way to go is by using an autorun. I would either set up individual reactive dependencies on each item, or use observe/observeChange.
First idea
parent.html:
{{#each items}}
{{> child}}
{{/each}}
parent.js:
Template.parent.helpers({
// Returns only item ids
items: function() {
return Items.find({}, { fields: { _id: 1 } });
}
});
child.html:
{{#each items}}
{{#with item=getItem}}
<div>{{item.title}}</div>
<p>{{item.description}}</p>
{{/with}}
{{/each}}
child.js:
Template.child.helpers({
getItem: function() {
// Get the item and set up a reactive dependency on this particular item
var item = Items.find(this._id);
// After item has been displayed, do something with the dom
Tracker.afterFlush(function () {
doSomething(item.title, item.description);
});
return item;
}
});
Second idea
parent.html:
{{#each items}}
{{> child}}
{{/each}}
parent.js:
function do(item) {
Tracker.afterFlush(function () {
doSomething(item.title, item.description);
});
}
Template.parent.onCreated({
this.items = Items.find({});
this.handle = this.items.observe({
added: function(item) { do(item); },
changed: function(oldItem, newItem) { do(newItem); },
});
});
Template.parent.onDestroyed({
this.handle.stop();
});
Template.parent.helpers({
items: function() {
return Template.instance().items;
}
});
child.html:
{{#each items}}
<div>{{title}}</div>
<p>{{description}}</p>
{{/each}}
There's a tool just for this - 3stack:embox-value provides reactive isolation, and value caching.
Using your example, you could isolate changes to title/description like so:
first up, add the packages
meteor add 3stack:embox-value
meteor add ejson
Then, update your code:
Template.child.onRendered(function(){
// creates a reactive data source, that only triggers "changes"
// when the output of the function changes.
var isolatedData = this.emboxValue(function(){
var data = Template.currentData();
return {title: data.title, description: data.description}
}, {equals: EJSON.equals})
this.autorun(function() {
// This autorun will only execute when title or description changes.
var data = isolatedData()
doSomething(data.title,data.description)
});
});

EmberJS - object proxying is deprecated - accessing property of a controller in template

I'm trying to understand certain peculiarity.
Setting xxx property and iterating #each in one controller works, while seemingly same operation with yyy #each doesn't...
I'm including highlights of the code and the runnable code snippet:
App.IndexController = Ember.Controller.extend({
xxx : [{name:"a"}, {name:"b"}], // this works just fine
});
{{#each item in xxx}}
<li>{{item.name}}</li>
{{/each}}
App.ColorController = Ember.Controller.extend({
yyy : [{name:"c"}, {name:"d"}], // this triggers deprecation
// You attempted to access `yyy` from ...
// But object proxying is deprecated. Please use `model.yyy` instead
});
{{#each item in yyy}}
<li>{{item.name}}</li>
{{/each}}
App = Ember.Application.create();
App.Color = DS.Model.extend({
name: DS.attr('string')
});
App.Router.map(function() {
this.resource('color', function(){
this.route('show', { path: ':color_id' });
});
});
App.IndexRoute = Ember.Route.extend({
model: function() {
return [
{ id: 1, name: "Red" },
{ id: 2, name: "Blue" },
];
}
});
App.IndexController = Ember.Controller.extend({
xxx : [{name:"a"}, {name:"b"}], // this works just fine
});
App.ColorController = Ember.Controller.extend({
init : function() {
this._super();
console.info("Just to double check, this controller gets initialised");
},
yyy : [{name:"c"}, {name:"d"}], // this triggers deprecation
// You attempted to access `yyy` from ...
// But object proxying is deprecated. Please use `model.yyy` instead
});
<script type="text/x-handlebars">
<h2>Ember Starter Kit</h2>
{{outlet}}
</script>
<script type="text/x-handlebars" id="index">
<h3>Index</h3>
<ul>
{{#each color in model}}
<li>{{#link-to "color.show" color}} {{color.name}} {{/link-to}}</li>
{{/each}}
</ul>
<ul>
{{#each item in xxx}}
<li>{{item.name}}</li>
{{/each}}
</ul>
</script>
<script type="text/x-handlebars" id="color/show">
<h3>color/show</h3>
<h4>{{ model.name }}</h4>
<ul>
{{#each item in yyy}}
<li>{{item.name}}</li>
{{/each}}
</ul>
{{#link-to "application"}}Go back to the list{{/link-to}}
</script>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<script src="http://builds.emberjs.com/tags/v1.13.2/ember.debug.js"></script>
<script src="http://builds.emberjs.com/tags/v1.13.2/ember-template-compiler.js"></script>
<script src="http://builds.emberjs.com/tags/v1.13.2/ember-data.js"></script>
I'd like to learn more:
why it works in one case and doesn't work in another?
what is the Ember way of fixing it?
EDIT: Updated code snippet include Color model. To trigger deprecation warning click on one of the colours (Red, Blue)... This is what happens when I run the snippet:
Okay, as I expected - problem lies in naming conventions and relics of the past(ObjectController). Declaring ColorController creates controller for model, not a route. You need here controller for route, so changing ColorController to ColorShowController solves problem and values render. Deprecation's gone.
App = Ember.Application.create();
App.Color = DS.Model.extend({
name: DS.attr('string')
});
App.Router.map(function() {
this.resource('color', function(){
this.route('show', { path: ':color_id' });
});
});
App.IndexRoute = Ember.Route.extend({
model: function() {
return [
{ id: 1, name: "Red" },
{ id: 2, name: "Blue" },
];
}
});
App.IndexController = Ember.Controller.extend({
xxx : [{name:"a"}, {name:"b"}], // this works just fine
});
App.ColorShowController = Ember.Controller.extend({
init : function() {
this._super();
console.info("Just to double check, this controller gets initialised");
},
yyy : [{name:"c"}, {name:"d"}], // this triggers deprecation
// You attempted to access `yyy` from ...
// But object proxying is deprecated. Please use `model.yyy` instead
});
<script type="text/x-handlebars">
<h2>Ember Starter Kit</h2>
{{outlet}}
</script>
<script type="text/x-handlebars" id="index">
<h3>Index</h3>
<ul>
{{#each color in model}}
<li>{{#link-to "color.show" color}} {{color.name}} {{/link-to}}</li>
{{/each}}
</ul>
<ul>
{{#each item in xxx}}
<li>{{item.name}}</li>
{{/each}}
</ul>
</script>
<script type="text/x-handlebars" id="color/show">
<h3>color/show</h3>
<h4>{{ model.name }}</h4>
<ul>
{{#each item in yyy}}
<li>{{item.name}}</li>
{{/each}}
</ul>
{{#link-to "application"}}Go back to the list{{/link-to}}
</script>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<script src="http://builds.emberjs.com/tags/v1.13.2/ember.debug.js"></script>
<script src="http://builds.emberjs.com/tags/v1.13.2/ember-template-compiler.js"></script>
<script src="http://builds.emberjs.com/tags/v1.13.2/ember-data.js"></script>

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.

Ractive computed property

I've got a simple test application using firebase with the following template and model in ractive. I want to access the price value in the computed newprice to format it to look like currency with 2 decimal places. I can't figure out though how to get at the .price value which displays fine in the output but nothing I've tried seems to be able to see .price inside the computed. The call to newprice works fine as I can just return text and see it in the output. The reason it am using .price is that the returned data from firebase has each make,model,price wrapped in a unique autogenerated id so I see a top level object with each entry id and the data within as an object with make,model,price.
<script id='template' type='text/ractive'>
{{#each listdata:i}}
<p>{{ .make }} {{ .model }}{{.price}} ${{ newprice() }}!</p>
{{/each}}
</script>
<script>
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',
computed: {
newprice: function() {
// CAN'T FIGURE OUT WHAT TO DO HERE TO SEE price
return ;
}
}
});
</script>
Need some direction on how to get to .price value.
Computed properties are referenced as properties, not functions. And, more importantly, are "absolute" keypaths so they won't work against a collection. To get this to work you have two options:
Use a data function
Instead of a computed property, use a data function:
<script id='template' type='text/ractive'>
{{#each listdata:i}}
<p>{{ .make }} {{ .model }}{{.price}} ${{ newprice(.price) }}!</p>
{{/each}}
</script>
<script>
var ractive = new Ractive({
el: 'container',
template: '#template',
data: {
newprice: function(price) {
return price + 1;
},
// other data
}
});
</script>
You may find it more convientent to place the helper function "globally":
Ractive.defaults.data.newprice = function(price){...}
If you have an existing/favorite library, you can also use this technique and access methods inline in your template
<script src="path/to/accounting.js"></script>
Ractive.defaults.data.accounting = accounting
<p>{{ .make }} {{ .model }}{{.price}} ${{ accounting.formatMoney(.price) }}!</p>
Use a computed property, but at the component level
Use a component to render each item and then the computed property will be per item:
<script id='template' type='text/ractive'>
{{#each listdata:i}}
<line/>
{{/each}}
</script>
<script id='line' type='text/ractive'>
<p>{{ make }} {{ model }}{{price}} ${{ newprice }}!</p>
</script>
<script>
Ractive.components.line = Ractive.extend({
template: '#line',
computed : {
newprice: function(){
return this.get('price')
}
}
})
var ractive = new Ractive({
el: 'container',
template: '#template',
});
</script>
Computed properties apply to the whole template - in other words there's no listdata[0].newprice and so on in the example above, only one newprice.
Instead, you need to create a function that Ractive can access from inside the template, and pass the old price into it:
<!-- language: lang-html -->
{{#each listdata:i}}
<p>{{ .make }} {{ .model }}: {{.price}} -> ${{ newprice( .price ) }}!</p>
{{/each}}
<!-- language: lang-js -->
var ractive = new Ractive({
el: 'main',
template: '#template',
data: {
listdata: [
{ make: 'Toyota', model: 'Prius', price: 25000 },
{ make: 'Dodge', model: 'Challenger', price: 30000 },
{ make: 'Jeep', model: 'Grand Cherokee', price: 35000 },
{ make: 'Bugatti', model: 'Veyron', price: 2000000 }
],
discount: 0.1,
newprice: function ( oldprice ) {
return oldprice * ( 1 - this.get( 'discount' ) );
}
}
});
Here's a JSFiddle to demonstrate: http://jsfiddle.net/rich_harris/nsnhwobg/

scoping of an action helper within a handlebars #each loop

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

Categories