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()
}
}
})
Related
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.
I'm building a new webapp and I need to know how can I pass an object between 2 custom elements in polymer.
In the code below, I set the value of mydata in "my-child-element-1" and I need to see this value in "my-child-element-2"...I think that it's not very hard to do but i'm loosing my mind to find a good solution...
In my opinion, i should create a temporary object in "my-host-element" to share the value but i'm not convinced about this...
This is my code:
<dom-module id="my-host-element">
<template>
<my-child-element-1 mydata="{{mydata}}"></my-child-element-1>
<my-child-element-2 mydata="{{mydata}}"></my-child-element-2>
</template>
<script>
Polymer({
is: "my-host-element",
properties:
{
mydata: {
type: Object
}
}
});
</script>
</dom-module>
Thank you!!
Your example looks like it should work without the host element needing a property, if the property on the child elements are set up correctly. Remember that Polymer's data binding syntax is basically syntactic sugar around firing and handling custom events. So take a look in child element 1 and make sure that you've set the property to notify when changed. For example:
Polymer({
is: "my-child-element-1",
properties: {
mydata: {
type: Object,
notify: true // fire mydata-change CustomEvent on change
}
}
});
Yes, afaik it is correct to have the parent element act as the mediator between the children, which means it needs to have its own property even if its only used for that purpose.
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.
I want to set filters dynamically. Is it possible?
dynamicFilter is a variable with name of the Polymer expression.
<template is="auto-binding">
<span>{{value | dynamicFilter}}</span>
</template>
AFAIK, there is no handy way to assign Filter to element in runtime. But there is a simple workaround you might find useful:
We are to define the staticFilter function, which would be a proxy (wrapper) to calls to dynamicFilters. Assuming dynamic filters to be instances of PolymerExpression, this might be put together as follows:
<polymer-element name="my-element" attributes="dynamicFilter">
<template>
<span>{{value | staticFilter(dynamicFilter)}}</span>
</template>
<script>
PolymerExpressions.prototype.uppercase = function(input) {
return input.toUpperCase();
},
PolymerExpressions.prototype.lowercase = function(input) {
return input.toLowerCase();
},
Polymer({
value: '¡Hola!',
dynamicFilter: null,
staticFilter: function(v, df) {
return df ? PolymerExpressions.prototype[df](v) : v;
}
});
</script>
</polymer-element>
<my-element></my-element>
<my-element dynamicFilter='uppercase'></my-element>
<my-element dynamicFilter='lowercase'></my-element>
Now you are free to set the dynamicFilter attribute of my-element even in runtime.
The reason is that filters are compiled and bound during element initialization; for security reasons there is no eval behind and therefore you cannot simply pass the arbitrary dynamic value there. On the other hand, filters are ready to receive parameters and that fact actually does the trick. BTW, you might even pass the function instance there whether you are not satisfied with PolymerExpressions for this purpose.
Your use case is not really clear, but you could use this.injectBoundHTML as a workaround.
When you need to change the filter dynamically, just reinject the content of the span
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