Compare commits

...

27 Commits

Author SHA1 Message Date
cd4c5b43d7 Added an OnEscape helper function to capture when the escape key is pressed on an element quickly. 2025-05-27 18:24:00 -07:00
ed49ad1392 Changed element.data to nodeValue since that is the official property. 2024-11-20 07:50:29 -08:00
812f8352f5 Fixed an issue where not all the results of the intersection observer were being checked. 2024-09-02 16:19:40 -07:00
939d1ec83a Added a new after render function for elements. 2024-03-18 09:33:16 -07:00
9040bb8d45 Added ability to use arrays of properties for the property function. 2023-12-24 15:21:43 -08:00
82834320f6 Added ol template. 2023-12-24 09:51:36 -08:00
4e0bb96747 Fixed an issue where the converter wasn't running on the first construct. 2023-12-11 16:58:18 -08:00
23f8fa5b72 Moved option construction before setting element value. 2023-11-28 19:16:15 -08:00
7f2b6465c2 Added new options function that can generate a set of options from an input and allow them to be converted. Modified the converter template to support an array of ignite properties as the input. The converter now supports the value converter returning an array of ignite templates. This allows for more complex conversions. 2023-11-10 12:10:17 -08:00
2d41cb26a0 Fixed an issue where the source element wouldn't load the video when the src attribute is changed. 2023-11-07 09:25:51 -08:00
3eadebec0c Fixed some major issues with list splice construction and other templates incorrectly constructing an element after a specified sibling. Improved documentation as well. 2023-11-07 08:25:54 -08:00
f1b1ae5c13 Added video template and source template. Added code so that if the src attribute is changed on a source element it automatically pauses the video and resets the position to 0. 2023-11-06 16:35:02 -08:00
def9a0c837 Added new type ahead feature, documented it and tested it. 2023-11-03 20:43:45 -07:00
7c8ce52537 Updated more documentation. 2023-11-01 08:25:14 -07:00
ab7209f5bf Updated style docs to include the use of an array of ignite properties. 2023-11-01 08:12:13 -07:00
7e1e2b96ff Fixed a minor bug where list splice wouldn't insert the item in the DOM at the correct position in the list. 2023-10-31 22:45:02 -07:00
a584eb68a7 Fixed a minor issue with attachOnDisconnect for the list template. 2023-10-17 15:32:22 -07:00
f4e314c7be Added iframe element template. 2023-10-11 18:01:33 -07:00
645af63c30 Modified ignite template properties function to dynamically change properties. Fixed a few bugs as well. 2023-08-11 17:33:39 -07:00
d080d8e175 Cleaned up the converter template and fixed a bug. 2023-07-26 17:37:39 -07:00
f531a518b9 Added new converter template that can be used to convert a value into something that can be rendered. 2023-07-25 23:21:54 -07:00
841d03d2c3 Added a special onDeconstruct template function that allows a function to be invoked when the element is being destroyed. 2023-07-24 08:51:54 -07:00
e4602b710d Fixed a bug where on a class change we didn't check to see if the element existed. 2023-07-23 22:05:00 -07:00
63ed3a42f9 Fixed an issue where the text constructor wasn't handling the rendering state when call the converter. 2023-07-21 10:24:06 -07:00
7905758894 Added a null and length check for pages before trying to modify their elements. 2023-07-17 12:33:46 -07:00
08a526ad4a Merge remote-tracking branch 'origin/master' 2023-07-11 08:08:59 -07:00
aaa593e846 Removed old code that would try to force ignite properties to stay as the same value type as their previous value. This causes issues and the developer should be free to set properties to whatever value they want. 2023-07-11 08:08:23 -07:00
3 changed files with 695 additions and 59 deletions

View File

