From c01aad06137bfd7f2aa1ec50f3a27b38f269c5ae Mon Sep 17 00:00:00 2001 From: Hayden McParlane Date: Tue, 12 Sep 2017 20:18:10 -0500 Subject: [PATCH 1/9] Hybridized. 1.x almost working. --- bower.json | 32 ++++++++++++++++---- hc-nested-list-behavior.html | 32 +++++++++++--------- hc-nested-list-item.html | 24 ++++++++++----- hc-tree-node.html | 56 +++++++++++++++++++--------------- hc-tree-view-behavior.html | 58 +++++++++++++++++++++++------------- hc-tree-view.html | 18 +++++++---- 6 files changed, 143 insertions(+), 77 deletions(-) diff --git a/bower.json b/bower.json index a36fab4..b2dce2e 100644 --- a/bower.json +++ b/bower.json @@ -3,14 +3,34 @@ "description": "An optimized tree-view based on a flat data structure.", "main": "hc-tree-view.html", "dependencies": { - "polymer": "Polymer/polymer#^1.7.0", - "iron-list": "admwx7/iron-list#fix/issue-300", + "polymer": "Polymer/polymer#^1.9 || ^2.0", + "iron-list": "PolymerElements/iron-list#^1.0 || ^2.0", "paper-tristate-checkbox": "Hackception/paper-tristate-checkbox#^0.2.0" }, "devDependencies": { - "iron-component-page": "PolymerElements/iron-component-page#^1.0.0", - "iron-demo-helpers": "PolymerElements/iron-demo-helpers#^1.0.0", - "web-component-tester": "^4.0.0", - "webcomponentsjs": "webcomponents/webcomponentsjs#^0.7.0" + "iron-component-page": "PolymerElements/iron-component-page#^1.0 || ^2.0", + "iron-demo-helpers": "PolymerElements/iron-demo-helpers#^1.0 || ^2.0", + "web-component-tester": "^6.0.0", + "webcomponentsjs": "webcomponents/webcomponentsjs#^1.0 || ^2.0" + }, + "variants": { + "1.x": { + "dependencies": { + "iron-list": "admwx7/iron-list#fix/issue-300" + }, + "devDependencies": { + "iron-component-page": "PolymerElements/iron-component-page#^1.0.0", + "iron-demo-helpers": "PolymerElements/iron-demo-helpers#^1.0.0", + "web-component-tester": "^4.0.0", + "webcomponentsjs": "webcomponents/webcomponentsjs#^0.7.0" + }, + "resolutions": { + "iron-list": "fix/issue-300", + "webcomponentsjs": "^0.7.0" + } + } + }, + "resolutions": { + "webcomponentsjs": "^1.0.0" } } diff --git a/hc-nested-list-behavior.html b/hc-nested-list-behavior.html index ba3f66f..e8918b3 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,15 @@ _items: { type: Array, readOnly: true, - computed: '_computeItems(parts)' - } + computed: '_computeItems(parts)', + }, }, observers: ['_computeParts(items, itemIdName, itemParentIdName)'], - listeners: { - 'item-open-changed': 'itemOpenChanged' + attached() { + this.addEventListener('item-open-changed', this.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 @@ -111,11 +114,12 @@ * @param {String} [idName=this.itemIdName] * @param {Object} [parts = this.parts] */ - 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; @@ -161,7 +165,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 +220,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 +245,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 +266,9 @@ return Object.assign({ open: false, state: parentState, - depth: this._getItemDepth(item, parts, parentId) + depth: this._getItemDepth(item, parts, parentId), }, item); - } + }, }; /** diff --git a/hc-nested-list-item.html b/hc-nested-list-item.html index cefba90..51ddc4f 100644 --- a/hc-nested-list-item.html +++ b/hc-nested-list-item.html @@ -31,10 +31,13 @@ is: 'hc-nested-list-item', properties: { /* The item holding all data */ - item: Object + item: Object, }, - listeners: { - tap: 'fireOpenEvent' + 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 +50,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-tree-node.html b/hc-tree-node.html index 6632e7a..3dde10d 100644 --- a/hc-tree-node.html +++ b/hc-tree-node.html @@ -87,34 +87,34 @@ /* 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 @@ -123,8 +123,8 @@ */ __init: { type: Boolean, - value: true - } + value: true, + }, }, observers: ['_updateDepth(item.depth)'], ready() { @@ -137,7 +137,7 @@ * @protected * @param {Event} event */ - this.$.checkbox._regularTap = function (event) { + this.$.checkbox._regularClick = function(event) { event.stopPropagation(); this.state = this.state === 'on' ? 'off' : 'on'; }; @@ -147,7 +147,7 @@ * * @private * @param {Boolean} open - * @returns {String} + * @return {String} */ _getIcon(open) { return open ? this.openIcon : this.closedIcon; @@ -158,21 +158,26 @@ * @private * @fires hc-tree-view-behavior#item-state-changed * @param {CustomEvent} event - * @param {Object} detail */ - _stateChanged(event, detail) { + _stateChanged(event) { + const detail = 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 - }); + this.dispatchEvent(new CustomEvent('item-state-changed', { + detail: { + item: this.item, + state: detail.value, + }, + bubbles: true, + composed: true, + cancelable: true, + })); }, _updateDepth(depth) { this.updateStyles({ - '--hc-tree-node-depth': String(depth) + '--hc-tree-node-depth': String(depth), }); }, /** @@ -190,13 +195,16 @@ this.__init = false; 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, + }, + bubbles: true, + composed: true, + cancelable: true, + })); + }, }); diff --git a/hc-tree-view-behavior.html b/hc-tree-view-behavior.html index 243fdc6..098cf69 100644 --- a/hc-tree-view-behavior.html +++ b/hc-tree-view-behavior.html @@ -16,12 +16,12 @@ /* 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, @@ -29,19 +29,25 @@ selectedItems: { type: Array, value: () => [], - notify: true + notify: true, }, /** * The parts object used by the list element * * @private */ - _parts: Object + _parts: Object, }, - listeners: { - 'item-selected-changed': 'itemSelectedChanged', - 'item-state-changed': 'itemStateChanged' + attached() { + this.addEventListener('item-selected-changed', this.itemSelectedChanged); + this.addEventListener('item-state-changed', this.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 * @@ -102,7 +108,7 @@ * Fetches the parent for the given item. * * @param {Object} item - * @returns {Object|undefined} + * @return {Object|undefined} */ getParent(item) { return this.$.list.getParent(item); @@ -112,19 +118,23 @@ * * @listens hc-tree-view-behavior#item-selected-changed * @param {CustomEvent} [event] - * @param {Object} detail */ - itemSelectedChanged(event, detail) { - this[detail.selected ? 'selectItems' : 'deselectItems']([detail.item], true); + itemSelectedChanged(event) { + const detail = event.detail; + if (detail.selected) { + this.selectItems([detail.item], true); + } else { + this.deselectItems([detail.item], true); + } }, /** * Updates the parents and children of this item that it's state was changed, update thiers as well if needed. * * @listens hc-tree-view-behavior#item-state-changed * @param {CustomEvent} event - * @param {Object} detail */ - itemStateChanged(event, detail) { + itemStateChanged(event) { + const detail = event.detail; const {item, state} = detail; // Reject duplicated events @@ -133,11 +143,20 @@ // Change selected status for parents up the chain const parent = this._updateParents(item, state); - if (parent) - this[parent.state === 'on' ? 'selectItems' : 'deselectItems']([parent], true); + if (parent) { + if (parent.state === 'on') { + this.selectItems([parent], true); + } else { + this.deselectItems([parent], true); + } + } // Change selected status for children, even if they are hidden. - this[state === 'on' ? 'selectItems' : 'deselectItems'](this._updateChildren(item, state), true); + if ( state === 'on') { + this.selectItems(this._updateChildren(item, state), true); + } else { + this.deselectItems(this._updateChildren(item, state), true); + } this.debounce('itemStateChanged', () => { this.set('_parts', Object.assign({}, this._parts)); }); @@ -173,7 +192,7 @@ * @param {Object} item * @param {Boolean} state * @param {Object} [parts = this._parts] - * @returns {Array.} + * @return {Array.} */ _updateChildren(item, state, parts = this._parts) { const children = parts[item[this.itemIdName]]; @@ -241,12 +260,11 @@ // Trigger a state change that will propagate this.itemStateChanged(null, { item: partItem, - state + state, }); }); - } + }, }; - /** * Event for changing the selection status of a tree node * diff --git a/hc-tree-view.html b/hc-tree-view.html index eb6d14d..9fc3987 100644 --- a/hc-tree-view.html +++ b/hc-tree-view.html @@ -34,13 +34,13 @@ items="[[items]]" parts="{{_parts}}" item-id-name="[[itemIdName]]" - item-parent-id-name="[[itemParentIdName]]" - > + item-parent-id-name="[[itemParentIdName]]"> @@ -49,7 +49,15 @@ Polymer({ /* global hc */ is: 'hc-tree-view', - behaviors: [hc.TreeViewBehavior] + behaviors: [hc.TreeViewBehavior], + properties: { + /* The path to the label for the item */ + itemLabelPath: { + type: String, + value: 'label', + notify: true + }, + }, }); From 10be5a75cc2567cb850afdd4b02ef267c954b57b Mon Sep 17 00:00:00 2001 From: Hayden McParlane Date: Tue, 12 Sep 2017 20:32:23 -0500 Subject: [PATCH 2/9] 1.x working in hybrid --- hc-tree-view-behavior.html | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/hc-tree-view-behavior.html b/hc-tree-view-behavior.html index 098cf69..53ae1fc 100644 --- a/hc-tree-view-behavior.html +++ b/hc-tree-view-behavior.html @@ -258,9 +258,11 @@ const parentId = this.$.list._getParentId(item); const partItem = parts[parentId].find((i) => i[this.itemIdName] === item[this.itemIdName]); // Trigger a state change that will propagate - this.itemStateChanged(null, { - item: partItem, - state, + this.itemStateChanged({ + detail: { + item: partItem, + state, + }, }); }); }, From 7df22783efeda2dc45c2017ba9fed080ef549966 Mon Sep 17 00:00:00 2001 From: Austin D Murdock Date: Wed, 13 Sep 2017 16:28:08 -0500 Subject: [PATCH 3/9] Fix: addressing hybrid migration issues, including some performance stuff associated with iron-ajax 2.0 and some deviations in implementation --- bower.json | 2 +- hc-nested-list-behavior.html | 4 +++ hc-nested-list.html | 1 - hc-tree-node.html | 67 ++++++++++++++++++------------------ hc-tree-view-behavior.html | 59 ++++++++++++++++--------------- test/hc-tree-view_test.html | 1 - 6 files changed, 70 insertions(+), 64 deletions(-) diff --git a/bower.json b/bower.json index b2dce2e..5f17afb 100644 --- a/bower.json +++ b/bower.json @@ -4,7 +4,7 @@ "main": "hc-tree-view.html", "dependencies": { "polymer": "Polymer/polymer#^1.9 || ^2.0", - "iron-list": "PolymerElements/iron-list#^1.0 || ^2.0", + "iron-list": "admwx7/iron-list#fix/data-propagation", "paper-tristate-checkbox": "Hackception/paper-tristate-checkbox#^0.2.0" }, "devDependencies": { diff --git a/hc-nested-list-behavior.html b/hc-nested-list-behavior.html index e8918b3..1c29be6 100644 --- a/hc-nested-list-behavior.html +++ b/hc-nested-list-behavior.html @@ -60,6 +60,10 @@ observers: ['_computeParts(items, itemIdName, itemParentIdName)'], 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); + } }, detached() { this.removeEventListener('item-open-changed', this.itemOpenChanged); diff --git a/hc-nested-list.html b/hc-nested-list.html index 2ddc9d3..0a4bf81 100644 --- a/hc-nested-list.html +++ b/hc-nested-list.html @@ -43,7 +43,6 @@
diff --git a/hc-tree-node.html b/hc-tree-node.html index 3dde10d..a7c9ea3 100644 --- a/hc-tree-node.html +++ b/hc-tree-node.html @@ -71,7 +71,7 @@ checked="[[checked]]" unchecked="[[unchecked]]" indeterminate="[[indeterminate]]" - on-state-changed="_stateChanged" + on-click="__userInteraction" > @@ -133,19 +133,23 @@ * 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._regularClick = function(event) { - event.stopPropagation(); + this.$.checkbox._regularTap = function(event) { this.state = this.state === 'on' ? 'off' : 'on'; }; }, + attached() { + Polymer.RenderStatus.afterNextRender(this, () => { + this.__init = false; + }); + }, /** * Returns the openIcon when open is true, otherwise it returns the closedIcon. + * @protected * - * @private * @param {Boolean} open * @return {String} */ @@ -153,28 +157,11 @@ 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 {Number} depth */ - _stateChanged(event) { - const detail = 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.dispatchEvent(new CustomEvent('item-state-changed', { - detail: { - item: this.item, - state: detail.value, - }, - bubbles: true, - composed: true, - cancelable: true, - })); - }, _updateDepth(depth) { this.updateStyles({ '--hc-tree-node-depth': String(depth), @@ -182,19 +169,14 @@ }, /** * 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 - if (!this.item) return; - // Prevent the initial event that happens upon loading - else if (this.__init) { - this.__init = false; - return; - } + // Prevent firing events if we don't have the necessary data or if it's still initializing + if (!this.item || this.__init) return; this.dispatchEvent(new CustomEvent('item-selected-changed', { detail: { item: this.item, @@ -205,6 +187,25 @@ cancelable: 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(); + this.dispatchEvent(new CustomEvent('item-state-changed', { + detail: { + item: this.item, + state: this.item.state, + }, + bubbles: true, + composed: true, + cancelable: true, + })); + }, }); diff --git a/hc-tree-view-behavior.html b/hc-tree-view-behavior.html index 53ae1fc..1872578 100644 --- a/hc-tree-view-behavior.html +++ b/hc-tree-view-behavior.html @@ -76,33 +76,37 @@ }); }, /** - * 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.} [items = this.selectedItems] + * @param {Array.} [items] * @param {Boolean} [preventUpdate] - blocks the call to _updateState to block template updating */ - deselectItems(items = this.selectedItems, preventUpdate) { - let selectedItems = []; - - const getNodeId = (i) => i[this.itemIdName]; - - // Map to Ids for perf - selectedItems = this.selectedItems.map(getNodeId); - const passedItems = items.map(getNodeId); - - // Filter by Ids for perf - selectedItems = selectedItems.filter((i) => passedItems.indexOf(i) === -1); - - // Update selectedItems if there was an item removed - if (this.selectedItems.length !== selectedItems.length) { - const getItemById = (id) => this.selectedItems.find((i) => getNodeId(i) === id); + deselectItems(items, preventUpdate) { + if (!items) { + items = this.selectedItems; + this.set('selectedItems', []); + } else { + const getNodeId = (i) => i[this.itemIdName]; + const itemsToUpdate = []; - // Re-map ids to items if not empty - if (selectedItems.length > 0) selectedItems = selectedItems.map(getItemById); + items.forEach((item) => { + const index = this.selectedItems.findIndex((i) => { + const match = getNodeId(i) === getNodeId(item); + if (match) { + itemsToUpdate.push(i); + } + return match; + }); + if (index >= 0) { + this.selectedItems.splice(index, 1); + } + }); - this.set('selectedItems', selectedItems); - if (!preventUpdate) this._updateState(items, 'off'); + items = itemsToUpdate; + this.set('selectedItems', Object.assign([], this.selectedItems)); } + if (!preventUpdate) this._updateState(items, 'off'); }, /** * Fetches the parent for the given item. @@ -137,10 +141,6 @@ const detail = event.detail; const {item, state} = detail; - // Reject duplicated events - if (item.state === state) return; - item.state = state; - // Change selected status for parents up the chain const parent = this._updateParents(item, state); if (parent) { @@ -251,9 +251,12 @@ _updateState(items, state, parts = this._parts) { if (!items || !parts) return; // Filter items to remove children that have a parent in the array - items.filter((item) => !items.some((i) => i[this.itemIdName] === item[this.itemParentIdName])). - // Pass the filtered items into _updateChildren - forEach((item) => { + items = items.filter((item) => { + item.state = state; + return !items.some((i) => i[this.itemIdName] === item[this.itemParentIdName]); + }); + // Pass the filtered items into _updateChildren + items.forEach((item) => { // Find the item in the parts object const parentId = this.$.list._getParentId(item); const partItem = parts[parentId].find((i) => i[this.itemIdName] === item[this.itemIdName]); diff --git a/test/hc-tree-view_test.html b/test/hc-tree-view_test.html index f7ba62e..7f5638b 100644 --- a/test/hc-tree-view_test.html +++ b/test/hc-tree-view_test.html @@ -179,7 +179,6 @@ flush(() => { const result = Object.keys(element._parts).some((k) => { return element._parts[k].some((itemPart) => { - console.log(itemPart) return itemPart.open; }); }); From e342ffe0159c78cce1e1fafbc775d4ae06b4f6e0 Mon Sep 17 00:00:00 2001 From: Austin D Murdock Date: Thu, 14 Sep 2017 15:42:02 -0500 Subject: [PATCH 4/9] Update: unit tests to support hybrid --- hc-nested-list-behavior.html | 12 ++- hc-nested-list-item.html | 3 + hc-tree-node.html | 23 ++--- hc-tree-view-behavior.html | 25 +++-- test/hc-nested-list-item_test.html | 18 +++- test/hc-nested-list_test.html | 43 +++++---- test/hc-tree-node_test.html | 81 ++++++++-------- test/hc-tree-view_test.html | 146 +++++++++++++++-------------- 8 files changed, 188 insertions(+), 163 deletions(-) diff --git a/hc-nested-list-behavior.html b/hc-nested-list-behavior.html index 1c29be6..856bae2 100644 --- a/hc-nested-list-behavior.html +++ b/hc-nested-list-behavior.html @@ -57,7 +57,12 @@ computed: '_computeItems(parts)', }, }, - observers: ['_computeParts(items, itemIdName, itemParentIdName)'], + 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) => { @@ -117,6 +122,7 @@ * @param {Object} detail * @param {String} [idName=this.itemIdName] * @param {Object} [parts = this.parts] + * @return {Promise} */ itemOpenChanged(event, idName = this.itemIdName, parts = this.parts) { // Block interactions while loading items @@ -140,7 +146,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); @@ -155,7 +161,7 @@ res(parts); }); } else res(parts); - })).then(() => { + })).then((parts) => { this.set('parts', Object.assign({}, parts)); this.isLoading = false; }); diff --git a/hc-nested-list-item.html b/hc-nested-list-item.html index 51ddc4f..7cdae2a 100644 --- a/hc-nested-list-item.html +++ b/hc-nested-list-item.html @@ -33,6 +33,9 @@ /* The item holding all data */ item: Object, }, + ready() { + this.fireOpenEvent = this.fireOpenEvent.bind(this); + }, attached() { this.addEventListener('click', this.fireOpenEvent); }, diff --git a/hc-tree-node.html b/hc-tree-node.html index a7c9ea3..38c10e5 100644 --- a/hc-tree-node.html +++ b/hc-tree-node.html @@ -116,15 +116,6 @@ type: Boolean, 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() { @@ -141,11 +132,6 @@ this.state = this.state === 'on' ? 'off' : 'on'; }; }, - attached() { - Polymer.RenderStatus.afterNextRender(this, () => { - this.__init = false; - }); - }, /** * Returns the openIcon when open is true, otherwise it returns the closedIcon. * @protected @@ -176,7 +162,12 @@ */ _valueChanged(value) { // Prevent firing events if we don't have the necessary data or if it's still initializing - if (!this.item || this.__init) return; + if (!this.item) return; + if (!this.__initComplete) { + this.__initComplete = true; + return; + } + this.dispatchEvent(new CustomEvent('item-selected-changed', { detail: { item: this.item, @@ -184,7 +175,6 @@ }, bubbles: true, composed: true, - cancelable: true, })); }, /** @@ -203,7 +193,6 @@ }, bubbles: true, composed: true, - cancelable: true, })); }, }); diff --git a/hc-tree-view-behavior.html b/hc-tree-view-behavior.html index 1872578..3b762dd 100644 --- a/hc-tree-view-behavior.html +++ b/hc-tree-view-behavior.html @@ -42,12 +42,10 @@ this.addEventListener('item-selected-changed', this.itemSelectedChanged); this.addEventListener('item-state-changed', this.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 * @@ -104,7 +102,9 @@ }); items = itemsToUpdate; - this.set('selectedItems', Object.assign([], this.selectedItems)); + if (items.length) { + this.set('selectedItems', Object.assign([], this.selectedItems)); + } } if (!preventUpdate) this._updateState(items, 'off'); }, @@ -121,10 +121,9 @@ * Makes calls to select or deselect the item based on the selected in the event. * * @listens hc-tree-view-behavior#item-selected-changed - * @param {CustomEvent} [event] + * @param {CustomEvent} event */ - itemSelectedChanged(event) { - const detail = event.detail; + itemSelectedChanged({detail}) { if (detail.selected) { this.selectItems([detail.item], true); } else { @@ -137,10 +136,13 @@ * @listens hc-tree-view-behavior#item-state-changed * @param {CustomEvent} event */ - itemStateChanged(event) { - const detail = event.detail; + itemStateChanged({detail}) { const {item, state} = detail; + if (item.state !== state) { + item.state = state; + } + // Change selected status for parents up the chain const parent = this._updateParents(item, state); if (parent) { @@ -152,11 +154,12 @@ } // Change selected status for children, even if they are hidden. - if ( state === 'on') { + if (state === 'on') { this.selectItems(this._updateChildren(item, state), true); } else { this.deselectItems(this._updateChildren(item, state), true); } + this.debounce('itemStateChanged', () => { this.set('_parts', Object.assign({}, this._parts)); }); @@ -259,7 +262,9 @@ items.forEach((item) => { // Find the item in the parts object const parentId = this.$.list._getParentId(item); - const partItem = parts[parentId].find((i) => i[this.itemIdName] === item[this.itemIdName]); + const partItem = parts[parentId].find((i) => { + return i[this.itemIdName] === item[this.itemIdName]; + }); // Trigger a state change that will propagate this.itemStateChanged({ detail: { diff --git a/test/hc-nested-list-item_test.html b/test/hc-nested-list-item_test.html index 7614e1b..45c3f92 100644 --- a/test/hc-nested-list-item_test.html +++ b/test/hc-nested-list-item_test.html @@ -36,7 +36,7 @@ element.item = {test: 'test'}; event.stopPropagation = sinon.spy(); element.set = sinon.spy(); - fireSpy = sinon.spy(element, 'fire'); + fireSpy = sinon.spy(element, 'dispatchEvent'); }); it('should block event propagation', () => { @@ -45,12 +45,22 @@ }); it('should fire a item-open-changed event providing the item data', () => { element.fireOpenEvent(event); - expect(fireSpy.called).to.equal(true); - expect(fireSpy.calledWithMatch('item-open-changed', {item: element.item, open: !element.item.open})). - to.equal(true); + const e = new CustomEvent('item-open-changed', { + detail: { + item: element.item, + open: !element.item.open, + }, + bubbles: true, + composed: true, + cancelable: true, + }); + expect(fireSpy.calledWithMatch(e)).to.equal(true); }); it('should be bound to a click event', done => { element.fireOpenEvent = sinon.spy(); + const parent = element.parentNode; + parent.removeChild(element); + parent.appendChild(element); element.querySelector('#header').click(); flush(() => { diff --git a/test/hc-nested-list_test.html b/test/hc-nested-list_test.html index 61e2736..27ae94c 100644 --- a/test/hc-nested-list_test.html +++ b/test/hc-nested-list_test.html @@ -66,35 +66,35 @@ it('should return the parent from parts if it exists', () => expect(element.getParent(parts[parts.root[0].id][0])).to.deep.equal(parts.root[0])); }); - describe('itemOpenChanged(event, detail, idName)', () => { + describe('itemOpenChanged(event, idName, parts)', () => { const event = {}; let defaultPartSpy, fetchChildrenSpy; beforeEach(() => { event.detail = { item: Object.assign({}, parts.root[0]), - open: true + open: true, }; defaultPartSpy = sinon.spy(element, '__setDefaultPart'); fetchChildrenSpy = sinon.spy(element, 'fetchChildren'); - element._computeItems = () => {}; + element._computeItems = sinon.spy(); }); - it('should update the open state for the item', (done) => { + it('should update the open state for the item', () => { element.parts = parts; - element.itemOpenChanged(event, event.detail); - flush(() => { - expect(element.set.calledOnce).to.equal(true); - expect(element.set.calledWith('parts')).to.equal(true); - expect(element.set.args[0][1].root[0].open).to.equal(event.detail.open); - expect(fetchChildrenSpy.calledWithMatch(element.parts.root[0], element.parts)).to.equal(true); - done(); - }); + return element.itemOpenChanged(event).then( + () => { + expect(element.set.calledOnce).to.equal(true); + expect(element.set.calledWith('parts')).to.equal(true); + expect(element.set.args[0][1].root[0].open).to.equal(event.detail.open); + expect(fetchChildrenSpy.calledWithMatch(element.parts.root[0], element.parts)).to.equal(true); + } + ); }); it('should set open to false for all other items', (done) => { parts.root[1].open = true; element.parts = parts; - element.itemOpenChanged(event, event.detail); + element.itemOpenChanged(event); flush(() => { expect(element.set.calledOnce).to.equal(true); expect(element.set.calledWith('parts')).to.equal(true); @@ -105,14 +105,19 @@ }); it('should not run if isLoading is true', () => { element.isLoading = true; - element.itemOpenChanged(event, event.detail); + element.itemOpenChanged(event); expect(fetchChildrenSpy.called).to.equal(false); }); it('should run when the item-open-changed event is fired', (done) => { - element.itemOpenChanged = sinon.spy(); - element.fire('item-open-changed'); + const spy = sinon.spy(element, 'itemOpenChanged'); + const parent = element.parentNode; + parent.removeChild(element); + parent.appendChild(element); + element.isLoading = true; + const event = new CustomEvent('item-open-changed'); + element.dispatchEvent(event); flush(() => { - expect(element.itemOpenChanged.calledOnce).to.equal(true); + expect(spy.calledOnce).to.equal(true); done(); }); }); @@ -128,7 +133,7 @@ update(parts); }; element.parts = parts; - element.itemOpenChanged(event, event.detail); + element.itemOpenChanged(event); expect(defaultPartSpy.calledWithMatch(bar, parts, 'foo', 'off')).to.equal(true); }); it('should inherit on state from opened item', () => { @@ -145,7 +150,7 @@ parts.root[0].state = 'on'; element.parts = parts; - element.itemOpenChanged(event, event.detail); + element.itemOpenChanged(event); expect(defaultPartSpy.calledWithMatch(bar, parts, 'foo', 'on')).to.equal(true); }); }); diff --git a/test/hc-tree-node_test.html b/test/hc-tree-node_test.html index b44a0e7..114b9eb 100644 --- a/test/hc-tree-node_test.html +++ b/test/hc-tree-node_test.html @@ -25,7 +25,7 @@ beforeEach(() => { element = fixture('basic'); - fireSpy = sinon.spy(element, 'fire'); + fireSpy = sinon.spy(element, 'dispatchEvent'); }); // Lifecycle Functions @@ -63,20 +63,6 @@ element.$.checkbox.click(); expect(element.item.state).to.equal('on'); }); - it('should prevent bubbling when _regularTap', done => { - const spy = sinon.spy(); - element.item = { - state: 'off' - }; - element.addEventListener('tap', () => { - spy(); - }); - element.$.checkbox.click(); - flush(() => { - expect(spy.called).to.equal(false); - done(); - }) - }); }); // Private Functions describe('_getIcon(open)', () => { @@ -85,50 +71,63 @@ it('should return closedIcon when open is false', () => expect(element._getIcon(false)).to.equal(element.closedIcon)); }); - describe('_stateChanged(event, detail)', () => { - const event = {}; - - it('should do nothing when there is no item', () => { - element._stateChanged(); - expect(fireSpy.called).to.equal(false); - }); - it('should fire a item-state-changed event', () => { - element.item = {test: 'test'}; - event.detail = { - value: 'off' - }; - element._stateChanged(event, event.detail); - expect(fireSpy.calledWith('item-state-changed', { - item: element.item, - state: event.detail.value - })).to.equal(true); - }); - }); describe('_valueChanged(value)', () => { it('should do nothing when there is no item', () => { element._valueChanged(); expect(fireSpy.called).to.equal(false); }); - it('should reject the first run after item is set, while __init is true', () => { + it('should reject the first run after item is set, while __initComplete is false', () => { element._valueChanged(); expect(fireSpy.called).to.equal(false); - expect(element.__init).to.equal(true); + expect(element.__initComplete).not.to.be.ok; element.item = {test: 'test'}; fireSpy.reset(); element._valueChanged(); - expect(element.__init).to.equal(false); + expect(element.__initComplete).to.be.ok; expect(fireSpy.called).to.equal(false); }); it('should fire the item-selected-changed event', () => { element.item = {test: 'test'}; + const e = new CustomEvent('item-selected-changed', { + detail: { + item: element.item, + selected: true, + }, + bubbles: true, + composed: true, + }); // Call twice since the first one is rejected element._valueChanged(true); element._valueChanged(true); - expect(fireSpy.calledWith('item-selected-changed', { - item: element.item, - selected: true - })).to.equal(true); + expect(fireSpy.calledWith(e)).to.equal(true); + }); + }); + describe('__userInteraction(event)', () => { + let event; + + beforeEach(() => { + element.item = { + state: 'on', + }; + event = new CustomEvent('item-state-changed', { + detail: { + item: element.item, + state: element.item.state, + }, + bubbles: true, + composed: true, + }); + }); + + it('should stop event propagation', () => { + event.stopPropagation = sinon.spy(); + element.__userInteraction(event); + expect(event.stopPropagation).to.have.been.called; + }); + it('should dispatch an item-state-changed event', () => { + element.__userInteraction(event); + expect(fireSpy).to.have.been.calledWithMatch(event); }); }); }); diff --git a/test/hc-tree-view_test.html b/test/hc-tree-view_test.html index 7f5638b..ff688e4 100644 --- a/test/hc-tree-view_test.html +++ b/test/hc-tree-view_test.html @@ -35,6 +35,60 @@ }); // Public Functions + describe('collapse()', () => { + beforeEach(() => { + sinon.spy(element, 'debounce'); + element.items = items; + Object.keys(parts).forEach((k) => { + parts[k].forEach((itemPart) => { + itemPart.open = true; + }); + }); + }); + it('should collapse all nodes and maintain their state', (done) => { + element.collapse(undefined, parts); + expect(element.debounce.callCount).to.equal(1); + flush(() => { + const result = Object.keys(element._parts).some((k) => { + return element._parts[k].some((itemPart) => { + return itemPart.open; + }); + }); + expect(result).to.equal(false); + done(); + }); + }); + it('should not collapse any nodes when empty array is passed', (done) => { + element.collapse([], parts); + expect(element.debounce.callCount).to.equal(0); + flush(() => { + const result = Object.keys(element._parts).every((k) => { + return element._parts[k].every((itemPart) => itemPart.open); + }); + expect(result).to.equal(true); + done(); + }); + }); + it('should collapse the passed items and maintain their state', (done) => { + const item = items[0]; + element.collapse([item], parts); + expect(element.debounce.callCount).to.equal(1); + flush(() => { + const itemId = item.id; + const parentId = item.parentId; + let collapsedResult = ( + !element._parts[itemId].every((i) => i.open) && + !element._parts[parentId].find((i) => i.id === itemId).open + ); + const untouchedResult = Object.keys(element._parts).some((k) => { + return element._parts[k].some((itemPart) => itemPart.open); + }); + expect(collapsedResult).to.equal(true); + expect(untouchedResult).to.equal(true); + done(); + }); + }); + }); describe('deselectItems(items)', () => { beforeEach(() => element._updateState = sinon.spy()); @@ -76,7 +130,7 @@ expect(element.$.list.getParent.calledWithMatch(item)).to.equal(true); }); }); - describe('itemSelectedChanged(event, detail)', () => { + describe('itemSelectedChanged(event)', () => { const item = {id: 'test'}; beforeEach(() => { element.selectItems = sinon.spy(); @@ -84,12 +138,12 @@ }); it('should call selectItems with the item when detail.selected', () => { - element.itemSelectedChanged(null, {item, selected: true}); + element.itemSelectedChanged({detail: { item, selected: true, }}); expect(element.selectItems.calledWithMatch([item], true)).to.equal(true); expect(element.deselectItems.calledWithMatch([item], true)).to.equal(false); }); it('should call deselectItems with the item when detail.selected', () => { - element.itemSelectedChanged(null, {item, selected: false}); + element.itemSelectedChanged({detail: { item, selected: false, }}); expect(element.selectItems.calledWithMatch([item], true)).to.equal(false); expect(element.deselectItems.calledWithMatch([item], true)).to.equal(true); }); @@ -116,34 +170,34 @@ }); it('should update item state', () => { const state = 'on'; - element.itemStateChanged(null, {item, state}); + element.itemStateChanged({ detail: {item, state} }); expect(item.state).to.equal(state); }); it('should call _updateParents', () => { const state = 'on'; - element.itemStateChanged(null, {item, state}); + element.itemStateChanged({ detail: {item, state} }); expect(element._updateParents.calledWithMatch(item, state)).to.equal(true); }); it('should call selectItems when state is changed to "on"', () => { const state = 'on'; - element.itemStateChanged(null, {item, state}); + element.itemStateChanged({ detail: {item, state} }); expect(element.selectItems.calledWithMatch([{id: 'test'}], true)).to.equal(true); }); it('should call deselectItems when state is changed to !"on"', () => { const state = 'off'; item.state = 'on'; - element.itemStateChanged(null, {item, state}); + element.itemStateChanged({ detail: {item, state} }); expect(element.deselectItems.calledWithMatch([{id: 'test'}], true)).to.equal(true); }); it('should debounce', () => { const state = 'on'; - element.itemStateChanged(null, {item, state}); + element.itemStateChanged({ detail: {item, state} }); expect(element.debounce.calledOnce).to.equal(true); }) it('should set _parts', done => { const state = 'on'; element._parts = parts; - element.itemStateChanged(null, {item, state}); + element.itemStateChanged({ detail: {item, state} }); flush(() => { expect(setSpy.calledWithMatch('_parts', parts)).to.equal(true); done(); @@ -163,60 +217,6 @@ expect(setSpy.calledWithMatch('items', items)).to.equal(true); }); }); - describe('collapse()', () => { - beforeEach(() => { - sinon.spy(element, 'debounce'); - element.items = items; - Object.keys(parts).forEach((k) => { - parts[k].forEach((itemPart) => { - itemPart.open = true; - }); - }); - }); - it('should collapse all nodes and maintain their state', (done) => { - element.collapse(undefined, parts); - expect(element.debounce.callCount).to.equal(1); - flush(() => { - const result = Object.keys(element._parts).some((k) => { - return element._parts[k].some((itemPart) => { - return itemPart.open; - }); - }); - expect(result).to.equal(false); - done(); - }); - }); - it('should not collapse any nodes when empty array is passed', (done) => { - element.collapse([], parts); - expect(element.debounce.callCount).to.equal(0); - flush(() => { - const result = Object.keys(element._parts).every((k) => { - return element._parts[k].every((itemPart) => itemPart.open); - }); - expect(result).to.equal(true); - done(); - }); - }); - it('should collapse the passed items and maintain their state', (done) => { - const item = items[0]; - element.collapse([item], parts); - expect(element.debounce.callCount).to.equal(1); - flush(() => { - const itemId = item.id; - const parentId = item.parentId; - let collapsedResult = ( - !element._parts[itemId].every((i) => i.open) && - !element._parts[parentId].find((i) => i.id === itemId).open - ); - const untouchedResult = Object.keys(element._parts).some((k) => { - return element._parts[k].some((itemPart) => itemPart.open); - }); - expect(collapsedResult).to.equal(true); - expect(untouchedResult).to.equal(true); - done(); - }); - }); - }) describe('selectItems(items)', () => { beforeEach(() => element._updateState = sinon.spy()); @@ -334,14 +334,22 @@ element.$.list.parts = parts; }); - it('should call _updateChildren all items that do not have a parent in items', () => { - const subItems = [parts.root[0], parts[parts.root[0].id][0], parts[parts.root[1].id][0]]; + it('should call itemStateChanged on all of the "root" items', () => { + const subItems = [ + parts.root[0], + parts[parts.root[0].id][0], + parts[parts.root[1].id][0], + ]; element._updateState(subItems, state, parts); expect(element.itemStateChanged.callCount).to.equal(2); - expect(element.itemStateChanged.calledWithMatch(null, {item: subItems[0], state})). - to.equal(true); - expect(element.itemStateChanged.calledWithMatch(null, {item: subItems[2], state})). - to.equal(true); + expect(element.itemStateChanged.calledWithMatch({ detail: { + item: subItems[0], + state, + }})).to.equal(true); + expect(element.itemStateChanged.calledWithMatch({ detail: { + item: subItems[2], + state, + }})).to.equal(true); }); it('should do nothing when items is undefined', () => expect(element._updateState).to.not.throw(TypeError)); From bec4fc7ab426eee6453d8bf5006fd0d7918c4d83 Mon Sep 17 00:00:00 2001 From: Austin D Murdock Date: Thu, 14 Sep 2017 15:52:50 -0500 Subject: [PATCH 5/9] Fix: downsizing demo load for hc-nested-list until we fix some performance issues --- demo/hc-nested-list.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo/hc-nested-list.html b/demo/hc-nested-list.html index f4b2e89..1882529 100644 --- a/demo/hc-nested-list.html +++ b/demo/hc-nested-list.html @@ -62,7 +62,7 @@

hc-nested-list demo

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) } } }); From 03cfdecfccf753ce44e03f3f75acf009d5d0f32e Mon Sep 17 00:00:00 2001 From: Austin Murdock Date: Wed, 20 Sep 2017 17:05:48 -0500 Subject: [PATCH 6/9] Update: finishing off the update to hybrid --- .gitignore | 2 ++ bower.json | 10 +++--- demo/index.html | 68 +++++++++++++++++++----------------- hc-nested-list-behavior.html | 19 ++++++++++ hc-nested-list.html | 10 +++--- hc-tree-node.html | 21 ++++++----- hc-tree-view-behavior.html | 11 +++++- hc-tree-view.html | 38 +++++++++++++++++--- 8 files changed, 125 insertions(+), 54 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0d47f36 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +bower_components* +bower-* diff --git a/bower.json b/bower.json index 5f17afb..2e0c2e0 100644 --- a/bower.json +++ b/bower.json @@ -5,23 +5,25 @@ "dependencies": { "polymer": "Polymer/polymer#^1.9 || ^2.0", "iron-list": "admwx7/iron-list#fix/data-propagation", - "paper-tristate-checkbox": "Hackception/paper-tristate-checkbox#^0.2.0" + "paper-tristate-checkbox": "Hackception/paper-tristate-checkbox#^0.3.0" }, "devDependencies": { "iron-component-page": "PolymerElements/iron-component-page#^1.0 || ^2.0", "iron-demo-helpers": "PolymerElements/iron-demo-helpers#^1.0 || ^2.0", - "web-component-tester": "^6.0.0", + "web-component-tester": "Polymer/web-component-tester#^6.0.0", "webcomponentsjs": "webcomponents/webcomponentsjs#^1.0 || ^2.0" }, "variants": { "1.x": { "dependencies": { - "iron-list": "admwx7/iron-list#fix/issue-300" + "polymer": "Polymer/polymer#^1.9", + "iron-list": "admwx7/iron-list#fix/issue-300", + "paper-tristate-checkbox": "Hackception/paper-tristate-checkbox#^0.2.0" }, "devDependencies": { "iron-component-page": "PolymerElements/iron-component-page#^1.0.0", "iron-demo-helpers": "PolymerElements/iron-demo-helpers#^1.0.0", - "web-component-tester": "^4.0.0", + "web-component-tester": "Polymer/web-component-tester#^4.0.0", "webcomponentsjs": "webcomponents/webcomponentsjs#^0.7.0" }, "resolutions": { 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 856bae2..ed75a55 100644 --- a/hc-nested-list-behavior.html +++ b/hc-nested-list-behavior.html @@ -55,6 +55,7 @@ type: Array, readOnly: true, computed: '_computeItems(parts)', + observer: '__updateList', }, }, observers: [ @@ -279,6 +280,24 @@ 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.html b/hc-nested-list.html index 0a4bf81..65b1503 100644 --- a/hc-nested-list.html +++ b/hc-nested-list.html @@ -1,6 +1,6 @@ - + +
+ +
-
- -
diff --git a/hc-tree-node.html b/hc-tree-node.html index 38c10e5..10100ba 100644 --- a/hc-tree-node.html +++ b/hc-tree-node.html @@ -72,6 +72,7 @@ unchecked="[[unchecked]]" indeterminate="[[indeterminate]]" on-click="__userInteraction" + disable-indeterminate > @@ -172,6 +173,7 @@ detail: { item: this.item, selected: value, + state: this.item.state, }, bubbles: true, composed: true, @@ -186,14 +188,17 @@ */ __userInteraction(event) { event.stopPropagation(); - this.dispatchEvent(new CustomEvent('item-state-changed', { - detail: { - item: this.item, - state: this.item.state, - }, - bubbles: true, - composed: true, - })); + // 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 3b762dd..cc9b5d1 100644 --- a/hc-tree-view-behavior.html +++ b/hc-tree-view-behavior.html @@ -38,6 +38,10 @@ */ _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); @@ -129,6 +133,9 @@ } else { this.deselectItems([detail.item], true); } + if (detail.state !== undefined && Polymer.Element) { + this._updateState([detail.item], detail.state) + } }, /** * Updates the parents and children of this item that it's state was changed, update thiers as well if needed. @@ -282,13 +289,15 @@ * @type {Object} * @property {Object} item - the item to change selection status for * @property {Boolean} selection - the updated selection status + * @property {String} [state] - triggers a state update, can be 'on', 'off', or null */ /** * Event for changing the state of a tree node * + * @deprecated * @event hc-tree-view-behavior#item-state-changed * @type {Object} * @property {Object} item - the item to change state for - * @property {Boolean} state - the updated state + * @property {String} state - the updated state, can be 'on', 'off', or null */ diff --git a/hc-tree-view.html b/hc-tree-view.html index 9fc3987..5a38d60 100644 --- a/hc-tree-view.html +++ b/hc-tree-view.html @@ -27,6 +27,9 @@ .item { @apply --hc-tree-view-item; } + [hidden] { + display: none; + } @@ -57,6 +78,13 @@ value: 'label', notify: true }, + /* Determine if we are in v1 or v0 shadow dom so we can conditionally stamp */ + __v1: { + type: Boolean, + value() { + return Boolean(Polymer.Element); + }, + } }, }); From 4bcb8a490464bdae5d7d5b2179e06e886b3fc479 Mon Sep 17 00:00:00 2001 From: Austin Murdock Date: Thu, 21 Sep 2017 11:27:23 -0500 Subject: [PATCH 7/9] Fix: lint issue and cleanup some performance overhead --- hc-nested-list.html | 5 +-- hc-tree-view-behavior.html | 31 ++++++++------- hc-tree-view.html | 78 ++++++++++++++++++++++---------------- 3 files changed, 65 insertions(+), 49 deletions(-) diff --git a/hc-nested-list.html b/hc-nested-list.html index 65b1503..06f0e78 100644 --- a/hc-nested-list.html +++ b/hc-nested-list.html @@ -48,8 +48,7 @@ + as="item"> @@ -58,7 +57,7 @@ Polymer({ /* global hc */ is: 'hc-nested-list', - behaviors: [hc.NestedListBehavior] + behaviors: [hc.NestedListBehavior], }); diff --git a/hc-tree-view-behavior.html b/hc-tree-view-behavior.html index cc9b5d1..3ea3dd8 100644 --- a/hc-tree-view-behavior.html +++ b/hc-tree-view-behavior.html @@ -25,6 +25,11 @@ }, /* 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, @@ -119,7 +124,7 @@ * @return {Object|undefined} */ getParent(item) { - return this.$.list.getParent(item); + return this.list.getParent(item); }, /** * Makes calls to select or deselect the item based on the selected in the event. @@ -267,19 +272,19 @@ }); // Pass the filtered items into _updateChildren items.forEach((item) => { - // Find the item in the parts object - const parentId = this.$.list._getParentId(item); - const partItem = parts[parentId].find((i) => { - return i[this.itemIdName] === item[this.itemIdName]; - }); - // Trigger a state change that will propagate - this.itemStateChanged({ - detail: { - item: partItem, - state, - }, - }); + // Find the item in the parts object + const parentId = this.list._getParentId(item); + const partItem = parts[parentId].find((i) => { + return i[this.itemIdName] === item[this.itemIdName]; + }); + // Trigger a state change that will propagate + this.itemStateChanged({ + detail: { + item: partItem, + state, + }, }); + }); }, }; /** diff --git a/hc-tree-view.html b/hc-tree-view.html index 5a38d60..42bf056 100644 --- a/hc-tree-view.html +++ b/hc-tree-view.html @@ -32,38 +32,45 @@ } - - - + + + From 381020c9ccb89e39d146f5b76d14920a707f68af Mon Sep 17 00:00:00 2001 From: Austin Murdock Date: Thu, 21 Sep 2017 11:43:44 -0500 Subject: [PATCH 8/9] Bumping version for next release --- bower.json | 1 + 1 file changed, 1 insertion(+) diff --git a/bower.json b/bower.json index 2e0c2e0..97090ca 100644 --- a/bower.json +++ b/bower.json @@ -2,6 +2,7 @@ "name": "hc-tree-view", "description": "An optimized tree-view based on a flat data structure.", "main": "hc-tree-view.html", + "version": "v0.5.0", "dependencies": { "polymer": "Polymer/polymer#^1.9 || ^2.0", "iron-list": "admwx7/iron-list#fix/data-propagation", From c62fa447524e5c2009bcfcd31159c60a1b7272fe Mon Sep 17 00:00:00 2001 From: Hayden McParlane Date: Thu, 28 Sep 2017 15:34:07 -0500 Subject: [PATCH 9/9] Debouncing the state update functionality --- hc-tree-view-behavior.html | 41 +++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/hc-tree-view-behavior.html b/hc-tree-view-behavior.html index 3ea3dd8..ab4299a 100644 --- a/hc-tree-view-behavior.html +++ b/hc-tree-view-behavior.html @@ -261,28 +261,29 @@ * @private * @param {Array.} items * @param {String} state - * @param {Object} [parts = this._parts] */ - _updateState(items, state, parts = this._parts) { - if (!items || !parts) return; - // Filter items to remove children that have a parent in the array - items = items.filter((item) => { - item.state = state; - return !items.some((i) => i[this.itemIdName] === item[this.itemParentIdName]); - }); - // Pass the filtered items into _updateChildren - items.forEach((item) => { - // Find the item in the parts object - const parentId = this.list._getParentId(item); - const partItem = parts[parentId].find((i) => { - return i[this.itemIdName] === item[this.itemIdName]; + _updateState(items, state) { + if (!items || !this._parts) return; + this.debounce('_updateState', () => { + // Filter items to remove children that have a parent in the array + items = items.filter((item) => { + item.state = state; + return !items.some((i) => i[this.itemIdName] === item[this.itemParentIdName]); }); - // Trigger a state change that will propagate - this.itemStateChanged({ - detail: { - item: partItem, - state, - }, + // Pass the filtered items into _updateChildren + items.forEach((item) => { + // Find the item in the parts object + const parentId = this.list._getParentId(item); + const partItem = this._parts[parentId].find((i) => { + return i[this.itemIdName] === item[this.itemIdName]; + }); + // Trigger a state change that will propagate + this.itemStateChanged({ + detail: { + item: partItem, + state, + }, + }); }); }); },