Parent element is ready before its child - Polymer 1.0 - polymer

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.

Related

Data Bindings across template tags

I'm wondering, is there a possibility to have databindings "out of" a template? Say I have a <template/>-Tag somewhere which I put into the slot of a different component - that component stamps it to its context. Then I want to bind data from the root element to the <template/>-Tag. Also, event bindings (on-x-changed) don't work, because you can't assign a function which is defined in the hosting component. Any ideas?
Example:
... host
{{boundData}}
<binding-component>
<template>
{{boundData}}
</template>
</binding-component>
I don't see changes when I observe boundData in the hosting component. Is there a way to get around this? Or is firing a custom event my only chance?
If you are looking for binding a property outside of polymer something like from index.html you may bind value with element. an example ; index.html
<dom-bind>
<template>
<binding-component bound-data="{{boundData}}"></binding-component>
</template>
</dom-bind>
<script>
// set a value a string, Number or Object etc.
// Optionally wrap this code into a listener ie;
// window.addEventListener('load', e=> { ...below code ... })
var boundData= document.querySelector('dom-bind');
boundData = {} //
</script>
Now in your binding-component element has a property as boundData
hope its helps or provide more code to understand better.
I've made it work the way dom-if does it, too. Like in dom-if (reference), I'm creating a Templatize-instance which then uses forwardHostProp to handle the "inside"-properties
this.__ctor = Templatize.templatize(template, this, {
mutableData: true,
forwardHostProp(prop, value) {
// handling item updates, item being the only property
// from within the binding component
// everything else is automatically bound by templatize
this.set(prop, value);
this.update(this.item);
},
});
this.__instance = new this.__ctor();
this.root.appendChild(this.__instance.root);
This all happens in connectedCallback.
Because the Templatize-instance is passed this, it's bound to the current context as well.
Good luck!

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.

Initializing dynamic default values for sub-elements on creation

I'm trying to work out how to make dynamic values available to sub-elements before the local DOM is initialised. I've created a custom element with a Google Map element embedded in it:
<dom-module id="place-picker">
<template>
<google-map api-key="..." />
</template>
</dom-module>
Polymer({
is: "place-picker",
created: function() {
this.apiKey = someFunctionToRetreiveApiKey()
}
})
I want to provide the API key to the google-map element immediately. It needs to be before the DOM is initialised because the google-map element attempts to load immediately. But Polymer's properties are not evaluated . Using data binding api-key="[[apiKey]]" doesn't work because I haven't declared it as a property.
I worked it out in a slightly hacky fashion. The context of the created callback is a raw HTML element, so you can simply set its attribute, which for some reason is evaluated as a property before local DOM creation. I ended up with:
# CoffeeScript
Polymer
is: "place-picker"
properties:
googleWebApiKey:
type: String
created: ->
#setAttribute("google-web-api-key", functionToRetrieveApiKey())
# Slim HTML
template
google-map api-key="[[googleWebApiKey]]"
I prefer to use the "value" key of the properties object to set the value, but is should work similar to the created function mentioned above, you could also you the computed key.
<dom-module id="place-picker">
<template>
<google-map api-key="[[apiKey]]" />
</template>
</dom-module>
Polymer({
is: "place-picker",
properties : {
apiKey : {
type: String,
value: functionToRetrieveApiKey()
}
}
})

How to access content of dom-if inside a custom element?

In a custom element I want to access a span and append a child to it but all usual accessors give undefined:
<template>
<template is="dom-if" if="[[condition]]" restamp>
<span id="myspan"></span>
</template>
</template>
ready() {
var a = this.$.myspan; //<------- is undefined
var b = this.$$.myspan; //<------- is undefined
var c = document.getElementById("myspan"); //<------- is undefined
var d = this.$$("#myspan"); //<------- is undefined
}
How to access a span in this case?
UPDATE: here is plunk
The reason this didn't work inside the lifecycle callback without setTimeout or this.async is that right after attaching your element the dom-if template has not yet rendered. Upon attaching your element, Polymer calls the attached callback. However, when the value gets set on the the dom-if, an observer runs and debounces its own _render function. The debounce waits an amount of time to catch any other calls to it, and then it executes the ._render function and attaches the element to the DOM. In other words, when the attached callback runs, normally the dom-if template hasn't rendered yet.
The reason for this debounce is performance. If several changes were made within a very short span of time, this debounce prevents the template from rendering several times when the result we would care about is the end result.
Fortunately, dom-if provides a .render() method which allows you to make it render synchronously. All you need to do is add an id to your dom-if, switch to an attached callback and call like this:
<template>
<template id="someDomIf" is="dom-if" if="[[condition]]" restamp>
<span id="myspan"></span>
</template>
</template>
attached() {
this.$.someDomIf.render();
var c = document.getElementById("myspan"); //<------- should be defined
var d = this.$$("#myspan"); //<------- should be defined
}
Triggering a synchronous render on the dom-if shouldn't be a huge performance problem, since luckily your element should only be getting attached once.
Edit: As it turns it, this even works in a ready callback:
<template>
<template id="someDomIf" is="dom-if" if="[[condition]]" restamp>
<span id="myspan"></span>
</template>
</template>
ready() {
this.$.someDomIf.render();
var c = document.getElementById("myspan"); //<------- should be defined
var d = this.$$("#myspan"); //<------- should be defined
}
See this fork of your plunker:
http://plnkr.co/edit/u3richtnt4COpEfx1CSN?p=preview
Try to do it asynchronously in the attached method as follows, this method works:
attached: function(){
this.async(function(){
var d = this.$$("#myspan");
console.log(d);
},someTimeIfThereAreManyItemsToLoad);
}
The responses above only work if your condition is true initially. Please see my answer to your initial question that lead to this one :
https://stackoverflow.com/a/34137955/3085985
Not sure if you should mix in the .render-stuff from Dogs, but I still think the observer would be the right place for it as it otherwise does not work if condition is false initially.
As in Polymer documentation:
Note: Nodes created dynamically using data binding (including those in dom-repeat and dom-if templates) are not added to the this.$ hash. The hash includes only statically created local DOM nodes (that is, the nodes defined in the element’s outermost template).
You will need to use this.$$('#yourElementId");

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.