Polymer 1.x: Toggle <array-selector> - polymer

I want to toggle items using <array-selector>.
When I click an item from the selected list, I expect it to disappear from that list. But instead, nothing happens.
To recreate the problem, follow these steps.
Open this jsBin.
Click one or more items from the employee list.
See those items appear below in the selected list.
From still in the employee list at the top, click an item that appears in the selected list at the bottom.
See that item disappear (toggle) from the selected list.
At the selected list, click an item.
Notice that item does not disappear (toggle).
How does one make the selected list items toggle?
http://jsbin.com/lodetopuzu/edit?html,output
<!doctype html>
<head>
<meta charset="utf-8">
<base href="https://polygit.org/components/">
<script src="webcomponentsjs/webcomponents-lite.min.js"></script>
<link href="polymer/polymer.html" rel="import">
</head>
<body>
<dom-module id="employee-list">
<template>
<array-selector id="selector"
items="{{employees}}"
selected="{{selected}}"
toggle
multi></array-selector>
<div><strong>Employees</strong>:</div>
<template is="dom-repeat"
id="employeeList"
items="{{employees}}">
<div on-tap="_toggleSelection">
<span>{{item.first}}</span>
<span>{{item.last}}</span>
</div>
</template>
<br />
<div><strong>Selected</strong>:</div>
<template is="dom-repeat" items="{{selected}}">
<div on-tap="_toggleSelection">
<span>{{item.first}}</span>
<span>{{item.last}}</span>
</div>
</template>
</template>
<script>
Polymer({
is: 'employee-list',
properties: {
employees: {
type: Array,
value: function() {
return [
{ first: 'Vince' , last: 'Lombardi' } ,
{ first: 'Weeb' , last: 'Ewbank' } ,
{ first: 'Hank' , last: 'Stram' } ,
{ first: 'Don' , last: 'McCafferty' } ,
{ first: 'Tom' , last: 'Landry' } ,
{ first: 'Don' , last: 'Shula' } ,
{ first: 'Chuck' , last: 'Knoll' } ,
{ first: 'John' , last: 'Madden' } ,
]
},
},
},
_toggleSelection: function(e) {
console.log(e.target);
var item = this.$.employeeList.itemForElement(e.target);
this.$.selector.select(item);
},
});
</script>
</dom-module>
<employee-list></employee-list>
</body>

Solution change the function
_toggleSelection: function(e) {
var item1 = this.$.selectedList.itemForElement(e.target);
var item = this.$.employeeList.itemForElement(e.target);
this.$.selector.select(item||item1);
}
and add id to the selected list to selectedList
<template id="selectedList" is="dom-repeat" items="{{selected}}">
http://jsbin.com/cuyosucame/edit?html,output

The problem is that you use the same _toggleSelection for both <template is="dom-repeat">.
This line in _toggleSelection
var item = this.$.employeeList.itemForElement(e.target);
is invalid when an item from the second dom-repeat is clicked.

As #GünterZöchbauer said, the method .itemForElement(e.target) is applied to the element referenced by $.employeeList which is the first list, not the second.
The full text of the solution provided by #Alon is as follows:
http://jsbin.com/yowicumiyu/1/edit?html,output
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<base href="https://polygit.org/components/">
<script src="webcomponentsjs/webcomponents-lite.min.js"></script>
<link href="polymer/polymer.html" rel="import">
</head>
<body>
<dom-module id="employee-list">
<template>
<array-selector id="selector"
items="{{employees}}"
selected="{{selected}}"
toggle
multi></array-selector>
<div><strong>Employees</strong>:</div>
<template is="dom-repeat"
id="employeeList"
items="{{employees}}">
<div on-tap="_toggleSelection">
<span>{{item.first}}</span>
<span>{{item.last}}</span>
</div>
</template>
<br />
<div><strong>Selected</strong>:</div>
<template is="dom-repeat"
id="selectedList"
items="{{selected}}">
<div on-tap="_toggleSelection">
<span>{{item.first}}</span>
<span>{{item.last}}</span>
</div>
</template>
</template>
<script>
Polymer({
is: 'employee-list',
properties: {
employees: {
type: Array,
value: function() {
return [
{ first: 'Vince' , last: 'Lombardi' } ,
{ first: 'Weeb' , last: 'Ewbank' } ,
{ first: 'Hank' , last: 'Stram' } ,
{ first: 'Don' , last: 'McCafferty' } ,
{ first: 'Tom' , last: 'Landry' } ,
{ first: 'Don' , last: 'Shula' } ,
{ first: 'Chuck' , last: 'Knoll' } ,
{ first: 'John' , last: 'Madden' } ,
]
},
},
},
_toggleSelection: function(e) {
var item1 = this.$.selectedList.itemForElement(e.target);
var item2 = this.$.employeeList.itemForElement(e.target);
this.$.selector.select(item1||item2);
},
});
</script>
</dom-module>
<employee-list></employee-list>
</body>
</html>

