Handling of loading indicator for multiple ajax calls in Polymer - polymer

I'm using Polymer 1.0 to create a web application.
I have various elements doing one or more iron-ajax calls and I have another element for showing the loading-overlay. But in my current sollution I have added the loading-overlay, with its logic to show or not, to every element doing ajax calls.
<dom-module id="backend-call-application">
<template>
<iron-ajax id='loadA' loading="{{_loadingA}}" ...></iron-ajax>
<iron-ajax id='loadB' loading="{{_loadingB}}" ...></iron-ajax>
<loading-overlay id="loadingOverlay" with-backdrop></loading-overlay>
</template>
<script>
Polymer({
is: 'backend-call-application',
observers:[
"_isXhrLoading(_loadingA,_loadingB,....)"
],
_isXhrLoading: function() {
for (var i = 0; i < arguments.length; i++) {
if (arguments[i]) {
this.$.loadingOverlay.open()
return;
}
}
this.$.loadingOverlay.close()
}
});
</script>
</dom-module>
Now my question is, what is the best way to show such a loading-overlay?
One idea of mine would be, to have something like an observer in the loading overlay. So every element doing requests will bind its properties to the observer. These properties could be stored in an array and everytime on change, the loading-overlay checks if at least one have loading properties set to true. When one or more properties are true the loading-overlay will be opened and when all requests finished loading it will be closed.
Another idea was to use events to tell the loading-overlay when a element starts/stops loading. But here will be the problem, that I have more than one request at the same time (The first request closes the overlay, but the page hasn't finished loading).
Edit:
The loading-overlay is an element containing the IronOverlayBehavior.
<link rel="import" href="../../../bower_components/polymer/polymer.html">
<link rel="import" href="../../../bower_components/iron-overlay-behavior/iron-overlay-behavior.html">
<link rel="import" href="../../../bower_components/paper-spinner/paper-spinner.html">
<dom-module id="loading-overlay">
<template>
<paper-spinner active="true"></paper-spinner>
</template>
<script>
Polymer({
is: 'loading-overlay',
behaviors: [
Polymer.IronOverlayBehavior
]
});
</script>
</dom-module>

use one property for loading instead of 2!!
<iron-ajax id='loadA' loading="{{_loading}}" ...></iron-ajax>
<iron-ajax id='loadB' loading="{{_loading}}" ...></iron-ajax>
bind loading value to loading-overlay with some attribute and when loading is true display loader
<loading-overlay id="loadingOverlay" is-loading="[[_loading]]" with-backdrop></loading-overlay>
so each time any ajax is made then _loading will become true so displays loader

I think the only thing you could shorten is the _isXhrLoading observer.
_isXhrLoading: function() {
if(Array.from(arguments).indexOf(true) >= 0) {
this.$.loadingOverlay.open()
} else {
this.$.loadingOverlay.close()
}
}

Related

On attached, `this.$` an empty object

I'm trying to observe added and removed dom nodes to my custom Polymer element without success. I've noticed that the this.$ is an empty object.
Polymer({
is: 'dramatic-view',
attached: function () {
this._observer = Polymer.dom(this.$.contentNode).observeNodes(function (info) {
console.log('kromid info', info);
});
}
});
The callback is being called only once (even tho I change content afterwards) with the following strange parameters:
I was following the docs here.
this.$ is a hash of elements in your template that have an id attribute (see Automatic node finding).
So, for this.$.contentNode to exist, you need an element with id="contentNode" in your <dom-module>'s <template>, and it must not be inside a dom-if or dom-repeat template:
<dom-module id="x-foo">
<template>
<content id="contentNode"></content>
</template>
</dom-module>
Nodes created dynamically (i.e., those inside dom-if or dom-repeat) must be queried with this.$$() (e.g., this.$$('#contentNode')). If you're trying to set them up in the attached callback, you'll need to wait until after the nodes are stamped, which could be observed with Polymer.RenderStatus.afterNextRender().
<dom-module id="x-foo">
<template>
<template is="dom-if" if="[[useDynamicContent]]">
<content id="contentNode"></content> <!-- unavailable to this.$ -->
</template>
</template>
<script>
Polymer({
is: 'x-foo',
attached: function() {
Polymer.RenderStatus.afterNextRender(this, function() {
var contentNode = this.$$('#contentNode');
if (contentNode) {
contentNode.observeNodes(...);
}
});
}
});
</script>
</dom-module>
To trigger the node observer, use Polymer's DOM API like this:
Polymer.dom(this).appendChild(node);
Polymer.dom(this).removeChild(node);
codepen
UPDATE It seems you're integrating Polymer with Elm, and expecting to see the observer callback for node-changes made my Elm. You'll need to hook Elm's calls to appendChild/removeChild somehow so that it uses Polymer's DOM API.

observeNodes for distributed content

Let's say I have an element that has an insertion point inside.
<dom-module id="some-el">
<template>
<content id="con"></content>
</template>
<script>
(function () {
Polymer({
is: 'some-el',
ready:function(){
var placeholder = Polymer.dom(this).querySelector('div[id*="placeholder"]');
this._observer=Polymer.dom(placeholder ).observeNodes(function(info){
console.log(info.addedNodes)
});
}
});
})();
</script>
</dom-module>
and in DOM it looks like this where #placeholder... is a spinning wheel container, which replaces the span inside with content received via ajax.
<some-el><div id="placeholder_GUIDstuff"><span>spinner</span></div></some-el>
So basically I need to know when the #placeholder_... is changed, but I can't subscribe to it (when i do, nothing is returned when it changes), only to the content element, but it doesn't trigger when its immediate children get changed.
What can be the solution?

Polymer: how to parse the index to the elements using template repeat

I've been trying to use Polymer for a project I'm working on. And although I enjoyed it quite a lot so far, I ran into a problem I just can't solve.
I dumped it down to a simple example. Basically it's just a list element (item-list) that contains item elements (item-card) and i want to parse the items position to the element via the attribute pos. But for some reason the items attribute is allways undefined! Is this because the attribute is bound to the variable i, which dies after the template repeat? If so, how do I work around it? Is there a different approach I should be using here?
SOLUTION: You can find the solution by reading through all the comments, but to sum it up: apperantly there was a timing issue and the attribute wasn't ready at the ready callback. But I found out about the domReady callback (polymer lifecycle documentation). Using domReady it works just fine! Thanks to Günter Zöchbauer for the help!
This is the item-list.html:
<link rel="import" href="components/polymer/polymer.html">
<link rel="import" href="item-card.html">
<polymer-element name="item-list">
<template>
<style>
</style>
<template repeat="{{values, i in data.data}}">
<item-card pos="{{i}}"></item-card>
</template>
</template>
<script>
Polymer({
created: function()
{
this.num = 123456;
this.data = { "data":
[
{
"value":999
},
{
"value":666
},
{
"value":555
},
{
"value":222
}
]
};
}
});
</script>
</polymer-element>
This is the item-card.html
<link rel="import" href="components/polymer/polymer.html">
<polymer-element name="item-card" attributes="pos">
<template>
<style>
</style>
</template>
<script>
Polymer({
ready: function()
{
console.log("ready: " + this.pos);
}
});
</script>
</polymer-element>
I didn't bother putting the index.html in, since it just containts one item-list element.
Thanks alot!!
I think you need a pos field in <item-card> in addition to the attributes="pos" declaration.
The repeated element also references the bound model which can be accessed like querySelector('item-card').templateInstance.model property.
See https://github.com/PolymerLabs/polymer-selector/blob/master/polymer-selector.html#L286 for an example.
Info:
According to the comments it turned out to be a timing issue. The value wasn't yet assigned when the ready callback was called but using domReady worked.

Can I select any element in the document using Polymer's Automatic Node Finding?

I am working on a Polymer project and I a few custom elements nested. For example, I have a custom element called <example-main>. Suppose that this element has a function called displayMessage()
I have another custom element defined inside <example-main> called <example-alerts>. I want the alerts element to be able to use the function in its parent, main, to display a message.
To achieve this, I am trying to select the element <example-main id='mainEl'> by using the Polymer's Automatic Node Finding hash, this.$.mainEl and using the function like so: this.$.mainEl.displayMessage();
However, I get this error message:
Uncaught TypeError: Cannot read property 'displayMessage' of undefined
Am I making a wrong approach of having Polymer elements call functions in other Polymer objects?
To visualize things a little better, the index.html would look something like:
...
<body>
<example-main id='mainEl'></example-main>
</body>
...
while example-main.html looks something like:
<polymer-element name='example-main'>
<template>
...
<example-alerts>
</template>
<script>
Polymer(example-element, {
displayMessage: function(){
//do message showing magic
}
});
</script>
</polymer-element>
and lastly, example-alerts.html looking like:
<polymer-element name='example-alerts'>
<template>
...
<paper-button on-tap='{{callFunction}}'></button>
</template>
<script>
Polymer(example-alerts, {
callFunction: function(){
//call the function in parent
this.$.mainEl.displayMessage();
}
});
</script>
</polymer-element>
You are not doing this correct way , you cannot find nodes this way .
Node finding just search within same element defined in elements outermost template.
You should either change your approach or you can use events to handle the situation pls see the sample demo which can work for you.
<polymer-element name='example-alerts'>
<template>
...
<paper-button on-tap='{{callFunction}}'></button>
</template>
<script>
Polymer(example-alerts, {
callFunction: function(){
//call the function in parent
this.fire('ouch', {msg: 'That hurt!'});
}
});
</script>
</polymer-element>
--Second Element
<polymer-element name='example-main'>
<template>
...
<example-alerts on-ouch='{{displayMessage}}'>
</template>
<script>
Polymer(example-element, {
displayMessage: function(){
//do message showing magic
}
});
</script>
</polymer-element>

Paper-toast not showing

I am trying to display a toast when an ajax request fails using core-ajax and paper-toast elements. I created an handler that calls show on the paper-toast element. However it is still not showing...
What am I doing wrong?
Is there a better way to do that? (Maybe having the same toast element for all the application messages)
Here it follows my custom element code:
<link rel="import" href="../bower_components/polymer/polymer.html">
<link rel="import" href="../bower_components/core-ajax/core-ajax.html">
<link rel="import" href="../bower_components/paper-toast/paper-toast.html">
<polymer-element name="fd-rest-element-service" attributes="fditems fdtype">
<template>
<style>
:host {
display: block;
}
paper-toast {
z-index: 1000;
bottom: 40px;
left: 10px;
}
</style>
<paper-toast id="toast" text="There was a problem loading {{fdtype}} data.">
</paper-toast>
<core-ajax id="ajax"
auto on-core-error="{{errorHandler}}"
url="https://wrong.url.com:9113/{{fdtype}}/"
disabled-on-core-response="{{elementsLoaded}}"
response="{{fditems}}"
handleAs="json" withCredentials >
</core-ajax>
</template>
<script>
Polymer('fd-rest-element-service', {
fdtype:'environments',
created: function() {
this.fditems = [];
},
elementsLoaded: function() {
// Make a copy of the loaded data
console.log(this.fdtype +" : "+ this.$.ajax.response);
this.fditems = this.$.ajax.response.slice(0);
},
errorHandler: function(event){
console.log(event);
console.log(this.$.toast);
this.$.toast.show();
}
});
</script>
</polymer-element>
Since I have got no console error and the logged objects are as expected I believe the problem arises because the element is used inside an element managed by core-animated-pages that is not displayed. Any suggestion on how to create a shared toast element that can be accessed by the other elements in my application?
I ended up creating the paper-toast element inside the outmost element and then pass it through the children element via a toast attribute.
Here it follows some sample code. In my root element I created a paper-toast element referenced by id and share "top-down" in the other inner elements.
<paper-toast
id="toast"
text="There was a problem loading data.">
</paper-toast>
<fow-login user="{{user}}" userPhoto="{{userPhoto}}"
class="loginButton"
toast="{{$.toast}}"
token="{{token}}">
</fow-login>
In my inner element I use it like this:
<polymer-element name="fow-login" attributes="toast user userPhoto globals appID token">
...
<script>
...
loginFail: function(event){
console.log("Error:", event);
if(this.toast){
this.toast.text="There was a login problem.";
this.toast.show();
}
},
...
</script>
</polymer-element>