How can i bind a dynamic function within a polymer component? - polymer

As far as my Polymer knowledge goes I can
bind a function using the "on-*" syntax to a webcomponent method
bind a function available in the global window namespace using vanilla html js binding (using onClick="...")
But I want to bind a function (provided as property of datamodel objects) to the webcomponent template.
One sidenote : Moving the datamodel objects to the global javascript namespace (i.e. window.*) is not an option.
The example below does'nt work but reflects exactly my use case :
...
Polymer('x-foo', {
items : [
...,
{
label : "ExampleCommand",
action : function() {
// do something
}
}
...
]
})
...
<template>
<template repeat="{{item in items}}">
<paper-button onClick="{{item.action}}">
{{item.label}});
</paper-button>
</template>
</template>
...
one more question if someone has an idea how to solve the question above) : how can i provide additional arguments to function ?
Any help is appreciated :-)

I had to ask the team about this because it's kinda confusing. Declarative event "bindings" are not the same thing as a Polymer expression. Unfortunately, both event bindings and Polymer expressions use the {{ }} syntax, which implies they work the same. They don't. The scope of event bindings is the element itself, whereas as an expression is scoped to the model for the template instance.
In Polymer 0.8, I believe the syntax has changed, so event bindings no longer use {{ }}. Hopefully that will clear it up a bit.
To achieve the effect you want, you can define a method on the element, which looks at the event target, grabs its model, and calls the function you've defined.
<polymer-element name="x-foo">
<template>
<template repeat="{{items}}">
<button on-click="{{doAction}}">{{label}}</button>
</template>
</template>
<script>
Polymer({
items: [
{
label: 'ExampleCommand',
action: function() {
alert('hello world');
}
},
{
label: 'AnotherCommand',
action: function() {
alert('another command');
}
}
],
doAction: function(e) {
e.target.templateInstance.model.action();
}
});
</script>
</polymer-element>
Here's the example running on jsbin

Related

this.shadowRoot.querySelector('#upload') return always null Polymer 2.x

I'm having trouble accessing an element in the following dom-if template to add a custom event listener:
<template is="dom-if" if=[[!uploaded]]>
<vaadin-upload id="upload" target="http://localhost:3000/uploadFile" form-data-name="doc" max-files="1">
<iron-icon slot="drop-label-icon" icon="description"></iron-icon>
<span slot="drop-label">....</span>
</vaadin-upload>
</template>
<template is="dom-if" if="[[uploaded]]">
<pdf-element src=[[....]]></pdf-element>
</template>
I tried to add event listeners for upload-response and upload-success in the connectedCallback this way:
connectedCallback() {
super.connectedCallback();
console.log(this.shadowRoot.querySelector('#upload'))
this.shadowRoot.querySelector('#uploadFile').addEventListener('upload-response', event=>this._uploadFile(event))
this.shadowRoot.querySelector('#uploadFile').addEventListener('upload-success', event=>this._uploadFileSuccessful(event))
}
The uploaded property is false by default. I searched in the Polymer 2.0 docs, and didn't find a way to make it work. I always get null.
I have already used this.shadowRoot.querySelector in dom-repeat template without a problem. How do I make querySelector work with the dom-if?
The <dom-if> contents aren't yet stamped in the connectedCallback even though the if condition is true, so you'd have to wait until the next render-frame with Polymer.RenderStatus.afterNextRender():
Polymer.RenderStatus.afterNextRender(this, () => {
const uploader = this.shadowRoot.querySelector('#upload');
uploader.addEventListener('upload-success', () => { /* ... */ });
});
demo

How to properly bind iron-selector to iron-pages?

