Polymer, issue with binding array to paper slider value - data-binding

Here is example of the issue: Plunk
The initial value, 31, is not binding when changing the slider.
Array value 31 is seated on the initiation, but can not be reseated after change.
How to properly bind slider to the array?
<base href="http://polygit.org/polymer+:master/components/">
<script src="webcomponentsjs/webcomponents-lite.min.js"></script>
<link href="polymer/polymer.html" rel="import">
<link href="paper-input/paper-input.html" rel="import">
<link href="paper-slider/paper-slider.html" rel="import">
<link href="google-chart/google-chart.html" rel="import">
<dom-module id="dynamic-chart">
<template>
Binded values:
<br>
arrayItem: {{arrayItem(rows.*, 0, 1)}}
<br>
arrayBase: {{arrayBase(rows.*)}}
<hr>
Jan slider:
<paper-slider min="1"
max="31"
value="{{rows.0.1}}"
pin
editable>
</paper-slider>
</template>
<script>
Polymer({
is: 'dynamic-chart',
properties: {
rows: {
type: Array,
notify: true,
},
},
//domReady:
attached: function() {
this.async(function() {
this.rows=[ ["Jan", 31],["Feb", 28],["Mar", 31] ];
console.log('domReady');
});
},
// first argument is the change record for the array change,
// change.base is the array specified in the binding
arrayItem: function(change, index, path) {
console.log('arrayItem');
return this.get(path, change.base[index]);
},
arrayBase: function(change) {
console.log('arrayBase');
return change.base;
},
});
</script>
</dom-module>
<dynamic-chart>
</dynamic-chart>
Update:
array-selector (simple example) element can be used for this task too.

You are trying to bind your array first element rows.0.1 which is a constant value, 31 to the value of the paper-slider.
What is happening that the arrayItem get notifies when its value change i.e !== 31.
What you should do is to bind the max value like this.
Plunkr
<base href="http://polygit.org/polymer+:master/components/">
<!--<base href="http://polygit.org/components/">-->
<script src="webcomponentsjs/webcomponents-lite.min.js"></script>
<link href="polymer/polymer.html" rel="import">
<link href="paper-input/paper-input.html" rel="import">
<link href="paper-slider/paper-slider.html" rel="import">
<link href="google-chart/google-chart.html" rel="import">
<dom-module id="dynamic-chart">
<template>
init value, 31, can't be binded back to arrayItem once the slider in changed:
<br>
<br> Binded values:
<br><i>the arrayItem get notifies when its value change i.e !== 31</i>
<br> arrayItem: {{arrayItem(rows.*, 0, 1)}}
<br> arrayBase: {{arrayBase(rows.*)}}
<h1>paper-slider 1</h1>
<div>Rows First Element: <span>{{rows.0.1}}</span> ==> Constant value </div>
<div>paper-slider Value: <span id="ps1Value"></span></div>
<paper-slider min="1" max="31" pin editable value="{{rows.0.1}}" id="ps1">
</paper-slider>
<!-- You need to bind the paper-slider max to the selected rows item-->
<!-- Changing the max value to {{rows.1.1}} rows second element -->
<h1>paper-slider 2</h1>
<div>Rows Second Element: <span>{{rows.1.1}}</span> ==> Constant value </div>
<div>paper-slider Value: <span id="ps2Value"></span></div>
<paper-slider min="1" max="{{rows.1.1}}" pin editable value="{{_value2}}" id="ps2">
</paper-slider>
</template>
<script>
Polymer({
is: 'dynamic-chart',
properties: {
rows: {
type: Array,
notify: true,
},
_value2: {
type: Number,
value:0,
observer: '_value2Changed'
}
},
// you can also use an obsersver instead of the addEventListener
_value2Changed: function(val) {
console.log("this is paper-slider #2 value "+ val);
},
ready: function() {
//events for paper slider #1
document.querySelector('#ps1').addEventListener('value-change', function(e) {
document.querySelector('#ps1Value').textContent = e.target.value;
});
//events for paper slider #1
document.querySelector('#ps2').addEventListener('value-change', function(e) {
document.querySelector('#ps2Value').textContent = e.target.value;
});
},
//domReady:
attached: function() {
this.async(function() {
//init value, 31, can't be seated back once the slider in changed:
this.rows = [
["Jan", 31],
["Feb", 28],
["Mar", 31]
];
console.log('domReady');
//console.log(this.rows);
});
},
// first argument is the change record for the array change,
// change.base is the array specified in the binding
arrayItem: function(change, index, path) {
console.log('arrayItem');
//console.log(change);
//console.log(index);
//console.log(path);
//console.log(this.get(path, change.base[index]));
// this.get(path, root) returns a value for a path
// relative to a root object.
return this.get(path, change.base[index]);
},
arrayBase: function(change) {
console.log('arrayBase');
return change.base;
},
});
</script>
</dom-module>
<dynamic-chart>
</dynamic-chart>
It will be better to have your rows as object instead of Array of objects,this way:
rows:{
type: Array,
notify: true,
value:function(){
return [
{"label":"Jan", max:31},
{"label":"Feb", max:28},
{"label":"Mar", max:31}
];
}
},

