How to tell when Polymer is done with all the data-binding? - polymer

Let's say I have a Polymer element x-foo which uses templates for data-binding.
<template>
<!--shadow DOM-->
<template repeat='{{item in items}}'>
<div class='content'>{{item}}</div>
</template>
</template>
items is a property of x-foo which decides what is present in the view.
Now, on the fly in one of the methods of x-foo I do:
this.items = getNewItemList();
and then try to access shadow DOM content,
this.shadowRoot.querySelectorAll('.content') // was supposed to return 5 elements
I find that Polymer still hasn't iterated through the template loop and generated my shadow DOM content. Is there a way to know when it has finished it?

By design, Polymer waits until your JavaScript has finished processing before it does expensive things like messing with DOM. That way you can do several operations at once and not worry about thrashing DOM and slowing down your application.
The short answer to your question is to do something like this:
this.items = getNewItemList();
this.async(
// `async` lets the main loop resume and perform tasks, like DOM updates,
// then it calls your callback
function() {
this.contents = this.shadowRoot.querySelectorAll('.content');
}
);
A better answer is to avoid needing to query for the elements. Instead, let the elements communicate with the container via events or even using the 'item' objects (the data model). If you can drive your UI from your data-model, and not the reverse, you will have a better time.

Related

Component initialization order in polymer 2

