Polymer two-way binding between custom elements [example included] - javascript

I'm trying to make custom element within a custom element and have the inner custom element able to change its value from within and the outer able sync its binding on that change. What can I do to get this working?
I've thoroughly scoured the documentation, but it's very lackluster in this department. I believe Node.bind() may be something of interest, but not sure how it would apply in this case.
Here's a simplified test case and plunker demo:
<polymer-element name='test-app'>
<template>
First:
<test-input id='one' value='{{value}}'></test-input>
<br/>
<br/>
Second:
<test-input id='two' value='{{value}}'></test-input>
<br/>
<br/>
Value:
{{value}}
</template>
<script>
Polymer({
value: 5
})
</script>
</polymer-element>
<polymer-element name='test-input'>
<template>
<style>
#val {
font-size: 50px;
}
</style>
<div id='val'>{{value}}</div>
<button on-tap='{{increment}}'>+</button>
<button on-tap='{{decrement}}'>-</button>
</template>
<script>
Polymer({
publish: {
value: 4
},
increment: function() {
this.value = this.value + 1;
},
decrement: function() {
this.value = this.value - 1;
}
})
</script>
</polymer-element>
<test-app></test-app>
http://plnkr.co/edit/KjQ9DusaFg2jp1BTFUde?p=preview
If this was working, the value property of the test-app parent element should be in sync with both of the test-inputs value property.

Notice this warning in the console:
Attributes on test-input were data bound prior to Polymer upgrading the element. This may result in incorrect binding types.
test-app uses test-input before Polymer knows about test-input. All of an element's dependencies must be declared before that element is declared. Move test-input above test-app and it works as expected.
http://plnkr.co/edit/ZaIj60S3lAHT18k5T3sn?p=preview

Related

Passing an object to element attribute in Polymer 2