I am trying to do the same basic task as this other question, iron-selector selected={{}} binding with iron-pages selected={{}}, but I believe my case must be different, since I cannot replicate the solution.
I want to have a menu of buttons with iron-selector, and when clicked, the content with iron-pagesshould change.
So I have the actual page about.htm that has 1 webcomponent for the button-menu, called about-buttons.htm and then 1 webcomponent for each page, that should load according to which button is pushed by the user; about-who.htm, about-manifesto and about-team.
My question is:
How can I with this structure of my web components bind my buttons with my pages - and / or why is my current method wrong?
There's a lot of different ways to do a simple data binding like this. My method here is definitely not simple, and it does not work (by clicking buttons, the pages does not change).
So my about.htm looks like this (and this is the page people will visit):
<about-buttons selected="{{who}}">
</about-buttons>
<about-iron-pages attr-for-selected="name" selected="{{who}}" fallback-selection="who">
<about-us name="who">
</about-us>
<about-manifesto name="manifesto">
</about-manifesto>
<about-team name="team">
</about-team>
</about-iron-pages>
My about-buttons.htm looks like this:
<iron-selector
attr-for-selected="name"
selected="{{buttonSelected}}"
fallback-selection="who"
class="f-column f-column_3 f-column_mobile_2">
<button class="f-button-group__button" name="manifesto">Manifesto</button>
<button class="f-button-group__button" name="who">Who we are</button>
<button class="f-button-group__button" name="team">Team</button>
</iron-selector>
With this script:
<script>
Polymer({
is: 'about-buttons',
properties: {
buttonSelected: {
type: String,
notify: true,
value: 'who'
}
}
});
</script>
And here's my version of iron-pages:
<dom-module id="about-iron-pages">
<template>
<style>
:host {
display: block;
}
:host > ::content > :not(.iron-selected) {
display: none !important;
}
</style>
<content>
</content>
</template>
<script>
Polymer({
is: 'about-iron-pages',
behaviors: [
Polymer.IronResizableBehavior,
Polymer.IronSelectableBehavior
],
properties: {
activateEvent: {
type: String,
value: null,
}
},
observers: [
'_selectedPageChanged(selected)'
],
_selectedPageChanged: function(selected, old) {
this.async(this.notifyResize);
}
});
</script>
</dom-module>
As already pointed out, your attribute in <about-buttons selected="..."> does not match the actual property name. For <about-buttons>.buttonSelected, your attribute should be button-selected, and that's the only change to your code needed to get the selector working (plunker):
<about-buttons button-selected="{{who}}" ...>
Perhaps there's more context to your need for <about-buttons> and <about-iron-pages>, but otherwise, if you're only trying to implement a tabbed view, you could just use Polymer's components.
Specifically, <about-buttons> could be replaced by <paper-tabs> and <about-iron-pages> by <iron-pages> (plunker).

Data binding between published properties of two custom elements inside an auto binding template - Polymer 1.0

Problem: I have an auto binding template in my main index.html page. Inside the template I am using two of my custom elements. One element is the producer of some data and the other one is the consumer of that data. These custom elements expose published/declared properties for each other to use and bind to. I was able to do that in Polymer 0.5 fairly easily (an example shown below). How do I do the same in Polymer 1.0?
How I used to do in Polymer 0.5?
In Polymer 0.5 I used to data bind between published properties of two custom elements using curly brace syntax and then inside it used the auto node finding concept to directly bind to other element's published property. An example shown below,
<template is="auto-binding">
<my-navigation selectedLabel="Home" id="my_navigation"></my-navigation>
<my-scaffold toolbartitle="{{ $.my_navigation.selectedLabel }}" id="my_scaffold"></my-scaffold>
</template>
I tried something similar in Polymer 1.0 as shown in the example below
<template is="dom-bind">
<my-navigation selectedLabel="Home" id="my_navigation"></my-navigation>
<my-scaffold toolbartitle="{{ $.my_navigation.selectedLabel }}" id="my_scaffold"></my-scaffold>
</template>
But it throws an error:-
Uncaught TypeError: Cannot read property '$' of undefined
You can't do $.* bindings inside the template in Polymer 1.0. Instead, either refactor or use computed functions.
In your situation, since selectedLabel and toolbartitle shares the same value, it is much better to simply bind them to the same property.
Also, attribute names that are declaratively passed in (through the element tag) need to be serialized, so selectedLabel becomes selected-label.
<body>
...
<template id="tpl" is="dom-bind">
<my-navigation selected-label="{{myLabel}}" id="my_navigation"></my-navigation>
<my-scaffold toolbartitle="{{myLabel}}" id="my_scaffold"></my-scaffold>
</template>
<script>
...
window.addEventListener("WebComponentsReady", function (e) {
document.querySelector("#tpl").myLabel = "Home";
...
});
...
</script>
</body>
There is probably a better way to do that, but you can try this:
<body>
<template id="app" is="dom-bind">
<my-navigation selectedLabel="Home" id="my_navigation"></my-navigation>
<my-scaffold toolbartitle="{{ selectedLabel }}" id="my_scaffold"></my-scaffold>
</template>
<script>
var app = document.querySelector('#app');
app.addEventListener('template-bound', function () {
console.log('Our app is ready to rock!');
});
window.addEventListener('WebComponentsReady', function () {
document.querySelector('body').removeAttribute('unresolved');
var my-navigation = document.querySelector('my-navigation');
// This will add the variable to the 'app' context (template)
app.selectedLabel = my-navigation.selectedLabel;
});
</script>
</body>

