Polymer: changing the array and template using JQuery - polymer

I've established an array, like this:
<template>
<ul id="iconContainer" style="list-style: none; padding: 0; margin: 0;">
<template repeat="{{icon in icons}}">
<li class="flex icon-container"><app-icon label="{{icon.label}}" src="{{icon.src}}"></app-icon></li>
</template>
</ul>
</template>
<script>
Polymer('app-grid', {
ready: function() {
this.icons = [];
for(i = 1; i < 21; i++) {
this.icons.push({label: 'Item ' + i, src: '*'});
},
iconChanged: function() {
this.$.iconContainer.getElementsByTagName('template')[0].iterator_.updateIteratedValue();
}
});
</script>
And I'm getting frustrated on trying to find out how to alter this array from jquery on index.html as shown below to update the template.
<app-grid></app-grid>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script>
$(document).querySelector('app-grid').icons.push({label: 'foo', src: '*'});
</script>
This doesn't seem to work though.
Thoughts?

I'm not sure why you need jquery for this. The document.querySelector('app-grid') works just fine to get the element.
Before poking around at element properties, the element needs to have upgraded. The polymer-ready event is your signal that all elements are upgraded:
document.addEventListener('polymer-ready', function(e) {
document.querySelector('app-grid').icons.push({label: 'foo', src: '*'});
});
Demo: http://jsbin.com/wasafomesuba/1/edit

Related

iron-list and load data with "pages"

I have Google Endpoints service with list-method what returned some data.
I can display this data in iron-list - everything is ok here.
But - since array of data is big - I like to return it from list by some "pages" - for example by 100 elements.
So, question is - how to fire getting new portion of data then iron-list will be scrolled down to the end of already received array? Any samples or suggestion on it?
iron-scroll-threshold is appropriate here. In the following example, _loadMoreData() is called when the iron-list is scrolled to 200px from the bottom, which in your case is where you'd query your Google Endpoints service to fetch more data.
// template
<iron-scroll-threshold id="threshold"
lower-threshold="200"
on-lower-threshold="_loadMoreData">
<iron-list scroll-target="threshold" items="[[items]]">
<template>
<div>[[index]]: [[item]]</div>
</template>
</iron-list>
</iron-scroll-threshold>
// script
Polymer({
...
_loadMoreData: function() {
var data = this.queryGoogleEndpointService();
// append data to `this.items`
}
}
<head>
<base href="https://polygit.org/polymer+1.11.1/iron-scroll-threshold+PolymerElements+:1.x/webcomponents+webcomponents+:v0/components/">
<script src="webcomponentsjs/webcomponents-lite.js"></script>
<link rel="import" href="iron-list/iron-list.html">
<link rel="import" href="iron-scroll-threshold/iron-scroll-threshold.html">
<link rel="import" href="paper-progress/paper-progress.html">
</head>
<body>
<x-foo></x-foo>
<dom-module id="x-foo">
<template>
<style>
iron-list {
height: 400px;
}
</style>
<iron-scroll-threshold id="threshold"
lower-threshold="200"
on-lower-threshold="_loadMoreData"
lower-triggered="{{nearBottom}}">
<iron-list scroll-target="threshold" items="[[items]]">
<template>
<div>[[index]]: [[item]]</div>
</template>
</iron-list>
</iron-scroll-threshold>
<template is="dom-if" if="[[nearBottom]]">
<paper-progress indeterminate></paper-progress>
</template>
</template>
<script>
HTMLImports.whenReady(function() {
Polymer({
is: 'x-foo',
properties: {
items: {
type: Array,
value: function() { return []; }
}
},
_loadMoreData: function() {
console.log('loading 100 more...');
// simulate network delay
this.async(function() {
for (let i = 0; i < 100; i++) {
this.push('items', Math.random());
}
this.$.threshold.clearTriggers();
}, 500);
}
});
});
</script>
</dom-module>
</body>
codepen

Using one iron-ajax element for multiple requests

Theoretically, it should be possible to use one iron-ajax element for multiple requests by setting the auto attribute and then repeatedly setting the url property on the element. iron-ajax has a property called activeRequests, which is a read-only array, so it seems like it has supports for queueing up multiple requests simultaneously. However in practice it does not appear to work.
For example, in the JS Bin below, I retrieve a list of book IDs for books that contain the word polymer, and then use a for loop to repeatedly set the value of url.
<!doctype html>
<html>
<head>
<meta name="description" content="single iron-ajax for multiple requests">
<meta charset="utf-8">
<base href="https://polygit.org/components/">
<script href="webcomponentsjs/webcomponents-lite.min.js"></script>
<link href="polymer/polymer.html" rel="import">
<link href="iron-ajax/iron-ajax.html" rel="import">
</head>
<body>
<dom-module id="my-el">
<template>
<iron-ajax id="ajax"
url="https://www.googleapis.com/books/v1/volumes?q=polymer"
handle-as="json"
on-response="onResponse"
last-response="{{response}}" auto></iron-ajax>
</template>
<script>
Polymer({
is: 'my-el',
properties: {
response: {
type: Object,
notify: true
}
},
onResponse: function(e) {
var ajax = this.$.ajax;
var originalUrl = 'https://www.googleapis.com/books/v1/volumes?q=polymer';
var url = ajax.lastRequest.xhr.responseURL;
if (url.includes(originalUrl)) {
console.log('this is the first request');
for (var i = 0; i < ajax.lastResponse.items.length; i++) {
ajax.url = this.url(ajax.lastResponse.items[i].id);
}
} else {
console.log(ajax.lastResponse.selfLink);
}
},
url: function(id) {
return "https://www.googleapis.com/books/v1/volumes/" + id;
}
});
</script>
</dom-module>
<my-el></my-el>
</body>
</html>
It's indeed possible to use iron-ajax for multiple requests but not with auto enabled, or else you'll hit iron-ajax's debouncer. From the Polymer docs for iron-ajax:
With auto set to true, the element performs a request whenever its url, params or body properties are changed. Automatically generated requests will be debounced in the case that multiple attributes are changed sequentially.
In your question's code:
// template
<iron-ajax auto ...>
// script
onResponse: function(e) {
...
for (var i = 0; i < ajax.lastResponse.items.length; i++) {
ajax.url = this.url(ajax.lastResponse.items[i].id);
}
}
...you're presumably expecting iron-ajax to generate a new request with each URL, but the debouncer collapses the requests into one (taking only the last invocation).
Also worth noting: The response handler's event detail (i.e., e.detail) is the corresponding iron-request, which contains the AJAX response (i.e., e.detail.response). Using the event detail is preferrable because it avoids a race condition in simultaneous requests from iron-ajax, where this.$.ajax.lastResponse or this.$.ajax.lastRequest are overwritten asynchronously.
onResponse: function(e) {
var request = e.detail;
var response = request.response;
}
To reuse iron-ajax with a new URL, disable auto (which disables the debouncer) and manually call generateRequest() after updating the URL. This would allow multiple simultaneous async requests (and activeRequests would populate with more than one request).
// template
<iron-ajax ...> <!-- no 'auto' -->
// script
onResponse: function(e) {
var request = e.detail;
var response = request.response;
...
for (var i = 0; i < response.items.length; i++) {
ajax.url = this.url(response.items[i].id);
ajax.generateRequest();
}
},
ready: function() {
this.$.ajax.generateRequest(); // first request
}
Here's a modified version of your code:
<head>
<base href="https://polygit.org/polymer+1.5.0/components/">
<script src="webcomponentsjs/webcomponents-lite.min.js"></script>
<link rel="import" href="polymer/polymer.html">
<link rel="import" href="iron-ajax/iron-ajax.html">
</head>
<body>
<x-foo></x-foo>
<dom-module id="x-foo">
<template>
<!-- We're reusing this iron-ajax to fetch more data
based on the first response, and we don't want
iron-ajax's debouncer to limit our requests,
so disable 'auto' (i.e., remove the attribute
from <iron-ajax>). We'll call generateRequest()
manually instead.
-->
<iron-ajax id="ajax"
url="https://www.googleapis.com/books/v1/volumes?q=polymer"
handle-as="json"
on-response="onResponse"
on-error="onError">
</iron-ajax>
</template>
<script>
HTMLImports.whenReady(function() {
Polymer({
is: 'x-foo',
onError: function(e) {
console.warn('iron-ajax error:', e.detail.error.message, 'url:', e.detail.request.url);
},
onResponse: function(e) {
var ajax = this.$.ajax;
var originalUrl = 'https://www.googleapis.com/books/v1/volumes?q=polymer';
var url = e.detail.url;
if (url.includes(originalUrl)) {
var books = e.detail.response.items || [];
console.log('this is the first request');
for (var i = 0; i < books.length && i < 3; i++) {
ajax.url = this.url(books[i].id);
console.log('fetching:', ajax.url);
ajax.generateRequest();
}
} else {
var book = e.detail.response;
console.log('received:', e.detail.url, '"' + book.volumeInfo.title + '"');
}
},
url: function(id) {
return "https://www.googleapis.com/books/v1/volumes/" + id;
},
ready: function() {
// generate first request
this.$.ajax.generateRequest();
}
});
});
</script>
</dom-module>
</body>
https://jsbin.com/qaleda/edit?html,console
I don't know what's up with the activeRequests property, but I was able to get it to work by re-structuring my code a little. Basically, just implement a queue, and pop off an item from the queue and set url once the last request has finished.
<!doctype html>
<html>
<head>
<meta name="description" content="http://stackoverflow.com/questions/37817472">
<meta charset="utf-8">
<base href="https://polygit.org/components/">
<script href="webcomponentsjs/webcomponents-lite.min.js"></script>
<link href="polymer/polymer.html" rel="import">
<link href="iron-ajax/iron-ajax.html" rel="import">
</head>
<body>
<dom-module id="my-el">
<template>
<iron-ajax id="ajax"
url="https://www.googleapis.com/books/v1/volumes?q=polymer"
handle-as="json"
on-response="onResponse"
last-response="{{response}}" auto></iron-ajax>
</template>
<script>
Polymer({
is: 'my-el',
properties: {
response: {
type: Object,
notify: true
},
queue: {
type: Array,
notify: true,
value: function() { return []; }
}
},
onResponse: function(e) {
var ajax = this.$.ajax;
var originalUrl = 'https://www.googleapis.com/books/v1/volumes?q=polymer';
var url = ajax.lastRequest.xhr.responseURL;
if (url.includes(originalUrl)) {
console.log('this is the first request');
for (var i = 0; i < ajax.lastResponse.items.length; i++) {
this.push('queue', ajax.lastResponse.items[i].id);
}
ajax.url = this.url(this.pop('queue'));
} else {
console.log(ajax.lastResponse.selfLink);
ajax.url = this.url(this.pop('queue'));
}
},
url: function(id) {
return "https://www.googleapis.com/books/v1/volumes/" + id;
}
});
</script>
</dom-module>
<my-el></my-el>
</body>
</html>
https://jsbin.com/beyawe/edit?html,console
You can try out iron-multiple-ajax-behavior.
Let me know if this can be useful to you.

Polymer 1.0 - data-bound arrays not updating correctly

Demo here: http://jsbin.com/wiqowo/15/edit?html,output
When I loop through this.data in the example below, it iterates over the correct number of items, however the value of the individual items is not output. Is this a bug, or am I missing something?
<dom-module id="test-element">
<template>
<h3>data</h3>
<p>data: <span>{{data}}</span></p>
<ul>
<template is="dom-repeat" items="{{data}}">
<li>{{item}}</li>
</template>
</ul>
</template>
<script>
Polymer({
ready: function() {
this.data = ['Item #1', 'Item #2', 'Item #3'];
for (var i=this.data.length; i<10; i++) {
this.data.push('Item #' + (i+1));
}
console.log(this.data);
}
});
</script>
</dom-module>
Figured it out. Apparently array mutation is not supported in 1.0. You have to call the new Polymer.push, Polymer.pop, etc. methods.
Demo: http://jsbin.com/wiqowo/25/edit?html,output
for (var i=this.data.length; i<10; i++) {
this.push('data', 'Item #' + (i+1));
}
Define an array var inside of the the ready function, and push data to it. Then, assign it to this.data.
Polymer({
ready: function() {
var localData = ['Item #1', 'Item #2', 'Item #3'];
for (var i=localData.length; i<10; i++) {
localData.push('Item #' + (i+1));
}
this.data = localData;
console.log(this.data);
}
});

Web Component: How to listen to Shadow DOM load event?

I want to execute a JavaScript code on load of the Shadow DOM in my custom element.
I tried the following code but it did not work
x-component.html:
<template id="myTemplate">
<div>I am custom element</div>
</template>
<script>
var doc = this.document._currentScript.ownerDocument;
var XComponent = document.registerElement('x-component', {
prototype: Object.create(HTMLElement.prototype, {
createdCallback: {
value: function() {
var root = this.createShadowRoot();
var template = doc.querySelector('#myTemplate');
var clone = document.importNode(template.content, true);
clone.addEventListener('load', function(e) {
alert('Shadow DOM loaded!');
});
root.appendChild(clone);
}
}
})
});
</script>
Then I use it in another html as follows -
index.html:
<!doctype html>
<html >
<head>
<script src="bower_components/webcomponentsjs/webcomponents.min.js"></script>
<link rel="import" href="x-component.html">
</head>
<body>
<x-component></x-component>
</body>
</html>
The doc variable is used as I am using Polymer webcomponents.js polyfill and the polyfill needs it.
What is the right syntax to listen to load event of Shadow DOM?
AFAIK, the only way to achieve this is to use MutationObserver:
attachedCallback: {
value: function() {
var root = this.createShadowRoot();
var template = document.querySelector('#myTemplate');
var clone = document.importNode(template.content, true);
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
if(mutation.addedNodes) { // this is definitely a subject to change
alert('Shadow is loaded');
};
});
})
observer.observe(root, { childList: true });
root.appendChild(clone);
}
}
I would be glad to know if there is more elegant way, but for now I use this one.
Live preview: http://plnkr.co/edit/YBh5i2iCOwqpgsUU6En8?p=preview

