In the todo-list app from PolymerLabs , I added a ready() function in todo-data.html as follows:
<dom-module id="todo-data">
<template>
<style>
</style>
<firebase-collection location="{{userLocation}}"
data="{{fbTodos}}"
on-firebase-value="_firebaseLoaded">
</firebase-collection>
.....
</template>
<script>
Polymer({
is: 'todo-data',
properties: {
todos: {
notify: true
},
user: {
observer: '_userChanged'
},
.......
ready: function() {
console.log(this.user);
}
});
The user attribute value is bounded to it from index.html as follows:
//index.html
<template is="dom-bind" id="app">
<todo-auth id="auth"
user="{{user}}"
location="[[firebaseURL]]"
user="{{user}}">
</todo-auth>
<todo-data location="[[firebaseURL]]"
todos="{{todos}}"
user="{{user}}">
</todo-data>
..........
May I know why user is always printed out as undefined in ready()? It seems that when ready() is called, the bounded data is not yet populated with the correct data. When can i reliably know when a bounded data has been initialised?
When ready() is called this only means that Polymer is done creating and initializing the elements. The call to Firebase is only just invoked at that time and Polymer doesn't wait for the call to the Firebase server to respond. This is done by code in <firebase-collection> Polymer is not aware of.
When data arrives from the server it is passed to fbTodos
Create a property fbTodos and and observer that is executed when fbTodos changes. This observer will be called when the data arrive.
properties: {
fbTodos: {
observer: '_dataArrvied'
},
user: {
observer: '_userChanged'
},
The <firebase-collection> element doesn't provide an firebase-value event therefore
on-firebase-value="_firebaseLoaded"
seems redundant.
I haven't used this element myself yet.
Related
When initializing a property to an object or array value, use a function to ensure that each element gets its own copy of the value, rather than having an object or array shared across all instances of the element.
this is from the official polymer document my question here is why to not to share this default value across multiple instance as this default value will only be called once during initialization ??
<dom-module id="title-element">
<template>
<h1 style$="background-color: {{backgroundColor}}">{{config.title}}</h1>
</template>
<script>
Polymer({
is: 'title-element',
properties: {
config: {
type: Object,
notify: true,
value: {
title: 'This is my element',
}
},
backgroundColor: String,
},
ready: function () {
this.addEventListener('config-changed', function () {
console.log('config had changed');
this.querySelector('h1').style.backgroundColor = 'blue';
})
}
})
</script>
</dom-module>
<title-element background-color="yellow"></title-element>
<title-element background-color="green"></title-element>
in the above example i tried to change the value of config.title by selecting that element in chrome console and change it once using $0.config = {"title":"any other"} and also using notifyPath and as expected it changed only in the selected element not all instances
what is the purpose of using function wrap then ?
So that every element gets it own copy instead of sharing it.
If you provide a function, Polymer calls the function once per element instance.
When initializing a property to an object or array value, use a
function to ensure that each element gets its own copy of the value,
rather than having an object or array shared across all instances of
the element.
Here's the link to documentation
Here's a simple test case to depict the same.
<link rel="import" href="http://polygit.org/components/polymer/polymer.html">
<dom-module id="shared-object">
<template>
<style></style>
<div on-tap='changeValue'>Shared Object: {{shared.key}}</div>
<div on-tap='changeValue'>Not Shared Object: {{notShared.key}}</div>
</template>
</dom-module>
<script>
Polymer({
is: 'shared-object',
properties: {
shared: {
type: Object,
value: {
key: 'value'
}
},
notShared: {
type: Object,
value: function() {
return {
key: 'value'
}
}
}
},
changeValue: function() {
this.set('shared.key', 'value1');
this.set('notShared.key', 'value1');
}
})
</script>
Instance one
<br>
<shared-object id='obj1'></shared-object>
<br>
<br>Instance two
<br>
<shared-object id='obj2'></shared-object>
<script>
document.querySelector('shared-object').addEventListener('tap', function() {
console.log('First instance:\nshared value:', document.querySelector('#obj1').shared, '\nnot shared value:', document.querySelector('#obj1').notShared);
console.log('second instance:\nshared value:', document.querySelector('#obj2').shared, '\nnot shared value:', document.querySelector('#obj2').notShared);
})
</script>
After you tap on any of the value you'll notice that even though the display values are correct for all the cases but in console shared object has same value for both the instances, whereas notSharedObject has different value even in console.
<dom-module id="payment-list">
<template>
<template is="dom-repeat" items="{{clients}}">
<paper-item>
<span>{{item.Name}}</span>
|
<span>{{item.Amount}}</span>
</paper-item>
</template>
</template>
<script>
Polymer({
is: 'payment-list',
properties: {
clients: {
notify:true,
type: Array,
value: [{Name:'A', Amount:'100'},
{Name:'B', Amount:'200'}]
}
},
handleComplete: function(NewValues){
/***********/alert(NewValues);/***********/
},
ready: function(){
google.script.run.withSuccessHandler(this.handleComplete).GS_GetClients();
}
});
</script>
</dom-module>
I am using google.script.run to communicate with GAS function GS_GetClients(). GS_GetClients will be returning an object and I am trying to bind this new values to the property 'clients'.
When I do the alert I see that new values are passed to the handleComplete function from the server side GAS function. But I am not able assign the new values to the property 'clients'.
I cant set the values by using this.clients = NewValues. This is making the value to undefined.
The call to google.script.run is asynchronous.
The result seems to return, when the rendering of {{clients}} has already happend.
So in your success handler handleComplete(..) you somehow have to tell Polymer to redraw.
I do not know Polymer but from the docs Polymer data binding it seems as if you can do it like this :
Polymer({
...
setClients: function(clients) {
this.clients = clients;
// Notification required for binding to update!
this.notifyPath('clients', this.clients);
}
});
How to call custom methods in Polymer is explained here member-functions , sorry can not provide a more detailed answer regarding Polymer.
Proper usage of the Polymer 1.0 element <iron-meta> is confusing. Here is the link on Github. And here is the link to the Polymer demo site.
Can someone please provide a proper code example of how to make it work?
This is the code I have so far.
<dom-module id="generic-element">
<style>...</style>
<template>
<iron-meta id="meta" key="info" value="foo/bar"></iron-meta>
The <code>value</code> stored at <code>key="info"</code> is <code><span>{{test}}</span></code>.
</template>
</dom-module>
<script>
(function() {
Polymer({
is: 'generic-element',
properties: {
test: {
value: function(){
return "Hello world"; // This is the only thing I can get to work so far.
// return (new Polymer.IronMetaQuery({key: 'info'}).value); // Doesn't totally break.
// All my other below attempts totally fail. Everything breaks.
// return this.$.meta.IronMetaQuery({key: 'info'}).value;
// return this.IronMetaQuery({key: 'info'}).value;
// return this.$.meta.byKey('info').getAttribute('value');
// return this.$.meta.byKey('info').value;
}
}
}
});
})();
</script>
Here is the Github link to the issue. And here is a Github repository that contains the complete problem code in context of the complete web app.
The issue with your code is that you are trying to set your element property's default value to something that's declared inside that same element's template itself. Two of the things that happen between the time when the element is created and when that element is attached include a) properties' default values are set; and b) the template undergoes preparations to be stamped into DOM. These tasks happen asynchronously so in essence you are generating a race condition.
Try setting your test default value inside the ready() callback - the ready() callback guarantees that DOM is ready to be accessed, which in your case is exactly where you declared your <iron-meta> key.
<dom-module id="generic-element">
<style>...</style>
<template>
<iron-meta id="meta" key="info" value="foo/bar"></iron-meta>
The <code>value</code> stored at <code>key="info"</code> is <code><span>{{test}}</span></code>.
</template>
</dom-module>
<script>
(function() {
Polymer({
is: 'generic-element',
properties: {
test: String
},
ready: function () {
// this will work
this.test = this.$.meta.byKey("info");
}
});
})();
</script>
jsbin: http://jsbin.com/vosekiwehu/edit?html,output
A flux architecture is trending in web applications and so is polymer elements.
Is there any example how to make a polymer application, which use flux architecture?
I've been thinking about using the Flux pattern with (Polymer) Web Components. Up to date I have come up with three possible solutions, all different from your way, so here they are:
DISCLAIMER I use Reflux library and not the Facebook's one.
Actions and Stores as elements
My first attempt was to make Flux pattern into elements so that any view, which need access to a store and invokes actions simply imports them.
<dom-module id="a-view">
<template>
<my-actions id="actions"></my-actions>
<my-store model="{{model}}"></my-store>
<span>{{model.property}}</span>
<button on-click="executeAction"></button>
</template>
</dom-module>
<script>
Polymer({
is: 'a-view',
executeAction: function() {
this.$.actions.doSomething({});
}
});
</script>
<my-actions> and <my-store> simply encapsulate actions and stores. There are some downsides to this method. First, potentially numerous non-visual elements are created, which can have detrimental effect on performance. Also creating those elements can be tricky if they should be Polymer elements, because they need static state. For a complete example see this repo
Flux without Flux
Recently I realized, again, what Web Components really are. With WC, your main API is the browser, namely elements, attributes and events. And Flux essentially is an event-driven data flow. So why not use Custom Events to communicate between custom elements? Here's an excerpt from my yesterday's plunk
<template is="dom-bind">
<a-view clicks="[[clicks]]" id="one"></a-view>
<a-view clicks="[[clicks]]" id="two"></a-view>
<a-view clicks="[[clicks]]" id="three"></a-view>
<a-store click-history="{{clicks}}"></a-store>
</template>
<script>
Polymer({
is: 'a-view',
properties: { clicks: Object },
fireClick: function() {
// invoking action is simply firing an event
this.fire('a-view-click', {});
}
});
Polymer({
is: 'a-store',
attached: function(){
document.addEventListener('a-view-click', function(ev) {
// do work and update store here
}.bind(this));
}
});
</script>
This is nice, because is not limited in any way to Polymer. Custom elements can be created with native API or other library and simply communicate with browser acting as your dispatcher. Of course this doesn't give you ways of synchronization out of the box, but is a simple and clean way without any clutter.
As you will see on Plunker, store updates by data-bindings. Another possibility is to fire off another event, though I'm not sure which would be better or when
Use Polymer's behaviors
Finally I've just had an idea, which improves upon the first, by replacing action/store custom elements by behaviors. There's no code yet, but here's a sketch:
var MyActionsBehaviour = PolymerFlux.createActions({ /*...*/ });
var MyStore = PolymerFlux.createStore({ /*...*/ });
Polymer({
is: 'a-view',
behaviours: [ MyActionsBehaviour, MyStore ],
onClick: function() {
this.behaviourAction.invoke({});
}
}});
Polymer({
is: 'a-store',
behaviours: [ MyActionsBehaviour, MyStore ],
attached: function() {
this.behaviourAction.listen(function() {
// 1. do work
// 2. update views
});
}
}});
I left the view updating part blank. It would likely take place by signalling an event but another possibility would be firing another action (Reflux has a nice concept of nested actions). Also I'm currently leaving the PolymerFlux.createActions and PolymerFlux.createStore for your imagination ;). The exact internals would ofc depend on the Flux implementation you choose.
I have made an attempt to use flux-type architecture in a polymer application.
Here is the main-app.html:
<link rel="import" href="./bower_components/polymer/polymer.html">
<link rel="import" href="store-cart.html">
<link rel="import" href="store-cart2.html">
<link rel="import" href="view-cart.html">
<link rel="import" href="view-additems.html">
<dom-module id="main-app">
<style>
</style>
<template>
<!-- Stores-->
<store-cart id="cart1" action=[[action]]></store-cart>
<store-cart2 id="cart2" action=[[action]]></store-cart2>
<!--Views and other stuff-->
<view-additems cart="cart1"></view-additems>
<view-additems cart="cart2" add="3"></view-additems>
<view-cart update="[[updateView]]"></view-cart>
</template>
</dom-module>
<script>
Polymer({
is: 'main-app',
properties: {
action: {
type: Object,
value: {}
},
updateView: {
value: ""
}
},
listeners: { //dispatcher event -> action
'viewAction': 'viewAction', // Action from view to be dispatched to the store/stores
'storeUpdated': 'storeUpdated' // storeUpdated-event from store to views
},
viewAction: function(e) {
action = e.detail;
switch (action.type) {
// "CombineCarts" is needed because both of the stores needs to be updated in order
case 'combineCarts':
this.$.cart1.addItems(this.$.cart2.nbItems);
this.$.cart1.updateViews();
this.$.cart2.emptyCart();
this.$.cart2.updateViews();
break;
// default action when store/stores can be updated independently
default:
this.action = action;
}
},
storeUpdated: function(e) {
this.updateView = e.detail;
}
});
</script>
The whole example: https://github.com/grohjy/polymer_flux_example
The main idea is that a "dispatcher" is located at the top most level of the polymer application and it's role is to redirect messages from stores to views and viceversa. Each store and view defines to which messages they reacts and how. At the dispatcher there is also an example how to update multiple stores in needed order.
The stores and some of the views are also located at the top most level of the application. A view can also have child views. A store shouldn't have any visual dom elements.
Please feel free to comment and share ideas.
I have a working Polymer prototype, a code snippet of which is:
Polymer({
myData: [],
observe:{
myData: 'myDataChange'
},
myDataChange: function(val, newVal){ ... }
...
However, under the the attribute hinting section of the developer API, it states that objects and arrays should be initialised in the created lifecycle callback, not on the prototype. So, I changed the code snippet above to:
Polymer({
created: function(){
this.myData = [];
},
observe:{
myData: 'myDataChange'
},
myDataChange: function(val, newVal){ ... }
...
As soon as I make this change, the change watcher function no long invokes.
The myData property of my element instance is being populated by jQuery in an document ready callback. Moving this code into a 'polymer-ready' callback on the containing page solves this issue.
My concern with this is that my pages are going to be littered with polymer-ready events for the initial data population.
I amended my prototype so that the custom element is added to the DOM after a 5 second timeout, after the polymer-ready event was fired. Injecting the DOM like this doesn't fire the polymer ready event again.
Is this the correct/best approach to initialising properties on a Polymer element? I could manually fire an event from my custom element to say its loaded but this seems a bit crude. Any better ideas?
You shouldn't use any custom element before polymer-ready event (unless you're doing it intentionally), I think the best you could do is to replace every ready callback with polymer-ready.
However if you still want to use ready callback you could call myDataChange inside the element's ready callback:
Polymer({
created: function(){
this.myData = [];
},
observe:{
myData: 'myDataChange'
},
ready: function() {
this.myDataChange([], this.myData);
},
myDataChange: function(val, newVal){ ... }
....