We are migrating a medium sized app from polymer 1 to polymer 3. Thus far we are stuck in the intermediate step of getting our hybrid components to work.
We are encounting some difficulties regarding component initialization timing. For example:
<my-app>
<my-component slot='componentslot'><my-component>
</my-app>
It seems there are cases where my-component is initialized before my-app is initialized. It might vary wether my-component is part of shadow- or light-dom.
We have a lot of tightly coupled components which depend on deterministic initialization order. For example there is a tree-like structure where every edge and every leaf uses events to discover it's own depth in the tree. Therefore we need top-level elements to be initialized before inner components.
But what we found so far was essentially: There is no garantuee for any initialization order of the components.
Is there an established pattern for solving this problem? Will this problem be solved in polymer 3 (so we don't need to care about it anyway)?
Edit
I was asked for some more specific examples
Example 1
<my-layout>
<my-complex-component id="1">
<my-reuseable-part/>
</my-complex-component>
<my-complex-component id="2">
<my-reuseable-part/>
</my-complex-component>
<some-other-component>
<my-reuseable-part/>
</some-other-component>
</my-layout>
I have some reuseable components which need to know if they are inside my-complex-component orsome-other-component. my-complex-component uses a context-discovery-behavior which fires an event containing a callback as payload. my-complex-component and some-other-component have context-behaviors which listen to that event and answer it by invoking the callback.
But as my-reusable-part might be attached before my-complex-component or some-other-component is attached, this pattern does not work.
Registration of event listeners as well as firing the disovering event is done in attached (i.e. connectedCallback).
Example 2
<my-tree>
<my-tree-edge>
<my-tree-edge>
<my-leaf/>
<my-tree-edge>
<my-leaf/>
</my-tree-edge>
</my-tree-edge>
<my-tree-edge>
<my-leaf/>
</my-tree-edge>
<my-leaf/>
</my-tree-edge>
</my-tree>
In the example above every leaf and edge needs to know how deep it is nested. Again every elements fires an event and its parent will answer the event. Again listener registration and event-firing is done in attached/connectedCallback. Again the mechanik fails if an inner node is attached before it's parents are attached.
Hope this helps.
You can use dom-if element if you stricly want to be sure first render my-app then you can let render my-component something like:
<my-app ready="{{myAppReady}}>
<template is='dom-if' if="[[myAppReady]]">
<my-component slot='componentslot'><my-component>
</template>
</my-app>
at my-app script:
static get properties(){return {
ready:{type:Boolean,
notify:true,
value:false
}}
at this part, you may add computed:"checkThisValuesToBeSUre(x,[y]..) in order to be sure if depended to some values or you may add various conditions in order to render my-component
Also, you may import my-component.js dynamically like:
At my-app 's parent script:
static get observers(){return ['_checkMyAppReady(myAppReady)']}
_checkMyAppReady(r){
if(r) import('./my-component.js');
}
EDIT
If there are many elements occurs the same problem, then better to use lazy-import.js:
_checkMyAppReady(r){
if(r) import('./lazy-import.js');
}
lazy-import.js
import './my-component.js';
import './my-component2.js';
import './my-component3.js';
...

Dynamically injecting a template in Polymer 1.0

I'm trying to inject and display a <template> the right way into a Polymer Webapp, but I have a few difficulties with it. (… or maybe I misunderstood the 1.0 Documentation?)
The documentation about manipulation the DOM says:
Polymer provides a custom API for manipulating DOM such that local DOM and light DOM trees are properly maintained. These methods and properties have the same signatures as their standard DOM equivalents, except that properties and methods that return a list of nodes return an Array, not a NodeList.
Note: All DOM manipulation must use this API, as opposed to DOM API directly on nodes.
So I guess I have to use Polymer.dom API everywhere to manipulate the DOM, which makes sense to me, because this way Polymer can stay in sync with the generated shady DOM. No DOM.appendChild(), instead Polymer.dom().appendChild(). And manipulating the shady DOM directly wouldn't be a great idea … or would it?
Imagine a simple page structure:
<body>
<template is="dom-bind" id="app">
<main>
<template is="dom-bind" id="content">
<p>Lorem ipsum dolor sit amet.</p>
</template>
</main>
</template>
</body>
And a second small snippet which I can import into the page.
<template id="snippet">
<p>Consectetur adipisici elit.</p>
</template>
This template should be replaced/referenced with the #content. So, let's start.
Importing the snippet is easy. I can fetch it and get the DOM Element of it.
Polymer.Base.importHref('/snippet', function(e) {
// on success: imported content is in e.target.import
var template = Polymer.dom(e.target.import).querySelector('#snippet');
// until here it works, `template` is the template from my snippet
...
Now I guess I have to append this to my template#app and change the ref of template#content to content… if changing the ref is still supported? And how am I supposed to do that? I get stuck every time, no matter how I approach this.
var app = Polymer.dom(this).querySelector('#app'); // Works, is template#app
var app = document.querySelector('#app'); // Same result
Polymer.dom(app).appendChild(template); // will append it, but outside of the document fragment
Polymer.dom(app.root).appendChild(template); // won't do anything
Polymer.dom(app).querySelector('template'); // undefined
Polymer.dom(app.root).querySelector('template'); // undefined
app.querySelector('template'); // undefined
I looked hours and days into this, trying to find a solution. It works with the standard DOM API, but I don't think that's the right way to do this. If somebody could solve my confusion, it would be really great.
EDIT: Or will Polymer.dom(this) do it's thing and I don't need to call Polymer.dom(app)? But again, I tried it and it won't work. Aaargh, it's just so confusing.
If I understood you correctly and you want to insert the template to local dom (inserting it somewhere else doesn't really make sense) then it's Polymer.dom(this.root).appendChild.
From https://www.polymer-project.org/1.0/docs/devguide/local-dom.html#dom-api: In order to insert/append into the local dom of a custom element, use this.root as the parent.

How can I know that Template Repeat has finished?

Element needs some time for template-repeat to render all content, so paper-spinner is used to notify the user to wait.
How can I know that template-repeat has finished so I can turn off the spinner?
And related question: how can inner element "item-details" be selected? Again, template-repeat has to be finished first.
Here's the code I am using:
<polymer-element name="item-list">
<template>
<paper-spinner active></paper-spinner>
<template id="repeat_items" repeat="{{ item in car.items }}">
<item-details id="item_details" item="{{item}}"></item-details>
</template>....
This is some simulation of the problem: plnkr.co
Edit
links from research:
spinner example
why does onmutation disconnect after first mutation?
polymer-how-to-watch-for-change-in-content-properties
There are component lifecycle hooks.
You are probably looking for domReady.
Called when the element’s initial set of children are guaranteed to exist. This is an appropriate time to poke at the element’s parent or light DOM children. Another use is when you have sibling custom elements (e.g. they’re .innerHTML‘d together, at the same time). Before element A can use B’s API/properties, element B needs to be upgraded. The domReady callback ensures both elements exist.
Polymer('tag-name', {
domReady: function() {
// hide the spinner
// select the first item details element
}
});
As for selecting elements, you can traverse the component's shadow dom like so:
this.shadowRoot.querySelector(selector);
EDIT...
The domReady hook is great if you have all of your data up-front. If you get data asynchronously, then you can use a change watcher.
Here's is a fork of your plunkr that successfully selects the child components after the data changes. Notice the setTimeout(f, 1) that defers selection until after the DOM updates.
carsChanged: function(){
var _this = this;
setTimeout(function(){
console.log(_this.shadowRoot.querySelectorAll('item-details'))
},1)
}
I suggest something like this - http://jsbin.com/bifene/4/edit
Leverages Polymer's onMutation function to watch for changes to a DOM node. Note that it only gets called once so you'll need to re-register it every time you load new items & restart the spinner.

How to write effective polymer element

I am writing a web site front-end by polymer, but I found that sometimes it response slowly, so can you give me any common tips to improve performance?
And I have some questions:
When show/hide some fields, which is better: <... hidden?= "{{property}}"> vs <template if = "{{property}}">;?
Can I unbind some default events? For example, I have a block <span>{{name}}</span>, this.name won't change once set, can I unbind all events relate to it to improve performance?
Can some fields won't be rendered until they are scrolled into the window?
Suggest there is a block inside a polymer-element: <div id = 'container'></div>, and content inside it will be generated by user's interaction. When I set this.$.container.innerHTML = '<some-other-polymer-element></some-other-polymer-element>', I got a warning: "bind prior to ....", and it renders slowly, but using <template repeat> will attach too many events, what's the better way?
Many thanks~~
1) <template if="..."> is slower because it needs to remove/add nodes but if you have big parts of the DOM you show hide and you don't do this often it might be advantageous because the DOM becomes smaller.
2) You can use one-time binding [[ ... ]] instead of {{...}}
3) You have to code this yourself <template if="..."> or similar
4) it depends/not enough information

Polymer paper-dropdown's core-select event fired by another element's core-selector

I have run into a problem where paper-dropdown element's on-core-select event is being fired by a core-selector belonging to a separate element in my polymer app. Here are excerpts from the polymer element that includes the paper-dropdown (along with the polymer event script):
<paper-dropdown id="widthUnits" class="unitSelection" selected="{{item.data.designWidth[1]}}" on-core-select="{{ conditionUnitSelectionChanged }}" valueattr="label">
<paper-item label="mm"></paper-item>
<paper-item label="cm"></paper-item>
<paper-item label="m"></paper-item>
</paper-dropdown>
conditionUnitSelectionChanged: function(e, detail, sender) {
// Ensure the selection has in fact changed
if (sender.selected != detail.item.label)
{
this.ajaxUpdateUnit(sender);
}
},
And here is the core-selector and related code that is part of an entirely different element within the application. FYI, SelectedItemKey is watched by polymer 'Changed' events in both of the elements involved...if that matters.
<core-selector id="itemSelector" target="{{$.itemList}}" multi="false" selected="{{selectedItemKey}}" selectedAttribute="active"></core-selector>
<div id="itemList" layout horizontal wrap>
<template repeat="{{item, i in items}}">
<div id="{{item.name}}">
<entriesHeading name="{{item.name}}" horizontal layout center>
<div flex>{{item.name}}</div>
<paper-menu-button icon="more-vert" halign="right">
<paper-item label="Edit" on-tap="{{ itemEdit }}"></paper-item>
<paper-item label="Copy" on-tap="{{ itemCopy }}"></paper-item>
<paper-item label="Delete" on-tap="{{ itemDelete }}"></paper-item>
</paper-menu-button>
</entriesHeading>
<entriesContainer vertical layout>
*** container contents ***
</entriesContainer>
</div>
</template>
</div>
Any suggestions on how I can avoid this unwanted interplay with core-select events? Perhaps a specific listener of some sort (limited to listening for paper-dropdown(s) core-select event)?
It's not possible for the paper-dropdown to receive an event from anywhere but inside it's own subtree. You have to present a jsbin or some kind of reproduction, otherwise I must suggest your diagnosis is incorrect.
You should try to figure out what is going on with the events, to make sure you have good understanding of the system.
Having said that, another way of approaching the problem is by being data-driven and not control-driven.
IOW, it's best to react to data-changes instead of events. It's hard to give really good suggestions because I can only see a tiny piece of your application, but here are some suggestions:
You have
<paper-dropdown id="widthUnits" class="unitSelection"
selected="{{item.data.designWidth[1]}}"
on-core-select="{{ conditionUnitSelectionChanged }}" valueattr="label">
It's a bit unfortunate that this important data is referenced as item.data.designWidth[1]. Generally one wants to factor the application data so that you aren't using deeply nested expressions like that. Just as an example, if you could build a UI like design-width-editor and bind it to <design-width-editor designWidth="{{item.data.designWidth[1]}}"> then you could put logic inside of design-width-editor that just deals with designWidth and doesn't need to know about item or data. This gives you a lot more flexibility with your data structures and makes it easier to think about.
In any case, given the construction you have, one thing you could do is observe the data directly:
observe: {
'item.data.designWidth[1]`: 'designWidth1Changed'
}
Now you can implement designWidth1Changed() to take the needed action. The key bit here is that you are no longer dependent on any particular UI for modifying the designWidth data. You can replace that UI at will; all that matters is that if the value changes, some action is taken.
Scott put me on the right track. After some refactoring as described in the previous comments, I used async to my advantage in order to avoid observers executing when I didn't want them to (such as when the elements model item object changed...and therefore all of its observed properties). Here is a sample of some of the script code from the host element mentioned above that was implemented to resolve the final issue:
ignoreChanges: null,
observe: {
'item.data.designWidth[0]': 'designWidthValueChanged',
'item.data.designWidth[1]': 'designWidthUnitChanged',
}
designWidthValueChanged: function(oldVal, newVal) {
if (!this.ignoreChanges) {
// send update of width value input via ajax
this.ajaxUpdateCondition("designWidth", newVal, this.item.data.designWidth[1]);
}
},
designWidthUnitChanged: function(oldVal, newVal) {
if (!this.ignoreChanges) {
// send update of width unit selection via ajax
this.ajaxUpdateCondition("designWidth", this.item.data.designWidth[0], newVal);
}
},
itemKeyChanged: function(oldVal, newVal) {
// itemKey is a published attribute that is 2 way bound to a parent element (where item selection occurs from a collection)
this.toggleIgnoreChanges(true); //set flag to ignore changes to observed values while item object switches
this.item = this.items[newVal]; //point to the correct/selected item in the collection (items is published attribute of this element)
this.async(this.toggleIgnoreChanges); //reset flag after observe functions have executed
},
toggleIgnoreChanges: function(flagstatus) {
this.ignoreChanges = flagstatus || !this.ignoreChanges;
}