Related

Multiple polymer 2 elements with independent properties?

I have a question to my custom polymer element. First, I made an element with a simple paper-input. My problem is, that I don't know, how to use this element as an "independent" element. My example is here in this jsfiddle. Type in the first input "asd" and hit Enter and then in the second input "asd" and hit Enter. You can see, that both elements are sharing the properties (console log "-1" not found in array and second log will be "1")
<!doctype html>
<html>
<head>
<title>2.0 preview elements</title>
<base href="http://polygit.org/polymer+v2.0.0-rc.4/webcomponentsjs+webcomponents+v1.0.0-rc.6/shadycss+webcomponents+1.0.0-rc.2/paper*+polymerelements+:2.0-preview/iron*+polymerelements+:2.0-preview/app*+polymerelements+:2.0-preview/neon*+polymerelements+:2.0-preview/components/">
<script src="webcomponentsjs/webcomponents-lite.js"></script>
<link rel="import" href="polymer/polymer.html">
<link rel="import" href="paper-input/paper-input.html">
</head>
<body>
<input-test></input-test>
<input-test></input-test>
<dom-module id="input-test">
<template>
<paper-input value="{{aValue}}" on-keydown="_keydown"></paper-input>
</template>
<script>
window.addEventListener('WebComponentsReady', function() {
class InputTest extends Polymer.Element {
static get is() {
return 'input-test';
}
static get properties() {
return {
aValue: {
type: String,
value: ''
},
_inputStore: {
type: Array,
value: []
}
};
}
_keydown(event) {
const keyCode_enter = '13';
if (event.keyCode == keyCode_enter) {
console.log(this._inputStore.indexOf(this.aValue))
this.push('_inputStore', this.aValue);
}
}
}
customElements.define(InputTest.is, InputTest);
})
</script>
</dom-module>
</body>
</html>
What can I do, to have independent properties?
Thanks!
I found the answer.
The problem is the default value declaration of the array.
_inputStore: {
type: Array,
value: function () {
return [];
}
}
This code solves the problem.

Access current item when clicking link in dom-if inside dom-repeat

