diff --git a/src/ignite-element.js b/src/ignite-element.js index ada87cb..3a84ecf 100644 --- a/src/ignite-element.js +++ b/src/ignite-element.js @@ -50,6 +50,7 @@ class IgniteElement extends HTMLElement { constructor() { super(); + this.connected = false; this.onDisconnected = null; this.template = null; this.elements = []; @@ -156,6 +157,15 @@ class IgniteElement extends HTMLElement { * @ignore */ connectedCallback() { + //Only run this if we haven't been connected before. + //If this element is moved the instance will be the same but it will call this again + //and we want to trap this and not run the code below twice. + if (this.connected) { + return; + } else { + this.connected = true; + } + //See if a styling sheet has been created for this element if it's needed. if (this.styles !== null && this.styles !== "") { if (document.getElementsByClassName(`_${this.tagName}_styling_`).length == 0) { @@ -205,16 +215,26 @@ class IgniteElement extends HTMLElement { * @ignore */ disconnectedCallback() { - //If we still have a reference to our template, deconstruct it. - if (this.template) { - this.template.deconstruct(); - } + //Run a test here, if the element was moved but not removed it will have a disconnect + //get called but then be reconnected, so use a timer to see if this is what happened. + setTimeout(() => { + //If the element is connected then don't do this, more than likely + //the element was moved or something. + if (this.isConnected) { + return; + } - //If we have a onDisconnected callback, call it and then remove the reference. - if (this.onDisconnected) { - this.onDisconnected(); - this.onDisconnected = null; - } + //If we still have a reference to our template, deconstruct it. + if (this.template) { + this.template.deconstruct(); + } + + //If we have a onDisconnected callback, call it and then remove the reference. + if (this.onDisconnected) { + this.onDisconnected(); + this.onDisconnected = null; + } + }, 1); } /** diff --git a/src/ignite-template.js b/src/ignite-template.js index 3ac614f..12629f8 100644 --- a/src/ignite-template.js +++ b/src/ignite-template.js @@ -349,6 +349,16 @@ class IgniteTemplate { return this.attribute("src", value, converter); } + /** + * Sets the value attribute of the element to be constructed by this template. + * @param {String|IgniteProeprty} value The value to set for the href attribute of the element to be constructed by this template. + * @param {Function} converter An optional function that can convert the value if needed. + * @returns This ignite template so function calls can be chained. + */ + href(value, converter = null) { + return this.attribute("href", value, converter); + } + /** * Sets the name attribute of the element to be constructed by this template. * @param {String|IgniteProperty} value The value to set for the name attribute of the element to be constructed by this template. @@ -514,6 +524,7 @@ class IgniteTemplate { * on the template's element. * @param {any} oldValue * @param {any} newValue + * @param {Function} converter Optional converter for the value if needed. * @ignore */ onClassChanged(oldValue, newValue, converter) { @@ -522,21 +533,12 @@ class IgniteTemplate { newValue = converter(newValue); } - var oldClasses = (oldValue != null ? oldValue.toString().split(" ") : []); - var newClasses = (newValue != null ? newValue.toString().split(" ") : []); + var oldClasses = (oldValue != null && oldValue != "" ? oldValue.toString().split(" ") : []); + var newClasses = (newValue != null && newValue != "" ? newValue.toString().split(" ") : []); if (this.element) { - oldClasses.forEach((cl) => { - if (cl.length > 0) { - this.element.classList.remove(cl); - } - }); - - newClasses.forEach((cl) => { - if (cl.length > 0) { - this.element.classList.add(cl); - } - }); + oldClasses.forEach((cl) => this.element.classList.remove(cl)); + newClasses.forEach((cl) => this.element.classList.add(cl)); } //Remove the old values from the template, but only remove one copy. @@ -611,6 +613,7 @@ class IgniteTemplate { * @param {any} oldValue * @param {any} newValue * @param {string} propertyName + * @param {Function} converter * @ignore */ onPropertyChanged(oldValue, newValue, propertyName, converter) { @@ -675,14 +678,19 @@ class IgniteTemplate { * @param {any} oldValue * @param {any} newValue * @param {any} style + * @param {Function} converter * @ignore */ onCssValueChanged(oldValue, newValue, name, converter) { - if (this.element) { - this.element.style.setProperty(name, (converter != null ? converter(newValue) : newValue), this.styles[name].priority); + if (converter != null) { + newValue = converter(newValue); } - this.styles[name].value = (converter != null ? converter(newValue) : newValue); + if (this.element) { + this.element.style.setProperty(name, newValue, this.styles[name].priority); + } + + this.styles[name].value = newValue; } } @@ -806,6 +814,30 @@ class p extends IgniteTemplate { } } +/** + * An ignite template that can be used to construct a ul element. + */ +class ul extends IgniteTemplate { + /** + * @param {...String|Number|IgniteProperty|IgniteTemplate} children A series of children to be added to this template. + */ + constructor(...children) { + super("ul", children); + } +} + +/** + * An ignite template that can be used to construct a li element. + */ +class li extends IgniteTemplate { + /** + * @param {...String|Number|IgniteProperty|IgniteTemplate} children A series of children to be added to this template. + */ + constructor(...children) { + super("li", children); + } +} + /** * An ignite template that can be used to construct a span element. */ @@ -1056,10 +1088,20 @@ class list extends IgniteTemplate { /** * A slot template that mimicks the functionality of a slot element in Web Components. This can * be used to place children of a IgniteElement anywhere in the DOM. Slots don't actually construct an element, - * they simply just place children in place where the slot was used. + * they simply just place children in place where the slot was used. If classes, styles, or attributes are applied + * to the slot they will be applied to the children of the slot. * * @example + * //You must pass the ignite element who owns the slot of the first param. * new slot(this) + * + * @example + * //Slots can apply classes, attributes, and styles to children within the slot + * new slot(this).class("active") //< Would apply .active to all children + * + * @example + * //You can also use properties to have dynamic classes, styles, or attributes on slot children + * new slot(this).class(this.someClass) */ class slot extends IgniteTemplate { /** @@ -1071,6 +1113,12 @@ class slot extends IgniteTemplate { this.parent = element; } + /** + * Constructs this slot from this template. + * @param {HTMLElement} parent + * @param {HTMLElement} sibling + * @ignore + */ construct(parent, sibling) { //Don't construct if we have no parent, no sibling and no element. if (!parent && !sibling && !this.element) { @@ -1087,9 +1135,117 @@ class slot extends IgniteTemplate { } //Add any slot elements after this element. - this.parent.elements.forEach((item) => this.element.parentElement.insertBefore(item, this.element)); + this.parent.elements.forEach((item) => { + this.element.parentElement.insertBefore(item, this.element); + + //Set the classes on the item + for (var i = 0; i < this.classes.length; i++) { + if (this.classes[i] !== null && this.classes[i] !== undefined && this.classes[i] !== "") { + item.classList.add(this.classes[i]); + } + } + + //Set the attributes on the item + var keys = Object.keys(this.attributes); + for (var i = 0; i < keys.length; i++) { + if (this.attributes[keys[i]] !== null && this.attributes[keys[i]] !== undefined) { + item.setAttribute(keys[i], this.attributes[keys[i]]); + } + } + + //Set the styles on the item + var keys = Object.keys(this.styles); + for (var i = 0; i < keys.length; i++) { + var style = this.styles[keys[i]]; + item.style.setProperty(style.name, style.value, style.priority); + } + }); } } + + /** + * Called when a class on this slot changes and needs to be updated on the + * elements within this slot. + * @param {Any} oldValue + * @param {Any} newValue + * @param {Function} converter + * @ignore + */ + onClassChanged(oldValue, newValue, converter) { + if (converter !== null) { + oldValue = converter(oldValue); + newValue = converter(newValue); + } + + var oldClasses = (oldValue != null && oldValue != "" ? oldValue.toString().split(" ") : []); + var newClasses = (newValue != null && newValue != "" ? newValue.toString().split(" ") : []); + + //Remove the old values from the template, but only remove one copy. + oldClasses.forEach((cl) => this.classes.splice(this.classes.indexOf(cl), 1)); + + //Add the new classes to the template. + newClasses.forEach((cl) => this.classes.push(cl)); + + //Add the classes to the elements + this.parent.elements.forEach((element) => { + //Remove the old ones first. + oldClasses.forEach((cl) => element.classList.remove(cl)); + + //Add the new ones. + newClasses.forEach((cl) => element.classList.add(cl)); + + //Add any missing ones + this.classes.forEach((cl) => { + if (!element.classList.contains(cl)) { + element.classList.add(cl); + } + }); + }); + } + + /** + * 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 + * @param {Function} converter Optional converter function for the value if needed. + * @ignore + */ + onAttributeChanged(oldValue, newValue, attributeName, converter) { + if (converter !== null) { + newValue = converter(newValue); + } + + this.parent.elements.forEach((element) => { + if (newValue == null || newValue == undefined) { + element.removeAttribute(attributeName); + } else { + element.setAttribute(attributeName, newValue); + } + }); + + this.attributes[attributeName] = newValue; + } + + /** + * Called when a css value was changed and we need to update the styling. + * @param {any} oldValue + * @param {any} newValue + * @param {any} style + * @ignore + */ + onCssValueChanged(oldValue, newValue, name, converter) { + if (converter != null) { + newValue = converter(newValue); + } + + this.parent.elements.forEach((element) => { + element.style.setProperty(name, newValue, this.styles[name].priority); + }); + + this.styles[name].value = newValue; + } } export { @@ -1108,6 +1264,8 @@ export { p, span, i, + ul, + li, br, img, label,