I am playing with Polymer 2.0, and I don't understand how to pass an object as an element attribute.
Here's my code:
<dom-module id="notes-app">
<template>
<style>
:host {
display: block;
}
</style>
<button on-click="loadNotes">Get the notes</button>
<template is="dom-repeat" items="[[notes]]" as="note">
<note recipe='JSON.stringify(note)'></note>
</template>
</template>
<script>
class NotesApp extends Polymer.Element {
static get is() { return 'notes-app'; }
static get properties() {
return {
notes: {
type: Array,
value: []
}
};
}
loadNotes() {
this.notes = [
{"desc":"desc1", "author":"auth1", "type":"type1"},
{"desc":"desc2", "author":"auth2", "type":"type2"},
{"desc":"desc3", "author":"auth3", "type":"type3"}
];
}
}
window.customElements.define(NotesApp.is, NotesApp);
</script>
</dom-module>
simple-note is the element who has a property of type Object:
<dom-module id="note">
<template>
<style>
:host {
display: block;
}
</style>
<div>
<fieldset>
<label>description</label>{{note.desc}}<br>
<label>author</label>{{note.author}}<br>
<label>type</label>{{note.type}}
</fieldset>
</div>
</template>
<script>
class SimpleNote extends Polymer.Element {
static get is() { return 'simple-note' }
static get properties() {
return {
note: {
type: Object,
value: {},
notify: true
}
};
}
}
customElements.define(SimpleNote.is, SimpleNote);
</script>
</dom-module>
As you can see I want note-app to display all the objects in its notes property by passing an object representing a note to every simple-note elements (don't known if it is the right way to make elements interact each other). I want it to happen when I press the notes-app button. How can I pass an object to an element attribute in this case?
Since you're trying to pass the variable as an object, you should use property bindings instead of attribute bindings (which only supports strings).
Polymer data bindings require curly or square brackets ({{twoWayBinding}} or [[oneWayBinding]]). For example, to set the foo property of the <x-child> element to the value of note, the template would look something like this:
<template is="dom-repeat" items="[[notes]]" as="note">
<x-child foo="[[note]]">
</template>
Given that SimpleNote.is equals "simple-note", I assume your usage of <note> and <dom-module id="note"> were only typos in your question. They should be set to simple-note, as the element name must start with a lowercase ASCII letter and must contain a dash.
It looks like you're binding a recipe property, but <simple-note> declares a note property (and no recipe) and binds to note sub-properties in its template. I assume recipe is another typo.
working demo

Polymer Check for Insertion Points

I'm starting to learn Polymer 1.0 and I couldn't figure out how to programatically search for insertion points. I realize I could wrap a <div> around the <content> tag and check if that <div> has children or not, but that requires rendering a <div> for every element, which seems wasteful. Is there a way, with JavaScript, to check if any insertion points have been loaded? Ideally, I'd have a function thereAreInsertionPoints which would determine whether or not the <p> tag would render. My Polymer code looks like this:
<template>
<h1>{{title}}</h1>
<p>{{body}}</p>
<content id="content"></content>
<p if="{{thereAreInsertionPoints()}}">There are insertion points!</p>
</template>
<script>
Polymer({
is: "post-content",
properties: {
title: String,
body: String
},
thereAreInsertionPoints: function(){
//determine whether or not we have insertion points
}
});
</script>
There are various Polymer APIs for working with the DOM including Content APIs.
Content APIs:
Polymer.dom(contentElement).getDistributedNodes()
Polymer.dom(node).getDestinationInsertionPoints()
These APIs can be used in various ways to check for distributed nodes and insertion points. I have created a working implementation that shows the post-content element with additional methods to check for distributed nodes and destination insertion points.
<script src="http://www.polymer-project.org/1.0/samples/components/webcomponentsjs/webcomponents-lite.js"></script>
<link rel="import"
href="http://www.polymer-project.org/1.0/samples/components/polymer/polymer.html">
<dom-module id="post-content">
<template>
<h1>{{title}}</h1>
<p>{{body}}</p>
<content></content>
<template is="dom-if" if="{{destinationInsertionPointsExist()}}">
<p>Destination insertion point(s) exist.</p>
</template>
<template is="dom-if" if="{{distributedNodesExist()}}">
<p>Distributed node(s) exist.</p>
</template>
</template>
<script>
Polymer({
is: "post-content",
properties: {
title: String,
body: String
},
destinationInsertionPointsExist: function () {
var distributedNodes = Polymer.dom(this).childNodes;
var countDestinationInsertionPoints = 0;
distributedNodes.forEach(function (distributedNode) {
var distributedNodeHasDestinationInsertionPoints = Polymer.dom(distributedNode).getDestinationInsertionPoints().length > 0 ? true : false;
if (distributedNodeHasDestinationInsertionPoints) {
countDestinationInsertionPoints++;
}
});
return countDestinationInsertionPoints > 0 ? true : false;
},
distributedNodesExist: function () {
var contentNodes = Polymer.dom(this.root).querySelectorAll("content");
var countDistributedNodes = 0;
contentNodes.forEach(function(contentNode) {
var contentNodehasDistributedNodes = Polymer.dom(contentNode).getDistributedNodes().length > 0 ? true : false;
if (contentNodehasDistributedNodes) {
countDistributedNodes++;
}
});
return countDistributedNodes > 0 ? true : false;
}
});
</script>
</dom-module>
<post-content title="This is the title" body="This is the body">
<p>This is distributed content</p>
</post-content>
A few notes about the code:
I made a lot of the variable names and ternary checks very verbose for clarity in this answer. Changes could be made to simplify the code.
For example:
var distributedNodeHasDestinationInsertionPoints = Polymer.dom(distributedNode).getDestinationInsertionPoints().length > 0 ? true : false;
could become something like
var hasInsertionPoints = Polymer.dom(distributedNode).getDestinationInsertionPoints().length
Use the new (Polymer 1.0) dom-if conditional template.
<p if="{{thereAreInsertionPoints()}}">There are insertion points!</p>
becomes
<template is="dom-if" if="{{destinationInsertionPointsExist()}}">
<p>Destination insertion point(s) exist.</p>
</template>
I would recommend stepping through the destinationInsertionPointsExist and distributedNodesExist methods to insure that you fully understand what is actually being checked. You may need to modify these methods to suit your particular needs and requirements.
For example, even if you have a single space between the post-content element start and end tag both of these methods will return true.
<post-content title="This is the title" body="This is the body"> </post-content>

Hiding a table row in custom Polymer element with named scopes

I'm relatively new to Polymer and am trying to hide a table row in my custom element using the index of my named scope. It's not working at all, and I suspect I'm not on the right track. Could someone explain what I should be doing? Also, can {{index}} be used in element class names and IDs?
<link rel="import" href="bower_components/polymer/polymer.html">
<polymer-element name="task-lists" attributes="name">
<template repeat="{{t in tasks}}">
<template repeat="{{t, tindex in tasks}}">
<table>
<tr id="{{tindex}}">
<td>
<input type="checkbox" id="task1" class="checkBox" value="None" name="check" />
<label for="task1"></label>
<span on-click="{{hideLink}}">{{t.name}}</span>
</td>
</tr>
</table>
</template>
</template>
<script>
Polymer('task-lists', {
ready: function () {
this.tasks = [
{name: 'Painting'},
{name: 'Cleaning'}
]
},
hideLink: function () {
var row = document.getElementById("{{tindex}}");
row.display = 'none';
}
})
</script>
</polymer-element>
First issue:
You can't access the model properties directly in the event handler. Take a look at Event handling and data binding in the docs. You have to access the model associated with the template instance that created the event. Your event handler would then look something like this:
hidelink: function (e) {
var tindex = e.target.templateInstance.model.tindex;
...
}
Second issue:
document.getElementById only searches elements in the main document (light DOM) but the element you are looking for is in your element's shadow DOM, so you have to use this.shadowRoot.getElementById instead.

Polymer binding tap to an object method

I'm trying to bind a method to an on-tap attribute of a paper-button. After much testing, I've found that I can only bind a (for lack of a better word) top-level function, and not a method of an object in the template.
For example, I have a template, to which I have bound a number of objects, one of which is a user object. Object user has a bunch of methods and variables, like 'isNew' or 'reputation'. The user object also has a method 'addReputation'
I can use the object variables like this :
<template if = '{{user.new}}'><h1>{{user.name}}</h1></template>
And I can bind button taps like this:
<paper-button on-tap='{{addReputation}}'>Add Rep</paper-button>
But not like this:
<paper-button on-tap='{{user.addReputation}}'>Add Rep</paper-button>
Does anyone know why this may be?
if you set the method to a handler on your element's prototype it works. That way you can still keep things dynamic:
<script src="http://www.polymer-project.org/webcomponents.min.js"></script>
<script src="http://www.polymer-project.org/polymer.js"></script>
<polymer-element name="my-element" on-tap="{{tapHandler}}">
<template>
<style>
:host {
display: block;
}
</style>
click me
<content></content>
</template>
<script>
Polymer({
created: function() {
this.user = {
method: function() {
alert('hi');
}
};
this.tapHandler = this.user.method;
}
});
</script>
</polymer-element>
<my-element></my-element>
i'm sharing my plunk to resolve above problem. plunk link
In the template
<button on-tap="{{fncall}}" data-fnname="b">b call</button>
In the script
x.fncall = function(e) {
var target = e.target;
var fnName = target.getAttribute("data-fnname");
return x.datamodel[fnName]();
}
Polymer(x);

Polymer: Instance polymer element from another element

I'm wondering how could I instance a Polymer element when I click on another element. Is there a way to instance window-base from dock-icon? (code down). I though I could use the constructor every element has but I can't figure out how this works. How could I pass a variable to that constructor.
Code of the two elements involved:
<polymer-element name="dock-icon" attributes="name" on-click="{{click}}">
<template>
<link rel="stylesheet" href="dock-icon.css">
</template>
<script>
Polymer('dock-icon', {
name: "",
click: function (event, detail, sender) {
alert(this.name);
//instance <window-base> and pass name parameter
}
});
</script>
Polymer element that has to be instanced
<polymer-element name="window-base" attributes="name height width left top">
<template>
<link rel="stylesheet" href="window-base.css">
<div id="box">
<header id="header"><h2>{{name}}</h2></header>
</div>
</template>
<script>
Polymer('window-base', {
name: "name",
//more stuff here
});
</script>
Thanks
This should be straightforward as:
var el = document.createElement('window-base');
el.name = 'some name';

Categories