Related

In Polymer 1 how can I wrap a distributed template with another element?

I have a custom element (let's say my-view) which receives as effective children a template with some annotations for the data binding.
How can I wrap the distributed template with another custom element, let's say paper-item?
This is my working code.
<my-view>
<template>[[ item.name ]]</template>
</my-view>
Inside my-view I have
<template id="Repeater" is="dom-repeat">
</template>
and
_templatize() {
const repeater = this.$.Repeater
const template = this.queryEffectiveChildren('template')
repeater.templatize(template)
}
What I want to achieve is wrapping the template effective children with another custom element (let's say paper-item).
Something like
_templatize() {
const repeater = this.$.Repeater
const template = this.queryEffectiveChildren('template')
const item = this.create('paper-item')
item.appendChild(template.content)
repeater.templatize(item)
}
which of course doesn't work.
Perhaps I understood you wrong, but you don't create a page structure like the example you gave. Use the HTML elements first, and spice it up with javascript, if needed.
<dom-module id="my-view">
<template>
<template is="dom-repeat" items="[[anArrayWithStrings]]" as="someValue">
<paper-item>[[someValue]]</paper-item>
</template>
<template is="dom-repeat" items="[[anArrayWithObjects]]" as="employee">
<paper-item two-line>
<div>[[employee.name]]</div>
<div>[[employee.title]]</div>
</paper-item>
</template>
</template>
<script>
Polymer({
is: 'my-view',
properties: {
anArrayWithStrings: {
type: Array,
value: function() { return ['firstOne', 'secondOne', 'thirdOne']; }
},
anArrayWithObjects: {
type: Array,
value: function() { return [
{'name': 'Sarah', 'title': 'accountant'},
{'name': 'Ingrid', 'title': 'engineer'} ]; }
},
},
ready: function() {
//enter code here
},
</script>
</dom-module>
I just wrote this on freehand, without testing, so there could be some faulty code in there, but this is an example of how it could look like.

firebase-collection : input value only updates first keystroke

I have a master-detail scenario. I'm using paper-datatable by David Mulder for my user-list. Data is populated through firebase collection
When tapping a row, a paper-dialog pops up with the details of the selected user.
When trying to edit a field, updating at firebase stops after one keystroke.
What am I missing?
<dom-module id="user-list">
<template>
<style>
:host {
#apply(--layout-vertical);
}
#editDialog {
min-width: 500px;
}
</style>
<firebase-collection location="https://<FIREBASE_APP>.firebaseio.com/users" data="{{users}}"></firebase-collection>
<paper-dialog id="editDialog" entry-animation="scale-up-animation" exit-animation="fade-out-animation" with-backdrop>
<div>
<paper-input value="{{selectedUser.name}}" label="Name" class="flex"></paper-input>
<paper-input value="{{selectedUser.username}}" label="Username" class="flex"></paper-input>
</div>
<div class="buttons">
<paper-button dialog-confirm autofocus>Ok</paper-button>
</div>
</paper-dialog>
<paper-datatable id="datatable" selected-item="{{selectedUser}}" selectable on-row-tap="_onDetail" data="{{users}}">
<div no-results>
Loading or no more items...
</div>
<paper-datatable-column header="Name" property="name" type="String" sortable style="min-width: 160px"></paper-datatable-column>
<paper-datatable-column header="Username" property="username" type="String" sortable style="min-width: 40px"></paper-datatable-column>
</paper-datatable>
</template>
<script>
Polymer({
is: 'user-list',
behaviors: [
Polymer.NeonAnimatableBehavior
],
properties: {
type: String,
selectedUser: {
type: Object,
notify: true
},
users: {
type: Array,
notify: true
},
animationConfig: {
value: function() {
return {
'entry': {
name: 'fade-in-animation',
node: this
},
'exit': {
name: 'fade-out-animation',
node: this
}
}
}
}
},
_onDetail: function() {
var dialog = document.getElementById('editDialog');
if (dialog) {
dialog.open();
}
}
})
</script>
</dom-module>
It seems firebase-collection isn't currently meant to be used in this way, it's more of a view into a Firebase location with data that's in an array-like structure. Although with the exception that you can add/delete new items but not update existing ones. See https://elements.polymer-project.org/elements/firebase-element?active=firebase-collection.
That said, each item in the collection has a __firebaseKey__ property that you could use to directly update that item in firebase.

Define object & pass it to a partial

What I want to do:
{{>myPartial foo={bar:1} }}
I want to define an object while passing it to a partial. Is that possible?
I know it's possible to pass an existing object like
{{>myPartial foo=foo}}
But I want to define my object within my markup.
Why? Well basically because it's just to define layout. I want to avoid to determine layout decisions on the backend.
My partial is a table layout, and I want to hide specific columns.
But instead of using multiple properties like
{{>myPartial hideFoo=true hideBar=true}}
I want to use a single object hide
{{>myPartial hide={foo:true,bar:true} }}
You can pass a new context to a partial:
{{> myPartial context }}
Example:
var data = {
title: "Foo Bar",
foo: ["foo1", "foo2"],
bar: ["bar1", "bar2"],
hide: {
foo: true,
bar: false
}
};
var content = "{{title}} {{> myPartial hide }}";
var partialContent = "<div class=\"{{#if foo}}hideFoo{{/if}} {{#if bar}}hideBar{{/if}}\">Hide</div>";
var template = Handlebars.compile(content);
Handlebars.registerPartial("foo", partialContent);
template(data);
Output:
<div class="hideFoo hideBar">Hide</div>
Another way is to pass a JSON string, instead of an object, using a helper in the way:
//helper
Handlebars.registerHelper("parseJSON", function(string, options) {
return options.fn(JSON.parse(string));
});
//template
{{#parseJSON '{"foo": true,"bar": true}'}}
{{> myPartial}}
{{/parseJSON}}
Demo:
//Compile main template
var template = Handlebars.compile($("#template").html());
//Register partial
Handlebars.registerPartial("myPartial", $("#myPartial").html());
//Register parseJSON helper
Handlebars.registerHelper("parseJSON", function(string, options) {
return options.fn(JSON.parse(string));
});
//Your data
var data = {
title: "Foo Bar"
};
document.body.innerHTML = template(data);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/4.0.5/handlebars.min.js"></script>
<!-- template.html -->
<script id="template" type="text/x-handlebars-template">
<h1>{{title}}</h1>
<h3>First Partial:</h3>
{{#parseJSON '{"foo": true,"bar": false}'}}
{{> myPartial}}
{{/parseJSON}}
<h3>Second Partial:</h3>
{{#parseJSON '{"foo": false,"bar": false}'}}
{{> myPartial}}
{{/parseJSON}}
</script>
<script id="myPartial" type="text/x-handlebars-template">
<div>hide.foo: {{foo}}</div>
<div>hide.bar: {{bar}}</div>
</script>

Attribute bind-value is not set in iron-input when initiated

Here's a pattern I try to do with Polymer:
<link rel="import" href="/bower_components/polymer/polymer.html" />
<link rel="import" href="/bower_components/iron-ajax/iron-ajax.html" />
<link rel="import" href="/bower_components/iron-input/iron-input.html" />
<dom-module id="my-entity">
<template>
<iron-ajax auto url="{{urlGet}}" handle-as="json" last-response="{{entity}}"></iron-ajax>
<iron-ajax id="updateEntityCall" url="{{urlUpdate}}" handle-as="json" on-response="handleUpdateResponse"></iron-ajax>
<div>
<div class="input-wrapper">
<label>Name : </label>
<input is="iron-input" bind-value="{{entityName}}" value="{{entity.name}}" />
</div>
<div>
<button on-click="saveEntity">Save</button>
</div>
</div>
</template>
<script>
Polymer(
{
is: "my-entity",
properties: {
entityid: {
notify: true
},
entityName: {
notify: true
}
urlGet: {
computed: 'getUrlGet(entityid)'
},
urlTemplate: {
computed: 'getUrlUpdate(entityid, entityName)'
},
},
getUrlGet: function (entityId) {
return ['/path/to/get', entityId].join('/');
},
getUrlUpdate: function (entityId, entityName) {
return ['/path/to/update', entityId, entityName].join('/');
},
saveEntity: function (e, detail, sender) {
this.$.updateEntityCall.generateRequest();
}
}
);
</script>
</dom-module>
I first create a iron-ajax that will get some data from an entity. This will be printed in a iron-input element so a person will be able to update this entity.
The iron-ajax is bound to a {{entity}} variable that is used by the value in the iron-input element (entity.name). I have created a property called entityName that will be bound at the bind-value attribute of this same iron-input.
Once someone has made the change, the goal is that he/she clicks on the button that calls the function saveEntity, that basically call the other iron-ajax element.
There is no problem with this code when someone actually change the value of the iron-input. The problem appear when the user don't change the value and click the button. It sends nothing, like if entityName is not set.
This is a simple example, in my use-case I have 3 fields and 2 of them can have "empty strings" as valid value.

Data-binding between nested polymer elements

Suppose I have two distinct polymer-elements
One should be embedded inside the other using the content placeholder.
Is it possible to do data-binding between these two nested polymer-elements ?
I tried, but I can't get it to work: http://jsbin.com/IVodePuS/11/
According to http://www.polymer-project.org/articles/communication.html#binding data-binding between polymer-elements should work (in those examples they were done inside the template tag without using a content placeholder).
Update:
Scott Miles clarified that data-binding only works on the template level.
However in my case I don't know the exact template beforehand but I want to allow the user of my parent-element to specify which child-element it should contain (provided that there are different child-elements.
I think this question is related to this one: Using template defined in light dom inside a Polymer element
I updated the example below to highlight his:
<polymer-element name="parent-element" >
<template >
<div>Parent data: {{data1}} </div>
<content />
</template>
<script>
Polymer('parent-element', {
data1 : '',
ready: function() {
this.data='parent content';
}
});
</script>
</polymer-element>
<polymer-element name="child-element" attributes="data2">
<template>
<div>Parent data: {{data2}} </div>
</template>
<script>
Polymer('child-element', {
data2 : '',
ready: function() {
}
});
</script>
</polymer-element>
<polymer-element name="child2-element" attributes="data2">
<template>
<div>Parent data: {{data2}} </div>
</template>
<script>
Polymer('child2-element', {
data2 : '',
ready: function() {
}
});
</script>
</polymer-element>
The user can choose which child-element to embed:
<parent-element data1 = "test">
<child-element data2="{{data1}}"/>
</parent-element>
<parent-element data1 ="test" >
<child2-element data2="{{data1}}"/>
</parent-element>
Workaround:
The only workaround I found was to add change watcher and use getDistributedNodes() to get the child element and manually set data2 to data:
<polymer-element name="parent-element" >
<template >
<div>Parent data: {{data}} </div>
<content id="content"/>
</template>
<script>
Polymer('parent-element', {
data : '',
ready: function() {
this.data='parent content';
},
dataChanged : function() {
contents = this.$.content.getDistributedNodes();
if (contents.length > 0) {
contents[0].data2 = this.data;
}
},
});
</script>
</polymer-element>
Polymer data-binding works by attaching a model to a whole subtree.
You wrote:
<parent-element>
<child-element data2="{{data}}"/>
</parent-element>
this implies a rule that the parentNode provides the binding model. But now imagine you wanted to write:
<parent-element>
<div>
<child-element data2="{{data}}"></child-element>
</div>
</parent-element>
Now you have a problem.
Instead, in Polymer examples, you will notice that the {{}} are (almost always) inside of a template. For example, if I define:
<polymer-element name="host-element" attributes="data" noscript>
<template>
<parent-element data1="{{data}}">
<child-element data2="{{data}}"></child-element>
</parent-element>
</template>
</polymer-element>
Now, I have a model context (host-element) that I can use to bind things together in the entire subtree described by the template.
Note that I don't need attributes="data" for this to work. I added that so host-element exposes data and I can do this:
<host-element data="test"></host-element>
http://jsbin.com/IVodePuS/15/edit
Works like a charm. Parent element is publishing property data, that can by 2 way data bound. This way the child element gets the same data.
<polymer-element name="custom-element">
<template>
<parent data="{{data}}">
<child data="{{data}}"></child>
</parent>
</template>
<script>
Polymer({
ready: function() {
},
data: {}
});
</script>
I think in Polymer >= 1.0 this should be done using "Auto-binding template".

Resources