How To Access Polymer Custom Element From Callback

I need to access the custom element and call its method from the click event callback.
<polymer-element name="my-element">
<template>
<style type="text/css" media="screen">
...
</style>
<ul id="my_data"></ul>
</template>
<script>
Polymer('my-element', {
dataSelected: function(selectedText) {
//...
},
setData: function(data) {
for (var i = 0; i < data.length; i++) {
var li = document.createElement('li');
li.addEventListener('click', function(e) {
// how can I call dataSelected() from here?
});
li.innerText = data[i];
this.$.my_data.appendChild(li);
}
}
});
</script>
</polymer-element>
How can I call the custom element's dataSelected() method from the callback?
You can use bind to attach a this context to any function, so:
li.addEventListener('click', function(e) {
this.dataSelected(e.target.innerText);
}.bind(this));
http://jsbin.com/xorex/4/edit
But you can make things easier by using more Polymer sugaring. For example, you can publish data and use the observation system, like so:
<polymer-element name="my-element" attributes="data">
...
data: [], // type hint that data is an array
...
dataChanged: function() { // formerly setData
http://jsbin.com/xorex/5/edit
Also, you can use the built-in event system instead of addEventListener
<polymer-element name="my-element" attributes="data">
...
<ul id="my_data" on-tap="{{dataTap}}"></ul>
...
dataTap: function(e) { // `tap` supports touch and mouse
if (e.target.localName === 'li') {
this.dataSelected(e.target.textContent);
}
}
http://jsbin.com/xorex/6/edit
But the biggest win is using <template repeat> instead of creating elements in JavaScript. At that point, the complete element can look like this:
<polymer-element name="my-element" attributes="data">
<template>
<ul id="my_data">
<template repeat="{{item in data}}">
<li on-tap="{{dataTap}}">{{item}}</li>
</template>
</ul>
</template>
<script>
Polymer('my-element', {
data: [],
dataTap: function(e) {
console.log('dataSelected: ' + e.target.textContent);
}
});
</script>
</polymer-element>
http://jsbin.com/xorex/7/edit
You could insert element = this; at the beginning of your setData() function and call element.dataSelected(); in the event handler.
But i think for what you want to achieve, you'd better use a repeat template (Iterative templates) and a direct binding to your click handler function (Declarative event mapping).