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/
Related
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}}
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.
I try to bind custom sub elements to values of local storage by using polymer's template repeat functionality like this:
<polymer-element name="aw-outerElement">
<template>
<template repeat="{{group in grouplist}}">
<aw-innerElement groupId="{{group.groupId}}" name="{{group.name}}" val="{{group.val}}"></aw-innerElement>
</template>
</template>
<script>
Polymer('aw-outerElement', {
ready : function () {
// Binding the project to the data-fields
this.prj = au.app.prj;
this.grouplist = [
{ groupId: 100, name: 'GroupName1', val: this.prj.ke.groupVal100},
{ groupId: 200, name: 'GroupName2', val: this.prj.ke.groupVal200}
];
}
</script>
In the code above I try to pass the data binding this.prj.ke.groupVal100 and this.prj.ke.groupVal200
to my inner element aw-innerElement through the attribute val. The aw-innerElement is a custom paper-input element where the value attribute should be set to e.g. this.prj.ke.groupVal100. It seems that only the stored initial value 0 will be set and NOT the data-binding string this.prj.ke.groupVal100 inside the value attribute. Is there a way to make a data-binding with template repeat inside inner elements?
My inner elements looks like this:
<polymer-element name="aw-innerElement" attributes="groupId name val">
<template>
<paper-input type="number" floatingLabel label="{{groupId}} {{name}}" value="{{val}}" error="{{i18nnrerror}}"></paper-input>
</template>
<script>
Polymer('aw-innerElement', {
publish: {
groupId: 0,
name: '',
val: 0
},
ready : function () {
// Binding the project to the data-fields
this.prj = au.app.prj;
...
}
</script>
As you can see above the value="{{val}}" of my innerElement should be set to this.prj.ke.groupVal100 and this.prj.ke.groupVal200.
Thanks in advance!
I know I'm digging up an old question, but for future searchers this might come in handy.
Polymer does not allow a variable as your key, so you need to pull it through a function like so:
...
<template is="dom-repeat" items="{{users}}">
<li>{{showValue(item)}}</li>
</template>
...
<script>
Polymer('aw-outerElement', {
// Standard Polymer code here
showValue: function(item){
return item[myVar];
}
});
</script>
You can then manipulate as much as you want in Javascript and return the output for that one item in items.
Passing in a value seems to work fine for me in this example: http://jsbin.com/kalih/4/edit
<polymer-element name="x-bar">
<template>
<paper-input id="input"
label="{{name}}"
value="{{val}}">
</paper-input>
<button on-tap="{{logVal}}">Log val</button>
</template>
<script>
Polymer('x-bar', {
publish: {
val: 0
},
logVal: function() {
console.log(this.$.input.value);
}
});
</script>
</polymer-element>
<polymer-element name="x-foo">
<template>
<template repeat="{{item in items}}">
<x-bar name="{{item.name}}" val="{{item.val}}"></x-bar>
</template>
</template>
<script>
Polymer('x-foo', {
ready: function() {
this.items = [
{
name: 'baz',
val: 28
},
{
name: 'qux',
val: 42
}
];
}
});
</script>
</polymer-element>
<x-foo></x-foo>
Btw, your aw-innerElement doesn't need to have an attributes attribute because you're using the publish object (they serve the same purpose, but publish lets you set default values, which you've done). Also, we recommend that you don't camel-case your element names, the HTML parser is actually going to lowercase them all anyway.
I'm new with polymer so maybe my answer is not correct. But I done something like this that you're trying to do.
here is a sample of my code
I guess that your problem is that you didn't use bind on your template
<template bind="{{grouplist}}">
<template repeat="{{group in grouplist}}">
</template>
</template>
<script>
Polymer('aw-outerElement', {
ready : function () {
// Binding the project to the data-fields
this.prj = au.app.prj;
this.grouplist = [
{ groupId: 100, name: 'GroupName1', val: this.prj.ke.groupVal100},
{ groupId: 200, name: 'GroupName2', val: this.prj.ke.groupVal200}
];
}
</script>
How can you filter a data-list to render into multiple outlets in emberjs.
What I have now in not really working, but may help you understand what I want to achieve.
I can solve this by making multiple file-list.hbs template-files ( where I change file in the each to fileList1 or fileList2, ...), but that doesn't seem right.
What I want to achieve
I have a documents page where I want to list all of the document in the file list (see fixtures file). But instead of printing out one files-list, I want to split the lists so I have multiple lists according to the filter.
Please look at the code to understand it better ^^
Can anyone help? :)
File.FIXTURES
App.File.FIXTURES = [
{
id: 1,
showHomepage: false,
filter: 'filter1',
url: '/file1.pdf',
description: 'file1'
},
{
id: 2,
showHomepage: false,
filter: 'filter2',
url: '/file2.pdf',
description: 'file2'
},
{
id: 3,
showHomepage: true,
filter: 'filter2',
url: '/file3.pdf',
description: 'file3'
},
{
id: 4,
showHomepage: true,
filter: 'filter3',
url: '/file4.pdf',
description: 'file4'
}
];
Route
App.InfoDocumentenRoute = Ember.Route.extend({
model: function() {
var store = this.store;
return Ember.RSVP.hash({
fileList1: store.find('file' , { filter: "filter1" }),
fileList2: store.find('file' , { filter: "filter2" }),
fileList3: store.find('file' , { filter: "filter3" })
});
},
renderTemplate: function() {
this.render('file-list', { // the template to render
into:'info.documenten', // the route to render into
outlet: 'file-list-filter1', // the name of the outlet in the route's template
controller: 'file' // the controller to use for the template
});
this.render('file-list', { // the template to render
into:'info.documenten', // the route to render into
outlet: 'file-list-filter2', // the name of the outlet in the route's template
controller: 'file' // the controller to use for the template
});
this.render('file-list', { // the template to render
into:'info.documenten', // the route to render into
outlet: 'file-list-filter3', // the name of the outlet in the route's template
controller: 'file' // the controller to use for the template
});
}
});
info/documents.hbs
{{ outlet file-list-filter1 }}
{{ outlet file-list-filter2 }}
{{ outlet file-list-filter3 }}
file-list.hbs
<ul class="download-list">
{{#each file in file}}
<li class="download-list__item">
<a {{bind-attr href=file.url}} target="_blank" class="download-list__link">
<i class="icon-download download-list__link__icon"></i>
{{file.description}}
</a>
</li>
{{else}}
<li>
Geen documenten beschikbaar.
</li>
{{/each}}
I think the best way to go about this would be to declare your file-list.hbs as a partial and include it within your other templates where needed as: {{partial "file-list"}}. In your showHomepage where you only want to use it a single time, merely include the {{partial "file-list"}} within your showHomepage.hbs.
Then, for your InfoDocumentRoute, put the following to declare your model as an array of filelists:
App.InfoDocumentenRoute = Ember.Route.extend({
model: function() {
var store = this.store;
return [
store.find('file' , { filter: "filter1" }),
store.find('file' , { filter: "filter2" }),
store.find('file' , { filter: "filter3" })
];
}
});
And your InfoDocument.hbs as:
{{#each file in model}}
{{partial "file-list"}}
{{/each}}
Which will then render the file-list template for each item in the model array.
More info about partials
So from what i gather about your question you want to filter your model on your filter property on the model. I am sure there are a few ways to accomplish this but here is another possible solution that could spark another solution.
So in the route I returned the models. Then in the controller I created properties that are filtering the array of models from the route. Then in the template I loop over the array that filter property gives me in the controller and output in the template.
Heres JSBin. http://emberjs.jsbin.com/vunugida/5/edit
App.IndexRoute = Ember.Route.extend({
model: function() {
return this.store.findAll('File');
}
});
App.IndexController = Ember.ArrayController.extend({
filter1: function() {
return this.filter(function(item) {
return item.get('filter') === "filter1";
});
}.property(),
filter2: function() {
return this.filter(function(item) {
return item.get('filter') === "filter2";
});
}.property(),
filter3: function() {
return this.filter(function(item){
return item.get('filter') === "filter3";
});
}.property()
});
TEMPLATE:
<script type="text/x-handlebars" data-template-name="index">
<h1>Index Template</h1>
<ul>
{{#each}}
<li>{{url}}</li>
{{/each}}
</ul>
<p>Filter 1</p>
{{#each filter1}}
<li>{{url}}</li>
{{/each}}
<p>Filter 2</p>
{{#each filter2}}
<li>{{url}}</li>
{{/each}}
<p>Filter 3</p>
{{#each filter3}}
<li>{{url}}</li>
{{/each}}
</script>
I have the following two models:
App.Child = DS.Model.extend({
name: DS.attr('string')
});
And:
App.Activity = DS.Model.extend({
children: DS.hasMany('child',{async:true}),
name: DS.attr('string')
});
I want to use checkboxes to choose between the existing children, for the hasMany relation.
For example, I have these three children:
App.Child.FIXTURES = [
{ id: 1, name: 'Brian' },
{ id: 2, name: 'Michael' },
{ id: 3, name: 'James' }
];
The user should be able to use checkboxes, while creating or editing an activity, for choosing which children, to add to the hasMany relation.
I've created a JSFiddle to illustrate my question: http://jsfiddle.net/Dd6Wh/. Click 'Create a new activity' to see what I'm trying to do.
Basically it's the same as Ember.Select [ ... ] multiple="true", but for checkboxes.
What's the correct approach for something like this with Ember.js?
You can use an itemController in your each view helper to manage the selection. In the code below I created one called ChildController:
App.ChildController = Ember.ObjectController.extend({
selected: function() {
var activity = this.get('content');
var children = this.get('parentController.children');
return children.contains(activity);
}.property(),
selectedChanged: function() {
var activity = this.get('content');
var children = this.get('parentController.children');
if (this.get('selected')) {
children.pushObject(activity);
} else {
children.removeObject(activity);
}
}.observes('selected')
});
With a itemController you can expose some properties and logics, without add it directlly to your models. In that case the selected computed property and the selectedChanged observer.
In your template, you can bind the selection using checkedBinding="selected". Because the itemController proxy each model, the selected property of the itemcontroller will be used, and the {{name}} binding, will lookup the name property of the model:
<script type="text/x-handlebars" data-template-name="activities/new">
<h1>Create a new activity</h1>
{{#each childList itemController="child"}}
<label>
{{view Ember.Checkbox checkedBinding="selected"}}
{{name}}
</label><br />
{{/each}}
{{view Ember.TextField valueBinding="name"}}
<button {{action create}}>Create</button>
</script>
The same aproach in edit template:
<script type="text/x-handlebars" data-template-name="activities/edit">
<h1>Edit an activity</h1>
{{#each childList itemController="child"}}
<label>
{{view Ember.Checkbox checkedBinding="selected"}}
{{name}}
</label><br />
{{/each}}
{{view Ember.TextField valueBinding="name"}}
<button {{action update}}>Update</button>
</script>
This is a fiddle with this working http://jsfiddle.net/marciojunior/8EjRk/
Component version
Template
<script type="text/x-handlebars" data-template-name="components/checkbox-select">
{{#each elements itemController="checkboxItem"}}
<label>
{{view Ember.Checkbox checkedBinding="selected"}}
{{label}}
</label><br />
{{/each}}
</script>
Javascript
App.CheckboxSelectComponent = Ember.Component.extend({
/* The property to be used as label */
labelPath: null,
/* The model */
model: null,
/* The has many property from the model */
propertyPath: null,
/* All possible elements, to be selected */
elements: null,
elementsOfProperty: function() {
return this.get('model.' + this.get('propertyPath'));
}.property()
});
App.CheckboxItemController = Ember.ObjectController.extend({
selected: function() {
var activity = this.get('content');
var children = this.get('parentController.elementsOfProperty');
return children.contains(activity);
}.property(),
label: function() {
return this.get('model.' + this.get('parentController.labelPath'));
}.property(),
selectedChanged: function() {
var activity = this.get('content');
var children = this.get('parentController.elementsOfProperty');
if (this.get('selected')) {
children.pushObject(activity);
} else {
children.removeObject(activity);
}
}.observes('selected')
});
Updated fiddle http://jsfiddle.net/mgLr8/14/
I hope it helps