Show Polymer indeterminate paper-progress when iron-ajax is loading - javascript

I am trying to show a paper-progress when an iron-ajax request is in progress with no success. Below is the code for a custom element get-products-service which hosts the iron-ajax request, and a products-list which hosts the paper-progress.
This is the whole products-list dom-module:
<dom-module id="product-list">
<style>
:host {
display: block;
width: 100%;
text-align: center;
}
product-card {
margin-left: 10px;
margin-bottom: 30px;
}
</style>
<template>
<template is="dom-if" if="{{loading}}">
<paper-progress value="10" indeterminate="true" style="width:100%;"></paper-progress>
</template>
<paper-button id="previous" on-tap='previousPage'>Previous</paper-button>
<paper-button id="next" on-tap='nextPage'>Next</paper-button>
<get-products-service products="{{products}}" id="productservice" page="{{page}}" loading="{{loading}}"></get-products-service>
<div>
<template is="dom-repeat" items="{{products}}">
<product-card on-cart-tap="handleCart" product="{{item}}">
<img width="100" height="100">
<h3>{{item.name}}</h3>
<h4>{{item.display_price}}</h4>
</product-card>
</template>
</div>
</template>
</dom-module>
<script>
(function() {
Polymer({
is: 'product-list',
properties: {
page: {
type: Number,
notify: true,
value: 1
}
},
handleCart: function(event) {
},
previousPage: function(event){
this.page = --this.page;
console.log("page: "+this.page);
},
nextPage: function(event){
this.page = ++this.page;
console.log("page: "+this.page);
}
});
})();
</script>
This is the whole get-products-service
<dom-module id="get-products-service">
<style>
:host {
display: none;
}
</style>
<template>
<iron-ajax id="productsajax"
url="http://localhost:3003/api/products"
params='{"token":"mytoken"}'
method='GET'
on-response='productsLoaded'
handleAs="json"
loading="{{loading}}" >
</iron-ajax>
</template>
</dom-module>
<script>
(function() {
Polymer({is: 'get-products-service',
properties: {
products: {
type: Array,
notify: true
},
page: {
type: String,
notify: true,
},
perpage: {
type: String,
readOnly: true,
value: "6"
},
loading: {
type: Boolean,
readOnly: true,
notify: true,
value: false
}
},
productsLoaded: function(request) {
console.log(this.$.productsajax.lastResponse);
responseObject = this.$.productsajax.lastResponse;
this.products = responseObject.products;
},
ready: function(){
this.$.productsajax.params.page = this.page;
this.$.productsajax.params.per_page = this.perpage;
},
observers: [
'attributesReady(page)'
],
attributesReady: function(page) {
this.page = page;
this.$.productsajax.params.page = page;
this.$.productsajax.params.per_page = this.perpage;
console.log("service page: "+page);
this.async(function() {
this.$.productsajax.generateRequest();
}, 3000);
}
});
})();
</script>

Your best option is to separate your concerns. First, your get-products-service:
<dom-module id="get-products-service">
<style>
:host {
display: none;
}
</style>
<template>
<iron-ajax id="productsajax"
url="http://localhost:3003/api/products"
method='GET'
loading="{{loading}}"
handleAs="json">
</iron-ajax>
</template>
<script>
Polymer({
is: 'get-products-service',
properties: {
loading: {
type: Boolean,
readOnly: true,
notify: true,
value: false
}
}
});
</script>
</dom-module>
Then, in your product-list:
<template>
<template is="dom-if" if="{{loading}}">
<paper-progress value="10" indeterminate="true" style="width:100%;"></paper-progress>
</template>
<get-products-service loading="{{loading}}"></get-products-service>
</template>
This way get-products-service and paper-progress don't have to know anything about each other, and should be more composable elsewhere in your application.

