Polymer : this.$[elementId] is undefined - polymer

Here is a simple polymer element which just access an inner element using its id and this.$.[elementId], and then log it. Running, this simple code, you will be able to see that the returned element undefined. Why?
<dom-module id="custom-element">
<template>
<template is="dom-if" if="[[condition]]">
<div id="main"></div>
</template>
</template>
<script>
class CustomElement extends Polymer.Element {
static get is() { return "custom-element"; }
static get properties() {
return {
condition : Boolean
};
}
ready(){
super.ready();
console.log(this.$.main); // logs "undefined"
}
}
customElements.define(CustomElement.is, CustomElement);
</script>
</dom-module>

The Polymer this.$ only references elements from local DOM which are not added dynamicly. Thus, elements placed inside dom-if or dom-repeat templates are not added to the this.$ object.
If you wish to select a dynamic element you need to go to the dom and use something like this.shadowRoot.querySelector('#main')
Here's a solution to get rid of this problem. Add the following code in your ready():
if (this.$)
this.$ = new Proxy(this.$, {
get: ($, id) => $[id] || this.shadowRoot.getElementById(id),
set: ($, id, element) => {
$[id] = element;
return true;
}
});

Related

how to use data from one page in polymer 2.0 in another

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

Polymer 2.0 iron-form response trigger dom-if

How do I use the response data from iron-form to trigger the dom-if template? I want to show the template if there was data was returned. This code is not working. What am I doing wrong here?
<link rel="import" href="../components/polymer/polymer-element.html">
<link rel="import" href="../components/polymer/lib/elements/dom-if.html">
<link rel="import" href="../components/iron-form/iron-form.html">
<dom-module id="a-tag">
<template>
<iron-form>
<form action="/" method="get">
<input name="test">
<button></button>
</form>
</iron-form>
<template class="iftemplate" is="dom-if" if="[[data]]">
<p>HELLO</p>
</template>
</template>
<script>
class ATag extends Polymer.Element {
static get is() {return 'a-tag'}
ready() {
super.ready()
this.shadowRoot.querySelector('iron-form').addEventListener('iron-form-response', (event) => {
this.shadowRoot.querySelector('.iftemplate').data = event.detail.response
})
}
}
customElements.define(ATag.is, ATag)
</script>
</dom-module>
You are doing many wrong things here,
first I would advice to add an id to the form element iron-form so you can point easily in your Polymer element's code.
<iron-form id="my-form">
...
</iron-form>
and add the event listener like that :
this.$['my-form'].addEventListener('iron-form-response', ...);
Also you are trying to add a property data to the template dom-if I don't understand why. [[data]] references a property in your element's scope. You have to define this property in the correct section.
static get properties () {
return {
data: {
type: String,
value: ''
}
};
}
and in your event listener callback :
ready() {
super.ready()
this.$['my-form'].addEventListener('iron-form-response', (event) => {
// this.data = event.detail.response;
this.data = 'some data'; // for testing
});
}

Polymer data-binding not updated on array mutation

I noticed that template/data-binding in Polymer doesn't seem to reflect when a array property is mutated (i.e. push()). Sample code below:
<body>
<dom-module id="my-element">
<template>
<pre>
[my-element]
myArray: [[jsonStringify(myArray)]]
</pre>
</template>
</dom-module>
<my-element id="elm"></my-element>
<button onclick="pushArray()">pushArray</button>
<button onclick="setArray()">setArray</button>
<script>
(function registerElements() {
Polymer({
is: 'my-element',
properties: {
myArray: {
type: Array,
value: function () {
return [];
}
}
},
pushArray: function(value) {
this.push('myArray', {id: value});
},
setArray: function(value) {
this.set('myArray', [{id: value}]);
},
jsonStringify: function(obj) {
return JSON.stringify(obj);
}
});
})();
function pushArray () {
var elm = document.querySelector('#elm');
elm.pushArray('Push');
}
function setArray () {
var elm = document.querySelector('#elm');
elm.setArray('Set');
}
</script>
</body>
Whenever I click the pushArray button, an item "Push" should be added in myArray, but it wasn't reflected in the template [[jsonStringify(myArray)]]. is this an expected behavior? Anyway to work around this?
The Array change observer is a bit tricky. In your code, by using myArray, you implicitly observe (only) the reference for the (whole) array, which only changes when you run setArray.
In order to overcome this, you must also use a deep observer, namely myArray.*. The correct code for your dom-module is therefore the following:
<dom-module id="my-element">
<template>
<pre>
[my-element]
myArray: [[jsonStringify(myArray, myArray.*)]]
</pre>
</template>
</dom-module>
Live demo: http://jsbin.com/yulivuwufu/1/edit?html,css,output
No other code changes are necessary.

Polymer data from router to dom-element

I started with polymer 1.0 and created a new route endpoint as mentioned below.
page('/users/:name', scrollToTop, function(data) {
app.route = 'user-info';
app.params = data.params;
});
so if I go to users/bob it will go to route "user-info"
in my index.html the route is defined as below
<section data-route="user-info">
<web-homepage></web-homepage>
</section>
where web-homepage is a custom element.
the custom element is as defined below
<dom-module id="web-homepage">
<template>
This is homepage
</template>
<script>
(function() {
'use strict';
Polymer({
is: 'web-homepage',
ready:function() {
//want to get the route parameters (eg: bob) here
}
});
})();
</script>
</dom-module>
Is there a way to get the value route parameter ":name" i.e bob inside the ready function?
ready function is executed when the element is imported and created. If you access route params there, it will most likely be null/empty/undefined.
However you can pass route params to the element as below.
The params in app.params is the property of app. You can simply pass it to its child elements.
index.html should look like this
<section data-route="user-info">
<web-homepage route-params=[[params]]></web-homepage>
</section>
Define the route-params in web-homepage
<dom-module id="web-homepage">
<template>
This is homepage
</template>
<script>
(function() {
'use strict';
Polymer({
is: 'web-homepage',
properties: {
routeParams: {
type: Object,
observer: '_routeParamsChanged'
}
},
ready:function() {
//ready function is run during element creation. route-params will most likely be empty/null/undefined
//Just random
//want to get the route parameters (eg: bob) here
},
_routeParamsChanged: function(newValue) {
console.log('route params: ', newValue);
}
});
})();
</script>
</dom-module>

How to pass required attributes to element constructor

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