Added deconstruct functionality and cleanup code so that everything gets correctly cleaned up once a element is removed from the DOM. Added a new property template that allows properties to be used as content or html anywhere within a template's children. Next up is slot support.
This commit is contained in:
@ -1,4 +1,4 @@
|
||||
import { IgniteProperty, IgniteAttribute } from './ignite-html.js';
|
||||
import { IgniteProperty } from './ignite-html.js';
|
||||
|
||||
class IgniteTemplate {
|
||||
constructor(items) {
|
||||
@ -10,13 +10,12 @@ class IgniteTemplate {
|
||||
this.element = null;
|
||||
this.properties = {};
|
||||
this.refs = [];
|
||||
this.callbacks = [];
|
||||
|
||||
if (items) {
|
||||
for (var i = 0; i < items.length; i++) {
|
||||
if (items[i] instanceof IgniteAttribute) {
|
||||
this.attributes.push(items[i]);
|
||||
} else if (items[i] instanceof IgniteProperty) {
|
||||
this.children.push(new propertyObserver(items[i]));
|
||||
if (items[i] instanceof IgniteProperty) {
|
||||
this.children.push(new property(items[i]));
|
||||
} else {
|
||||
this.children.push(items[i]);
|
||||
}
|
||||
@ -24,10 +23,15 @@ class IgniteTemplate {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a single or series of classes to this template
|
||||
* to be added once this template is constructed.
|
||||
* @param {...any} items
|
||||
*/
|
||||
class(...items) {
|
||||
for (var i = 0; i < items.length; i++) {
|
||||
if (items[i] instanceof IgniteProperty) {
|
||||
items[i].onPropertyChange.push((oldValue, newValue) => this.onClassChanged(oldValue, newValue));
|
||||
this.callbacks.push(items[i].attach(this.onClassChanged));
|
||||
this.classes.push(items[i].value);
|
||||
} else {
|
||||
this.classes.push(items[i]);
|
||||
@ -37,9 +41,14 @@ class IgniteTemplate {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a attribute to this template to be added once this template is constructed.
|
||||
* @param {string} name
|
||||
* @param {any} value
|
||||
*/
|
||||
attribute(name, value) {
|
||||
if (value instanceof IgniteProperty) {
|
||||
value.onPropertyChange.push((oldValue, newValue) => this.onAttributeChanged(oldValue, newValue, name));
|
||||
this.callbacks.push(value.attach((oldValue, newValue) => this.onAttributeChanged(oldValue, newValue, name)));
|
||||
this.attributes[name] = value.value;
|
||||
} else {
|
||||
this.attributes[name] = value;
|
||||
@ -48,9 +57,14 @@ class IgniteTemplate {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a property to this template to be added once this template is constructed.
|
||||
* @param {string} name
|
||||
* @param {any} value
|
||||
*/
|
||||
property(name, value) {
|
||||
if (value instanceof IgniteProperty) {
|
||||
value.onPropertyChange.push((oldValue, newValue) => this.onPropertyChanged(oldValue, newValue, name));
|
||||
this.callbacks.push(value.attach((oldValue, newValue) => this.onAttributeChanged(oldValue, newValue, name)));
|
||||
this.properties[name] = value.value;
|
||||
} else {
|
||||
this.properties[name] = value;
|
||||
@ -59,12 +73,27 @@ class IgniteTemplate {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a single or series of children to be added once this template
|
||||
* is constructed.
|
||||
* @param {...any} items
|
||||
*/
|
||||
child(...items) {
|
||||
this.children = this.children.concat(items);
|
||||
for (var i = 0; i < items.length; i++) {
|
||||
if (items[i] instanceof IgniteProperty) {
|
||||
this.children.push(new property(items[i]));
|
||||
} else {
|
||||
this.children.push(items[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a ref callback function to be invoked once this template is constructed.
|
||||
* @param {function} item
|
||||
*/
|
||||
ref(item) {
|
||||
if (item instanceof IgniteProperty) {
|
||||
this.refs.push((element) => {
|
||||
@ -77,11 +106,28 @@ class IgniteTemplate {
|
||||
return this;
|
||||
}
|
||||
|
||||
construct(parent) {
|
||||
/**
|
||||
* Constructs this template and adds it to the DOM if this template
|
||||
* has not already been constructed.
|
||||
* @param {HTMLElement} parent
|
||||
* @param {HTMLElement} sibling
|
||||
*/
|
||||
construct(parent, sibling) {
|
||||
//Construct this element if we haven't already
|
||||
if (!this.element) {
|
||||
this.element = window.document.createElement(this.tagName);
|
||||
parent.appendChild(this.element);
|
||||
|
||||
if (sibling) {
|
||||
parent.insertBefore(this.element, sibling);
|
||||
} else {
|
||||
parent.appendChild(this.element);
|
||||
}
|
||||
|
||||
//If the element has a onDisconnected function, attach to it
|
||||
//(This way if a custom element is removed we can deconstruct and cleanup)
|
||||
if (this.element.onDisconnected !== undefined) {
|
||||
this.element.onDisconnected = () => this.deconstruct();
|
||||
}
|
||||
|
||||
//Invoke any refs we have
|
||||
this.refs.forEach((ref) => { ref(this.element); });
|
||||
@ -118,9 +164,48 @@ class IgniteTemplate {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deconstructs this template and cleans up all resources to make sure
|
||||
* there are no memory leaks.
|
||||
*/
|
||||
deconstruct() {
|
||||
console.log(`Deconstructing ${this.tagName}`);
|
||||
//Remove our element if we have one.
|
||||
if (this.element) {
|
||||
this.element.remove();
|
||||
this.element = null;
|
||||
}
|
||||
|
||||
//Deconstruct all children elements.
|
||||
if (this.children) {
|
||||
for (var i = 0; i < this.children.length; i++) {
|
||||
if (this.children[i] instanceof IgniteTemplate || this.children[i].prototype instanceof IgniteTemplate) {
|
||||
this.children[i].deconstruct();
|
||||
}
|
||||
}
|
||||
this.children = null;
|
||||
}
|
||||
|
||||
//disconnect all callbacks
|
||||
if (this.callbacks) {
|
||||
this.callbacks.forEach((item) => item.disconnect());
|
||||
this.callbacks = null;
|
||||
}
|
||||
|
||||
//remove any refs
|
||||
if (this.refs) {
|
||||
this.refs = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a class on this template was changed and needs to be updated
|
||||
* on the template's element.
|
||||
* @param {any} oldValue
|
||||
* @param {any} newValue
|
||||
*/
|
||||
onClassChanged(oldValue, newValue) {
|
||||
console.log(`Class changed, oldValue: ${oldValue} newValue: ${newValue}`);
|
||||
|
||||
if (oldValue !== null && oldValue !== undefined && oldValue !== "" && oldValue !== " ") {
|
||||
this.element.classList.remove(oldValue);
|
||||
}
|
||||
@ -130,11 +215,15 @@ class IgniteTemplate {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a attribute on this template was changed and needs to be updated
|
||||
* on the template's element.
|
||||
* @param {any} oldValue
|
||||
* @param {any} newValue
|
||||
* @param {string} attributeName
|
||||
*/
|
||||
onAttributeChanged(oldValue, newValue, attributeName) {
|
||||
console.log(`Attribute changed, oldValue: ${oldValue} newValue: ${newValue} attribute: ${attributeName}`);
|
||||
console.log("this element:");
|
||||
console.log(this.element);
|
||||
|
||||
if (newValue == null || newValue == undefined) {
|
||||
this.element.removeAttribute(attributeName);
|
||||
} else {
|
||||
@ -142,6 +231,13 @@ class IgniteTemplate {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a property on this template was changed and needs to be updated
|
||||
* on the template's element.
|
||||
* @param {any} oldValue
|
||||
* @param {any} newValue
|
||||
* @param {string} propertyName
|
||||
*/
|
||||
onPropertyChanged(oldValue, newValue, propertyName) {
|
||||
console.log(`Property changed, oldValue: ${oldValue} newValue: ${newValue} property: ${propertyName}`);
|
||||
this.properties[propertyName] = newValue;
|
||||
@ -169,6 +265,7 @@ class html extends IgniteTemplate {
|
||||
constructor(code) {
|
||||
super([]);
|
||||
this.code = code;
|
||||
this.tagName = "shadow html";
|
||||
}
|
||||
|
||||
construct(parent) {
|
||||
@ -183,12 +280,13 @@ class html extends IgniteTemplate {
|
||||
class list extends IgniteTemplate {
|
||||
constructor(list, forEach) {
|
||||
super([]);
|
||||
this.tagName = "shadow list";
|
||||
|
||||
if (list instanceof IgniteProperty) {
|
||||
list.onPropertyChange.push((oldValue, newValue) => {
|
||||
this.list = list.value;
|
||||
this.construct(null); //If the list changed, reconstruct this template
|
||||
});
|
||||
this.callbacks.push(list.attach((oldValue, newValue) => {
|
||||
this.list = newValue;
|
||||
this.construct(null); //If the list changed, reconstruct this template.
|
||||
}));
|
||||
|
||||
this.list = list.value;
|
||||
} else {
|
||||
@ -199,10 +297,12 @@ class list extends IgniteTemplate {
|
||||
this.elements = [];
|
||||
}
|
||||
|
||||
construct(parent) {
|
||||
construct(parent, sibling) {
|
||||
if (!this.element) {
|
||||
this.element = window.document.createTextNode(""); //Use a textnode as our placeholder
|
||||
parent.appendChild(this.element);
|
||||
} else {
|
||||
parent = this.element.parentElement;
|
||||
}
|
||||
|
||||
//If we already have elements we created, destroy them.
|
||||
@ -213,25 +313,65 @@ class list extends IgniteTemplate {
|
||||
this.elements = [];
|
||||
}
|
||||
|
||||
//Create a temporary container
|
||||
var container = window.document.createElement("div");
|
||||
//If we already have children elements, deconstruct them.
|
||||
//(Because we are about to recreate them)
|
||||
if (this.children.length > 0) {
|
||||
for (var i = 0; i < this.children.length; i++) {
|
||||
this.children[i].deconstruct();
|
||||
}
|
||||
this.children = [];
|
||||
}
|
||||
|
||||
//Construct all the items in our list and use the container
|
||||
if (this.list) {
|
||||
for (var i = 0; i < this.list.length; i++) {
|
||||
this.forEach(this.list[i]).construct(container);
|
||||
var template = this.forEach(this.list[i]);
|
||||
template.construct(parent, this.element);
|
||||
|
||||
this.children.push(template);
|
||||
this.elements.push(template.element);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class property extends IgniteTemplate {
|
||||
constructor(prop) {
|
||||
super(null);
|
||||
|
||||
this.property = prop;
|
||||
this.elements = [];
|
||||
this.callbacks.push(this.property.attach((oldValue, newValue) => this.onPropertyChanged(oldValue, newValue)));
|
||||
}
|
||||
|
||||
construct(parent, sibling) {
|
||||
if (!this.element) {
|
||||
this.element = window.document.createTextNode("");
|
||||
|
||||
if (sibling) {
|
||||
parent.insertBefore(this.element, sibling);
|
||||
} else {
|
||||
parent.appendChild(this.element);
|
||||
}
|
||||
}
|
||||
|
||||
//If we have any child nodes in the container, save the element, and add it after our placeholder
|
||||
while (container.childNodes.length > 0) {
|
||||
this.elements.push(container.childNodes[0]);
|
||||
this.element.parentElement.insertBefore(container.childNodes[0], this.element);
|
||||
}
|
||||
//Remove any elements that already exist.
|
||||
this.elements.forEach((item) => item.remove());
|
||||
|
||||
//Make sure we cleanup the container to be safe.
|
||||
container.remove();
|
||||
container = null;
|
||||
//Create a template to hold the elements that will be created from the
|
||||
//properties value and then add them to the DOM and store their pointer.
|
||||
var template = window.document.createElement("template");
|
||||
template.innerHTML = this.property.value;
|
||||
while (template.content.childNodes.length > 0) {
|
||||
var item = template.content.childNodes[0];
|
||||
this.element.parentElement.insertBefore(item, this.element);
|
||||
this.elements.push(item);
|
||||
}
|
||||
template.remove();
|
||||
}
|
||||
|
||||
onPropertyChanged(oldValue, newValue) {
|
||||
this.construct(null, null);
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user