Finally I was able to have this working using a combination of #Zikes and #Scott answers. First I seperated the concerns as suggested by #Zikes so i put this in product-list element.
<template>
<template is="dom-if" if="{{loading}}">
<paper-progress value="10" indeterminate="true" style="width:100%;"></paper-progress>
<template>
<get-products-service loading="{{loading}}"></get-products-service>
</template>
In the get-products-service element is where i had to make some changes to have it working. For some reason, i wouldn't get the loading property from the iron-ajax set to true. Therefore #Zikes answer wouldn't work as it was. I had to make a few changes:
Explicitly set the value of the loading property to true just before the iron-ajax request is fired.
Explicitly set the value of loading to false when the on-response event is fired.
For this to work I changed #Zikes answer's loading property by removing readOnly: true; line.
Using #Scott suggestion, I slowed things down by using:
this.async(function() {
this.$.productsajax.generateRequest();
}, 5000);
to get a chance to see the progress-bar .
The resulting get-products-service element looks like this:
<dom-module id="get-products-service">
<style>
:host {
display: none;
}
</style>
<template>
<iron-ajax id="productsajax"
url="http://localhost:3003/api/products"
method='GET'
on-response='productsLoaded'
handleAs="json">
</iron-ajax>
</template>
</dom-module>
<script>
Polymer({is: 'get-products-service',
properties: {
loading: {
type: Boolean,
notify: true,
value: false
}
},
//on-response callback
productsLoaded: function(request) {
this.loading = false;
},
//An observer method where ajax request is generated
attributesReady: function(page) {
//set the value of loading to true just before generating request
this.loading = true;
//slow things to get a chance to see the progress-bar
this.async(function() {
this.$.productsajax.generateRequest();
}, 5000);
}
});
</script>
I believe #Zikes answer should work too and I'm the one who failed to implement it correctly so I will accept it as the correct answer.

#Zikes has the right answer. I'm supplementing here with a fully working implementation (only for Chrome, x-platform requires a few tweaks):
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<base href="http://milestech.net/components/">
<link href="iron-ajax/iron-ajax.html" rel="import">
<link href="paper-progress/paper-progress.html" rel="import">
</head>
<body>
<use-service></use-service>
<dom-module id="get-products-service">
<template>
<iron-ajax url="iron-ajax/bower.json" auto="{{go}}" handle-as="text" last-response="{{products}}"></iron-ajax>
</template>
<script>
Polymer({
properties: {
products: {
notify: true,
value: null
}
},
ready: function() {
// this bit just because otherwise it's too fast to see
this.async(function() {
this.go = true;
}, 1000);
}
});
</script>
</dom-module>
<dom-module id="use-service">
<template>
<template is="dom-if" if="{{!products}}">
<paper-progress value="10" indeterminate="true" style="width:100%;"></paper-progress>
</template>
<get-products-service products="{{products}}"></get-products-service>
<pre>{{products}}</pre>
</template>
<script>
Polymer({});
</script>
</dom-module>
</body>
</html>

I know it's kinda late, but I ran into a similar dilemma and decided to expose the API.
This way I could show the exact percent of progress of the XHR request.
Note: Very Hacky
I did this by overriding the default generateRequest() method, with a few additions.
app.$.productsajax.generateRequest = function() {
var request = (document.createElement('iron-request'));
var requestOptions = this.toRequestOptions();
///**=============================================///
///** Added to expose progress - Super Hacky **///
var me = this;
request.ap_obs = function(a) {
me.progress = a;
me._notifyChange("progress")
};
request._addObserverEffect("progress", "ap_obs")
///**===========================================**///
this.activeRequests.push(request);
request.completes.then(
this._boundHandleResponse
).catch(
this._handleError.bind(this, request)
).then(
this._discardRequest.bind(this, request)
);
request.send(requestOptions);
this._setLastRequest(request);
this._setLoading(true);
this.fire('request', {
request: request,
options: requestOptions
});
return request;
}
So now your element can look like:
<template>
<iron-ajax url="iron-ajax/bower.json" progress="{{progress}}" loading="{{activePageReq}}" auto="{{go}}" handle-as="text" last-response="{{products}}"></iron-ajax>
<paper-progress hide$="{{!activePageReq}}" value="{{progress.loaded}}" ,max="{{progress.total}}"></paper-progress>
</template>

