Polymer 1.0: <iron-meta> usage - 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

Related

Pass data to another Polymer component

I am working on a dashboard, in which I have a search panel at the top (let's call it component A), where users can enter a query. The value of this input will change a lot of other components in the dashboard (not only components that are its direct descendants or siblings). I want to send the search value from component A to component B, which should then respond by performing some action with the input value.
I have tried a few things:
Directly calling the function in component B. Haven't been able to get that to work at all.
Manually setting B's local property value and using an observer to trigger a function call. I manager to set the value, but the observer does not trigger.
Using a global variable, which I can easily access across components, but I still can't trigger functions in specific components.
How can I best do this?
I'm relatively new to Polymer, so forgive me if my ideas aren't completely 'Polymerised' :)
Approach 1
<dom-module id="component-B">
<template>
</template>
<script>
Polymer({
is: 'component-B',
properties: {
id: '',
observer: '_idUpdate'
},
_idUpdate: function(){
console.log("HELLO");
}
});
</script>
</dom-module>
<dom-module id="component-A">
<template>
</template>
<script>
Polymer({
is: 'component-A',
idSearch: function() {
var id = this.$.search.value;
document.querySelector('component-B').properties.id = id;
},
});
</script>
</dom-module>
As you want to send data to multiple elements (which might not be siblings of the firing element) you can use any of these two methods
Use iron-signal to fire the signal and then in all the elements where you want the data use iron-signal tag to listen to the signal
<iron-signals on-iron-signal-<signal-name>="<function>"></iron-signals>
You can also use standard HTML method dispatchEvent to fire a signal and then add eventListeners in all the element where you want data.

Polymer: calling a dynamic custom element's method from another script

i'm trying to call a dynamic custom element's method. It works only when the call is made inside the script tag of the custom element's html file. But when call is made in another custom element's script tag or in the script tag of index.html, i get the error: 'method-name' not a function in the console. Thanks for your response. for context, here is a snippet
// in my custom element html file
....
<script type="text/javascript">
Polymer( {
is: "my-new-view",
toggleContent: function() {
this.$.collapse.toggle();
},
insertContent: function (userContent) {
console.log("inserting userContent...");
}
});
</script>
</dom-module>
</html>
Now in another file my-app.html
...
<link rel="import" href="my-new-view.html">
...
<dom-module is="my-app">
...
<script>
...
// i want to test my-new-view. insertContent() here.
var dynamicView = document.createElement('my-new-view');
// in the following line i get the error insertContent is
// not a function
dynamicView.insertContent();
</script>
</dom-module>
pls help. what am i doing wrong. i tried the last 2 lines of javascript in my index.html as well but i get the same error. Thanks.

I think that maybe you're trying to call that method too early, when the elements have not been registered yet.
In index.html you can wrap you code in handler of WebComponentsReady event
window.addEventListener('WebComponentsReady', function(e) {
document.createElement('my-new-view').insertContent();
});
In other Polymer elements you could move your code inside of the my-app element rather than directly in the script.
Also, to check whether a custom element is available I look at document.createElement('my-new-view').constructor. If it says function HTMLElement() { [native code] } (in Chrome), it means that it's not available (usually an import is missing).

Server response to polymer property values

<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.

Parent element is ready before its child - Polymer 1.0

This question had been modified to match the actual problem.
The original question mistakingly focused on iron-ajax, please see the original problem below. The question should have been:
Please advice why child iron-ajax element is not ready during the 'ready' callback of my-component defined as follows:
<dom-module id="my-component">
<template>
<link rel="import" href="../../../bower_components/iron-ajax/iron-ajax.html">
<iron-ajax
id="selectionLoader"
url=""
method="GET"
handle-as="json"
debounce-duration="300"
last-response="{{ suggestedOptions }}"
last-error="{{ lastError }}"
verbose=true
>
</iron-ajax>
</template>
</dom-module>
<script>
(function () {
Polymer({
is : 'paper-select',
ready : function() {
console.log(this.$.selectionLoader.generateRequest); // undefined
}
})
})()
</script>
Original question
Original title: 'WebComponentsReady' fires before iron-ajax ready - Polymer 1.0
I need to assign some values to an observed property of a custom component that internally uses iron-ajax with disabled auto - so I need to call .generateRequest on the iron-ajax element. This should happen when host page/component is ready, in order to fetch from the server some defaults based on data in the host component code.
selected is an array property on the component observed like this:
observers: [
'_selectedChanged(selected.splices)' // _selectedChanged calls .generateRequest
]
The observer is triggered by:
window.addEventListener('WebComponentsReady', function() {
document.querySelector('paper-select').selected = [{id : 11855},{id : 11856}];
});
The problem is that WebComponentsReady fires before .generateRequest is available on the iron-ajax. So my component is initialized, _selectedChanged is called, but iron-ajax inside it is missing the method and in fact other properties/methods as well.
I've implemented a "deferred" workaround using setTimeout inside the component and it works like charm but it's obviously not the way. Also everything works if the observer is triggered some time later after the page load, e.g. by user's typing. This shows that the logic works, it's just the timing that is wrong.
What am I missing?
The real issue was having the html imports inside my component's <template>.
The 'wrong' order of events makes sense as iron-ajax is not even registered at the time when its host calls the 'ready' callback.
I've moved the imports outside <dom-module> and now everything works as expected.

Flux architecture with polymer elements

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.