From 08f4f9006f5450f0d98d8fdbdfb7bca31c929356 Mon Sep 17 00:00:00 2001 From: Matt Mo Date: Wed, 7 Oct 2020 08:30:03 -0700 Subject: [PATCH] Multiple bug fixes and improvements. Shift, Unshift, and Splice are now supported to do inline array modifications without losing the original array reference across elements. --- src/ignite-html.js | 135 +++++++++++++++++++++++++++++++------ src/ignite-template.js | 149 +++++++++++++++++++++++++++++++++++------ 2 files changed, 241 insertions(+), 43 deletions(-) diff --git a/src/ignite-html.js b/src/ignite-html.js index 0f69a23..64ae704 100644 --- a/src/ignite-html.js +++ b/src/ignite-html.js @@ -8,6 +8,10 @@ class IgniteProperty { this.onChangeCallbacks = []; this.onPushCallbacks = []; this.onPopCallbacks = []; + this.onShiftCallbacks = []; + this.onUnshiftCallbacks = []; + this.onSpliceCallbacks = []; + this.arrayCallbacks = []; this.reflected = []; this._value = val; @@ -64,31 +68,70 @@ class IgniteProperty { if (Array.isArray(this._value) && this._value.onPushCallbacks == undefined) { this._value.onPushCallbacks = []; this._value.onPopCallbacks = []; + this._value.onShiftCallbacks = []; + this._value.onUnshiftCallbacks = []; + this._value.onSpliceCallbacks = []; this._value.push = function () { var len = Array.prototype.push.apply(this, arguments); - this.onPushCallbacks.forEach((callback) => callback.invoke(arguments[0])); + this.onPushCallbacks.forEach(callback => callback.invoke(Array.from(arguments))); return len; }; this._value.pop = function () { var len = Array.prototype.pop.apply(this, arguments); - this.onPopCallbacks.forEach((callback) => callback.invoke()); + this.onPopCallbacks.forEach(callback => callback.invoke()); return len; } + this._value.unshift = function () { + var len = Array.prototype.unshift.apply(this, arguments); + this.onUnshiftCallbacks.forEach(callback => callback.invoke(Array.from(arguments))); + return len; + } + + this._value.shift = function () { + var len = Array.prototype.shift.apply(this, arguments); + this.onShiftCallbacks.forEach(callback => callback.invoke()); + return len; + }; + + this._value.splice = function (start, deleteCount = 0, ...items) { + var removed = Array.prototype.splice.apply(this, arguments); + this.onSpliceCallbacks.forEach(callback => callback.invoke(start, deleteCount, items)); + return removed; + } + this._value.attachOnPush = function (func) { - var callback = new IgniteCallback(func, (detach) => this.detachOnPush(detach)); + var callback = new IgniteCallback(func, detach => this.detachOnPush(detach)); this.onPushCallbacks.push(callback); return callback; } this._value.attachOnPop = function (func) { - var callback = new IgniteCallback(func, (detach) => this.detachOnPop(detach)); + var callback = new IgniteCallback(func, detach => this.detachOnPop(detach)); this.onPopCallbacks.push(callback); return callback; } + this._value.attachOnUnshift = function (func) { + var callback = new IgniteCallback(func, detach => this.detachOnUnshift(detach)); + this.onUnshiftCallbacks.push(callback); + return callback; + } + + this._value.attachOnShift = function (func) { + var callback = new IgniteCallback(func, detach => this.detachOnShift(detach)); + this.onShiftCallbacks.push(callback); + return callback; + } + + this._value.attachonSplice = function (func) { + var callback = new IgniteCallback(func, detach => this.detachonSplice(detach)); + this.onSpliceCallbacks.push(callback); + return callback; + } + this._value.detachOnPush = function (callback) { this.onPushCallbacks = this.onPushCallbacks.filter(push => push != callback); } @@ -97,51 +140,89 @@ class IgniteProperty { this.onPopCallbacks = this.onPopCallbacks.filter(pop => pop != callback); } - this.arrayCallbacks.push(this._value.attachOnPush(() => this.invokeOnPush())); - this.arrayCallbacks.push(this._value.attachOnPop(() => this.invokeOnPop())); - } else if (Array.isArray(this._value) && this._value.onPushCallbacks) { + this._value.detachOnUnshift = function (callback) { + this.onUnshiftCallbacks = this.onUnshiftCallbacks.filter(unshift => unshift != callback); + } + + this._value.detachOnShift = function (callback) { + this.onShiftCallbacks = this.onShiftCallbacks.filter(shift => shift != callback); + } + + this._value.detachonSplice = function (callback) { + this.onSpliceCallbacks = this.onSpliceCallbacks.filter(slice => slice != callback); + } + } + + if (Array.isArray(this._value) && this._value.onPushCallbacks) { //This array has already been patched but attach to it so we get callbacks. - this.arrayCallbacks.push(this._value.attachOnPush(() => this.invokeOnPush())); + this.arrayCallbacks.push(this._value.attachOnPush((items) => this.invokeOnPush(items))); this.arrayCallbacks.push(this._value.attachOnPop(() => this.invokeOnPop())); + this.arrayCallbacks.push(this._value.attachOnShift(() => this.invokeOnShift())); + this.arrayCallbacks.push(this._value.attachOnUnshift((items) => this.invokeOnUnshift(items))); + this.arrayCallbacks.push(this._value.attachonSplice((start, deleteCount, items) => this.invokeonSplice(start, deleteCount, items))); } } invokeOnChange(oldValue, newValue) { - for (var i = 0; i < this.onChangeCallbacks.length; i++) { - this.onChangeCallbacks[i].invoke(oldValue, newValue); - } + this.onChangeCallbacks.forEach(callback => callback.invoke(oldValue, newValue)); } - invokeOnPush() { - for (var i = 0; i < this.onPushCallbacks.length; i++) { - this.onPushCallbacks[i].invoke(null, this._value); - } + invokeOnPush(items) { + this.onPushCallbacks.forEach(callback => callback.invoke(this._value, items)); } invokeOnPop() { - for (var i = 0; i < this.onPopCallbacks.length; i++) { - this.onPopCallbacks[i].invoke(null, this._value); - } + this.onPopCallbacks.forEach(callback => callback.invoke(this._value)); + } + + invokeOnShift() { + this.onShiftCallbacks.forEach(callback => callback.invoke(this._value)); + } + + invokeOnUnshift(items) { + this.onUnshiftCallbacks.forEach(callback => callback.invoke(this._value, items)); + } + + invokeonSplice(start, deleteCount, items) { + this.onSpliceCallbacks.forEach(callback => callback.invoke(this._value, start, deleteCount, items)); } attachOnChange(onChange) { - var callback = new IgniteCallback(onChange, (detach) => this.detachOnChange(detach)); + var callback = new IgniteCallback(onChange, detach => this.detachOnChange(detach)); this.onChangeCallbacks.push(callback); return callback; } attachOnPush(onPush) { - var callback = new IgniteCallback(onPush, (detach) => this.detachOnPush(detach)); + var callback = new IgniteCallback(onPush, detach => this.detachOnPush(detach)); this.onPushCallbacks.push(callback); return callback; } attachOnPop(onPop) { - var callback = new IgniteCallback(onPop, (detach) => this.detachOnPop(detach)); + var callback = new IgniteCallback(onPop, detach => this.detachOnPop(detach)); this.onPopCallbacks.push(callback); return callback; } + attachOnShift(onShift) { + var callback = new IgniteCallback(onShift, detach => this.detachOnShift(detach)); + this.onShiftCallbacks.push(callback); + return callback; + } + + attachOnUnshift(onUnshift) { + var callback = new IgniteCallback(onUnshift, detach => this.detachOnUnshift(detach)); + this.onUnshiftCallbacks.push(callback); + return callback; + } + + attachOnSplice(onSplice) { + var callback = new IgniteCallback(onSplice, detach => this.detachonSplice(detach)); + this.onSpliceCallbacks.push(callback); + return callback; + } + detachOnChange(callback) { this.onChangeCallbacks = this.onChangeCallbacks.filter(change => change != callback); } @@ -153,6 +234,18 @@ class IgniteProperty { detachOnPop(callback) { this.onPopCallbacks = this.onPopCallbacks.filter(pop => pop != callback); } + + detachOnShift(callback) { + this.onShiftCallbacks = this.onShiftCallbacks.filter(shift => shift != callback); + } + + detachOnUnshift(callback) { + this.onUnshiftCallbacks = this.onUnshiftCallbacks.filter(unshift => unshift != callback); + } + + detachonSplice(callback) { + this.onSpliceCallbacks = this.onSpliceCallbacks.filter(slice => slice != callback); + } } /** diff --git a/src/ignite-template.js b/src/ignite-template.js index bb6146b..0a7652e 100644 --- a/src/ignite-template.js +++ b/src/ignite-template.js @@ -39,7 +39,9 @@ class IgniteTemplate { if (children) { for (var i = 0; i < children.length; i++) { - if (children[i] instanceof IgniteProperty) { + if (children[i] === undefined || children[i] === null) { + continue; //Skip undefined or null children. + } else if (children[i] instanceof IgniteProperty) { this.children.push(new html(children[i])); } else if (children[i] instanceof String || typeof children[i] === 'string') { this.children.push(new html(children[i])); @@ -82,8 +84,11 @@ class IgniteTemplate { //Attack a callback for all the properties name.forEach(prop => { this.callbacks.push(prop.attachOnChange((oldValue, newValue) => this.onClassChanged(converter(...name.getOldPropertyValues(prop, oldValue)), converter(...name.getPropertyValues())))); - this.callbacks.push(prop.attachOnPush((oldValue, newValue) => this.onClassChanged(converter(...name.getOldPropertyValues(prop, oldValue)), converter(...name.getPropertyValues())))); - this.callbacks.push(prop.attachOnPop((oldValue, newValue) => this.onClassChanged(converter(...name.getOldPropertyValues(prop, oldValue)), converter(...name.getPropertyValues())))); + this.callbacks.push(prop.attachOnPush((list, items) => this.onClassChanged(converter(...name.getOldPropertyValues(prop, list)), converter(...name.getPropertyValues())))); + this.callbacks.push(prop.attachOnUnshift((list, items) => this.onClassChanged(converter(...name.getOldPropertyValues(prop, list)), converter(...name.getPropertyValues())))); + this.callbacks.push(prop.attachOnPop((list) => this.onClassChanged(converter(...name.getOldPropertyValues(prop, list)), converter(...name.getPropertyValues())))); + this.callbacks.push(prop.attachOnShift((list) => this.onClassChanged(converter(...name.getOldPropertyValues(prop, list)), converter(...name.getPropertyValues())))); + this.callbacks.push(prop.attachOnSplice((list, start, deleteCount, items) => this.onClassChanged(converter(...name.getOldPropertyValues(prop, list)), converter(...name.getPropertyValues())))); }); var value = converter(...name.getPropertyValues()); @@ -230,7 +235,9 @@ class IgniteTemplate { child(...items) { if (items) { for (var i = 0; i < items.length; i++) { - if (items[i] instanceof IgniteProperty) { + if (items[i] === undefined || items[i] === null) { + continue; //Skip undefined or null items. + } else if (items[i] instanceof IgniteProperty) { this.children.push(new html(items[i])); } else if (items[i] instanceof String || typeof items[i] === 'string') { this.children.push(new html(items[i])); @@ -298,7 +305,7 @@ class IgniteTemplate { /** * Adds a onblur event handler to this template. - * @param {Function|IgniteProperty} eventCallback THe callback function to be invoked by the event once it fires. + * @param {Function|IgniteProperty} eventCallback The callback function to be invoked by the event once it fires. * @returns This ignite template so function calls can be chained. */ onBlur(eventCallback) { @@ -307,13 +314,22 @@ class IgniteTemplate { /** * Adds a onfocus event handler to this template. - * @param {Function|IgniteProperty} eventCallback THe callback function to be invoked by the event once it fires. + * @param {Function|IgniteProperty} eventCallback The callback function to be invoked by the event once it fires. * @returns This ignite template so function calls can be chained. */ onFocus(eventCallback) { return this.on("focus", eventCallback); } + /** + * Adds a onchange event handler to this template. + * @param {Function|IgniteProperty} eventCallback The callback function to be invoked by the event once it fires. + * @returns This ignite template so function calls can be chained. + */ + onChange(eventCallback) { + return this.on("change", eventCallback); + } + /** * Adds a on enter key press event handler to this template. * @param {Function|IgniteProperty} eventCallback The callback function to be invoked by the event once it fires. @@ -452,8 +468,11 @@ class IgniteTemplate { //Attack a callback for all the properties value.forEach(prop => { this.callbacks.push(prop.attachOnChange((oldValue, newValue) => this.onStyleChanged(name, converter(...value.getPropertyValues())))); - this.callbacks.push(prop.attachOnPush((oldValue, newValue) => this.onStyleChanged(name, converter(...value.getPropertyValues())))); - this.callbacks.push(prop.attachOnPop((oldValue, newValue) => this.onStyleChanged(name, converter(...value.getPropertyValues())))); + this.callbacks.push(prop.attachOnPush((list, items) => this.onStyleChanged(name, converter(...value.getPropertyValues())))); + this.callbacks.push(prop.attachOnUnshift((list, items) => this.onStyleChanged(name, converter(...value.getPropertyValues())))); + this.callbacks.push(prop.attachOnPop((list) => this.onStyleChanged(name, converter(...value.getPropertyValues())))); + this.callbacks.push(prop.attachOnShift((list) => this.onStyleChanged(name, converter(...value.getPropertyValues())))); + this.callbacks.push(prop.attachOnSplice((list, start, deleteCount, items) => this.onStyleChanged(name, converter(...value.getPropertyValues())))); }); this.styles[name] = { name: name, value: converter(...value.getPropertyValues()), priority: priority }; @@ -1263,8 +1282,11 @@ class list extends IgniteTemplate { if (list instanceof IgniteProperty) { this.callbacks.push(list.attachOnChange((oldValue, newValue) => this.onListChanged(oldValue, newValue))); - this.callbacks.push(list.attachOnPush((oldValue, newValue) => this.onListPush(oldValue, newValue))); - this.callbacks.push(list.attachOnPop((oldValue, newValue) => this.onListPop(oldValue, newValue))); + this.callbacks.push(list.attachOnPush((list, items) => this.onListPush(list, items))); + this.callbacks.push(list.attachOnUnshift((list, items) => this.onListUnshift(list, items))); + this.callbacks.push(list.attachOnPop(list => this.onListPop(list))); + this.callbacks.push(list.attachOnShift(list => this.onListShift(list))); + this.callbacks.push(list.attachOnSplice((list, start, deleteCount, items) => this.onListSplice(list, start, deleteCount, items))); this.list = list.value; } else { @@ -1331,38 +1353,121 @@ class list extends IgniteTemplate { try { this.construct(null); //The list changed, reconstruct this template. } catch (error) { - console.error(error); + console.error("An error occurred during onListChanged:", error); } IgniteRenderingContext.leave(); } - onListPush(oldValue, newValue) { + onListPush(list, items) { IgniteRenderingContext.enter(); try { - var template = this.forEach(this.list[this.list.length - 1]); + items.forEach(item => { + var template = this.forEach(item); - if (this.elements.length > 0) { - template.construct(this.element.parentElement, this.elements[this.elements.length - 1].nextSibling); - } else { - template.construct(this.element.parentElement, this.element); - } + if (this.elements.length > 0) { + template.construct(this.element.parentElement, this.elements[this.elements.length - 1].nextSibling); + } else { + template.construct(this.element.parentElement, this.element); + } - this.children.push(template); - this.elements.push(template.element); - } catch { } + this.children.push(template); + this.elements.push(template.element); + }); + } catch (error) { + console.error("An error occurred during onListPush:", error); + } IgniteRenderingContext.leave(); } - onListPop(oldValue, newValue) { + onListUnshift(list, items) { + IgniteRenderingContext.enter(); + + try { + items.reverse(); + items.forEach(item => { + var template = this.forEach(item); + + if (this.elements.length > 0) { + template.construct(this.element.parentElement, this.elements[0]); + } else { + template.construct(this.element.parentElement, this.element); + } + + this.children.unshift(template); + this.elements.unshift(template.element); + }); + } catch (error) { + console.error("An error occurred during onListUnshift:", error); + } + + IgniteRenderingContext.leave(); + } + + onListPop(list) { if (this.children.length > 0) { this.children[this.children.length - 1].deconstruct(); this.children.pop(); this.elements.pop(); } } + + onListShift(list) { + if (this.children.length > 0) { + this.children[0].deconstruct(); + this.children.shift(); + this.elements.shift(); + } + } + + onListSplice(list, start, deleteCount, items) { + IgniteRenderingContext.enter(); + + //Remove any items that are needed. + if (deleteCount > 0 && this.children.length > 0) { + for (var i = start; i < Math.min(this.children.length, start + deleteCount); i++) { + this.children[i].deconstruct(); + } + + this.children.splice(start, deleteCount); + this.elements.splice(start, deleteCount); + } + + //If the start is greater than the length of the items adjust it. + if (start > this.children.length) { + start = Math.max(this.children.length - 1, 0); + } + + //Append any new items if there are any. + if (items) { + items.forEach(item => { + var template = this.forEach(item); + + if (this.elements.length > 0) { + template.construct(this.element.parentElement, this.elements[start]); + } else { + template.construct(this.element.parentElement, this.element); + } + + this.children.splice(start, 0, template); + this.elements.splice(start, 0, template.element); + + start += 1; + }); + } + + IgniteRenderingContext.leave(); + } + + onStyleChanged(name, newValue) { + this.elements.forEach((element) => { + element.style.setProperty(name, newValue, this.styles[name].priority); + }); + + this.styles[name].value = newValue; + } } /**