In my case, I wanted to keep the progress bar always visible in a menu area, and start / stop it from different elements when doing some work that might take some time.
The progress is indeterminate by default on loading
<paper-progress id="working" indeterminate ></paper-progress>
We have a global namespace object app and I just add there a function as follow
app.working = function(flag){
document.querySelector("#working").indeterminate = flag;
};
In that case, when the initial loading is done, I just do
app.working(false);
And it can be started / stopped later on from any element in the app, just by doing
app.working(true);
// ... long process
app.working(false);

Related

Polymer iron-scroll-threshold not triggering

I want to make a feed that automatically loads items when the bottom of the current page is reached, however the iron-scroll-threshold doesn't trigger. I'm using my api call to fill the items in the template and the restaurants load just fine. Also when I bind a load function to a button it works just fine. It seems that the iron-scroll-threshold never triggers. Can anyone explain to me what I'm missing/doing wrong?
Code:
<iron-scroll-threshold id="threshold" lower-threshold="100" on-lower-threshold="loadMoreData">
<div id="scroller" class="vertical layout center" fill>
<template is="dom-repeat" items="[[restaurants]]" filter="{{computeFilter(searchInput)}}" scroll-target="threshold" on-scroll="_scrollHandler">
<!-- Items -->
</template>
<div class="loadingIndicator" hidden$="[[!loadingRestaurants]]">
<paper-spinner active$="[[loadingRestaurants]]"></paper-spinner> Fetching restaurants</b>
</div>
</div>
</iron-scroll-threshold>
<script>
Polymer({
is: 'my-view2',
properties: {
restaurants:{
type: Array,
value: []
},
offset:{
type: Number,
value: 0
},
limit:{
type: Number,
value: 50
},
loadingRestaurants: Boolean
},
ready: function () {
this.$.requestRestaurants.generateRequest();
},
handleResponse: function (data) {
var self = this;
var response = data.detail.response;
response.forEach(function(restaurant){
self.push('restaurants', restaurant);
});
console.log(this.restaurants);
this.$.threshold.clearTriggers();
},
toggle: function(e) {
console.log(this.$.threshold.top);
var index = "#collapse" + e.model.__data.index;
this.$$(index).toggle();
this.loadMore();
},
loadMore: function() {
console.log("test");
this.offset+=50;
this.limit+=50;
this.$.requestRestaurants.generateRequest();
this.$.threshold.clearLower();
this.$.threshold.clearTriggers();
}
});
The naming was inconsistent
on-lower-threshold="loadMoreData"
loadMore: function()

Access element's property from dom-repeat