I have the following template:
<template is="dom-repeat" items="{{myItems}}">
<template is="dom-if" if="{{_shouldHaveLink(item)}}">
Link
</template>
</template>
Now, if the link was not wrapped in a dom-if, I can see the item which was pressed with:
_linkTapped: function (oEvent) {
console.log('Item link tapped:', oEvent.model.get('item'));
}
But inside the dom-if I can't. Seems like item is now out of scope. How can I get it?
This is a known bug with Polymer's dom-repeat yet to be solved, but there's a simple workaround in this scenario.
Since the dom-if template without restamp simply hides its contents when the if condition is false (as an optimization), you could simulate the original behavior while avoiding the dom-if-related bug by replacing the template with a hidden attribute based on the same condition negated:
<div hidden$="{{!_shouldHaveLink(item)}}">
Link
</div>
or:
Link
HTMLImports.whenReady(() => {
Polymer({
is: 'x-foo',
properties: {
items: {
type: Array,
value: () => [
{ name: 'google', link: 'http://www.google.com' },
{ name: 'facebook' },
{ name: 'twitter', link: 'http://www.twitter.com' },
]
}
},
_hasNoLink: function(item) {
return !item.link;
},
_linkTapped: function(e) {
console.log(e.model.item);
// for demo only...
e.preventDefault();
}
});
});
<head>
<base href="https://polygit.org/polymer+1.7.0/components/">
<script src="webcomponentsjs/webcomponents-lite.min.js"></script>
<link rel="import" href="polymer/polymer.html">
</head>
<body>
<x-foo></x-foo>
<dom-module id="x-foo">
<template>
<div>Facebook anchor is hidden because it has no link</div>
<template is="dom-repeat" items="[[items]]">
<a href="#"
hidden$="{{_hasNoLink(item)}}"
on-tap="_linkTapped">[[item.name]]</a>
</template>
</template>
</dom-module>
</body>
codepen
And as #DocDude suggested, another alternative is to use <dom-repeat>.modelForElement(e.target) if you have a reference to the <dom-repeat>:
//template
<template id="repeater" is="dom-repeat" items="[[items]]">
// script
_linkTapped: function(e) {
const m = this.$.repeater.modelForElement(e.target);
console.log(m.item);
...
}
HTMLImports.whenReady(() => {
Polymer({
is: 'x-foo',
properties: {
items: {
type: Array,
value: () => [
{ name: 'google', link: 'http://www.google.com' },
{ name: 'facebook' },
{ name: 'twitter', link: 'http://www.twitter.com' },
]
}
},
_hasLink: function(item) {
return item.link;
},
_linkTapped: function(e) {
const m = this.$.repeater.modelForElement(e.target);
console.log(m.item);
// for demo only...
e.preventDefault();
}
});
});
<head>
<base href="https://polygit.org/polymer+1.3.0/components/">
<script src="webcomponentsjs/webcomponents-lite.min.js"></script>
<link rel="import" href="polymer/polymer.html">
</head>
<body>
<x-foo></x-foo>
<dom-module id="x-foo">
<template>
<div>Facebook anchor is hidden because it has no link</div>
<template id="repeater" is="dom-repeat" items="[[items]]">
<template is="dom-if" if="{{_hasLink(item)}}">
[[item.name]]
</template>
</template>
</template>
</dom-module>
</body>
codepen

Polymer. Update view after model changed

This is a common question in Polymer about updating view when model was updated (answer is to use this.set or this.push Polymer methods).
But when I have two elements:
First element:
properties:{
items: {
type: Array
}
},
someFunction: {
this.push('items', {'Name': 'something'});
}
Second Element has property which is bound to 'items' from first element
ready: function(){
this.items = firstElement.items;
}
I would like second element 'items' to be updated when 'items' updated on firstElement. I can't manually notify secondElement, because of app restrictions.
So for now I see (in devTools) that model of second element is correct ('items' contains objects), but view isn't updated.
How to update it?
You need to set notity on the property of the first element to receive changes outside of the element itself
properties:{
items: {
type: Array,
notify: true
}
},
then, in secondElement you can
<firstElement items="{{items}}" />
Let Polymer's two-way binding handle the notifications for you.
Consider two elements, x-foo and x-bar, in a container element, x-app.
In x-foo, declare items with notify:true so that its changes would be propagated upward.
Polymer({
is: 'x-foo',
properties: {
items: {
type: Array,
value: function() { return []; },
notify: true
}
}
});
In x-app, bind x-foo's items to x-bar's.
<dom-module id="x-app">
<template>
<x-foo items="{{items}}"></x-foo>
<x-bar items="[[items]]"></x-bar>
</template>
<script>
Polymer({ is: 'x-app' });
</script>
</dom-module>
Now, x-bar would be notified of all changes to the items array (when an item is added/removed).
HTMLImports.whenReady(() => {
"use strict";
Polymer({
is: 'x-app'
});
Polymer({
is: 'x-foo',
properties: {
items: {
type: Array,
value: function() { return []; },
notify: true
}
},
_addItem: function() {
this.push('items', {name: 'item' + this.items.length});
}
});
Polymer({
is: 'x-bar',
properties: {
items: {
type: Array
}
}
});
});
<head>
<base href="https://polygit.org/polymer+1.6.0/components/">
<script src="webcomponentsjs/webcomponents-lite.min.js"></script>
<link rel="import" href="polymer/polymer.html">
</head>
<body>
<x-app></x-app>
<dom-module id="x-app">
<template>
<x-foo items="{{items}}"></x-foo>
<x-bar items="[[items]]"></x-bar>
</template>
</dom-module>
<dom-module id="x-foo">
<template>
<h2><code>x-foo</code></h2>
<button on-tap="_addItem">Add item</button>
</template>
</dom-module>
<dom-module id="x-bar">
<template>
<h2><code>x-bar</code></h2>
<ul>
<template is="dom-repeat" items="[[items]]">
<li>[[item.name]]</li>
</template>
</ul>
</template>
</dom-module>
</body>
codepen

