I have a list of attribute names attrs that I would like to iterate over, creating an input with a binding to that attribute on the model for each for each. The below expresses the functionality I'm after:
{{#each attr in attrs}}
{{input value=model.get(attr) }}
{{/each}}
Of course this does not actually work, because you can't make method calls in a helper. I found this question, but the solution doesn't work in my version of Ember (1.11.0)-- I gather because this is undefined in a helper definition as of version 1.11.0 (see here).
How can I make this work?
If you want to just get a property from the model, named by the current value of your each loop, you could write a helper like:
Ember.Handlebars.helper('input-helper',
function(model, attr) {
return Ember.get(model, attr);
}
);
And then use it like:
{{#each attr in attrs}}
{{input-helper model attr}}
{{/each}}
This will print property named to whatever attr holds in that #each iteration from the model
If you want to bound that value to an input, instead of making a helper, you could make a component or a view that will do this for you:
// component that binds 'object.property' to an input field
var InputBinderComponent = Ember.Component.extend({
prop: null, // passed in
obj: null, // passed in
value: null, // local
onValue: function() {
var obj = this.get('obj');
var prop = this.get('prop');
var value = this.get('value');
Ember.set(obj, prop, value);
}.observes('value'),
});
With input-binder component's template to actually contain the input:
{{input type="text" value=value}}
And then use it in the template:
{{#each attr in attrs}}
{{input-binder prop=attr obj=model}}
{{/each}}
Here is an Example
Related
I want to write a custom element like this.
<dom-module id="custom-element">
<template>
<!-- This part I want to use document.createElement -->
<template is="dom-repeat" items="[[data]]">
<div>
<span>[[item.name]]</span>
<span>[[item.count]]</span>
</div>
</template>
</template>
</dom-module>
The count value may be changed by other element.
Is it possible to bind properties count to a document.createElement element?
class CustomElement extend Polymer.Element {
static get is () { return "custom-element" }
static get properties () {
return {
data: {
type: Array,
value: [{
"name": "item1",
"count": 0
}, {
"name": "item2",
"count": 0
}, {
"name": "item3",
"count": 0
}],
notify: true,
observer: "_dataChanged"
}
}
}
_dataChanged: (data) => {
data.map((item) => {
let div = document.createElemnt("div")
let itemName = document.createElement("span")
itemName.textContent = item.name
let itemCount = document.createElement("span")
// I want to bind value count here
itemCount.textContent = item.count
div.appendChild(itemName)
div.appendChild(itemCount)
this.shadowRoot.appendChild(div)
})
}
}
window.customElements.define(CustomElement.is, CustomElement)
When other element change the count value, the element which create by document.createElement's count will be change.
Is this possible?
So, data binding works, between two custom elements
Whether you use a dom-repeat with the bind, or bind it to an element like an input in the shadow DOM,
OR,
you use document.createElement to append an element to your shadowDOM, with the bound value,
does not matter
you use {{}} for a two way binding, and a [[]] for a one way binding.
So, If other-element changes adata property bound to custom-element, and you want custom-element to accommodate them, use one way
If you want the changes made by custom-elemnt to also change values in other-element, use two way.
If you are using the console to mutate data of one element, I am afraid, those mutations aren't reflected to the custom element.
If however, you are having a binding between custom-element and other-element on the property data, and
other-element mutates the property data as the result of an event, then, these changes are notified to all communicating elements.
Edit: I see that you have a subsequent query in the comments, which asks why is the plunk you made not working
While you are now correctly binding the elements two-way via data ,
there is a bug in polymer, that doesn't notify observers on data mutation if data is an array.
Solution for now: Shallow clone your data using slice after you change it
like so:
add (e) {
this.set(`data.${e.target.index}.count`, (this.data[e.target.index].count + 1))
this.data = this.data.slice();
}
The following Ember Handlebars template renders the 1st row, but does not render the one inside the nested each (or inner each)
<table width="50%">
{{#each someData.items as |item|}}
<tr> <!-- This one RENDERS -->
<td width="25%"><span class="boldTxt">{{item.fld1}}</span></td>
<td width="25%">{{item.fld2}}</td>
</tr>
{{#each item.Reas as |rea|}}
<tr> <!-- This one does not RENDER -->
<td>{{rea}}</td>
</tr>
{{/each}}
{{/each}}
</table>
What is the issue??
I am using Ember version 1.13
Most likely, your problem is that you are using Ember2.0 or above (based on your outer each loop) so your inner each loop has a now invalid (formerly deprecated) format. Also, you are using the same variable name item for both loops, which won't work properly.
http://guides.emberjs.com/v2.1.0/templates/displaying-a-list-of-items/
Just use the same format as in the outer loop:
Change:
{{#each item in item.Reasons}}
To:
{{#each item.Reasons as |reason|}}
EDIT
If your Reas arrays look as you've described in the comments:
item.Reas = [null]; // arrays containing a single `null` value
Then handlebars will show an empty string for these values since Handlebars coerces undefined and null to an empty string.
{{reas}} {{!-- if reas is null then an empty string is printed --}
If you want to show null and undefined values, you can make a simple helper to do so:
// helpers/show-value.js
import Ember from "ember";
export default Ember.Helper.helper(function(params) {
let value = params[0];
if(value === undefined) { return 'undefined'; }
if(value === null) { return 'null'; }
return value;
});
EDIT 2
Based on your explanation in the comment:
Since you are using Ember 1.13, you need a work around to achieve this. Here is one way:
// components/each-keys.js
export default Ember.Component.extend({
object: null, // passed in object
items: Ember.computed('object', function() {
var object = Ember.get(this, 'object');
var keys = Ember.keys(object);
return keys.map(function(key) {
return { key: key, value: object[key]};
})
})
})
Usage:
{{#each-keys object=item.Reas as |key value|}}
<tr>
<td>{{key}}</td>
<td>{{value}}</td>
</tr>
{{/each-keys}}
Here is a running example
If you update to Ember 2.0, which should be pretty straightforward from 1.13 (since 2.0 is basically 1.13 without deprecations) you can use the each-in helper to iterate over an object and get access to both its keys and values. Here is a simple example:
{{#each-in items as |key value|}}
<p>{{key}}: {{value}}</p>
{{/each-in}}
I am trying to customize this working http://jsfiddle.net/markcoleman/JNqqU/ ,in current working fiddle directly object is assigned . where i am trying to change it to $scope.obj.items . passing object to directive is not working .
do i need to write some $watch fo r the variable ??? i am getting dynamic value that's why i am trying to pass Object value with this .
Code ::
<a href="#" pop-over items="obj.items", title="Mode of transport">
Show Pop over</a>
javascript Directive part ::
scope: {
items: '=',
title: '#'
}
Any suggestion ,
I am trying Below Fiddle
http://jsfiddle.net/JNqqU/652/
You can change your controller to this:
bootstrap.controller('maincnt', function ($scope) {
$scope.obj = { // declare the scope object here with a blank items
items: []
};
$scope.updateitem = function () {
alert('scope update called');
$scope.obj.items = ['car', 'truck', 'plane', 'bike']; // now update here
}
});
Checkout fiddle.
Yes you should be making a watcher.
$scope.$watchCollection('items', function (newValue, oldValue) {
if (newValue) {
buildTemplate(newValue);
}
});
Note: I used watchCollection because it is an array. If it were an object or simple value $watch would be used instead.
You don't need to wrap it into object, but don't 'rewrite' whole array in 'update' method, but push values into it:
bootstrap.controller('maincnt',function($scope){
$scope.items = [];
$scope.updateitem=function(){
alert('scope update called');
$scope.items.push('car', 'truck', 'plane', 'bike');
}
});
http://jsfiddle.net/btfu30k2/1/
$watch isn't necessary too.
You need two changes:
Change in HTML items :: {{obj.items}}
Change in Controller default obj items should be assigned with empty array ( $scope.obj={items:[]}; ) as popOver's $compile is looking for scope.items
See this Working fiddle
Also your testing code {{items | json }} in template can be removed after your observation.
I'm playing with knockout a little bit, and I'm stuck with something. I did the example that you create the first name, the last name and then create a ko.computed to make the fullName. That works ok, but let's say I have an observable array with a lot of objects containing first names and last names. How to use the computed function to create the full name? If i create something like:
function vm() {
....
self.fullName = ko.computed(function() {
return self.names().firstName + "" + self.names().lastName;
}
I can't use it because this is a viewmodel method, and inside a foreach binding knockout will look for local methods (in this case, methods of self.names())
Also can't use $root.fullName because then knockout will not retrieve the correct value.
Fiddle: http://jsfiddle.net/mtfv6q6a/
you can call it by assiging a variable to your vm sth. like :
appModel = new vm();
ko.applyBindings(appModel);
and
<h3 data-bind="text: appModel.fullName()"></h3>
this works, but would always return undefinedundefined http://jsfiddle.net/mtfv6q6a/1/ because firstName is not a property of names()
you rather need some simple function like:
self.returnFullName = function(item) {
return item.firstName + " " + item.lastName;
};
and call it like
<h3 data-bind='text: appModel.returnFullName($data); '></h3>
http://jsfiddle.net/mtfv6q6a/2/
function AppViewModel() {
self.tagbuttons=ko.observableArray([
{shotbar:false, frozendrinks: false, livemusic: false, patio:false, food:false}
]);
self.toggleTag = function(data,event) {
var id = event.target.id;
self.tagbuttons()[0][id] = !self.tagbuttons()[0][id];
console.log(self.tagbuttons()[0][id]);
if(self.tagbuttons()[0][id] == true)
{
$(event.target).closest('li').addClass("active");
console.log(event.target.id+":"+"active");
}
else
{
$(event.target).closest('li').removeClass("active");
console.log(event.target.id+":"+"inactive");}
}
}
ko.applyBindings(new AppViewModel());
My console.log(self.tagbuttons()[0][id]) outputs the correct bool value, but the value does not update in my array. Here is my html:
<div data-bind="text: tagbuttons()[0].shotbar"></di>
You need to apply the KO bindings after you declare your MyAppViewModel and after the UI is loaded:
$(document).ready(function(){
ko.ApplyBindings(new MyAppViewModel())?
});
Unless you are applying the KO bindings, nothing will happen outside of the view model.
Here is a quote from the ko documentation:
Key point: An observableArray tracks which objects are in the array, not the state of those objects
Simply putting an object into an observableArray doesn’t make all
of that object’s properties themselves observable. Of course, you can
make those properties observable if you wish, but that’s an
independent choice. An observableArray just tracks which objects it
holds, and notifies listeners when objects are added or removed.
So when you are changing array item's value knockout is not notified. You can use valueHasMutated function to notify subscribers manually:
self.tagbuttons()[0][id] = !self.tagbuttons()[0][id];
self.tagbuttons.valueHasMutated();
Or wrap items in array with observalbe:
self.tagbuttons = ko.observableArray([
ko.observable({
shotbar:false,
frozendrinks: false,
livemusic: false,
patio:false,
food:false})
]);