I'm using a dom-repeat template in my element, and I want to use the property typeElement of that element (<custom-element>) in a new element (<media-element>) from within dom-repeat:
<dom-module id="custom-element">
<template>
<template is="dom-repeat" items="{{array}}" as="file">
<media-element some-prop="{{typeElement}}" file="{{file}}"></media-element>
</template>
</template>
<script>
Polymer({
is: 'custom-element',
properties: {
typeElement: Number,
array: {
type: Array,
value: function() { return[]; }
}
}
});
</script>
</dom-module>
How can I do that?
I'm not sure what you mean by "use the property", so I'm making assumptions below.
Assuming you want to set <media-element>.someProp to the value of <custom-element>.typeElement, then your data binding is correct. Whenever the value of typeElement changes, someProp will be set to the same value. You could access the value in a method of <media-element> with this.someProp. Example:
<dom-module id="media-element">
<template>...</template>
<script>
Polymer({
is: 'media-element',
properties: {
someProp: Number
},
foo: function() {
console.log(this.someProp);
}
});
</script>
</dom-module>
Assuming you also want the changes to <media-element>.someProp to update <custom-element>.typeElement, then you'd need to set notify: true on the property declaration of someProp:
// media-element
properties: {
someProp: {
type: Number,
notify: true // <-- only needed for upward notifications (two-way data binding and observers)
}
}
HTMLImports.whenReady(() => {
Polymer({
is: 'custom-element',
properties: {
typeElement: {
type: Number,
value: 100,
observer: '_typeElementChanged'
}
},
_typeElementChanged: function(typeElement) {
console.log('new typeElement', typeElement);
}
});
Polymer({
is: 'media-element',
properties: {
someProp: {
type: Number,
notify: true
}
},
_logSomeProp: function() {
console.log('someProp', this.someProp);
},
_incrementSomeProp: function() {
this.someProp++;
}
});
});
<head>
<base href="https://polygit.org/polymer+1.8.1/components/">
<script src="webcomponentsjs/webcomponents-lite.min.js"></script>
<link rel="import" href="polymer/polymer.html">
</head>
<body>
<custom-element></custom-element>
<dom-module id="custom-element">
<template>
<media-element some-prop="{{typeElement}}"></media-element>
</template>
</dom-module>
<dom-module id="media-element">
<template>
<button on-tap="_logSomeProp">Log someProp</button>
<button on-tap="_incrementSomeProp">Incremeent someProp</button>
</template>
</dom-module>
</body>
codepen
I recommend reading Polymer Data Binding.
because settings property like:
typeElement: Number won't work. You have to define it like you defined array property. so:
typeElement: {
type: Number,
value: Number,
}
i don't know what value you want to have. After this it should work
Ok i figured myself, the problem isn't here. The problem was on the child element (<media-element>). So i'll post a new question te resolve the problem:
how do the properties of an element been initialized before the element creation? Or, how to trigger the dom-if recalling my condition function after propertie change/init?
My code:
<dom-module id="media-element">
<template>
<template is="dom-if" if="[[isType(0)]]">
....
</template>
</template>
<script>
Polymer({
is: 'media-element',
properties: {
myType: {
type: Number,
value: 0
}
},
isType: function(t){return (this.myType===t);}
});
</script>
</dom-module>
UPDATE: I sent my question and receive a goode answer: here

How to Troubleshooting Binding

I found a cool project (RoboJS), and I forked it: Forked Repo. My plan was to try to add a nice front end with Polymer 1.0 and learn a little in the process.
What I am having trouble with is getting the binding to show in my component. I've built a really simple "robot" component to show the status of the robot during the game.
To start, all I want to do is to show the name in the title, but it comes out blank. Here's the component:
<dom-module id="robojs-robot-status">
<template>
<div>Robot Name <span>[[robot]]</span><span>{{test}}</span></div>
</template>
</dom-module>
<script>
Polymer({
is: "robojs-robot-status",
properties: {
robot: {
type: String,
value: "testing"
},
test: {
type: String,
value: "testing2"
}
},
ready: function() {
},
init: function() {
console.log(this.robot);
console.log(this.test);
}
});
</script>
On the parent component, I set the robot attribute:
Here's the attribute:
<link rel="import" href="robojs-robot-status.html">
<robojs-robot-status robot="{{robot}}"></robojs-robot-status>
And, I have a script that, for now, sets the value on the ready event:
Polymer({
is: "robojs-arena",
properties: {
robot: {
type: String,
value: "hello"
}
},
ready: function() {
this.games = window.roboJS.games;
console.log(this.games);
//this.robot = {name: "hello"};
this.robot = "hello";
},
init: function() {
console.log("******* init *******");
console.log(this.robot);
document.querySelector("robojs-robot-status").init();
},
pause: function() {
window.roboJS.pause();
},
start: function() {
console.log(window.roboJS);
window.roboJS.resume();
}
});
[[robot]] is blank. {{test}} binds to "testing2".
Using {{robot}} or [[robot]] doesn't make a difference. So, that doesn't have an impact.
If I remove, the "robot" attribute in the parent component, the value works. It shows "testing". So, it is binding, but not with the actual value.
Beyond figuring out what I am doing wrong in this instance, is there a good way to troubleshoot? I am having similar issues in other places in the app.
If this were Angular + jQuery, I would do something like this:
$('robotjs-robot-status').scope().$eval("robot")
I could type that into the developer console in Chrome and see what it said and troubleshoot. I could also use the Batarang extension in Chrome.
With Polymer, I am not sure where to start. Any help/ideas?
If the parent snippet is posted here exactly as it appears in the code, then it's probably to blame. The
<link rel="import" href="robojs-robot-status.html">
should be outside , like
<dom-module id="robojs-robot">
<link rel="import" href="robojs-robot-status.html">
<template>
<robojs-robot-status robotname="{{robotname}}"></robojs-robot-status>
</template>
<script>
Polymer({
is: "robojs-robot",
ready: function() {
console.log('setting to Dilly');
this.robotname = "Dilly";
},
properties: {
robotname: {
type: String,
value: "hello"
}
},
});
</script>
</dom-module>
and then if status is
<dom-module id="robojs-robot-status">
<template>
<div>Robot Name <span>[[robotname]]</span></div>
</template>
<script>
Polymer({
is: "robojs-robot-status",
properties: {
robotname: {
type: String,
value: "testing",
observer: '_robotnameChanged'
}
},
_robotnameChanged: function(newValue, oldValue) {
console.log('_robotnameChanged: newValue='+newValue+' oldValue='+oldValue)
}
});
</script>
</dom-module>
everything works for me.
PS: properties seem to be not really needed here as binding is unidirectional.

