How can I know that Template Repeat has finished? - polymer

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.

Related

How to use Polymer lifecycle hooks

I have a component that contains video. My component is nested in a dom-if and can disappear. When this happens the video (and it sound) keep playing.
Is there a way in which my component can detect that is has disappeared from the DOM? I have tried to use the 'detached' callback as described here: https://www.polymer-project.org/1.0/docs/devguide/registering-elements
Polymer({
is: 'my-component-with-video',
properties: {
// some properties
},
detached: function() {
console.log('Component detached');
// more code to stop video
},
but when my element is removed by the dom-if nothing happens, I don't see the console.log message. What am I doing wrong?
There are two scenarios possible here:
You want your element to be discarded and recreated fresh when the condition changes.
You want to keep it in the dom but freeze it.
In the first case you need to add the restamp attribute to the dom-if to make sure the template DOM is destroyed, not hidden. By default the dom-if stamps the template at first initialization and then hides it from the view if the condition becomes falsy.
In the second case, the suggestion given by Intervalia will not work, because the dom-if in "hide" mode does not detach anything from the DOM. Setting restamp attribute will make the detached callback run but then no point in pausing anything since the element will be discarded.
If you want to keep it in the DOM and freeze it's state you need to listen to dom-change event on the dom-if and run the .pause() accordingly.
No need for any workaround other than simply using your dom-if and rather than
<dom-if if="[[condietionBoolean]]">
<your-video-element id="giveanId"></your-video-element>
</dom-if>
write the if statement like below and so each time your condition changes, you check and make sure the video is paused if when you like. See below.
...
<dom-if if="[[_shouldShowVideo(conditionBoolean)]]">
<your-video-element id="giveanId"></your-video-element>
</dom-if>
...
Polymer({
is: 'my-component-with-video',
properties: {
conditionBoolean : {
type: Boolean,
},
},
_shouldShowVideo: function(conditionBoolean ) {
if (!conditionBoolean) this.$$(#yourVideoElementId).pause();
return conditionBoolean ;
}
});
In your detached function you need to get the Video Element and call .pause() on it.
You will probably also need to call .pause() when your condition changes that would cause the dom-if to remove the player.

How to access Polymer functions from JS

Sorry if this comes out a bit garbled, I'm not sure how to ask this question.
What I am trying to do is keep the DOM synced with a localStorage value, and am updating the localStorage value with an interact.js mouse event.
Currently, I am able to properly set the localStorage value, but am having problems updating the DOM.
My current build is within the Polymer framework, so I am having trouble selecting shadow DOM content.
The DOM tree looks like
PARENT-ELEMENT
# SHADOW ROOT
EL
EL
DIV
CUSTOM ELEMENT
EL
EL
Here are some ways I have failed to solve the problem. The Custom Element is in pure JS, since I am not sure how to properly wrap interact.js function in Polymer:
I tried directly accessing the PARENT-ELEMENT's shadow DOM from the Custom Element in pure JS.
var shadowDOMNode = document.querySelector('PARENT-ELEMENT');
var dom_object_1 = shadowDOMNode.querySelector('#dom_object_1');
dom_object_1.innerHTML = localStorage.dom_object_1;
I tried selecting a helper updateDOM() function from the PARENT Polymer element and running it from the Custom Element's setter directly.
if (event.dy > 0) {
this.$$('PARENT-ELEMENT').updateDOM();
}
Maybe I am taking the wrong approach entirely, but I haven't been able to find analogues for interact.js in using native Polymer functions.
I hope this question was clear enough...
If we ignore the interact.js part of the problem and focus on Polymer, you could probably solve this without coupling the two.
To bind to a localStorage value with Polymer, use the <iron-localstorage> element. In the following example, the localStorage value named flavor_1_amount is loaded and stored into a property named _flavor1Amount. If the value doesn't exist in localStorage or is empty, the <iron-localstorage> element fires an event (iron-localstorage-load-empty), which allows you to bind to a callback (e.g., to initialize it).
<iron-localstorage name="flavor_1_amount"
value="{{_flavor1Amount}}"
use-raw
on-iron-localstorage-load-empty="_initFlavor1Amount">
</iron-localstorage>
In the same element, you could provide an input for the user to update the localStorage value.
<paper-input label="Flavor Amount (mL)" value="{{_flavor1Amount}}"></paper-input>
And you can use <iron-localstorage>.reload() to keep your data binding in sync, assuming it could be changed externally.
See this codepen for a full demo. Check your localStorage from Chrome DevTools:
Generally speaking you should use this.set() or any of the array mutation methods if it's an array in order for the ShadowDOM to be notified properly.
Since you want to perform this update from outside the element itself, imperatively, I'd suggest this:
Expose a couple of methods from your element that you can use to add/remove/change property values from outside your element.
These methods would internally use the proper channels to make the changes.
An example (you can call addItem() to add items from outside your element):
<base href="https://polygit.org/components/">
<script src="webcomponentsjs/webcomponents-lite.min.js"></script>
<link href="polymer/polymer.html" rel="import">
<dom-module id="x-example">
<template>
<template is="dom-repeat" items="[[data]]">
<div>{{item.name}}</div>
</template>
</template>
<script>
HTMLImports.whenReady(function() {
"use strict";
Polymer({
is: "x-example",
properties: {
data: {
type: Array,
value: [
{name: "One"},
{name: "Two"},
{name: "Three"}
]
}
},
// Exposed publicly, grab the element and use this method
// to add your item
addItem: function(item) {
this.push("data", item);
}
});
});
</script>
</dom-module>
<x-example id="x-example-elem"></x-example>
<script>
setTimeout(function() {
// simply 'grab' the element and use the
// `addItem()` method you exposed publicly
// to add items to it.
document.querySelector("#x-example-elem").addItem({name: "Four"});
}, 2500);
</script>
Important: That being said, this is not the "Polymeric" way of doing stuff as this programming-style is imperative, in constrast with Polymer's style which is more declarative. The most Polymeric solution is to wrap your interact.js functionality in an element itself and use data-binding between your 2 elements to perform the changes.

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;
}

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

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.

mootools - using addEvent to element not working properly?

bangin' my head against this and it's starting to hurt.
I'm having trouble with adding an event to an element.
I'm able to add the event, and then call it immediately with element.fireEvent('click'), but once the element is attached to the DOM, it does not react to the click.
example code:
var el = new Element('strong').setStyle('cursor','pointer');
el.addEvent('click',function () { alert('hi!'); });
el.replaces(old_element); // you can assume old_element exists
el.fireEvent('click'); // alert fires
however, once I attach this to the DOM, the element is not reactive to the click. styles stick (cursor is pointer when I mouseover), but no event fires. tried mouseover as well, to no avail.
any clues here? am I missing something basic? I am doing this all over the place, but in this one instance it doesn't work.
EDIT----------------
ok here's some more code. unfortunately I can't expose the real code, as it's for a project that is still under tight wraps.
basically, the nodes all get picked up as "replaceable", then the json found in the rel="" attribute sets the stage for what it should be replaced by. In this particular instance, the replaced element is a user name that should pop up some info when clicked.
again, if I fire the event directly after attaching it, all is good, but the element does not react to the click once it's attached.
HTML-----------
<p>Example: <span class='_mootpl_' rel="{'text':'foo','tag':'strong','event':'click','action':'MyAction','params':{'var1': 'val1','var2': 'val2'}}"></span></p>
JAVASCRIPT-----
assumptions:
1. below two functions are part of a larger class
2. ROOTELEMENT is set at initialize()
3. MyAction is defined before any parsing takes place (and is properly handled on the .fireEvent() test)
parseTemplate: function() {
this.ROOTELEMENT.getElements('span._mootpl_').each(function(el) {
var _c = JSON.decode(el.get('rel'));
var new_el = this.get_replace_element(_c); // sets up the base element
if (_c.hasOwnProperty('event')) {
new_el = this.attach_event(new_el, _c);
}
});
},
attach_event: function(el, _c) {
el.store(_c.event+'-action',_c.action);
el.store('params',_c.params);
el.addEvent(_c.event, function() {
eval(this.retrieve('click-action') + '(this);');
}).setStyle('cursor','pointer');
return el;
},
Works just fine. Test case: http://jsfiddle.net/2GX66/
debugging this is not easy when you lack content / DOM.
first - do you use event delegation or have event handlers on a parent / the parent element that do event.stop()?
if so, replace with event.preventDefault()
second thing to do. do not replace an element but put it somewhere else in the DOM - like document.body's first node and see if it works there.
if it does work elsewhere, see #1
though I realsie you said 'example code', you should write this as:
new Element('strong', {
styles: {
cursor: "pointer"
},
events: {
click: function(event) {
console.log("hi");
}
}
}).replaces(old_element);
no point in doing 3 separate statements and saving a reference if you are not going to reuse it. you really ought to show the ACTUAL code if you need advice, though. in this snippet you don't even set content text so the element won't show if it's inline. could it be a styling issue, what is the display on the element, inline? inline-block?
can you assign it a class that changes it on a :hover pseudo and see it do it? mind you, you say the cursor sticks which means you can mouseover it - hence css works. this also eliminates the possibility of having any element shims above it / transparent els that can prevent the event from bubbling.
finally. assign it an id in the making. assign the event to a parent element via:
parentEl.addEvent("click:relay(strong#idhere)", fn);
and see if it works that way (you need Element.delegate from mootools-more)
good luck, gotta love the weird problems - makes our job worth doing. it wouldn't be the worst thing to post a url or JSFIDDLE too...