Polymer deep path binding

In Polymer how do I databind to a Deep path property like shown in the example below
<!doctype html>
<html>
<head>
<script src='bower_components/webcomponentsjs/webcomponents.min.js'></script>
<link rel='import' href='bower_components/polymer/polymer.html'/>
</head>
<body>
<dom-module id='base-page'>
<template>
<input type='button' on-click='click'>
<div>{{item.mydeepproperty}}</div>
</template>
</dom-module>
<script>
Polymer({
is: 'base-page',
properties: {
item: {
type: Object,
value: function() { return { mydeepproperty: 'default' } }
}
},
click: function() {
this.item.mydeepproperty = 'woohoo';
}
});
</script>
<base-page></base-page>
</body>
Example also found here:
http://jsbin.com/qonedeleho/1/edit?html,output
Cheers
When updating a sub-property of an object, you need to be more explicit in your code to force a path change notification and either use the this.set() or this.notify() function:
click: function() {
this.set('item.mydeepproperty', 'woohoo');
}

Always keep a selected item in a iron-list

<iron-list id="list" class="flex" items="{{data}}" selected-item="{{selectedItem}}" x="[[itemChanged(selectedItem)]]" selection-enabled>
<template>
<div class$="[[_computedClass(selected)]]">
<span class="flex">test</span>
</div>
</template>
</iron-list>
The solution I am looking for is like a radio group where there is always one selection in the iron-list.
I tried
itemChanged: function() {
if (this.selectedItem) {
this.selected = this.$.list.items.indexOf(this.selectedItem);
} else {
if(this.selected != null) { this.$.list.selectItem(this.selected);}
}
},
This ends up in a Uncaught RangeError: Maximum call stack size exceeded.
Here's an implemenation of this behaviour:
<dom-module id="test-element">
<style>
.selected {
background: blue;
color: white;
}
iron-list {
height: 200px;
width:200px
}
</style>
<template>
<iron-list id="list" items="[[data]]" as="item" selection-enabled on-selected-item-changed="itemChanged">
<template>
<div class$="[[_computedClass(selected)]]">[[item.name]]</div>
</template>
</iron-list>
</template>
</dom-module>
<script>
Polymer({
is: "test-element",
properties: {
data: {type: Array,
notify: true,
value: function() { return [
{"name": "Bob"},
{"name": "Tim"},
{"name": "Mike"}
]
;}
},
last: Object
},
ready: function(){
this.last = this.data[0];
},
itemChanged: function(){
if (this.$.list.selectedItem === null) {
this.async(function(){
if (this.$.list.selectedItem === null) {
this.$.list.selectItem(this.last);
}
}.bind(this));
} else {
this.last = this.$.list.selectedItem;
}
},
_computedClass : function(selected) {
return selected? "selected": "";
}
});
</script>
The relevant bit is in the itemChangedchanged function.
One thing that made it tricky, is the fact that the iron-list will trigger two events for each selection. First, it will unselect the previously selected item and then select the new item. So in order to figure out if no new item is selected, you have to wait and see if a new selection event is triggered. That's what I'm using the async for. Then I also store a reference to the previously selected item in the last property. In case no new item is selected, I restore the selection to the last selection.