items: {
type: Array,
// This function is provided as part of test/utils.html, used for testing
- value: () => buildItems(100, 100, 100)
+ value: () => buildItems(10, 10, 10)
}
}
});
diff --git a/demo/index.html b/demo/index.html
index 428b33d..3043de2 100644
--- a/demo/index.html
+++ b/demo/index.html
@@ -38,39 +38,43 @@
Basic hc-tree-view demo
>
diff --git a/hc-nested-list-behavior.html b/hc-nested-list-behavior.html
index ba3f66f..ed75a55 100644
--- a/hc-nested-list-behavior.html
+++ b/hc-nested-list-behavior.html
@@ -17,17 +17,17 @@
isLoading: {
type: Boolean,
value: false,
- notify: true
+ notify: true,
},
/* The property name for the id on each item. */
itemIdName: {
type: String,
- value: 'id'
+ value: 'id',
},
/* The property name for the parentId on each item. */
itemParentIdName: {
type: String,
- value: 'parentId'
+ value: 'parentId',
},
/**
* The flat array of items to build the nested list structure from, requires an id and
@@ -44,7 +44,7 @@
*/
parts: {
type: Object,
- notify: true
+ notify: true,
},
/**
* The currently visible subset of items from the items property
@@ -54,12 +54,25 @@
_items: {
type: Array,
readOnly: true,
- computed: '_computeItems(parts)'
+ computed: '_computeItems(parts)',
+ observer: '__updateList',
+ },
+ },
+ observers: [
+ '_computeParts(items, itemIdName, itemParentIdName)',
+ ],
+ ready() {
+ this.itemOpenChanged = this.itemOpenChanged.bind(this);
+ },
+ attached() {
+ this.addEventListener('item-open-changed', this.itemOpenChanged);
+ this.$.list._propagateUpdates = (item, instance) => {
+ instance.notifyPath('item.state', item.state);
+ instance.notifyPath('item.open', item.open);
}
},
- observers: ['_computeParts(items, itemIdName, itemParentIdName)'],
- listeners: {
- 'item-open-changed': 'itemOpenChanged'
+ detached() {
+ this.removeEventListener('item-open-changed', this.itemOpenChanged);
},
/**
* Called when an item's open status is changed. Update parts with children if lazy-loading then call update
@@ -110,12 +123,14 @@
* @param {Object} detail
* @param {String} [idName=this.itemIdName]
* @param {Object} [parts = this.parts]
+ * @return {Promise}
*/
- itemOpenChanged(event, detail, idName = this.itemIdName, parts = this.parts) {
+ itemOpenChanged(event, idName = this.itemIdName, parts = this.parts) {
// Block interactions while loading items
if (this.isLoading) return;
this.isLoading = true;
+ const detail = event.detail;
const items = parts[this._getParentId(detail.item)];
const id = detail.item[idName];
let item;
@@ -132,7 +147,7 @@
} else items[i].open = false; // If it's not the item we want, ensure it's closed
}
- (new Promise((res) => {
+ return (new Promise((res) => {
if (item) {
// fetch children and update when done, when there's an item
const ids = Object.keys(parts);
@@ -147,7 +162,7 @@
res(parts);
});
} else res(parts);
- })).then(() => {
+ })).then((parts) => {
this.set('parts', Object.assign({}, parts));
this.isLoading = false;
});
@@ -161,7 +176,7 @@
* @param {Object} parts
* @param {Object[]} [items=parts.root]
* @param {String} [idName=this.itemIdName]
- * @returns {Object[]}
+ * @return {Object[]}
*/
_computeItems(parts, items = parts.root, idName = this.itemIdName) {
// Can't calculate items without parts or the name of the id property.
@@ -216,7 +231,7 @@
* @param {Object} item
* @param {Object} parts
* @param {String} [parentId = this._getParentId(item)]
- * @returns {Number|undefined}
+ * @return {Number|undefined}
*/
_getItemDepth(item, parts, parentId = this._getParentId(item)) {
const parent = this.getParent(item, parts);
@@ -241,7 +256,7 @@
* @protected
* @param {Object} item
* @param {String} [parentIdName=this.itemParentIdName]
- * @returns {String|Number}
+ * @return {String|Number}
*/
_getParentId(item, parentIdName = this.itemParentIdName) {
return (item[parentIdName] || item[parentIdName] === 0) ?
@@ -262,9 +277,27 @@
return Object.assign({
open: false,
state: parentState,
- depth: this._getItemDepth(item, parts, parentId)
+ depth: this._getItemDepth(item, parts, parentId),
}, item);
- }
+ },
+ /**
+ * This is a hack to make iron-list hide it's additional physical items since that's not actually part of it's
+ * functionality.
+ * @private
+ */
+ __updateList() {
+ this.debounce('parts-updating', () => {
+ const list = this.$.list;
+ const lastIndex = this._items.length - 1;
+ (list._physicalItems || []).forEach((item, index) => {
+ if (index > lastIndex) {
+ item.setAttribute('hidden', '');
+ } else {
+ item.removeAttribute('hidden');
+ }
+ });
+ }, 100);
+ },
};
/**
diff --git a/hc-nested-list-item.html b/hc-nested-list-item.html
index cefba90..7cdae2a 100644
--- a/hc-nested-list-item.html
+++ b/hc-nested-list-item.html
@@ -31,10 +31,16 @@
is: 'hc-nested-list-item',
properties: {
/* The item holding all data */
- item: Object
+ item: Object,
},
- listeners: {
- tap: 'fireOpenEvent'
+ ready() {
+ this.fireOpenEvent = this.fireOpenEvent.bind(this);
+ },
+ attached() {
+ this.addEventListener('click', this.fireOpenEvent);
+ },
+ detached() {
+ this.removeEventListener('click', this.fireOpenEvent);
},
/**
* Fires the toggle event required by hc-nested-list to open/close content sections.
@@ -47,11 +53,16 @@
event.stopPropagation();
// Trigger an event to toggle open state instead.
- this.fire('item-open-changed', {
- item: this.item,
- open: !this.item.open
- });
- }
+ this.dispatchEvent(new CustomEvent('item-open-changed', {
+ detail: {
+ item: this.item,
+ open: !this.item.open,
+ },
+ bubbles: true,
+ composed: true,
+ cancelable: true,
+ }));
+ },
});
diff --git a/hc-nested-list.html b/hc-nested-list.html
index 2ddc9d3..06f0e78 100644
--- a/hc-nested-list.html
+++ b/hc-nested-list.html
@@ -1,6 +1,6 @@
-
+
+
+
+
-
-
-
+ as="item">
@@ -57,7 +57,7 @@
Polymer({
/* global hc */
is: 'hc-nested-list',
- behaviors: [hc.NestedListBehavior]
+ behaviors: [hc.NestedListBehavior],
});
diff --git a/hc-tree-node.html b/hc-tree-node.html
index 6632e7a..10100ba 100644
--- a/hc-tree-node.html
+++ b/hc-tree-node.html
@@ -71,7 +71,8 @@
checked="[[checked]]"
unchecked="[[unchecked]]"
indeterminate="[[indeterminate]]"
- on-state-changed="_stateChanged"
+ on-click="__userInteraction"
+ disable-indeterminate
>
@@ -87,44 +88,35 @@
/* The value to use when state="on" */
checked: {
type: Boolean,
- value: true
+ value: true,
},
/* The icon to use when the item is closed */
closedIcon: {
type: String,
- value: 'icons:chevron-right'
+ value: 'icons:chevron-right',
},
/* The value to use when state=null */
indeterminate: {
type: Boolean,
- value: false
+ value: false,
},
/* This node's data */
item: Object,
/* The icon to use when the item is open */
openIcon: {
type: String,
- value: 'icons:expand-more'
+ value: 'icons:expand-more',
},
/* The value to use when state="off" */
unchecked: {
type: Boolean,
- value: false
+ value: false,
},
/* Holds the value that determines selected status for the item */
value: {
type: Boolean,
- observer: '_valueChanged'
+ observer: '_valueChanged',
},
- /**
- * Used to determine when this element is in initialization state to prevent extra events
- *
- * @private
- */
- __init: {
- type: Boolean,
- value: true
- }
},
observers: ['_updateDepth(item.depth)'],
ready() {
@@ -133,70 +125,81 @@
* and to prevent event bubbling. This is using an annonymous function to keep the correct scope
* that is provided by the checkbox element instead of an arrow function which will use the local
* scope.
- *
* @protected
+ *
* @param {Event} event
*/
- this.$.checkbox._regularTap = function (event) {
- event.stopPropagation();
+ this.$.checkbox._regularTap = function(event) {
this.state = this.state === 'on' ? 'off' : 'on';
};
},
/**
* Returns the openIcon when open is true, otherwise it returns the closedIcon.
+ * @protected
*
- * @private
* @param {Boolean} open
- * @returns {String}
+ * @return {String}
*/
_getIcon(open) {
return open ? this.openIcon : this.closedIcon;
},
/**
- * Watches for changes to the value for the checkbox then notifies the parent of selection changes.
+ * Updates the depth of this node to display as if it is nested
+ * @protected
*
- * @private
- * @fires hc-tree-view-behavior#item-state-changed
- * @param {CustomEvent} event
- * @param {Object} detail
+ * @param {Number} depth
*/
- _stateChanged(event, detail) {
- // Prevent firing events if we don't have the necessary data
- if (!this.item) return;
-
- // When a change event happens, inform the list
- this.fire('item-state-changed', {
- item: this.item,
- state: detail.value
- });
- },
_updateDepth(depth) {
this.updateStyles({
- '--hc-tree-node-depth': String(depth)
+ '--hc-tree-node-depth': String(depth),
});
},
/**
* Watches for changes to the value for the checkbox then notifies the parent of selection changes.
+ * @protected
*
- * @private
* @fires hc-tree-view-behavior#item-selected-changed
* @param {Boolean} value
*/
_valueChanged(value) {
- // Prevent firing events if we don't have the necessary data
+ // Prevent firing events if we don't have the necessary data or if it's still initializing
if (!this.item) return;
- // Prevent the initial event that happens upon loading
- else if (this.__init) {
- this.__init = false;
+ if (!this.__initComplete) {
+ this.__initComplete = true;
return;
}
- // When a change event happens, inform the list
- this.fire('item-selected-changed', {
- item: this.item,
- selected: value
- });
- }
+ this.dispatchEvent(new CustomEvent('item-selected-changed', {
+ detail: {
+ item: this.item,
+ selected: value,
+ state: this.item.state,
+ },
+ bubbles: true,
+ composed: true,
+ }));
+ },
+ /**
+ * Used to trigger interactions with the tree based on the user.
+ * @private
+ * @fires hc-tree-view-behavior#item-state-changed
+ *
+ * @param {ClickEvent} event
+ */
+ __userInteraction(event) {
+ event.stopPropagation();
+ // Resort to the 1.x methods
+ if (!Polymer.element) {
+ this.dispatchEvent(new CustomEvent('item-state-changed', {
+ detail: {
+ item: this.item,
+ state: this.item.state,
+ },
+ bubbles: true,
+ composed: true,
+ }));
+ }
+ },
});
diff --git a/hc-tree-view-behavior.html b/hc-tree-view-behavior.html
index 243fdc6..ab4299a 100644
--- a/hc-tree-view-behavior.html
+++ b/hc-tree-view-behavior.html
@@ -16,31 +16,44 @@
/* Used to allow changing what the tree node's id is */
itemIdName: {
type: String,
- value: 'id'
+ value: 'id',
},
/* Used to allow changing what the tree node's parent id is */
itemParentIdName: {
type: String,
- value: 'parentId'
+ value: 'parentId',
},
/* The list of items to display in the tree, using a flat structure */
items: Array,
+ /* The hc-nested-list element to use for rendering */
+ list: {
+ type: Object,
+ notify: true,
+ },
/* The list of all selected items without duplicates */
selectedItems: {
type: Array,
value: () => [],
- notify: true
+ notify: true,
},
/**
* The parts object used by the list element
*
* @private
*/
- _parts: Object
+ _parts: Object,
+ },
+ ready() {
+ this.itemSelectedChanged = this.itemSelectedChanged.bind(this);
+ this.itemStateChanged = this.itemStateChanged.bind(this);
+ },
+ attached() {
+ this.addEventListener('item-selected-changed', this.itemSelectedChanged);
+ this.addEventListener('item-state-changed', this.itemStateChanged);
},
- listeners: {
- 'item-selected-changed': 'itemSelectedChanged',
- 'item-state-changed': 'itemStateChanged'
+ detached() {
+ this.removeEventListener('item-selected-changed', this.itemSelectedChanged);
+ this.removeEventListener('item-state-changed', this.itemStateChanged);
},
/**
* Collapses the tree while maintaining the current selection states
@@ -70,74 +83,95 @@
});
},
/**
- * Takes an array of one or more items and removes them from the selectedItems array.
+ * Takes an array of one or more items and removes them from the selectedItems array. If no items are passed
+ * then all items will be deslected.
*
- * @param {Array.