Calling a polymer element within a polyment with JSON as parameter

I am calling a polymer element within another element. The inner polymer element has a published attribute to which I am binding JSON from the parent polymer. However it is not getting reflected.
<polymer-element name="parent-test" attributes="testData">
<template>
This is Parent test
<child-test testdatachild="{{testData}}"></child-test>
</template>
<script>
Polymer('parent-test', {
testData: [],
ready: function () {
debugger;
this.testData = [1, 2, 3, 4]
}
});
</script>
</polymer-element>
<polymer-element name="child-test" attributes="testDataChild">
<template>
<!--{{testDataChild}}-->
<template repeat="{{test in testDataChild}}">
{{test}}
</template>
</template>
<script>
Polymer('child-test', {
testDataChild: [],
ready: function () {
debugger;
}
});
</script>
</polymer-element>
I am not sure what could be the problem here.
Edit:
Seems like I am not having the actual parentContent at the time of generating the child-polymer-element.
If I assign hardcoded values in ready function for this.parentContent, it doesnt work as well.
If I assign hardcoded values in create function for this parent.Content, it works.
So, I am not not sure if this is something related to generating the child polymer element before the values getting binded to parent.
Thanks,
Sam
I modified your plunk example and get it working without your workaround :
Plunk
<polymer-element name="child-test" attributes="testdatachild">
<template>
<br><br>
In Child el.:
<br>
<template repeat="{{test in testdatachild}}">
{{test}}
<br>
</template>
</template>
<script>
Polymer('child-test', {
ready: function () {
}
});
</script>
This is Parent test
<child-test testdatachild="{{testData}}"></child-test>
<br>
</template>
<script>
Polymer('parent-test', {
created: function () {
this.testData = [1, 2, 3, 4];
}
});
</script>
The main problem seems to be the order of the code
I guess it works better to first declare the child, then the parent, as the child is used in the parent...
Also, as specified in the polymer documentation :
polymer
Important: For properties that are objects or arrays, you should always initialize the properties in the created callback. If you set the default value directly on the prototype (or on the publish object), you may run into unexpected “shared state” across different instances of the same element.
Here is modified example of you code that works : Plunk
Why your example is not working, I don't have all answers buy you are right for one:
<!-- This won't work cause:
"Attributes on child-test were data bound prior to Polymer upgrading the element.
This may result in incorrect binding types." -->
This is Parent test
<child-test testdatachild="{{testData}}"></child-test>

Custom filter not working in objects

How to make my custom filter work using bind?
Not Working Example:
JSON:
{ "name": "Adrian" }
HTML:
<template bind="{{user}}">
<p>{{name | filterName}}</p>
</template>
But it works normally when i use repeat.
Working Example:
JSON:
[
{ "name": "Adrian 1" },
{ "name": "Adrian 2" }
]
HTML:
<template repeat="{{user in users}}">
<p>{{user.name | filterName}}</p>
</template>
If you had defined filterName as a function under the elements's prototype...
Polymer('my-element', {
filterName: function(value){
return value.toUpperCase()
}
});
When we do
<template>
{{ user.name | filterName }}
<template>
you have access to your element and its properties 'user', 'users' and the filterName callback.
When you do
<template>
<template bind="{{user}}">
{{name | filterName}}
</template>
</template>
Your outer template has access to user and filterName.
But your inner template is now bound to see only the user object. Your scope is limited to user now. This is a special case when you use bind.
More info here... https://github.com/PolymerLabs/polymer-patterns/blob/master/snippets/basics/using-bind-to-create-a-single-template-instance.html
Nevertheless, there are options for you:
1- Less than ideal -> add the callback as a property in your object. Your model now is responsible for dom transformations. Sucks!
2- If you were to reuse the filter you can turn it into a global expression
PolymerExpressions.prototype.filterName = function (value) {
return value.toUpperCase();
};
And now you can use anywhere.