How to access attribute value in polymer?

HTML
<my-element username="{{params.username}}"></my-element>
Element
<dom-module id="my-element">
<style>
:host {
display: block;
}
</style>
<template>
</template>
<script>
(function() {
Polymer({
is: 'my-elemnt',
ready: function(){
// get attribute value
});
})();
</script>
</dom-module>
I can access the attribute from the template but don't know how to it access from javascript.
inside the element it would be this.username but you might need to add that to properties for it to work
Polymer({
is: 'my-element',
properties: {
username: {
type: Object,
observer: '_updateUsername'
}
},
_updateUsername: function () {
// use this.username
}
});
<link rel="import" href="polymer/polymer.html"/>
<dom-module id="my-element" username>
<style>
:host {
display: block;
}
</style>
<template>
<p>{{username.name}}</p>
</template>
</dom-module>
<script>
(function () {
Polymer({
is: 'my-elemnt',
properties: {
username: {
type: Object,
notify: true
}
},
ready: function () {
console.log(this.username);
}
});
})();
</script>
If you haven't declared the property it will log - undefined
For more information about the new declaration way check -
https://www.polymer-project.org/1.0/docs/devguide/properties.html

Change in data bound property is not propagating upwards

I have nested custom elements which interact with each other.
<dom-module id="dom-element">
<template>
<custom-element bindingTest="{{found}}"></custom-element>
</template>
<script>
Polymer({
is: "dom-element",
properties: {
found: {
type:String,
value:"4"
}
},
ready: function() {
this.addEventListener("found-changed", this.foundChanged);
},
foundChanged:function(e){
console.log("found changed");
}
});
</script>
</dom-module>
Child custom element:
<dom-module id="custom-element">
<template>
<button on-click="toParent">Send to parent</button>
</template>
<script>
Polymer({
is: "custom-element",
properties:{
bindingTest: {
type: String,
notify: true
}
},
toParent: function () {
this.bindingTest = "change of binding test"
}
});
</script>
</dom-module>
If I understood correctly, this.bindingTest = "change of binding test" should notify parent custom element and "found" parameter should become equal to "change of binding test" String, that is, toParent function has to be called, but it isn't being called for some reason. How do I notify parent when bindingTest changes?
EDIT:
The problem here is that you are accessing the bindingTest property of the child element incorrectly. From the documentation
In order to configure camel-case properties of elements using attributes, dash- case should be used in the attribute name.
So you will need to change your custom-element tag to be:
<custom-element binding-test="{{found}}"></custom-element>
I've set up a plunker here for you to look at.

Categories