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

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");

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.

Stamping Template Async? Race condition issue

Polymer 1.7
Is stamping a template a async operation? It's not I/O so I don't know why it would be. However, there is a race condition here where this.$$('#uploadedImage1') is undefined. It works if I use <div hidden="[[foo]]"> instead of template is="dom-if", so this is how I know it is a race condition.
<template is="dom-if" if="[[uploadedImage1]]">
<div id="uploadedImage1" class="row-image horizontal layout">
</div>
</template>
...
ready: function(e) {
function readURL(e) {
var el = e.target.id
var uploadedID = e.target.dataset.uploaded;
var file = Polymer.dom(e).localTarget.files[0];
var reader = new FileReader();
reader.onloadend = ()=> {
this[uploadedID] = true;
//RACE CONDITION HERE
this.$$('#uploadedImage1').style.backgroundImage = `url(${reader.result})`;
}
reader.readAsDataURL(file);
}
this.$['images-container'].addEventListener('change', readURL.bind(this), false)
}
Short answer: yes, template stamping is async. Here, you could use an attribute binding to style to apply the background image directly, so you don't need to manipulate the stamped DOM.
If you really need to get at the stamped DOM, you can either a) wait for a dom-change event, or b) force a synchronous render by calling render. (The latter is usually an anti-pattern--you don't want to force something to happen synchronously if you don't have to.)
https://www.polymer-project.org/1.0/docs/devguide/templates#synchronous-renders
https://www.polymer-project.org/1.0/docs/devguide/templates#dom-change
Note the docs as written may imply that only dom-repeat is rendered async, but render and dom-change are available on both elements.
Hope that helps.

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.

Polymer custom element with template-as-content

In the code below, the content "Foo" of template#bar are always empty when I try to access it programatically or when inspecting the DOM in Chrome. Can someone explain why?
In general, how does one provide a template defined in an outer element to an inner element so the inner element can access the content and conditionally clone or import that content?
I am using polymer 0.4.2.
<polymer-element name="x-inner" noscript>
<!--
How can I access the content "Foo" of <template>Foo</template>,
So that I can import/clone it here?
Using <content> moves the template from x-outer to x-inner,
but the template's .content property is empty, instead of 'Foo' as I expected.
-->
<content></content>
</polymer-element>
<polymer-element name="x-outer" noscript>
<template>
<x-inner>
<!--
How can I pass a template to a custom element?
I don't want the contents of this template to be rendered
here in x-outer, but instead conditionally rendered by x-inner
-->
<template id="bar">Foo</template>
</x-inner>
</template>
</polymer-element>
<x-outer></x-outer>
This topic is potentially complicated, below is something to get you started.
(This is the third update to this answer, confirming the bit above about 'complicated' =P).
Polymer includes the TemplateBinding.js library.
The TemplateBinding.jslibrary imbues <template> with numerous features, including data-binding to models, conditional stamping, and replication/iteration via arrays. It also adds a feature whereby cloned nested templates do not replicate their own contents, preventing a possible explosion of useless nodes when iterating. Instead, TemplateBinding.js creates references in cloned-nested-templates to original content-ful templates. The upshot is that if you are using TemplateBinding.js you should use template.createInstance() API for best results.
Now, when using raw templates without TemplateBinding.js, you can stamp a template simply using var nodes = document.importNode(template.content, true). Of course, in this case you do not get the nested template replication optimization (which may or may not matter).
Note:
I removed the <content> node from the <x-inner>
template because it serves no purpose. The code below plucks the
template directly out of light-dom, and stamps the instance into the
shadow-root.
Declare x-inner before x-outer because the latter depends on the former.
Example code:
<x-outer></x-outer>
<polymer-element name="x-inner">
<template>
</template>
<script>
Polymer({
domReady: function() {
this.renderTemplate();
},
renderTemplate: function() {
// note: this only works if `template` is a true child of `this`
// (as opposed to projected)
var template = this.querySelector('template');
// use createInstance from TemplateBinding.js library API
this.shadowRoot.appendChild(template.createInstance());
/*
// this would work for raw templates, but Polymer includes TemplateBinding.js
this.shadowRoot.appendChild(stampTemplate(template));
*/
/*
// if you don't know whether TemplateBinding.js exists or not,
// you could do something like this (see stampTemplate definition below)
this.shadowRoot.appendChild(stampTemplate(template));
*/
/*
// this way uses the binding feature of TemplateBinding.js
template.setAttribute('bind', '');
template.model = { /* some data */ };
*/
}
});
// use best available API
function stampTemplate(template) {
if (template.createInstance) {
return template.createInstance();
} else {
return document.importNode(template.content, true);
}
}
</script>
</polymer-element>
<polymer-element name="x-outer" noscript>
<template>
<x-inner>
<template id="bar">Foo</template>
</x-inner>
</template>
</polymer-element>
http://jsbin.com/nemaha/14/edit