@ -295,6 +295,11 @@ class IgniteElement extends HTMLElement {
//Leave the rendering context.
IgniteRendering.leave();
//Invoke the after render function, ensure we are not in a rendering context.
IgniteRendering.push();
this.afterRender();
IgniteRendering.pop();
//Let the rendering context know this element is ready.
IgniteRendering.ready(this.readyCallback);
}
@ -372,6 +377,14 @@ class IgniteElement extends HTMLElement {
return null;
}
/**
* Called right after this element is rendered.
* Note: It's not guaranteed this element is connected to the DOM yet, but it will at least be fully initialized.
*/
afterRender() {
}
/**
* Called when this ignite element is being initialized. When this is called
* the element has not been created. This is good for login checking code or special setup code.

View File

@ -107,17 +107,6 @@ class IgniteProperty {
//Get the old value
var old = this._value;
//Based on the old value, see if we need to convert the new value to match the original type.
if (typeof old === typeof true) {
val = val != null && val != undefined ? val.toString().toLowerCase().trim() : val;
val = val == "true" || val == "1" || val == "yes" || val == "t" || val == "y";
} else if (typeof old === typeof 0) {
val = Number(val != null && val != undefined ? val.toString().trim() : "0");
val = isNaN(val) ? 0 : val;
} else if (typeof old === typeof "") {
val = val != null && val != undefined ? val.toString() : null;
}
//Set the new value
this._value = val;

View File

@ -39,6 +39,8 @@ class IgniteTemplate {
this._attributes = {};
this._classes = [];
this._properties = {};
this._options = [];
this._optionElements = [];
this._variables = {};
this._reflecting = {};
this._refs = [];
@ -259,7 +261,7 @@ class IgniteTemplate {
/**
* Sets a property on the element this template will construct.
* @param {String} name Name of the property to set.
* @param {Any|IgniteProperty} value Value of the property to use. If a Property is passed the value will auto update.
* @param {Any|IgniteProperty|IgniteProperty[]} value Value of the property to use. If a Property is passed the value will auto update.
* @param {Boolean} reflect If true whenever this property is changed it's value will be passed back to the Property that was passed as value if one was passed.
* @param {Function} converter Optional function that can be used to convert the value if needed.
* @returns {IgniteTemplate} This ignite template so function calls can be chained.
@ -287,6 +289,28 @@ class IgniteTemplate {
value: (converter != null ? converter(value.value) : value.value),
reflect: (reflect == true ? value : null)
};
} else if (Array.isArray(value) && value.length > 0 && value[0] instanceof IgniteProperty) {
//There must be a converter for this to work correctly
if (!converter) {
throw "Cannot pass an array of properties without using a converter!";
}
//Attack a callback for all the properties
value.forEach(prop => {
if (prop instanceof IgniteProperty) {
this._callbacks.push(prop.attachOnChange((oldValue, newValue) => this.onPropertyChanged(name, converter(...value.getPropertyValues()))));
this._callbacks.push(prop.attachOnPush((list, items) => this.onPropertyChanged(name, converter(...value.getPropertyValues()))));
this._callbacks.push(prop.attachOnUnshift((list, items) => this.onPropertyChanged(name, converter(...value.getPropertyValues()))));
this._callbacks.push(prop.attachOnPop((list) => this.onPropertyChanged(name, converter(...value.getPropertyValues()))));
this._callbacks.push(prop.attachOnShift((list) => this.onPropertyChanged(name, converter(...value.getPropertyValues()))));
this._callbacks.push(prop.attachOnSplice((list, start, deleteCount, items) => this.onPropertyChanged(name, converter(...value.getPropertyValues()))));
}
});
this._properties[name] = {
value: converter(...value.getPropertyValues()),
reflect: (reflect == true ? value : null)
};
} else {
this._properties[name] = {
value: (converter != null ? converter(value) : value),
@ -376,6 +400,17 @@ class IgniteTemplate {
Object.getOwnPropertyNames(props).forEach(name => this.property(name, props[name], true));
}
else if (props instanceof IgniteProperty) {
this._callbacks.push(props.attachOnChange((oldValue, newValue) => {
if (newValue) {
Object.keys(newValue).forEach(name => this.onPropertyChanged(name, newValue[name]));
}
}));
if (props.value) {
Object.keys(props.value).forEach(name => this.property(name, props[name], false, null));
}
}
else {
Object.keys(props).forEach(name => this.property(name, props[name], false, null));
}
@ -383,9 +418,80 @@ class IgniteTemplate {
return this;
}
/**
* Sets the options elements to be constructed by this template.
* Valid options can be in this format:
* [{1: "Option 1"}, {2: "Option 2"}]
* ["A", "B", "C"]
* {1: "Option 1", 2: "Option 2"}
* @param {Array|Object|IgniteProperty|IgniteProperty[]} options The options to be constructed on this template.
* @param {Function} converter Optional function that can be used to convert the options input if needed.
* @returns {IgniteTemplate} This ignite template so function calls can be chained.
*/
options(options, converter = null) {
IgniteRendering.push();
var converted = null;
if (options instanceof IgniteProperty) {
this._callbacks.push(options.attachOnChange((oldValue, newValue) => this.onOptionsChanged(converter ? converter(newValue) : newValue)));
this._callbacks.push(options.attachOnPush((list, items) => this.onOptionsChanged(converter ? converter(list) : list)));
this._callbacks.push(options.attachOnUnshift((list, items) => this.onOptionsChanged(converter ? converter(list) : list)));
this._callbacks.push(options.attachOnPop((list) => this.onOptionsChanged(converter ? converter(list) : list)));
this._callbacks.push(options.attachOnShift((list) => this.onOptionsChanged(converter ? converter(list) : list)));
this._callbacks.push(options.attachOnSplice((list, start, deleteCount, items) => this.onOptionsChanged(converter ? converter(list) : list)));
converted = converter ? converter(options.value) : options.value;
} else if (Array.isArray(options) && options.length > 0 && options[0] instanceof IgniteProperty) {
//There must be a converter for this to work correctly
if (!converter) {
throw "Cannot pass an array of properties without using a converter!";
}
//Attack a callback for all the properties
options.forEach(option => {
if (option instanceof IgniteProperty) {
this._callbacks.push(option.attachOnChange((oldValue, newValue) => this.onOptionsChanged(converter(...options.getPropertyValues()))));
this._callbacks.push(option.attachOnPush((list, items) => this.onOptionsChanged(converter(...options.getPropertyValues()))));
this._callbacks.push(option.attachOnUnshift((list, items) => this.onOptionsChanged(converter(...options.getPropertyValues()))));
this._callbacks.push(option.attachOnPop((list) => this.onOptionsChanged(converter(...options.getPropertyValues()))));
this._callbacks.push(option.attachOnShift((list) => this.onOptionsChanged(converter(...options.getPropertyValues()))));
this._callbacks.push(option.attachOnSplice((list, start, deleteCount, items) => this.onOptionsChanged(converter(...options.getPropertyValues()))));
}
});
converted = converter(...options.getPropertyValues());
} else {
converted = converter ? converter(options) : options;
}
if (Array.isArray(converted)) {
this._options = converted.map((option, index) => {
if (option instanceof Object) {
var keys = Object.keys(option);
if (keys.length == 0) {
return null;
} else {
return { value: keys[0], name: option[keys[0]] };
}
} else {
return { value: index, name: option };
}
});
} else if (converted instanceof Object) {
this._options = Object.keys(converted).map(key => {
return { value: key, name: converted[key] };
});
}
IgniteRendering.pop();
return this;
}
/**
* Sets the inner html of the element to be constructed by this template.
* @param {String|IgniteProperty} value InnerHTML to set for element. If a property is passed the html will auto update.
* @param {String|IgniteProperty|IgniteProperty[]} value InnerHTML to set for element. If a property is passed the html will auto update.
* @param {Function} converter Optional function that can be used to convert the value if needed.
* @returns This ignite template so funciton calls can be chained.
*/
@ -657,6 +763,54 @@ class IgniteTemplate {
return this;
}
/**
* Adds a on escape key press event handler to this template.
* @param {Function|IgniteProperty} eventCallback The callback function to be invoked by the event once it fires.
* @returns {IgniteTemplate} This ignite template so function calls can be chained.
*/
onEscape(eventCallback) {
var eventName = "keydown";
if (!this._events[eventName]) {
this._events[eventName] = [];
}
if (eventCallback instanceof IgniteProperty) {
this._callbacks.push(eventCallback.attachOnChange((oldValue, newValue) => {
//Create a new wrapped function to check for the escape key being pressed.
var wrapped = (e) => {
if (e.key === 'Escape') {
newValue(e);
}
};
eventCallback._value = wrapped; //Store the wrapped function into the property so we can remove it later
this.onEventChanged(oldValue, wrapped, eventName); //Invoke event changed with the old value and wrapped one.
}));
//Create the initial wrapper
var target = eventCallback._value;
var wrapped = (e) => {
if (e.key === 'Escape') {
target(e);
}
};
//Store the wrapped so that it's the old value next time around.
eventCallback._value = wrapped;
this._events[eventName].push(wrapped);
} else {
this.on(eventName, (e) => {
if (e.key === 'Escape') {
eventCallback(e);
}
});
}
return this;
}
/**
* Adds a on backspace key press event handler to this template.
* @param {Function|IgniteProperty} eventCallback The callback function to be invoked by the event once it fires.
@ -775,10 +929,112 @@ class IgniteTemplate {
return this;
}
/**
* Adds an event handler that gets called when this element is being deconstructed from the DOM.
* @param {Function} eventCallback The callback function to be invoked once this element is disconnected.
* @returns {IgniteTemplate} This ignite template so function calls can be chained.
*/
onDeconstruct(eventCallback) {
if (eventCallback) {
this._destructors.push(() => {
IgniteRendering.push();
eventCallback();
IgniteRendering.pop();
});
}
return this;
}
/**
* Adds a special event handler to this element that invokes with the content of an input after a certain amount of time has passed after typing.
* This function supports inputs, text area's and content editable elements.
* @param {Function|IgniteProperty} eventCallback The callback function to be invoked when a typeAhead event occurs, it will be passed the typed value.
* @param {Number} minCharacters Min number of characters excluding whitespace that have to be typed before this event is invoked. Default is 2.
* @param {Number} callbackDelay The delay in miliseconds between each event callback. Default is 350.
* @param {Boolean} callbackReset Whether or not to reset the callback delay on each keypress. Default is true. If true, type ahead only occurrs when typing has stopped.
* @param {Boolean} enterCallback Whether or not the enter key should be considered for a type ahead. Default is true.
* @returns {IgniteTemplate} This ignite template so function calls can be chained.
*/
onTypeAhead(eventCallback, minCharacters = 2, callbackDelay = 350, callbackReset = true, enterCallback = true) {
var typeAheadTimeout = null;
var typeAheadRunning = false;
var lastValue = null;
var typeAhead = async (target, force) => {
var value = null;
typeAheadRunning = true;
//Get the value from the target if we can.
if (target instanceof HTMLElement) {
if (target.tagName == "INPUT" || target.tagName == "TEXTAREA") {
value = target.value;
} else if (target.contentEditable == "true") {
value = target.innerText;
}
}
//Trim the value if we can
value = value?.trim();
//If the value has at least the min number of characters after being trimmed invoke the callback.
if (value != null && value.length >= minCharacters && (lastValue != value || force)) {
try {
if (eventCallback instanceof IgniteProperty) {
await eventCallback.value(value);
} else if (eventCallback instanceof Function) {
await eventCallback(value);
}
} catch (error) {
console.error("Error occurred during type ahead callback", error);
}
//Request another type ahead incase the value changed while we were waiting for the event callback to finish.
typeAheadTimeout = setTimeout(() => typeAhead(target, false), callbackDelay);
} else {
typeAheadTimeout = null;
}
lastValue = value;
typeAheadRunning = false;
};
//Attach a keydown event and handle the type ahead.
this.on("keydown", (e) => {
//If the key was an enter key, dont allow if it's not allowed.
if (e.key == "Enter" && !enterCallback) {
return;
}
//If callback reset is true and there's a pending type ahead, clear it.
if (callbackReset && typeAheadTimeout && !typeAheadRunning) {
clearTimeout(typeAheadTimeout);
typeAheadTimeout = null;
}
//Schedule a new type ahead timeout if one isn't already running.
if (!typeAheadTimeout) {
if (e.key == "Enter") {
typeAheadTimeout = setTimeout(() => typeAhead(e.target, true), 0);
} else {
typeAheadTimeout = setTimeout(() => typeAhead(e.target, false), callbackDelay);
}
}
});
return this;
}
/**
* Adds a CSS property to this template with a value and priority.
* @param {String} name The name of the CSS property to set.
* @param {String|IgniteProperty} value The value to set for the property. If an IgniteProperty is used it will auto update this style.
* @param {String|IgniteProperty|IgniteProperty[]} value The value to set for the property. If an IgniteProperty is used it will auto update this style.
* @param {String} priority If set to "important" then the style will be marked with !important. Acceptable values: important, !important, true, false, null
* @param {Function} converter Optional function to convert the value if needed.
* @returns {IgniteTemplate} This ignite template so function calls can be chained.
@ -933,7 +1189,7 @@ class IgniteTemplate {
/**
* Adds a checked attribute to this template.
* @param {Boolean|IgniteProperty} value The value to set for the checked attribute.
* @param {*} converter Optional function that can convert the value if needed.
* @param {Any} converter Optional function that can convert the value if needed.
* @returns {IgniteTemplate} This ignite template so function calls can be chained.
*/
checked(value, converter = null) {
@ -942,8 +1198,8 @@ class IgniteTemplate {
/**
* Adds a disabled attribute and class to this template.
* @param {Boolean|IgniteProperty} value A value to determine whether or not the element should be marked as disable or not.
* @param {*} converter Optional function that can convert the value if needed.
* @param {Boolean|IgniteProperty|IgniteProperty[]} value A value to determine whether or not the element should be marked as disable or not.
* @param {Any} converter Optional function that can convert the value if needed.
* @returns {IgniteTemplate} This ignite template so function calls can be chained.
*/
disabled(value, converter = null) {
@ -1007,7 +1263,7 @@ class IgniteTemplate {
/**
* Adds a readonly attribute and class to this template.
* @param {Boolean|IgniteProperty} value A value to determine whether or not the element should be marked as readonly or not.
* @param {*} converter Optional function that can convert the value if needed.
* @param {Any} converter Optional function that can convert the value if needed.
* @returns {IgniteTemplate} This ignite template so function calls can be chained.
*/
readonly(value, converter = null) {
@ -1081,7 +1337,7 @@ class IgniteTemplate {
* Sets a data attribute on the element to be constructed by this template.
* @param {String} name The name of the data attribute to set on the element this template will construct.
* @param {String|IgniteProperty} value The value to set for the data attribute of the element this template will construct.
* @param {*} converter An optional function that can convert the value if needed.
* @param {Any} converter An optional function that can convert the value if needed.
* @returns {IgniteTemplate} This ignite template so function calls can be chained.
*/
data(name, value, converter = null) {
@ -1258,6 +1514,24 @@ class IgniteTemplate {
this.children[i].construct(this.element);
}
//Construct any options if needed
if (this._options.length > 0 && this._optionElements.length == 0) {
this._options.forEach(option => {
if (option) {
var element = window.document.createElement("option");
element.setAttribute("value", option.value);
element.innerHTML = option.name;
this._optionElements.push(element);
//Add this element to the dom.
if (this.element) {
this.element.appendChild(element);
}
}
});
}
//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.getAttribute("type").toLowerCase().trim() == "radio")) {
@ -1285,10 +1559,15 @@ class IgniteTemplate {
//Setup a intersect observer if needed
if (this._intersectObserverCallback && this._intersectObserverCallback.length > 0) {
this._intersectObserver = new IntersectionObserver(results => {
if (results[0].isIntersecting) {
for (var i = 0; i < this._intersectObserverCallback.length; i++) {
if (this._intersectObserverCallback[i]) {
this._intersectObserverCallback[i](results[0]);
if (results) {
for (var i = 0; i < results.length; i++) {
if (results[i].isIntersecting && results[i].target == this.element) {
for (var i2 = 0; i2 < this._intersectObserverCallback.length; i2++) {
if (this._intersectObserverCallback[i2]) {
this._intersectObserverCallback[i2](results[i]);
}
}
break;
}
}
}
@ -1302,8 +1581,8 @@ class IgniteTemplate {
//If our element has not been added to the dom yet, then add it.
if (this.element.isConnected == false && this.element.parentElement == null) {
if (sibling) {
parent.insertBefore(this.element, sibling);
if (sibling && sibling.nextSibling) {
sibling.parentElement.insertBefore(this.element, sibling.nextSibling);
} else {
parent.appendChild(this.element);
}
@ -1378,12 +1657,14 @@ class IgniteTemplate {
//For any classes that are missing on the element, add them. If we have duplicates this
//can happen.
if (this.element) {
this._classes.forEach((cl) => {
if (!this.element.classList.contains(cl)) {
this.element.classList.add(cl);
}
});
}
}
/**
* Called when a attribute on this template was changed and needs to be updated
@ -1461,8 +1742,12 @@ class IgniteTemplate {
IgniteRendering.leave();
}
if (!this._properties[propertyName]) {
this._properties[propertyName] = { value: newValue };
} else {
this._properties[propertyName].value = newValue;
}
}
/**
* Called when a variable on this template was changed and needs to be updated.
@ -1566,6 +1851,58 @@ class IgniteTemplate {
this._styles[name].value = newValue;
}
/**
* Called when the options for this template have changed and need to be updated.
* @param {Array} newValue
*/
onOptionsChanged(newValue) {
//First remove all existing options
if (this._optionElements) {
this._optionElements.forEach(element => element.remove());
this._optionElements = [];
}
//Set the options to null.
this._options = [];
//Convert the newValue into options
if (Array.isArray(newValue)) {
this._options = newValue.map((option, index) => {
if (option instanceof Object) {
var keys = Object.keys(option);
if (keys.length == 0) {
return null;
} else {
return { value: keys[0], name: option[keys[0]] };
}
} else {
return { value: index, name: option };
}
});
} else if (newValue instanceof Object) {
this._options = Object.keys(newValue).map(key => {
return { value: key, name: newValue[key] };
});
}
//Construct the new options
this._options.forEach(option => {
if (option) {
var element = document.createElement("option");
element.setAttribute("value", option.value);
element.innerHTML = option.name;
this._optionElements.push(element);
//Add this element to the dom.
if (this.element) {
this.element.appendChild(element);
}
}
});
}
}
/**
@ -1750,6 +2087,18 @@ class ul extends IgniteTemplate {
}
}
/**
* An ignite template that can be used to construct a ol element.
*/
class ol extends IgniteTemplate {
/**
* @param {...String|Number|IgniteProperty|IgniteTemplate} children A series of children to be added to this template.
*/
constructor(...children) {
super("ol", children);
}
}
/**
* An ignite template that can be used to construct a li element.
*/
@ -2002,6 +2351,63 @@ class footer extends IgniteTemplate {
}
}
/**
* An ignite template that can be used to construct a iframe element.
*/
class iframe extends IgniteTemplate {
/**
* @param {...String|Number|IgniteProperty|IgniteTemplate} children A series of children to be added to this template.
*/
constructor(...children) {
super("iframe", children);
}
}
/**
* An ignite template that can be used to construct a video element.
*/
class video extends IgniteTemplate {
/**
* @param {...String|Number|IgniteProperty|IgniteTemplate} children A series of children to be added to this template.
*/
constructor(...children) {
super("video", children);
}
}
/**
* An ignite template that can be used to construct a source element.
*/
class source extends IgniteTemplate {
/**
* @param {...String|Number|IgniteProperty|IgniteTemplate} children A series of children to be added to this template.
*/
constructor(...children) {
super("source", children);
}
/**
* Called when an attribute on this source element is changing.
* @param {String} name
* @param {Any} newValue
*/
onAttributeChanged(name, newValue) {
//If the src is changing, we need to stop the parent video player and reset it's position.
if (this.element && name && name.trim().toLowerCase() == "src") {
this.element.parentElement.pause();
this.element.parentElement.currentTime = 0;
//If there is a valid video source, call load on the player.
if (newValue && newValue.length > 0) {
this.element.parentElement.load();
}
}
//Call the original on attribute changed function.
super.onAttributeChanged(name, newValue);
}
}
/**
* An ignite template that can be used to construct a progress element.
*/
@ -2088,12 +2494,14 @@ class line extends IgniteTemplate {
class text extends IgniteTemplate {
/**
* Constructs a text template with the text to render.
* @param {String|IgniteProperty} text The text to render within this text template.
* @param {String|IgniteProperty|IgniteProperty[]} text The text to render within this text template.
* @param {Function} converter An optional function that can be used to convert the text.
*/
constructor(text, converter) {
super();
IgniteRendering.push();
if (text instanceof IgniteProperty) {
this._callbacks.push(text.attachOnChange((oldValue, newValue) => this.onTextChanged(converter != null ? converter(newValue) : newValue)));
this._callbacks.push(text.attachOnPush((list, items) => this.onTextChanged(converter != null ? converter(list) : null)));
@ -2125,8 +2533,16 @@ class text extends IgniteTemplate {
} else {
this._text = (converter != null ? converter(text) : text);
}
IgniteRendering.pop();
}
/**
* Constructs this template and adds it to the DOM if this template
* has not already been constructed.
* @param {HTMLElement} parent Parent element that will contain the constructed element.
* @param {HTMLElement} sibling Optional sibling element that can be used to add the element adjacantly.
*/
construct(parent, sibling) {
//Don't construct if we have no parent, no sibling and no element.
if (!parent && !sibling && !this.element) {
@ -2136,19 +2552,19 @@ class text extends IgniteTemplate {
if (!this.element) {
this.element = window.document.createTextNode("");
if (sibling) {
sibling.parentElement.insertBefore(this.element, sibling);
if (sibling && sibling.nextSibling) {
sibling.parentElement.insertBefore(this.element, sibling.nextSibling);
} else {
parent.appendChild(this.element);
}
}
this.element.data = this._text;
this.element.nodeValue = this._text;
}
onTextChanged(newValue) {
if (this.element) {
this.element.data = newValue;
this.element.nodeValue = newValue;
}
this._text = newValue;
@ -2180,6 +2596,12 @@ class html extends IgniteTemplate {
this.elements = [];
}
/**
* Constructs this template and adds it to the DOM if this template
* has not already been constructed.
* @param {HTMLElement} parent Parent element that will contain the constructed element.
* @param {HTMLElement} sibling Optional sibling element that can be used to add the element adjacantly.
*/
construct(parent, sibling) {
//Don't construct if we have no parent, no sibling and no element.
if (!parent && !sibling && !this.element) {
@ -2189,8 +2611,8 @@ class html extends IgniteTemplate {
if (!this.element) {
this.element = window.document.createTextNode("");
if (sibling) {
sibling.parentElement.insertBefore(this.element, sibling);
if (sibling && sibling.nextSibling) {
sibling.parentElement.insertBefore(this.element, sibling.nextSibling);
} else {
parent.appendChild(this.element);
}
@ -2278,6 +2700,12 @@ class list extends IgniteTemplate {
this.tagName = "shadow list";
}
/**
* Constructs this template and adds it to the DOM if this template
* has not already been constructed.
* @param {HTMLElement} parent Parent element that will contain the constructed element.
* @param {HTMLElement} sibling Optional sibling element that can be used to add the element adjacantly.
*/
construct(parent, sibling) {
//Don't construct if we have no parent, no sibling and no element.
if (!parent && !sibling && !this.element) {
@ -2287,8 +2715,8 @@ class list extends IgniteTemplate {
if (!this.element) {
this.element = window.document.createTextNode(""); //Use a textnode as our placeholder
if (sibling) {
sibling.parentElement.insertBefore(this.element, sibling);
if (sibling && sibling.nextSibling) {
sibling.parentElement.insertBefore(this.element, sibling.nextSibling);
} else {
parent.appendChild(this.element);
}
@ -2326,12 +2754,17 @@ class list extends IgniteTemplate {
if (this.list) {
for (var i = 0; i < this.list.length; i++) {
var template = this.forEachCallback(this.list[i], i, this.list.length);
if (template) {
if (this.elements.length > 0) {
template.construct(parent, this.elements[this.elements.length - 1]);
} else {
template.construct(parent, this.element);
}
//If we are reflecting, attach to the elements disconnect event.
if (this.reflecting) {
this.reflectCallbacks.push(template.element.attachOnDisconnect(disconnect => this.onItemRemove(this.list[i])));
this.reflectCallbacks.push(template.element.attachOnDisconnect(() => this.onItemRemove(this.list[i])));
}
this.children.push(template);
@ -2368,7 +2801,11 @@ class list extends IgniteTemplate {
items.forEach(item => {
var template = this.forEachCallback(item, this.children.length);
if (this.elements.length > 0) {
template.construct(this.element.parentElement, this.elements[this.elements.length - 1]);
} else {
template.construct(this.element.parentElement, this.element);
}
//If we are reflecting, attach to the elements disconnect event.
if (this.reflecting) {
@ -2393,11 +2830,7 @@ class list extends IgniteTemplate {
items.forEach(item => {
var template = this.forEachCallback(item, 0);
if (this.elements.length > 0) {
template.construct(this.element.parentElement, this.elements[0]);
} else {
template.construct(this.element.parentElement, this.element);
}
if (this.reflecting) {
this.reflectCallbacks.unshift(template.element.attachOnDisconnect(disconnect => this.onItemRemove(item)));
@ -2470,7 +2903,13 @@ class list extends IgniteTemplate {
items.forEach(item => {
var template = this.forEachCallback(item, start);
if (start == 0) {
template.construct(this.element.parentElement, this.element);
} else if (start <= this.elements.length) {
template.construct(this.element.parentElement, this.elements[start - 1]);
} else {
template.construct(this.element.parentElement, this.elements[this.elements.length - 1]);
}
if (this.reflecting) {
this.reflectCallbacks.splice(start, 0, template.element.attachOnDisconnect(disconnect => this.onItemRemove(item)));
@ -2503,6 +2942,173 @@ class list extends IgniteTemplate {
}
}
/**
* A special ignite template that converts a value to HTML or a Template and supports
* dynamic value changes and dynamic converter changes.
*/
class converter extends IgniteTemplate {
/**
* Creates a new converter with a value and a callback convert function.
* @param {Any|IgniteProperty|IgniteProperty[]} value
* @param {IgniteProperty|Function} converter
*/
constructor(value, converter) {
super();
if (converter instanceof IgniteProperty) {
this._callbacks.push(converter.attachOnChange((oldValue, newValue) => this.onConverterChanged(newValue)));
this.converter = converter.value;
} else if (converter instanceof Function) {
this.converter = converter;
}
if (!this.converter) {
throw "A valid converter must be passed.";
}
if (value instanceof IgniteProperty) {
this._callbacks.push(value.attachOnChange((oldValue, newValue) => this.onValueChanged(newValue)));
this._callbacks.push(value.attachOnPush((list, items) => this.onValueChanged(list)));
this._callbacks.push(value.attachOnUnshift((list, items) => this.onValueChanged(list)));
this._callbacks.push(value.attachOnPop(list => this.onValueChanged(list)));
this._callbacks.push(value.attachOnShift(list => this.onValueChanged(list)));
this._callbacks.push(value.attachOnSplice((list, start, deleteCount, items) => this.onValueChanged(list)));
this.value = value;
} else if (Array.isArray(value) && value.length > 0 && value[0] instanceof IgniteProperty) {
//Attach a callback for all the properties
value.forEach(prop => {
if (prop instanceof IgniteProperty) {
this._callbacks.push(prop.attachOnChange((oldValue, newValue) => this.onValueChanged(value)));
this._callbacks.push(prop.attachOnPush((list, items) => this.onValueChanged(value)));
this._callbacks.push(prop.attachOnUnshift((list, items) => this.onValueChanged(value)));
this._callbacks.push(prop.attachOnPop((list) => this.onValueChanged(value)));
this._callbacks.push(prop.attachOnShift((list) => this.onValueChanged(value)));
this._callbacks.push(prop.attachOnSplice((list, start, deleteCount, items) => this.onValueChanged(value)));
}
});
this.value = value;
} else if (value) {
this.value = value;
}
this.tagName = "converter";
}
/**
* Constructs this template and adds it to the DOM if this template
* has not already been constructed.
* @param {HTMLElement} parent Parent element that will contain the constructed element.
* @param {HTMLElement} sibling Optional sibling element that can be used to add the element adjacantly.
*/
construct(parent, sibling) {
//Don't construct if we have no parent, no sibling and no element.
if (!parent && !sibling && !this.element) {
return;
}
if (!this.element) {
this.element = window.document.createTextNode(""); //Use a textnode as our placeholder
if (sibling && sibling.nextSibling) {
sibling.parentElement.insertBefore(this.element, sibling.nextSibling);
} else {
parent.appendChild(this.element);
}
} else {
parent = this.element.parentElement;
}
//If we haven't converted the value yet, do it now
if (!this.converted) {
if (Array.isArray(this.value) && this.value.length > 0 && this.value[0] instanceof IgniteProperty) {
this.converted = this.converter ? this.converter(...this.value.getPropertyValues()) : this.value.getPropertyValues();
} else if (this.value instanceof IgniteProperty) {
this.converted = this.converter ? this.converter(this.value.value) : this.value.value;
} else {
this.converted = this.converter ? this.converter(this.value) : this.value;
}
}
//Construct the converted value
if (this.converted instanceof IgniteTemplate) {
this.converted.construct(parent, this.element);
} else if (Array.isArray(this.converted)) {
var last = this.element;
for (var i = 0; i < this.converted.length; i++) {
if (this.converted[i] instanceof IgniteTemplate) {
this.converted[i].construct(parent, last);
last = this.converted[i].element;
}
}
}
}
onValueChanged(newValue) {
this.value = newValue;
//Deconstruct any existing converted elements
if (this.converted) {
if (Array.isArray(this.converted)) {
this.converted.forEach(element => {
if (element instanceof IgniteTemplate) {
element.deconstruct();
}
});
} else if (this.converted instanceof IgniteTemplate) {
this.converted.deconstruct();
}
this.converted = null;
}
//Convert the value using the latest converter
if (Array.isArray(this.value) && this.value.length > 0 && this.value[0] instanceof IgniteProperty) {
this.converted = this.converter ? this.converter(...this.value.getPropertyValues()) : this.value.getPropertyValues();
} else if (this.value instanceof IgniteProperty) {
this.converted = this.converter ? this.converter(this.value.value) : this.value.value;
} else {
this.converted = this.converter ? this.converter(this.value) : this.value;
}
this.construct(null, null);
}
onConverterChanged(newConverter) {
this.converter = newConverter;
//Deconstruct any existing converted elements
if (this.converted) {
if (Array.isArray(this.converted)) {
this.converted.forEach(element => {
if (element instanceof IgniteTemplate) {
element.deconstruct();
}
});
} else if (this.converted instanceof IgniteTemplate) {
this.converted.deconstruct();
}
this.converted = null;
}
//Convert the value using the latest converter
if (Array.isArray(this.value) && this.value.length > 0 && this.value[0] instanceof IgniteProperty) {
this.converted = this.converter ? this.converter(...this.value.getPropertyValues()) : this.value.getPropertyValues();
} else if (this.value instanceof IgniteProperty) {
this.converted = this.converter ? this.converter(this.value.value) : this.value.value;
} else {
this.converted = this.converter ? this.converter(this.value) : this.value;
}
this.construct(null, null);
}
}
/**
* 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,
@ -2547,8 +3153,8 @@ class slot extends IgniteTemplate {
if (!this.element) {
this.element = window.document.createTextNode("");
if (sibling) {
sibling.parentElement.insertBefore(this.element, sibling);
if (sibling && sibling.nextSibling) {
sibling.parentElement.insertBefore(this.element, sibling.nextSibling);
} else {
parent.appendChild(this.element);
}
@ -2735,9 +3341,18 @@ class pagination extends IgniteTemplate {
}
//Show the elements in the current page
if (this.pages && this.pages.length > 0) {
this.pages[this.currentPage].forEach(item => item.style.removeProperty("display"));
}
}
/**
* Constructs this template and adds it to the DOM if this template
* has not already been constructed.
* @param {HTMLElement} parent Parent element that will contain the constructed element.
* @param {HTMLElement} sibling Optional sibling element that can be used to add the element adjacantly.
* @ignore
*/
construct(parent, sibling) {
//Don't construct if we have no parent, no sibling and no element.
if (!parent && !sibling && !this.element) {
@ -2747,8 +3362,8 @@ class pagination extends IgniteTemplate {
if (!this.element) {
this.element = window.document.createTextNode(""); //Use a textnode as our placeholder
if (sibling) {
sibling.parentElement.insertBefore(this.element, sibling);
if (sibling && sibling.nextSibling) {
sibling.parentElement.insertBefore(this.element, sibling.nextSibling);
} else {
parent.appendChild(this.element);
}
@ -3078,6 +3693,13 @@ class pager extends IgniteTemplate {
this.recalculate();
}
/**
* Constructs this template and adds it to the DOM if this template
* has not already been constructed.
* @param {HTMLElement} parent Parent element that will contain the constructed element.
* @param {HTMLElement} sibling Optional sibling element that can be used to add the element adjacantly.
* @ignore
*/
construct(parent, sibling) {
//Don't construct if we have no parent, no sibling and no element.
if (!parent && !sibling && !this.element) {
@ -3087,8 +3709,8 @@ class pager extends IgniteTemplate {
if (!this.element) {
this.element = window.document.createTextNode(""); //Use a textnode as our placeholder
if (sibling) {
sibling.parentElement.insertBefore(this.element, sibling);
if (sibling && sibling.nextSibling) {
sibling.parentElement.insertBefore(this.element, sibling.nextSibling);
} else {
parent.appendChild(this.element);
}
@ -3317,6 +3939,13 @@ class population extends IgniteTemplate {
this.tagName = "shadow population";
}
/**
* Constructs this template and adds it to the DOM if this template
* has not already been constructed.
* @param {HTMLElement} parent Parent element that will contain the constructed element.
* @param {HTMLElement} sibling Optional sibling element that can be used to add the element adjacantly.
* @ignore
*/
construct(parent, sibling) {
//Don't construct if we have no parent, no sibling and no element.
if (!parent && !sibling && !this.element) {
@ -3326,8 +3955,8 @@ class population extends IgniteTemplate {
if (!this.element) {
this.element = window.document.createTextNode(""); //Use a textnode as our placeholder
if (sibling) {
sibling.parentElement.insertBefore(this.element, sibling);
if (sibling && sibling.nextSibling) {
sibling.parentElement.insertBefore(this.element, sibling.nextSibling);
} else {
parent.appendChild(this.element);
}
@ -3390,6 +4019,7 @@ export {
text,
html,
list,
converter,
a,
input,
textarea,
@ -3409,6 +4039,7 @@ export {
i,
nav,
ul,
ol,
li,
br,
img,
@ -3434,5 +4065,8 @@ export {
line,
form,
header,
footer
footer,
iframe,
video,
source
};