Files
polymer/src/templatizer/array-selector.html

336 lines
9.7 KiB
HTML
Raw Normal View History

2016-12-20 16:21:35 -08:00
<!--
@license
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->
<!--
Keeping structured data in sync requires that Polymer understand the path
associations of data being bound. The `array-selector` element ensures path
linkage when selecting specific items from an array (either single or multiple).
The `items` property accepts an array of user data, and via the `select(item)`
and `deselect(item)` API, updates the `selected` property which may be bound to
other parts of the application, and any changes to sub-fields of `selected`
item(s) will be kept in sync with items in the `items` array. When `multi`
is false, `selected` is a property representing the last selected item. When
`multi` is true, `selected` is an array of multiply selected items.
```html
<dom-module id="employee-list">
<template>
<div> Employee list: </div>
<template is="dom-repeat" id="employeeList" items="{{employees}}">
<div>First name: <span>{{item.first}}</span></div>
<div>Last name: <span>{{item.last}}</span></div>
<button on-click="toggleSelection">Select</button>
</template>
<array-selector id="selector" items="{{employees}}" selected="{{selected}}" multi toggle></array-selector>
<div> Selected employees: </div>
<template is="dom-repeat" items="{{selected}}">
<div>First name: <span>{{item.first}}</span></div>
<div>Last name: <span>{{item.last}}</span></div>
</template>
</template>
<script>
Polymer({
is: 'employee-list',
ready() {
this.employees = [
{first: 'Bob', last: 'Smith'},
{first: 'Sally', last: 'Johnson'},
...
];
},
toggleSelection(e) {
var item = this.$.employeeList.itemForElement(e.target);
this.$.selector.select(item);
}
});
</script>
</dom-module>
```
-->
<link rel="import" href="../../polymer-element.html">
<link rel="import" href="../utils/utils.html">
<link rel="import" href="../utils/array-splice.html">
<script>
(function() {
let ArraySelectorMixin = Polymer.Utils.dedupingMixin(superClass => {
return class extends superClass {
static get config() {
return {
properties: {
/**
* An array containing items from which selection will be made.
*/
items: {
type: Array,
},
/**
* When `true`, multiple items may be selected at once (in this case,
* `selected` is an array of currently selected items). When `false`,
* only one item may be selected at a time.
*/
multi: {
type: Boolean,
value: false,
},
/**
* When `multi` is true, this is an array that contains any selected.
* When `multi` is false, this is the currently selected item, or `null`
* if no item is selected.
*/
selected: {
type: Object,
notify: true
},
/**
* When `multi` is false, this is the currently selected item, or `null`
* if no item is selected.
*/
selectedItem: {
type: Object,
notify: true
},
/**
* When `true`, calling `select` on an item that is already selected
* will deselect the item.
*/
toggle: {
type: Boolean,
value: false
}
},
observers: ['_updateSelection(multi, items.*)'],
}
}
2017-01-03 10:22:25 -08:00
constructor() {
super();
this.__lastItems = null;
this.__lastMulti = null;
}
2016-12-20 16:21:35 -08:00
_updateSelection(multi, itemsInfo) {
if (itemsInfo.path == 'items') {
let newItems = itemsInfo.base || [];
2016-12-20 16:21:35 -08:00
let lastItems = this.__lastItems;
let lastMulti = this.__lastMulti;
if (multi !== lastMulti) {
this.clearSelection();
}
2016-12-20 16:21:35 -08:00
if (lastItems) {
let splices = Polymer.ArraySplice.calculateSplices(newItems, lastItems);
2017-01-05 12:53:01 -08:00
this._applySplices(splices);
2016-12-20 16:21:35 -08:00
}
this.__lastItems = newItems;
this.__lastMulti = multi;
2016-12-20 16:21:35 -08:00
} else if (itemsInfo.path == 'items.splices') {
this._applySplices(itemsInfo.value.indexSplices);
}
}
_applySplices(splices) {
let selected = this._selectedMap;
2017-01-09 15:09:44 -08:00
// Adjust selected indices and mark removals
2016-12-20 16:21:35 -08:00
for (let i=0; i<splices.length; i++) {
let s = splices[i];
selected.forEach((idx, item) => {
if (idx < s.index) {
2017-01-09 11:30:33 -08:00
// no change
} else if (idx >= s.index + s.removed.length) {
2017-01-09 11:30:33 -08:00
// adjust index
selected.set(item, idx + s.addedCount - s.removed.length);
2017-01-05 12:53:01 -08:00
} else {
2017-01-09 11:30:33 -08:00
// remove index
selected.set(item, -1);
2016-12-20 16:21:35 -08:00
}
});
for (let j=0; j<s.addedCount; j++) {
let idx = s.index + j;
if (selected.has(this.items[idx])) {
selected.set(this.items[idx], idx);
}
}
2017-01-06 11:31:49 -08:00
}
2017-01-09 15:09:44 -08:00
// Update linked paths
2017-01-06 11:31:49 -08:00
this._updateLinks();
2017-01-09 15:09:44 -08:00
// Remove selected items that were removed from the items array
let sidx = 0;
selected.forEach((idx, item) => {
if (idx < 0) {
if (this.multi) {
this.splice('selected', sidx, 1);
} else {
this.selected = this.selectedItem = null;
}
selected.delete(item);
2017-01-06 11:31:49 -08:00
} else {
sidx++;
}
});
2016-12-20 16:21:35 -08:00
}
_updateLinks() {
this.__dataLinkedPaths = {};
if (this.multi) {
let sidx = 0;
this._selectedMap.forEach(idx => {
if (idx >= 0) {
this.linkPaths('items.' + idx, 'selected.' + sidx++);
}
});
} else {
this._selectedMap.forEach(idx => {
this.linkPaths('selected', 'items.' + idx);
this.linkPaths('selectedItem', 'items.' + idx);
});
2016-12-20 16:21:35 -08:00
}
}
/**
* Clears the selection state.
*
* @method clearSelection
*/
clearSelection() {
// Unbind previous selection
this.__dataLinkedPaths = {};
this._selectedMap = new Map();
2016-12-20 16:21:35 -08:00
// Initialize selection
this.selected = this.multi ? [] : null
2016-12-20 16:21:35 -08:00
this.selectedItem = null;
}
/**
* Returns whether the item is currently selected.
*
* @method isSelected
* @param {*} item Item from `items` array to test
* @return {boolean} Whether the item is selected
*/
isSelected(item) {
2017-01-09 15:09:44 -08:00
return this._selectedMap.has(item);
2016-12-20 16:21:35 -08:00
}
2017-01-06 11:31:49 -08:00
/**
2017-01-09 15:09:44 -08:00
* Returns whether the item is currently selected.
2017-01-06 11:31:49 -08:00
*
* @method isSelected
2017-01-09 15:09:44 -08:00
* @param {*} idx Index from `items` array to test
2017-01-06 11:31:49 -08:00
* @return {boolean} Whether the item is selected
*/
isIndexSelected(idx) {
2017-01-09 15:12:03 -08:00
return this.isSelected(this.items[idx]);
}
2016-12-20 16:21:35 -08:00
/**
* Deselects the given item if it is already selected.
*
* @method deselect
* @param {*} item Item from `items` array to deselect
*/
deselect(item) {
2017-01-09 15:09:44 -08:00
this.deselectIndex(this._selectedMap.get(item));
}
/**
* Deselects the given index if it is already selected.
*
* @method deselect
* @param {number} index Index from `items` array to deselect
*/
deselectIndex(idx) {
if (this._selectedMap.delete(this.items[idx])) {
let sidx;
if (this.multi) {
sidx = parseInt(this.__dataLinkedPaths['items.' + idx].slice('selected.'.length));
2016-12-20 16:21:35 -08:00
}
this._updateLinks();
if (this.multi) {
this.splice('selected', sidx, 1);
} else {
this.selected = this.selectedItem = null;
2016-12-20 16:21:35 -08:00
}
}
}
/**
* Selects the given item. When `toggle` is true, this will automatically
* deselect the item if already selected.
*
* @method select
* @param {*} item Item from `items` array to select
*/
select(item) {
this.selectIndex(this.items.indexOf(item));
}
/**
* Selects the given index. When `toggle` is true, this will automatically
* deselect the item if already selected.
*
* @method select
* @param {number} index Index from `items` array to select
*/
selectIndex(idx) {
2017-01-09 15:12:03 -08:00
let item = this.items[idx];
if (!this.isSelected(item)) {
if (!this.multi) {
this._selectedMap.clear();
2016-12-20 16:21:35 -08:00
}
2017-01-09 15:12:03 -08:00
this._selectedMap.set(item, idx);
this._updateLinks();
if (this.multi) {
this.push('selected', item);
2016-12-20 16:21:35 -08:00
} else {
this.selected = this.selectedItem = item;
2016-12-20 16:21:35 -08:00
}
} else if (this.toggle) {
this.deselectIndex(idx);
2016-12-20 16:21:35 -08:00
}
}
}
});
// export mixin
Polymer.ArraySelectorMixin = ArraySelectorMixin;
// define element class & export
class ArraySelector extends ArraySelectorMixin(Polymer.Element) { }
customElements.define('array-selector', ArraySelector);
Polymer.ArraySelector = ArraySelector;
})();
</script>