in my test, i have 3 elements imported from the main html file :
<html><head><title>my-app</title>
<script src="bower_components/webcomponentsjs/webcomponents.min.js"></script>
<link rel="import" href="elements/app-globals.html">
<link rel="import" href="/Polymer/my-app/elements/my-categories.html">
<link rel="import" href="/Polymer/my-app/elements/my-items.html">
</head>
<body>
<my-categories></my-categories>
<my-items></my-items>
</body>
</html>
with 'app-globals.html' being:
<link rel="import" href="../bower_components/polymer/polymer.html">
<polymer-element name="app-globals" attributes="data selectedCategoryId selectedItemId">
<script>
(function() {
var values = {};
Polymer('app-globals', {
ready: function() {
console.log("app-globals -> ready");
this.values = values;
console.dir(this.values);
},
});
})();
</script>
</polymer-element>
'my-categories.html':
<link rel="import" href="../bower_components/polymer/polymer.html">
<link rel="import" href="/Polymer/my-app/elements/app-globals.html">
<polymer-element name="my-categories">
<template>
<app-globals id="globals"></app-globals>
<div>selectedCategoryId = {{$.globals.values.selectedCategoryId}}</div>
<ul>
<template repeat="{{category in categories}}">
<li class="li-category" data-_id="{{category._id}}" on-tap="{{selectCategory}}">{{category.name}}</li>
</template>
</ul>
</template>
<script>
Polymer('my-categories', {
values: {},
ready: function() {
var HOST = 'xxx.yyy.zzz.www';
var PORT = '8888';
this.categories = <loaded from websocket>;
},
selectCategory: function(event, detail, sender) {
var elt = (event.target.nodeName == 'INPUT')? event.target.parentNode : event.target;
this.values.selectedCategoryId = elt.dataset._id;
this.$.globals.values.selectedCategoryId = elt.dataset._id;
return false;
}
});
</script>
</polymer-element>
here when one of the li element is clicked the 'selectedCategoryId' is updated.
this value is passed thru globals to the last element 'my-items.html':
<link rel="import" href="../bower_components/polymer/polymer.html">
<link rel="import" href="/Polymer/my-app/elements/app-globals.html">
<polymer-element name="my-items">
<template>
<app-globals id="globals"></app-globals>
<style>
</style>
<div >selectedCategoryId = {{$.globals.values.selectedCategoryId}}</div>
<ul>
<template repeat="{{item in items}}"
on-category-tap="{{handleCategoryTap}}">
<li class="li-item" data-_id="{{item._id}}" on-click="{{selectItem}}">{{item.title}}</li>
</template>
</ul>
</template>
<script>
Polymer('my-items', {
values: {},
ready: function() {
var HOST = 'xxx.yyy.zzz.www';
var PORT = '8888';
console.log("'my-items' -> this.$.globals.values:");
console.dir(this.$.globals.values);
for(var prop in this.$.globals.values) {
if(this.$.globals.values.hasOwnProperty(prop)) {
console.log("this.$.globals.values[" + prop + "] = " + this.$.globals.values[prop] + "");
}
}
console.log("'my-items' -> this.$.globals.values.selectedCategoryId = " + this.$.globals.values.selectedCategoryId);
},
handleCategoryTap: function(event) {...,
selectItem: function(event) {...}
return false;
} });
</script>
resulting in something strange to me :
console.dir(this.$.globals.values) give me the correcte respons:
Object {
selectedCategoryId: "547dfb6578be56f6630041a8"
}
However selecting this attribute 'selectedCategoryId' results with:
console.log("'my-items' -> this.$.globals.values.selectedCategoryId = " + this.$.globals.values.selectedCategoryId);
gives me:
'my-items' -> this.$.globals.values.selectedCategoryId = undefined
undefined why ?
I advise you to use: https://github.com/akc42/akc-meta
I did use it to share info across the DOM with local data binding!
See my project if you want a working example: https://github.com/MeTaNoV/firebase-element-extended
Related
From the Polymer Starter Kit prototype, I have a function from <my-view1>, which fires a function from the <my-app> through an event listener.
I want to be able to send some info along with that, so I have set up a data attribute which is captured in a variable.
How can I then send that variable to my main event listener and function in <my-app>?
<my-view1> Data attribute:
<paper-button raised on-tap="open" data-target="#dialog">Dialog</paper-button>
<my-view1> Function:
open: function(e) {
const target = e.target.dataset.target || e.target.parentElement.dataset.target;
this.fire('open-dialog'); //send target variable somehow?
}
<my-app> listener:
listeners: {
'open-dialog': 'handleOpenDialog' //get target variable from my-view1
}
<my-app> Function:
handleOpenDialog: function(e) {
console.log(target); //get target variable from listener
}
Thanks!
You could specify the event detail when calling fire(eventName, [detail], [options]). In your case, you'd pass target (the selector for the target dialog) in the event detail, and your event handler would query its children with that selector to fetch the dialog.
// my-view1
open: function(e) {
const target = e.target.dataset.target;
this.fire('open-dialog', target);
}
// my-app
handleOpenDialog: function(e) {
const target = e.detail;
const dialog = this.$$(target);
if (dialog) {
dialog.opened = true;
}
}
HTMLImports.whenReady(() => {
Polymer({
is: 'my-app',
listeners: {
'open-dialog': 'handleOpenDialog'
},
handleOpenDialog: function(e) {
const target = e.detail;
const dialog = this.$$(target);
if (dialog) {
dialog.opened = true;
}
}
});
Polymer({
is: 'my-view1',
open: function(e) {
const target = e.target.dataset.target;
this.fire('open-dialog', target);
}
});
});
<head>
<base href="https://polygit.org/polymer+1.7.0/components/">
<script src="webcomponentsjs/webcomponents-lite.min.js"></script>
<link rel="import" href="paper-button/paper-button.html">
<link rel="import" href="paper-dialog/paper-dialog.html">
</head>
<body>
<my-app>
<my-view1></my-view1>
</my-app>
<dom-module id="my-app">
<template>
<content></content>
<paper-dialog id="dialog1">
<h2>Dialog 1</h2>
<div class="buttons">
<paper-button dialog-dismiss>Cancel</paper-button>
<paper-button dialog-confirm autofocus>Accept</paper-button>
</div>
</paper-dialog>
<paper-dialog id="dialog2">
<h2>Dialog 2</h2>
<div class="buttons">
<paper-button dialog-dismiss>Cancel</paper-button>
<paper-button dialog-confirm autofocus>Accept</paper-button>
</div>
</paper-dialog>
</template>
</dom-module>
<dom-module id="my-view1">
<template>
<paper-button on-tap="open" data-target="#dialog1">Dialog 1</paper-button>
<paper-button on-tap="open" data-target="#dialog2">Dialog 2</paper-button>
</template>
</dom-module>
</body>
codepen
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
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.
When we use web component techniques to create a custom element, sometimes the implementation of a custom element involves the use of attributes present on the resulting element in the main document. As in the following example:
main doc:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="import" href="web-components/my-portrait.html">
</head>
<body>
<my-portrait src="images/pic1.jpg" />
</body>
</html>
my-portrait.html:
<template id="my-portrait">
<img src="" alt="portait">
</template>
<script>
(function() {
var importDoc = document.currentScript.ownerDocument;
var proto = Object.create(HTMLElement.prototype, {
createdCallback: {
value: function() {
var t = importDoc.querySelector("#my-portrait");
var clone = document.importNode(t.content, true);
var img = clone.querySelector("img");
img.src = this.getAttribute("src");
this.createShadowRoot().appendChild(clone);
}
}
});
document.registerElement("my-portrait", {prototype: proto});
})();
</script>
In the createdCallback, we use this.getAttribute("src") to get the src attribute defined on the portrait element.
However, this way of obtaining the attribute can be only used when the element is instantiated by element tag declaration. But what if the element is created using JavaScript: document.createElement("my-portrait")? When this statement is done being executed, the createdCallback has already been called and this.getAttribute("src") will return null since the element has no src attribute instantly when it is created.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="import" href="web-components/my-portrait.html">
</head>
<body>
<!--<my-portrait src="images/pic1.jpg" />-->
<script>
var myPortrait = document.createElement("my-portrait");
myPortrait.src = "images/pic2.jpg"; // too late
document.getElementsByTagName("body")[0].appendChild(myPortrait);
</script>
</body>
</html>
So how do we pass attribute to createdCallback when we instantiate a custom element using JavaScript? If there were a beforeAttach callback, we can set attributes there, but there are no such callback.
You may implement the lifecycle callback called attributeChangedCallback:
my-portrait.html:
proto.attributeChangedCallback = function ( name, old, value )
{
if ( name == "src" )
this.querySelector( "img" ).src = value
//...
}
name is the name of the modified (or added) attribute,
old is the old value of the attribute, or undefined if it was just created,
value is the new value of the attribute (of type string).
For the callback to be called, use the setAttribute method against your custom element.
main doc:
<script>
var myPortrait = document.createElement( "my-portrait" )
myPortrait.setAttribute( "src", "images/pic2.jpg" ) // OK
document.body.appendChild( myPortrait )
</script>
example:
var proto = Object.create(HTMLElement.prototype, {
createdCallback: {
value: function() {
this.innerHTML = document.querySelector("template").innerHTML
var path = this.getAttribute("src")
if (path)
this.load(path)
}
},
attributeChangedCallback: {
value: function(name, old, value) {
if (name == "src")
this.load(value)
}
},
load: {
value: function(path) {
this.querySelector("img").src = path
}
}
})
document.registerElement("image-test", { prototype: proto })
function add() {
var el = document.createElement("image-test")
el.setAttribute("src", "https://placehold.it/100x50")
document.body.appendChild(el)
}
<image-test src="https://placehold.it/100x100">
</image-test>
<template>
<img title="portrait" />
</template>
<button onclick="add()">
add
</button>
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).