diff --git a/src/ignite-template.js b/src/ignite-template.js index 1843622..d9ab3b2 100644 --- a/src/ignite-template.js +++ b/src/ignite-template.js @@ -34,6 +34,7 @@ class IgniteTemplate { this.callbacks = []; this.events = {}; this.styles = {}; + this.elementValue = null; if (children) { for (var i = 0; i < children.length; i++) { @@ -53,18 +54,31 @@ class IgniteTemplate { } /** - * Adds a CSS class to this template - * to be added once this template is constructed. - * @param {String|IgniteProperty} name Name of the CSS class to add. + * Adds a CSS class to be added once this template is constructed. + * @param {String|IgniteProperty} name Name of the CSS class to add. Multiple CSS classes are supported if they are separated by a space. * @param {Function} converter Optional function that can convert the class name into a different one. + * @example + * .class("row justify-content-center") * @returns This ignite template so function calls can be chained. */ class(name, converter = null) { if (name instanceof IgniteProperty) { this.callbacks.push(name.attachOnChange((oldValue, newValue) => this.onClassChanged(oldValue, newValue, converter))); - this.classes.push(converter != null ? converter(name.value) : name.value); + var value = (converter != null ? converter(name.value) : name.value); + var classes = (value != null ? value.toString().split(" ") : []); + classes.forEach((cl) => { + if (cl.length > 0) { + this.classes.push(cl); + } + }); } else { - this.classes.push(converter != null ? converter(name) : name); + var value = (converter != null ? converter(name) : name); + var classes = (value != null ? value.toString().split(" ") : []); + classes.forEach((cl) => { + if (cl.length > 0) { + this.classes.push(cl); + } + }); } return this; @@ -88,6 +102,43 @@ class IgniteTemplate { return this; } + /** + * Sets the value of the element this template is constructing with the option to reflect changes + * to the value. + * @param {String|IgniteProperty} value The value to set on the element. + * @param {Boolean} reflect Whether or not to reflect changes to the value of the element back to the property if one was used. + * @param {Function} converter Optional function that can convert the value if needed. + */ + value(value, reflect = false, converter = null) { + if (reflect && converter != null) { + throw `Cannot set a value on an IgniteTemplate: ${this.tagName} with reflect and a converter used at the same time.`; + } + + if (value instanceof IgniteProperty) { + this.callbacks.push(value.attachOnChange((oldValue, newValue) => this.onValueChanged(oldValue, newValue, converter))); + + if (reflect) { + this.on("change", (event) => { + var newValue = (this.element.hasAttribute("type") && this.element.getAttribute("type").toLowerCase().trim() == "checkbox" ? this.element.checked : this.element.value); + console.log("Element change triggered, setting new value:", newValue); + value.setValue(newValue, true); + }); + + this.on("keyup", (event) => { + var newValue = (this.element.hasAttribute("type") && this.element.getAttribute("type").toLowerCase().trim() == "checkbox" ? this.element.checked : this.element.value); + console.log("Element keyup triggered, setting new value:", newValue); + value.setValue(newValue, true); + }); + } + + this.elementValue = (converter != null ? converter(value.value) : value.value); + } else { + this.elementValue = (converter != null ? converter(value) : value); + } + + return this; + } + /** * Adds a property to this template to be added once this template is constructed. * @param {String} name Name of the property to set. @@ -269,6 +320,42 @@ class IgniteTemplate { return this; } + /** + * Sets the id attribute of the element to be constructed by this template. + * @param {String|IgniteProperty} value The value to set for the id attribute of the element this template will construct. + * @param {Function} converter An optional function that can convert the value if needed. + */ + id(value, converter = null) { + return this.attribute("id", value, converter); + } + + /** + * Sets the type attribute of the element to be constructed by this template. + * @param {String|IgniteProperty} value The value to set for the type attribute of the element this template will construct. + * @param {Function} converter An optional function that can convert the value if needed. + */ + type(value, converter = null) { + return this.attribute("type", value, converter); + } + + /** + * Sets the value attribute of the element to be constructed by this template. + * @param {String|IgniteProperty} value The value to set for the src attribute of the element to be constructed by this template. + * @param {Function} converter An optional function that can convert the value if needed. + */ + src(value, converter = null) { + return this.attribute("src", 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. + * @param {Function} converter An optional function that can convert the value if needed. + */ + name(value, converter = null) { + return this.attribute("name", value, converter); + } + /** * Constructs this template and adds it to the DOM if this template * has not already been constructed. @@ -343,6 +430,15 @@ class IgniteTemplate { } } + //Set the elements value if there is one. + if (this.elementValue != null) { + if (this.element.hasAttribute("type") && this.element.getAttribute("type").toLowerCase().trim() == "checkbox") { + this.element.checked = this.elementValue; + } else { + this.element.value = this.elementValue; + } + } + //Construct the children under this element for (var i = 0; i < this.children.length; i++) { this.children[i].construct(this.element); @@ -413,23 +509,36 @@ class IgniteTemplate { newValue = converter(newValue); } + var oldClasses = (oldValue != null ? oldValue.toString().split(" ") : []); + var newClasses = (newValue != null ? newValue.toString().split(" ") : []); + if (this.element) { - if (oldValue !== null && oldValue !== undefined && oldValue !== "" && oldValue !== " ") { - this.element.classList.remove(oldValue); - } + oldClasses.forEach((cl) => { + if (cl.length > 0) { + this.element.classList.remove(cl); + } + }); - if (newValue !== null && newValue !== undefined && newValue !== "" && newValue !== " ") { - this.element.classList.add(newValue); - } + newClasses.forEach((cl) => { + if (cl.length > 0) { + this.element.classList.add(cl); + } + }); } - //Remove the old value - this.classes = this.classes.filter(cl => cl != oldValue && cl != newValue); + //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 value if its valid. - if (newValue !== null && newValue !== undefined) { - this.classes.push(newValue); - } + //Add the new classes to the template. + newClasses.forEach((cl) => this.classes.push(cl)); + + //For any classes that are missing on the element, add them. If we have duplicates this + //can happen. + this.classes.forEach((cl) => { + if (!this.element.classList.contains(cl)) { + this.element.classList.add(cl); + } + }); } /** @@ -456,6 +565,33 @@ class IgniteTemplate { this.attributes[attributeName] = newValue; } + /** + * Called when a value for this template was changed and needs to be updated on the template's element. + * @param {any} oldValue + * @param {any} newValue + * @param {Function} converter + * @ignore + */ + onValueChanged(oldValue, newValue, converter) { + if (converter !== null) { + newValue = converter(newValue); + } + + //Only update the elements value if it actually changed. + //This is to prevent endless looping potentially. + if (this.element) { + if (this.element.hasAttribute("type") && this.element.getAttribute("type").toLowerCase().trim() == "checkbox") { + if (this.element.checked != newValue) { + this.element.checked = newValue; + } + } else { + if (this.element.value != newValue) { + this.element.value = newValue; + } + } + } + } + /** * Called when a property on this template was changed and needs to be updated * on the template's element. @@ -705,6 +841,18 @@ class img extends IgniteTemplate { } } +/** + * An ignite template that can be used to construct a label element. + */ +class label extends IgniteTemplate { + /** + * @param {...String|Number|IgniteProperty|IgniteTemplate} children A series of children to be added to this template. + */ + constructor(...children) { + super("label", children); + } +} + /** * Html is a special template that can construct raw html or properties into the dom and automatically * update the dom if the property changes. @@ -949,5 +1097,6 @@ export { i, br, img, + label, slot }; \ No newline at end of file diff --git a/src/main-app.js b/src/main-app.js index 1a0cde4..18f1350 100644 --- a/src/main-app.js +++ b/src/main-app.js @@ -11,7 +11,7 @@ class MainApp extends IgniteElement { return { name: "I'm a boss!", items: ["main1", "main2"], - sheetClass: "test", + sheetClass: "test1 test2 test3", sheet: null }; } @@ -24,6 +24,7 @@ class MainApp extends IgniteElement { .property("items", this.items, true) .ref(this.sheet) .class(this.sheetClass) + .class("a b c") .child(new html(`

Im a child for sheet!

`)) ) .child( diff --git a/src/sheet.js b/src/sheet.js index 8fd08e7..455f857 100644 --- a/src/sheet.js +++ b/src/sheet.js @@ -16,6 +16,8 @@ class Sheet extends IgniteElement { linkClick: this.onClick1, linkClick2: this.onClick2, enter: this.onEnter, + inputValue: "hello world!", + checkboxValue: false }; } @@ -33,7 +35,8 @@ class Sheet extends IgniteElement { .class(this.show) .child( new div( - new input().attribute("type", "text").onEnter(this.enter), + new input().type("text").onEnter(this.enter).value(this.inputValue, true), + new input().type("checkbox").value(this.checkboxValue, true), new h1(this.name), new html("

this is before

"), new div( @@ -63,6 +66,10 @@ class Sheet extends IgniteElement { onEnter(event) { console.log("Enter was pressed!"); } + + onChange(event) { + console.log("Input was changed, event:", event); + } } class SheetTemplate extends IgniteTemplate {