How to bind a dynamic path in Polymer?
For instance:
Lets say our component has 2 properties:
list: an array of objects.
map : a javascript object which map sub-objects.
Each item in the list has a property key which is the key to get the value from the map property.
I would like to "dynamic" bind a path like map[item.key]. The only way to do something like this is to make a function, but it will not be triggered on changes of properties and sub-properties of map. =/
In the following snippet, you can see, by clicking on the button, it will dynamicly place an object in the map.key2 property, using the Polymer.Element.set method. But this doesn't trigger any changes because Polymer doesn't bind a path. It only execute the display function once.
So this Stackoverflow answer doesn't help (even though it's the same question).
<script src="https://polygit.org/components/webcomponentsjs/webcomponents-loader.js"></script>
<link rel="import" href="employee-list.html">
<link rel="import" href="https://polygit.org/components/polymer/polymer-element.html">
<link rel="import" href="https://polygit.org/components/paper-button/paper-button.html">
<link rel="import" href="https://polygit.org/components/polymer/lib/elements/dom-repeat.html">
<dom-module id="my-element">
<template>
<ul>
<template is="dom-repeat" items="[[list]]">
<!--Bind something like this-->
<li> [[ _getAt(item.key) ]] </li>
</template>
</ul>
<!--This button will add the 2nd object-->
<paper-button on-tap="_onButtonClicked" raised>Add key 2</paper-button>
</template>
<script>
class MyElement extends Polymer.Element {
static get is(){
return "my-element";
}
static get properties(){
return {
list : {
type : Array,
value : function () {
return [
{
key : "key1",
// ...
},
{
key : "key2",
// ...
},
// ...
]
}
},
map : {
type : Object,
value : function () {
return {
key1 : {
message : "Hello",
// ...
},
// ...
}
}
}
};
}
_onButtonClicked(){
// Add the 2nd object
this.set("map.key2", {
message : "World",
});
console.log("key 2 added");
}
_getAt(key){
if (this.map[key])
return this.map[key].message;
}
}
customElements.define(MyElement.is, MyElement);
</script>
</dom-module>
<my-element></my-element>
The Polymer documentation says that it's possible to build a path in a array. But I didn't find a way to bind an array of string as a path.
"The only way to do something like this is to make a function, but it
will not be triggered on changes of properties and sub-properties of
map"
You can make this work by passing the objects/properties that are changing.
Basic example
this will NOT update:
<template is="dom-repeat" items="[[_getItems()]]"></template>
this WILL update:
<template is="dom-repeat" items="[[_getItems(list)]]"></template>
Now the dom-repeat will fire again once the property 'list' changes.
Let's say you have 2 properties, and you want to re-run dom-repeat when one of them changes:
<template is="dom-repeat" items="[[_getItems(list, somethingElse)]]"></template>
You might also want to take a look at https://www.polymer-project.org/1.0/docs/devguide/model-data#override-dirty-check
EDIT:
Where are you updating the property LIST? dom-repeat wont run again until that property is changed
DOUBLE EDIT:
try this (polygit is currently having server issues):
<script src="https://polygit.org/components/webcomponentsjs/webcomponents-loader.js"></script>
<link rel="import" href="employee-list.html">
<link rel="import" href="https://polygit.org/components/polymer/polymer-element.html">
<link rel="import" href="https://polygit.org/components/paper-button/paper-button.html">
<link rel="import" href="https://polygit.org/components/polymer/lib/elements/dom-repeat.html">
<dom-module id="my-element">
<template>
<ul>
<template is="dom-repeat" items="[[list]]">
<!--Bind something like this-->
<li> [[ _getAt(item.key, map, list) ]] </li>
</template>
</ul>
<!--This button will add the 2nd object-->
<paper-button on-tap="_onButtonClicked" raised>Add key 2</paper-button>
</template>
<script>
class MyElement extends Polymer.Element {
static get is() {
return "my-element";
}
static get properties() {
return {
list: {
type: Array,
value: function() {
return [{
key: "key1",
// ...
},
{
key: "key2",
// ...
},
// ...
]
}
},
map: {
type: Object,
value: function() {
return {
key1: {
message: "Hello",
// ...
},
// ...
}
}
}
};
}
_onButtonClicked() {
var foo = this.list;
this.set("map.key2", {
message: "World",
});
this.set("list", {});
this.set("list", foo);
console.log("key 2 added");
}
_getAt(key) {
if (this.map[key])
return this.map[key].message;
}
}
customElements.define(MyElement.is, MyElement);
</script>
</dom-module>
<my-element></my-element>
Related
I have a html file which makes a rest call and gets data in an array. How can I use this another file. I want to be able to access and manipulate the data and print each element in the array on click of next button. my-assessment.html contains the data loaded and my-quiz.html should contain the next and previous button to loop through the data.
my-assessment.html
<link rel="import" href="../bower_components/polymer/polymer-element.html">
<link rel="import" href="../bower_components/polymer-simple-slider/simple-slider.html">
<link rel="import" href="../bower_components/promise-polyfill/promise-polyfill-lite.html">
<link rel="import" href="../bower_components/iron-ajax/iron-ajax.html">
<dom-module id="my-assessment">
<template>
<button on-click="getAssessment">Load Assessment</button>
<!--Check the url is correct ! And last responce property should be {{}} instead [[]] (as up way data binding) -->
<iron-ajax
id="requestRepos"
url="http://192.168.178.31:8080/demo/assessment"
handle-as="json"
last-response="{{repos}}"
</iron-ajax>
</template>
<script>
class Assessment extends Polymer.Element {
static get is() { return 'my-assessment'; }
static get properties() {
return {
repos: {
type : Array,
observer : 'isQuestionsLoaded'
}
}
}
// set this element's employees property
constructor() {
super();
}
isQuestionsLoaded(q) {
if (q) {
console.log('loaded questions', q); // questions are loaded.
}
this.questions = q;
}
getAssessment() {
this.$.requestRepos.generateRequest();
}
}
window.customElements.define(Assessment.is, Assessment);
</script>
</dom-module>
my-quiz.html
<link rel="import" href="../bower_components/polymer/polymer-element.html">
<link rel="import" href="../bower_components/polymer-simple-slider/simple-slider.html">
<link rel="import" href="../bower_components/promise-polyfill/promise-polyfill-lite.html">
<link rel="import" href="../bower_components/iron-ajax/iron-ajax.html">
<link rel="import" href="my-assessment.html">
<dom-module id="my-quiz">
<template>
<my-assessment>
</my-assessment>
</template>
<script>
class Quiz extends Polymer.Element {
static get is() { return 'my-quiz'; }
static get properties() {
return {
index: {
type: Number,
value: 0
}
}
}
// set this element's employees property
constructor() {
super();
}
}
window.customElements.define(Quiz.is, Quiz);
</script>
</dom-module>
In your case that is shown above the two elements have child parent relation to each other. The element my-quiz contains the element my-assessment. Passing the data up from it's child element can be done with two way data binding. Therefor you will have to add the notify:true to your repos property and use the two way data binding syntax as shown in my example.
Child elements properties:
static get properties() {
return {
repos: {
type : Array,
notify: true,
observer : 'isQuestionsLoaded'
}
}
}
Parent Element:
<dom-module id="my-quiz">
<template>
<my-assessment repo-data="{{repos}}"></my-assessment>
</template>
<script>
class Quiz extends Polymer.Element {
static get is() { return 'my-quiz'; }
static get properties() {
return {
repoData: {
type: Array,
observer: '_dataChanged'
}
}
}
constructor() {
super();
}
_dataChanged(data) {
console.log('Data changed:', data)
}
}
window.customElements.define(Quiz.is, Quiz);
</script>
</dom-module>
As you can see I have used the two way data binding syntax {{}} to get the data that is send up from your child element. Changes will be observed and logged in the console.
To read more about this you can take a look in the Polymer documentation
how can I use dom-repeat to create different elements for iron-pages? something like this:
<template is="dom-repeat" items="{{pages}}">
<[[item.name]]></[[item.name]]>
</template>
...
pages: {
type: Array,
value() {
return [
{
"name": "element-a",
},
{
"name": "element-b",
},
{
"name": "element-c",
},
},
I'm using polymer 2.0.
since my comment had some interest, I put it as an answer.
The code examples will be in polymer 1.9 for now, I'll update my answer when I'll do the switch to 2.0 but the idea should be the same anyway
First you need a wrapper element, wich will be capable of creating another element dynamically from a property, and adding it to itself.
In my example the name of the element to create will be a property named type of a JSON object Data wich came from a database in XHR.
With a dynamically created element the binding won't work, so you have to do it by hand. That's what the _updateStatefunction is for, here it only update on one property but the idea is the same is there is more.
wrapper :
<link rel="import" href="../../bower_components/polymer/polymer.html">
<link rel="import" href="../../styles/shared-styles.html">
<dom-module id="element-wrapper">
<template></template>
<script>
Polymer({
is: 'element-wrapper',
properties: {
elementData: {
type: Object
},
saveFbPath: {
type: String
},
element: {
type: Object
},
formSubmitPressed: {
type: Boolean,
value: false
}
},
observers: [
'_updateState(elementData.*)'
],
attached: function () {
console.log("creating element : ", this.elementData);
this.async(function () {
this.element = this.create(this.elementData.type, {
"data": this.elementData
});
this.appendChild(this.element);
}.bind(this));
},
_updateState: function (elementData) {
if (typeof this.element !== "undefined") {
this.element.data = elementData.value;
console.log('Change captured', elementData);
}
}
});
</script>
</dom-module>
The this.element = this.create(this.elementData.type, {"data":this.elementData}); line is the one creating the element, first argument is the dom-modulename, and the second a JSON object wich will be binded to the properties of the element.
this.appendChild(this.element);will then add it to the dom
All this is in a this.async call for a smoother display
You then need a dom-repeat which will call this element, and give it the datas it need to create the dynamic ones.
Here is an example of an element-list but you don't necessary need a specific element for that, this logic can be in a bigger one.
<link rel="import" href="../../bower_components/polymer/polymer.html">
<link rel="import" href="../../styles/shared-styles.html">
<link rel="import" href="element-wrapper">
<dom-module id="element-list">
<template>
<style is="custom-style" include="shared-styles"></style>
<template is="dom-repeat" items="[[ datas ]]" initial-count="10" as="data">
<element-wrapper element-data="[[data]]"></element-wrapper>
</template>
</template>
<script>
Polymer({
is: 'element-list',
properties: {
datas: {
type: Array,
value: function () {
return [];
}
}
}
});
</script>
</dom-module>
This should do the trick :)
I have a question to my custom polymer element. First, I made an element with a simple paper-input. My problem is, that I don't know, how to use this element as an "independent" element. My example is here in this jsfiddle. Type in the first input "asd" and hit Enter and then in the second input "asd" and hit Enter. You can see, that both elements are sharing the properties (console log "-1" not found in array and second log will be "1")
<!doctype html>
<html>
<head>
<title>2.0 preview elements</title>
<base href="http://polygit.org/polymer+v2.0.0-rc.4/webcomponentsjs+webcomponents+v1.0.0-rc.6/shadycss+webcomponents+1.0.0-rc.2/paper*+polymerelements+:2.0-preview/iron*+polymerelements+:2.0-preview/app*+polymerelements+:2.0-preview/neon*+polymerelements+:2.0-preview/components/">
<script src="webcomponentsjs/webcomponents-lite.js"></script>
<link rel="import" href="polymer/polymer.html">
<link rel="import" href="paper-input/paper-input.html">
</head>
<body>
<input-test></input-test>
<input-test></input-test>
<dom-module id="input-test">
<template>
<paper-input value="{{aValue}}" on-keydown="_keydown"></paper-input>
</template>
<script>
window.addEventListener('WebComponentsReady', function() {
class InputTest extends Polymer.Element {
static get is() {
return 'input-test';
}
static get properties() {
return {
aValue: {
type: String,
value: ''
},
_inputStore: {
type: Array,
value: []
}
};
}
_keydown(event) {
const keyCode_enter = '13';
if (event.keyCode == keyCode_enter) {
console.log(this._inputStore.indexOf(this.aValue))
this.push('_inputStore', this.aValue);
}
}
}
customElements.define(InputTest.is, InputTest);
})
</script>
</dom-module>
</body>
</html>
What can I do, to have independent properties?
Thanks!
I found the answer.
The problem is the default value declaration of the array.
_inputStore: {
type: Array,
value: function () {
return [];
}
}
This code solves the problem.
//index.html
<html>
<head>
<link rel="import" href="test-table.html">
</head>
<body>
<template is="dom-bind" id="index">
<test-table data="{{data}}" ></test-table>
</template>
</body>
</html>
Polymer({
is: "test-table",
properties : {
data : {type : Array},
}
/*I dont know where should I put this stuff
"queryForIds:"
"getByIds :"
"set:"
"length:0"
*/
});
<dom-module id="test-table">
<template>
<paper-datatable-card id="datatableCard" header="Users" page-size="10" data-source="{{data}}" id-property="_id" selected-ids="{{selectedIds}}">
<paper-datatable id="datatable" data='{{data}}' selectable multi-selection selected-items="{{selectedItems}}">
<paper-datatable-column header="Id" property="_id" sortable>
<template>
<span>{{value}}</span>
</template>
</paper-datatable-column>
</paper-datatable>
</paper-datatable-card>
</template>
</dom-module>
as part of single page application I am using “paper-datatable-card” in my own custom-tag. I able to display the records but I’m not getting where I have to put the code for pagination. And I don’t want to put all records into dataSource at a time.
Any help is appreciated,
Thank you,
Venkat.
From within your Polymer component, you can set data in the ready method:
ready: function() {
this.data = {
queryForIds: function(sort, page, pageSize){
// implement me
},
getByIds: function(ids){
// implement me
},
set: function(item, property, value){
// implement me
},
length:0
};
}
Your comment question:
So I am unable to put the code from lines 117 to 133 in my custom elements as it doesnt support dom-bind
Answer (in Polymer 2.0) you can do it in your component constructor all the methods and all the data variables:
class YourComponent extends Polymer.Element {
contructor() {
super();
//in case if you use paper-datatable-card
this.data = {
get: function (sort, page, pageSize) {
return new Promise((resolve, reject) => {
const exampleData = [
{ 'name': 'someName1' },
{ 'name': 'someName2' }
];
resolve(exampleData);
});
},
set: function(item, property, value){...},
length: 1
};
//in case if you use paper-datatable only then without get or set properties
this.data = [ { name: 'someName' } ];
}
}
i have a polymer custom element like this
<script src="http://www.polymer-project.org/platform.js"></script>
<script src="http://www.polymer-project.org/polymer.js"></script>
<polymer-element name="my-foo" constructor="MyFoo" attributes="controller" noscript>
<template>
hello from {{options.name}}
</template>
<script>
Polymer({
get options() {
return this.controller.options;
}
});
</script>
</polymer>
<div id="container"></div>
<script>
document.addEventListener('polymer-ready',function() {
var foo = new MyFoo();
foo.controller = {
options : {
name : "here"
}
};
document.body.appendChild(foo);
});
</script>
attribute "controller" is expected to be a object having a property "options" of type object.
I can create a Instance of the custom element using
var foo = new Foo();
but i am unable to set the attribute "controller" which results in an error:
Uncaught TypeError: Cannot read property 'options' of undefined.
Sure - i could modify the options getter in the polymer component to be fail-safe like this :
...
get options() {
return this.controller ? this.controller.options : null;
}
...
But also in this case the custom element doesnt recognize the applied data.
Final question : How do I pass required attributes to an custom element constructor ?
One solution is to use a computed property:
<script src="http://www.polymer-project.org/components/platform/platform.js"></script>
<link rel='import' href='http://www.polymer-project.org/components/polymer/polymer.html'>
<polymer-element name="my-foo" constructor="MyFoo" attributes="controller" noscript>
<template>
hello from {{options.name}}
</template>
<script>
Polymer({
computed: {
options: 'controller.options'
}
});
</script>
</polymer-element>
<script>
document.addEventListener('polymer-ready',function() {
var foo = new MyFoo();
document.body.appendChild(foo);
foo.controller = {
options : {
name : "here"
}
}
});
</script>
You can even make the computed property just a call to a method. The key thing is that by doing it this way, Polymer can tell when to check if options has changed, because it knows the inputs necessary to compute options.
Polymer({
computed: {
options: 'getOptions(controller)'
},
getOptions: function(controller) {
return controller ? controller.options : null;
}
});