2020-07-28 22:23:49 -07:00
import { IgniteProperty } from './ignite-html.js' ;
2020-08-23 11:50:31 -07:00
import { IgniteElement } from './ignite-element.js' ;
2020-07-28 09:04:04 -07:00
2020-08-30 21:55:21 -07:00
/**
* The outline of a ignite template. Templates are a blueprint that specify's
* how to construct an element and then can be used to construct the element. Everything
* starts with a template.
*
* @example
* //You can easily create a template to construct any html element. See the following:
* class div extends IgniteTemplate {
* constructor(...items) {
* super("div", items);
* }
* }
*/
2020-07-28 09:04:04 -07:00
class IgniteTemplate {
2020-08-30 21:55:21 -07:00
/**
* Creates a new IgniteTemplate with the tag name of the element that will be constructed and an
* array of child elements.
* @param {String} tagName The tag name of the element this template will construct.
* @param {String|Number|IgniteProperty|IgniteTemplate} children An array of child elements to be added to this template.
*/
constructor ( tagName = null , children = null ) {
2020-07-28 09:04:04 -07:00
this . siblings = [];
this . children = [];
this . attributes = {};
this . classes = [];
2020-08-30 21:55:21 -07:00
this . tagName = tagName ;
2020-07-28 09:04:04 -07:00
this . element = null ;
this . properties = {};
2020-07-28 09:50:26 -07:00
this . refs = [];
2020-07-28 22:23:49 -07:00
this . callbacks = [];
2020-07-30 08:32:44 -07:00
this . events = {};
2020-08-11 12:53:57 -07:00
this . styles = {};
2020-08-31 22:11:19 -07:00
this . elementValue = null ;
2020-09-25 09:36:39 -07:00
this . elementInnerHTML = null ;
2020-07-28 09:04:04 -07:00
2020-08-30 21:55:21 -07:00
if ( children ) {
for ( var i = 0 ; i < children . length ; i ++ ) {
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 ]));
} else if ( children [ i ] instanceof Number || typeof children [ i ] === 'number' ) {
this . children . push ( new html ( children [ i ]));
} else if ( children [ i ] instanceof IgniteTemplate || children [ i ]. prototype instanceof IgniteTemplate ) {
this . children . push ( children [ i ]);
2020-07-29 14:01:41 -07:00
} else {
2020-08-30 21:55:21 -07:00
throw `Attempted to add a child for template: ${ this . tagName } which is not supported. Child: ${ children [ i ] } ` ;
2020-07-28 09:04:04 -07:00
}
}
}
}
2020-07-28 22:23:49 -07:00
/**
2020-08-31 22:11:19 -07:00
* 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.
2020-08-30 21:55:21 -07:00
* @param {Function} converter Optional function that can convert the class name into a different one.
2020-08-31 22:11:19 -07:00
* @example
* .class("row justify-content-center")
2020-08-30 21:55:21 -07:00
* @returns This ignite template so function calls can be chained.
2020-07-28 22:23:49 -07:00
*/
2020-08-23 22:27:12 -07:00
class ( name , converter = null ) {
2020-09-25 09:36:39 -07:00
IgniteRenderingContext . push ();
2020-08-23 22:27:12 -07:00
if ( name instanceof IgniteProperty ) {
this . callbacks . push ( name . attachOnChange (( oldValue , newValue ) => this . onClassChanged ( oldValue , newValue , converter )));
2020-08-31 22:11:19 -07:00
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 );
}
});
2020-08-23 22:27:12 -07:00
} else {
2020-08-31 22:11:19 -07:00
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 );
}
});
2020-07-28 09:04:04 -07:00
}
2020-09-25 09:36:39 -07:00
IgniteRenderingContext . pop ();
2020-07-28 09:04:04 -07:00
return this ;
}
2020-07-28 22:23:49 -07:00
/**
2020-08-30 21:55:21 -07:00
* Adds a html element attribute to this template to be added once this template is constructed.
* @param {String} name The name of the attribute to add
* @param {String|IgniteProperty} value The value of the attribute to set, can be anything. If Property is passed it will auto update.
* @param {Function} converter Optional function that can convert the value if needed.
* @returns This ignite template so function calls can be chained.
2020-07-28 22:23:49 -07:00
*/
2020-08-23 22:27:12 -07:00
attribute ( name , value , converter = null ) {
2020-09-25 09:36:39 -07:00
IgniteRenderingContext . push ();
2020-07-28 09:04:04 -07:00
if ( value instanceof IgniteProperty ) {
2020-08-23 22:27:12 -07:00
this . callbacks . push ( value . attachOnChange (( oldValue , newValue ) => this . onAttributeChanged ( oldValue , newValue , name , converter )));
this . attributes [ name ] = converter != null ? converter ( value . value ) : value . value ;
2020-07-28 09:04:04 -07:00
} else {
2020-08-23 22:27:12 -07:00
this . attributes [ name ] = converter != null ? converter ( value ) : value ;
2020-07-28 09:04:04 -07:00
}
2020-09-25 09:36:39 -07:00
IgniteRenderingContext . pop ();
2020-07-28 09:04:04 -07:00
return this ;
}
2020-08-31 22:11:19 -07:00
/**
* 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.
2020-09-01 08:50:11 -07:00
* @returns This ignite template so function calls can be chained.
2020-08-31 22:11:19 -07:00
*/
value ( value , reflect = false , converter = null ) {
2020-09-25 09:36:39 -07:00
IgniteRenderingContext . push ();
2020-08-31 22:11:19 -07:00
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 );
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 );
value . setValue ( newValue , true );
});
}
this . elementValue = ( converter != null ? converter ( value . value ) : value . value );
} else {
this . elementValue = ( converter != null ? converter ( value ) : value );
}
2020-09-25 09:36:39 -07:00
IgniteRenderingContext . pop ();
2020-08-31 22:11:19 -07:00
return this ;
}
2020-07-28 22:23:49 -07:00
/**
* Adds a property to this template to be added once this template is constructed.
2020-08-30 21:55:21 -07:00
* @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 {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 This ignite template so function calls can be chained.
2020-07-28 22:23:49 -07:00
*/
2020-08-30 21:55:21 -07:00
property ( name , value , reflect = false , converter = null ) {
2020-09-25 09:36:39 -07:00
IgniteRenderingContext . push ();
2020-08-23 11:50:31 -07:00
if ( this . properties [ name ]) {
throw `Attempted to set a property twice on a IgniteTemplate: ${ this . tagName } . This is not allowed and should be avoided.` ;
}
2020-08-30 21:55:21 -07:00
if ( reflect && converter != null ) {
throw `Cannot add a property to an IgniteTemplate: ${ this . tagName } with reflect and a converter used at the same time.` ;
}
2020-07-28 09:04:04 -07:00
if ( value instanceof IgniteProperty ) {
2020-08-30 21:55:21 -07:00
this . callbacks . push ( value . attachOnChange (( oldValue , newValue ) => this . onPropertyChanged ( oldValue , newValue , name , converter )));
this . properties [ name ] = {
value : ( converter != null ? converter ( value . value ) : value . value ),
reflect : ( reflect == true ? value : null )
};
2020-07-28 09:04:04 -07:00
} else {
2020-08-30 21:55:21 -07:00
this . properties [ name ] = {
value : ( converter != null ? converter ( value ) : value ),
reflect : null
};
2020-07-28 09:04:04 -07:00
}
2020-09-25 09:36:39 -07:00
IgniteRenderingContext . 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 {Function} converter Optional function that can be used to convert the value if needed.
* @returns This ignite template so funciton calls can be chained.
*/
innerHTML ( value , converter = null ) {
IgniteRenderingContext . push ();
if ( value instanceof IgniteProperty ) {
this . callbacks . push ( value . attachOnChange (( oldValue , newValue ) => this . onInnerHTMLChanged ( oldValue , newValue , converter )));
this . elementInnerHTML = ( converter != null ? converter ( value . value ) : value . value );
} else {
this . elementInnerHTML = ( converter != null ? converter ( value ) : value );
}
IgniteRenderingContext . pop ();
2020-07-28 09:04:04 -07:00
return this ;
}
2020-07-28 22:23:49 -07:00
/**
* Adds a single or series of children to be added once this template
2020-08-30 21:55:21 -07:00
* is constructed. Numbers, Strings, and Properties passed will be added as HTML child elements.
* @param {...Number|String|IgniteProperty|IgniteTemplate} items A series of children to be added to this template.
* @returns This ignite template so function calls can be chained.
2020-07-28 22:23:49 -07:00
*/
2020-07-28 09:04:04 -07:00
child (... items ) {
2020-08-30 21:55:21 -07:00
if ( items ) {
for ( var i = 0 ; i < items . length ; i ++ ) {
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 ]));
} else if ( items [ i ] instanceof Number || typeof items [ i ] === 'number' ) {
this . children . push ( new html ( items [ i ]));
} else if ( items [ i ] instanceof IgniteTemplate || items [ i ]. prototype instanceof IgniteTemplate ) {
this . children . push ( items [ i ]);
} else {
throw `Attempted to add a child for template: ${ this . tagName } which is not supported. Child: ${ items [ i ] } ` ;
}
2020-07-28 22:23:49 -07:00
}
}
2020-07-28 09:04:04 -07:00
return this ;
}
2020-07-28 22:23:49 -07:00
/**
2020-08-30 21:55:21 -07:00
* Adds a reference callback function to be invoked once this template is constructed. The function will be invoked
* with the constructed HTMLElement. If an IgniteProperty is passed it's value will be set to the constructed HTMLElement once
* the template is constructed.
* @param {Function|IgniteProperty} refCallback The callback to be invoked with the HTMLElement of the element this template constructed.
* @returns This ignite template so function calls can be chained.
2020-07-28 22:23:49 -07:00
*/
2020-08-30 21:55:21 -07:00
ref ( refCallback ) {
if ( refCallback instanceof IgniteProperty ) {
this . callbacks . push ( refCallback . attachOnChange (( oldValue , newValue ) => this . onRefChanged ( oldValue , newValue , refCallback )));
this . refs . push (( element ) => refCallback . value = element );
2020-07-28 09:50:26 -07:00
} else {
2020-08-30 21:55:21 -07:00
this . refs . push ( refCallback );
2020-07-28 09:50:26 -07:00
}
return this ;
}
2020-07-30 08:32:44 -07:00
/**
2020-08-30 21:55:21 -07:00
* Adds an event by its name and the function to invoke once the event fires. Properties may be used
* for the function, but their value must be a valid function in order to get a proper event callback.
* @param {String} eventName The name of the event to add.
* @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.
2020-07-30 08:32:44 -07:00
*/
2020-08-30 21:55:21 -07:00
on ( eventName , eventCallback ) {
2020-07-30 09:58:47 -07:00
if ( ! this . events [ eventName ]) {
this . events [ eventName ] = [];
}
2020-08-30 21:55:21 -07:00
if ( eventCallback instanceof IgniteProperty ) {
this . callbacks . push ( eventCallback . attachOnChange (( oldValue , newValue ) => this . onEventChanged ( oldValue , newValue , eventName )));
this . events [ eventName ]. push ( eventCallback . value );
2020-07-30 08:32:44 -07:00
} else {
2020-08-30 21:55:21 -07:00
this . events [ eventName ]. push ( eventCallback );
2020-07-30 08:32:44 -07:00
}
return this ;
}
2020-08-23 11:50:31 -07:00
/**
2020-08-30 21:55:21 -07:00
* Adds an onclick 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.
2020-08-23 11:50:31 -07:00
*/
2020-08-30 21:55:21 -07:00
onClick ( eventCallback ) {
2020-09-25 09:36:39 -07:00
return this . on ( "click" , eventCallback );
}
2020-08-21 22:13:06 -07:00
2020-09-25 09:36:39 -07:00
/**
* Adds a onblur 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.
*/
onBlur ( eventCallback ) {
return this . on ( "blur" , eventCallback );
}
/**
* Adds a onfocus 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.
*/
onFocus ( eventCallback ) {
return this . on ( "focus" , eventCallback );
2020-08-21 22:13:06 -07:00
}
2020-08-23 11:50:31 -07:00
/**
* Adds a on enter key press event handler to this template.
2020-08-30 21:55:21 -07:00
* @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.
2020-08-23 11:50:31 -07:00
*/
2020-08-30 21:55:21 -07:00
onEnter ( eventCallback ) {
2020-08-21 22:13:06 -07:00
var eventName = "keydown" ;
if ( ! this . events [ eventName ]) {
this . events [ eventName ] = [];
}
2020-08-30 21:55:21 -07:00
if ( eventCallback instanceof IgniteProperty ) {
this . callbacks . push ( eventCallback . attachOnChange (( oldValue , newValue ) => {
2020-08-21 22:13:06 -07:00
//Create a new wrapped function to check for the enter key being pressed.
var wrapped = ( e ) => {
if ( e . key === 'Enter' ) {
newValue ( e );
}
};
2020-09-25 09:36:39 -07:00
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.
2020-08-21 22:13:06 -07:00
}));
//Create the initial wrapper
2020-08-30 21:55:21 -07:00
var target = eventCallback . _value ;
2020-08-21 22:13:06 -07:00
var wrapped = ( e ) => {
if ( e . key === 'Enter' ) {
2020-08-21 22:14:57 -07:00
target ( e );
2020-08-21 22:13:06 -07:00
}
};
//Store the wrapped so that it's the old value next time around.
2020-08-30 21:55:21 -07:00
eventCallback . _value = wrapped ;
2020-08-21 22:13:06 -07:00
this . events [ eventName ]. push ( wrapped );
} else {
this . on ( eventName , ( e ) => {
if ( e . key === 'Enter' ) {
2020-08-30 21:55:21 -07:00
eventCallback ( e );
2020-08-21 22:13:06 -07:00
}
});
}
return this ;
}
2020-09-25 09:36:39 -07:00
/**
* 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.
* @returns This ignite template so function calls can be chained.
*/
onBackspace ( 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 enter key being pressed.
var wrapped = ( e ) => {
if ( e . key === 'Backspace' ) {
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 === 'Backspace' ) {
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 === 'Backspace' ) {
eventCallback ( e );
}
});
}
return this ;
}
2020-08-11 12:53:57 -07:00
/**
2020-08-30 21:55:21 -07:00
* 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.
2020-09-25 09:36:39 -07:00
* @param {String} priority If set to "important" then the style will be marked with !important. Acceptable values: important, !important, true, false, null
2020-08-30 21:55:21 -07:00
* @param {Function} converter Optional function to convert the value if needed.
* @returns This ignite template so function calls can be chained.
2020-08-11 12:53:57 -07:00
*/
2020-08-23 22:27:12 -07:00
style ( name , value , priority = null , converter = null ) {
2020-09-25 09:36:39 -07:00
IgniteRenderingContext . push ();
//If the name has a : remove it.
if ( name && typeof name === "string" && name . includes ( ":" )) {
name = name . replace ( ":" , "" );
}
//If the priority starts with a ! remove it.
if ( priority && typeof priority === "string" && priority . trim (). startsWith ( "!" )) {
priority = priority . split ( "!" )[ 1 ]. trim ();
} else if ( priority && typeof priority === "boolean" ) {
priority = "important" ; //If priority is true, set it to important
} else if ( ! priority && typeof priority === "boolean" ) {
priority = null ; //If priority is false, set it to null.
}
//If the value has a ; remove it.
if ( value && typeof value === "string" && value . includes ( ";" )) {
value = value . replace ( ";" , "" );
}
2020-08-11 12:53:57 -07:00
if ( value instanceof IgniteProperty ) {
2020-09-25 09:36:39 -07:00
this . callbacks . push ( value . attachOnChange (( oldValue , newValue ) => this . onStyleChanged ( name , ( converter ? converter ( newValue ) : newValue ))));
this . styles [ name ] = { name : name , value : ( converter ? converter ( value . value ) : value . value ), priority : priority };
} 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 => {
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 . styles [ name ] = { name : name , value : converter (... this . getValuesForProperties ( value )), priority : priority };
2020-08-11 12:53:57 -07:00
} else {
2020-08-23 22:27:12 -07:00
this . styles [ name ] = { name : name , value : ( converter != null ? converter ( value ) : value ), priority : priority };
2020-08-11 12:53:57 -07:00
}
2020-09-25 09:36:39 -07:00
IgniteRenderingContext . pop ();
2020-08-11 12:53:57 -07:00
return this ;
}
2020-09-25 09:36:39 -07:00
/**
* Hides the element this template is constructing if the value is true.
* @param {Boolean} value If true hides the element this template is constructing. If an IgniteProperty is passed it's value will auto update this.
* @param {Function} converter An optional function to convert the value if needed.
*/
hide ( value , converter = null ) {
return this . style ( "display" , value , true , (... params ) => {
return (( converter != null && converter (... params )) || ( converter == null && params [ 0 ])) ? "none" : null ;
});
}
2020-08-31 22:11:19 -07:00
/**
* 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.
2020-09-01 08:50:11 -07:00
* @returns This ignite template so function calls can be chained.
2020-08-31 22:11:19 -07:00
*/
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.
2020-09-01 08:50:11 -07:00
* @returns This ignite template so function calls can be chained.
2020-08-31 22:11:19 -07:00
*/
type ( value , converter = null ) {
return this . attribute ( "type" , value , converter );
}
2020-09-25 09:36:39 -07:00
/**
* 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.
* @returns This ignite template so function calls can be chained.
*/
data ( name , value , converter = null ) {
return this . attribute ( `data- ${ name } ` , value , converter );
}
2020-08-31 22:11:19 -07:00
/**
* 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.
2020-09-01 08:50:11 -07:00
* @returns This ignite template so function calls can be chained.
2020-08-31 22:11:19 -07:00
*/
src ( value , converter = null ) {
return this . attribute ( "src" , value , converter );
}
2020-09-09 08:24:18 -07:00
/**
* 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 );
}
2020-08-31 22:11:19 -07:00
/**
* 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.
2020-09-01 08:50:11 -07:00
* @returns This ignite template so function calls can be chained.
2020-08-31 22:11:19 -07:00
*/
name ( value , converter = null ) {
return this . attribute ( "name" , value , converter );
}
2020-09-01 08:50:11 -07:00
/**
* Sets the placeholder attribute of the element to be constructed by this template.
* @param {String|IgniteProperty} value The value to set for the placeholder attribute of the element.
* @param {Function} converter An optional function that can convert the value if needed.
* @returns This ignite template so function calls can be chained.
*/
placeholder ( value , converter = null ) {
return this . attribute ( "placeholder" , value , converter );
}
2020-07-28 22:23:49 -07:00
/**
* Constructs this template and adds it to the DOM if this template
* has not already been constructed.
2020-08-30 21:55:21 -07:00
* @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.
2020-07-28 22:23:49 -07:00
*/
construct ( parent , sibling ) {
2020-07-29 11:21:02 -07:00
//Don't construct if we have no parent, no sibling and no element.
if ( ! parent && ! sibling && ! this . element ) {
return ;
}
2020-07-28 09:04:04 -07:00
//Construct this element if we haven't already
if ( ! this . element ) {
this . element = window . document . createElement ( this . tagName );
2020-07-28 22:23:49 -07:00
2020-08-23 11:50:31 -07:00
//Pass back our template to the element we are creating if it's not a ignite element, since
//it will have it's own template automatically.
if ( ! ( this . element instanceof IgniteElement )) {
this . element . template = this ;
}
2020-07-28 22:23:49 -07:00
//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 ();
}
2020-07-28 09:50:26 -07:00
2020-08-23 11:50:31 -07:00
//Invoke any refs we have and pass back the element reference.
2020-07-29 12:24:36 -07:00
this . refs . forEach (( ref ) => ref ( this . element ));
2020-07-28 09:04:04 -07:00
}
//Set the classes on this element
for ( var i = 0 ; i < this . classes . length ; i ++ ) {
2020-08-23 22:27:12 -07:00
if ( this . classes [ i ] !== null && this . classes [ i ] !== undefined && this . classes [ i ] !== "" ) {
2020-07-28 09:04:04 -07:00
this . element . classList . add ( this . classes [ i ]);
}
}
//Set the attributes on this element
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 ) {
this . element . setAttribute ( keys [ i ], this . attributes [ keys [ i ]]);
}
}
2020-07-30 08:32:44 -07:00
//Set the events on this element
var keys = Object . keys ( this . events );
for ( var i = 0 ; i < keys . length ; i ++ ) {
2020-07-30 09:58:47 -07:00
this . events [ keys [ i ]]. forEach (( event ) => {
if ( event !== null && event !== undefined ) {
this . element . addEventListener ( keys [ i ], event );
}
});
2020-07-30 08:32:44 -07:00
}
2020-08-11 12:53:57 -07:00
//Set the styles on this element.
var keys = Object . keys ( this . styles );
for ( var i = 0 ; i < keys . length ; i ++ ) {
var style = this . styles [ keys [ i ]];
this . element . style . setProperty ( style . name , style . value , style . priority );
}
2020-07-28 09:04:04 -07:00
//Set the properties on this element
var keys = Object . keys ( this . properties );
for ( var i = 0 ; i < keys . length ; i ++ ) {
2020-08-23 11:50:31 -07:00
this . element [ keys [ i ]] = this . properties [ keys [ i ]]. value ;
if ( this . properties [ keys [ i ]]. reflect != null ) {
this . element [ keys [ i ]]. reflected . push ( this . properties [ keys [ i ]]. reflect );
}
2020-07-28 09:04:04 -07:00
}
2020-08-31 22:11:19 -07:00
//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 ;
}
}
2020-09-25 09:36:39 -07:00
//Set the elements inner html if it was set
if ( this . elementInnerHTML != null ) {
this . element . innerHTML = this . elementInnerHTML ;
}
2020-07-28 09:04:04 -07:00
//Construct the children under this element
for ( var i = 0 ; i < this . children . length ; i ++ ) {
2020-07-29 14:01:41 -07:00
this . children [ i ]. construct ( this . element );
2020-07-28 09:04:04 -07:00
}
2020-07-29 11:21:02 -07:00
//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 );
} else {
parent . appendChild ( this . element );
}
}
2020-07-28 09:04:04 -07:00
}
2020-07-28 22:23:49 -07:00
/**
* Deconstructs this template and cleans up all resources to make sure
* there are no memory leaks.
*/
deconstruct () {
2020-07-30 08:32:44 -07:00
//Remove and disconnect all events
if ( this . element && this . events ) {
var keys = Object . keys ( this . events );
for ( var i = 0 ; i < keys . length ; i ++ ) {
this . element . removeEventListener ( keys [ i ], this . events [ keys [ i ]]);
}
this . events = null ;
}
2020-07-28 22:23:49 -07:00
//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 ;
}
2020-07-29 12:24:36 -07:00
//Disconnect all callbacks
2020-07-28 22:23:49 -07:00
if ( this . callbacks ) {
this . callbacks . forEach (( item ) => item . disconnect ());
this . callbacks = null ;
}
2020-07-29 12:24:36 -07:00
//Remove any refs
2020-07-28 22:23:49 -07:00
if ( this . refs ) {
2020-09-25 09:36:39 -07:00
//Pass null to our refs so that the reference is updated.
this . refs . forEach ( ref => ref ( null ));
2020-07-28 22:23:49 -07:00
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
2020-09-09 08:24:18 -07:00
* @param {Function} converter Optional converter for the value if needed.
2020-08-30 21:55:21 -07:00
* @ignore
2020-07-28 22:23:49 -07:00
*/
2020-08-23 22:27:12 -07:00
onClassChanged ( oldValue , newValue , converter ) {
if ( converter !== null ) {
2020-09-25 09:36:39 -07:00
IgniteRenderingContext . push ();
2020-08-23 22:27:12 -07:00
oldValue = converter ( oldValue );
newValue = converter ( newValue );
2020-09-25 09:36:39 -07:00
IgniteRenderingContext . pop ();
2020-08-23 22:27:12 -07:00
}
2020-09-09 08:24:18 -07:00
var oldClasses = ( oldValue != null && oldValue != "" ? oldValue . toString (). split ( " " ) : []);
var newClasses = ( newValue != null && newValue != "" ? newValue . toString (). split ( " " ) : []);
2020-08-31 22:11:19 -07:00
2020-07-29 11:21:02 -07:00
if ( this . element ) {
2020-09-09 08:24:18 -07:00
oldClasses . forEach (( cl ) => this . element . classList . remove ( cl ));
newClasses . forEach (( cl ) => this . element . classList . add ( cl ));
2020-07-28 09:04:04 -07:00
}
2020-07-29 11:21:02 -07:00
2020-08-31 22:11:19 -07:00
//Remove the old values from the template, but only remove one copy.
oldClasses . forEach (( cl ) => this . classes . splice ( this . classes . indexOf ( cl ), 1 ));
2020-07-30 10:11:09 -07:00
2020-08-31 22:11:19 -07:00
//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 );
}
});
2020-07-28 09:04:04 -07:00
}
2020-07-28 22:23:49 -07:00
/**
* 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
2020-08-30 21:55:21 -07:00
* @ignore
2020-07-28 22:23:49 -07:00
*/
2020-08-23 22:27:12 -07:00
onAttributeChanged ( oldValue , newValue , attributeName , converter ) {
if ( converter !== null ) {
2020-09-25 09:36:39 -07:00
IgniteRenderingContext . push ();
2020-08-23 22:27:12 -07:00
newValue = converter ( newValue );
2020-09-25 09:36:39 -07:00
IgniteRenderingContext . pop ();
2020-08-23 22:27:12 -07:00
}
2020-07-29 11:21:02 -07:00
if ( this . element ) {
if ( newValue == null || newValue == undefined ) {
this . element . removeAttribute ( attributeName );
} else {
this . element . setAttribute ( attributeName , newValue );
}
2020-07-28 09:04:04 -07:00
}
2020-07-29 11:21:02 -07:00
this . attributes [ attributeName ] = newValue ;
2020-07-28 09:04:04 -07:00
}
2020-08-31 22:11:19 -07:00
/**
* 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 ) {
2020-09-25 09:36:39 -07:00
IgniteRenderingContext . push ();
2020-08-31 22:11:19 -07:00
newValue = converter ( newValue );
2020-09-25 09:36:39 -07:00
IgniteRenderingContext . pop ();
2020-08-31 22:11:19 -07:00
}
//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 ;
}
}
}
}
2020-07-28 22:23:49 -07:00
/**
* 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
2020-09-09 08:24:18 -07:00
* @param {Function} converter
2020-08-30 21:55:21 -07:00
* @ignore
2020-07-28 22:23:49 -07:00
*/
2020-08-30 21:55:21 -07:00
onPropertyChanged ( oldValue , newValue , propertyName , converter ) {
if ( converter !== null ) {
2020-09-25 09:36:39 -07:00
IgniteRenderingContext . push ();
2020-08-30 21:55:21 -07:00
newValue = converter ( newValue );
2020-09-25 09:36:39 -07:00
IgniteRenderingContext . pop ();
2020-08-30 21:55:21 -07:00
}
2020-07-29 11:21:02 -07:00
if ( this . element ) {
2020-08-23 11:50:31 -07:00
//Use the set value function and don't reflect the change because it came from above which
//would be the reflected property, this reduces some functions being called twice that don't need to be.
this . element [ `_ ${ propertyName } ` ]. setValue ( newValue , false );
2020-07-29 11:21:02 -07:00
}
2020-08-23 11:50:31 -07:00
this . properties [ propertyName ]. value = newValue ;
2020-07-28 09:04:04 -07:00
}
2020-07-29 12:24:36 -07:00
2020-09-25 09:36:39 -07:00
/**
* Called when the inner html 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
*/
onInnerHTMLChanged ( oldValue , newValue , converter ) {
if ( converter !== null ) {
IgniteRenderingContext . push ();
newValue = converter ( newValue );
IgniteRenderingContext . pop ();
}
if ( this . element ) {
this . element . innerHTML = newValue ;
}
this . elementInnerHTML = newValue ;
}
2020-07-29 12:24:36 -07:00
/**
* Called when a ref was changed and we need to update the refs
* value to match this elements reference.
* @param {any} oldValue
* @param {any} newValue
* @param {any} ref
2020-08-30 21:55:21 -07:00
* @ignore
2020-07-29 12:24:36 -07:00
*/
onRefChanged ( oldValue , newValue , ref ) {
//Only set the reference value to ourself if it's not our element.
//Otherwise we will get a never ending loop.
if ( this . element != newValue ) {
ref . value = this . element ;
}
}
2020-07-30 08:32:44 -07:00
/**
* Called when a event was changed and we need to update it.
* @param {any} oldValue
* @param {any} newValue
* @param {any} eventName
2020-08-30 21:55:21 -07:00
* @ignore
2020-07-30 08:32:44 -07:00
*/
onEventChanged ( oldValue , newValue , eventName ) {
if ( this . element ) {
if ( oldValue !== null && oldValue !== undefined ) {
this . element . removeEventListener ( eventName , oldValue );
}
if ( newValue !== null && newValue !== undefined ) {
this . element . addEventListener ( eventName , newValue );
}
2020-07-30 09:58:47 -07:00
//Remove the old value from the events
this . events [ eventName ] = this . events [ eventName ]. filter ( ev => ev != oldValue );
2020-07-30 10:11:09 -07:00
//Add the new value if it's needed
if ( newValue !== null && newValue !== undefined ) {
this . events [ eventName ]. push ( newValue );
}
2020-07-30 08:32:44 -07:00
}
}
2020-08-11 12:53:57 -07:00
/**
* Called when a css value was changed and we need to update the styling.
2020-09-25 09:36:39 -07:00
* @param {String} name
2020-08-11 12:53:57 -07:00
* @param {any} newValue
2020-08-30 21:55:21 -07:00
* @ignore
2020-08-11 12:53:57 -07:00
*/
2020-09-25 09:36:39 -07:00
onStyleChanged ( name , newValue ) {
//Remove the ; from the value if there is one.
if ( newValue && typeof newValue === "string" && newValue . includes ( ";" )) {
newValue = newValue . replace ( ";" , "" );
2020-09-09 08:24:18 -07:00
}
2020-08-11 12:53:57 -07:00
if ( this . element ) {
2020-09-09 08:24:18 -07:00
this . element . style . setProperty ( name , newValue , this . styles [ name ]. priority );
2020-08-11 12:53:57 -07:00
}
2020-08-23 22:27:12 -07:00
2020-09-09 08:24:18 -07:00
this . styles [ name ]. value = newValue ;
2020-08-11 12:53:57 -07:00
}
2020-09-25 09:36:39 -07:00
getValuesForProperties ( props ) {
var ret = [];
props . forEach ( prop => ret . push ( prop . value ));
return ret ;
}
2020-07-28 09:04:04 -07:00
}
2020-08-30 21:55:21 -07:00
/**
* An ignite template that can be used to construct a div element.
*/
2020-07-28 09:04:04 -07:00
class div extends IgniteTemplate {
2020-08-30 21:55:21 -07:00
/**
* @param {...String|Number|IgniteProperty|IgniteTemplate} children A series of children to be added to this template.
*/
constructor (... children ) {
super ( "div" , children );
2020-07-28 09:04:04 -07:00
}
}
2020-08-30 21:55:21 -07:00
/**
* An ignite template that can be used to construct a hyperlink element.
*/
2020-07-28 09:04:04 -07:00
class a extends IgniteTemplate {
2020-08-30 21:55:21 -07:00
/**
* @param {...String|Number|IgniteProperty|IgniteTemplate} children A series of children to be added to this template.
*/
constructor (... children ) {
super ( "a" , children );
2020-07-28 09:04:04 -07:00
}
}
2020-08-30 21:55:21 -07:00
/**
* An ignite template that can be used to construct a input element.
*/
2020-08-21 22:13:06 -07:00
class input extends IgniteTemplate {
2020-08-30 21:55:21 -07:00
/**
* @param {...String|Number|IgniteProperty|IgniteTemplate} children A series of children to be added to this template.
*/
constructor (... children ) {
super ( "input" , children );
}
}
2020-08-21 22:13:06 -07:00
2020-08-30 21:55:21 -07:00
/**
* An ignite template that can be used to construct a button element.
*/
class button extends IgniteTemplate {
/**
* @param {...String|Number|IgniteProperty|IgniteTemplate} children A series of children to be added to this template.
*/
constructor (... children ) {
super ( "button" , children );
2020-09-25 09:36:39 -07:00
this . type ( "button" );
2020-08-21 22:13:06 -07:00
}
}
2020-08-30 21:55:21 -07:00
/**
* An ignite template that can be used to construct a h1 element.
*/
2020-08-23 11:50:31 -07:00
class h1 extends IgniteTemplate {
2020-08-30 21:55:21 -07:00
/**
* @param {...String|Number|IgniteProperty|IgniteTemplate} children A series of children to be added to this template.
*/
constructor (... children ) {
super ( "h1" , children );
2020-08-23 11:50:31 -07:00
}
}
2020-08-30 21:55:21 -07:00
/**
* An ignite template that can be used to construct a h2 element.
*/
2020-08-23 11:50:31 -07:00
class h2 extends IgniteTemplate {
2020-08-30 21:55:21 -07:00
/**
* @param {...String|Number|IgniteProperty|IgniteTemplate} children A series of children to be added to this template.
*/
constructor (... children ) {
super ( "h2" , children );
2020-08-23 11:50:31 -07:00
}
}
2020-08-30 21:55:21 -07:00
/**
* An ignite template that can be used to construct a h3 element.
*/
2020-08-23 11:50:31 -07:00
class h3 extends IgniteTemplate {
2020-08-30 21:55:21 -07:00
/**
* @param {...String|Number|IgniteProperty|IgniteTemplate} children A series of children to be added to this template.
*/
constructor (... children ) {
super ( "h3" , children );
2020-08-23 11:50:31 -07:00
}
}
2020-08-30 21:55:21 -07:00
/**
* An ignite template that can be used to construct a h4 element.
*/
2020-08-23 11:50:31 -07:00
class h4 extends IgniteTemplate {
2020-08-30 21:55:21 -07:00
/**
* @param {...String|Number|IgniteProperty|IgniteTemplate} children A series of children to be added to this template.
*/
constructor (... children ) {
super ( "h4" , children );
2020-08-23 11:50:31 -07:00
}
}
2020-08-30 21:55:21 -07:00
/**
* An ignite template that can be used to construct a h5 element.
*/
2020-08-23 11:50:31 -07:00
class h5 extends IgniteTemplate {
2020-08-30 21:55:21 -07:00
/**
* @param {...String|Number|IgniteProperty|IgniteTemplate} children A series of children to be added to this template.
*/
constructor (... children ) {
super ( "h5" , children );
2020-08-23 11:50:31 -07:00
}
}
2020-09-25 09:36:39 -07:00
/**
* An ignite template that can be used to construct a h6 element.
*/
class h6 extends IgniteTemplate {
/**
* @param {...String|Number|IgniteProperty|IgniteTemplate} children A series of children to be added to this template.
*/
constructor (... children ) {
super ( "h6" , children );
}
}
2020-08-30 21:55:21 -07:00
/**
* An ignite template that can be used to construct a p element.
*/
2020-08-23 11:50:31 -07:00
class p extends IgniteTemplate {
2020-08-30 21:55:21 -07:00
/**
* @param {...String|Number|IgniteProperty|IgniteTemplate} children A series of children to be added to this template.
*/
constructor (... children ) {
super ( "p" , children );
2020-08-23 11:50:31 -07:00
}
}
2020-09-09 08:24:18 -07:00
/**
* 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 );
}
}
2020-08-30 21:55:21 -07:00
/**
* An ignite template that can be used to construct a span element.
*/
2020-08-23 11:50:31 -07:00
class span extends IgniteTemplate {
2020-08-30 21:55:21 -07:00
/**
* @param {...String|Number|IgniteProperty|IgniteTemplate} children A series of children to be added to this template.
*/
constructor (... children ) {
super ( "span" , children );
2020-08-23 11:50:31 -07:00
}
}
2020-08-30 21:55:21 -07:00
/**
* An ignite template that can be used to construct an i element.
*/
2020-08-23 11:50:31 -07:00
class i extends IgniteTemplate {
2020-08-30 21:55:21 -07:00
/**
* @param {...String|Number|IgniteProperty|IgniteTemplate} children A series of children to be added to this template.
*/
constructor (... children ) {
super ( "i" , children );
}
}
/**
* An ignite template that can be used to construct a br element.
*/
class br extends IgniteTemplate {
/**
* @param {...String|Number|IgniteProperty|IgniteTemplate} children A series of children to be added to this template.
*/
constructor (... children ) {
super ( "br" , children );
}
}
2020-08-23 11:50:31 -07:00
2020-08-30 21:55:21 -07:00
/**
* An ignite template that can be used to construct a img element.
*/
class img extends IgniteTemplate {
/**
* @param {...String|Number|IgniteProperty|IgniteTemplate} children A series of children to be added to this template.
*/
constructor (... children ) {
super ( "img" , children );
2020-08-23 11:50:31 -07:00
}
}
2020-08-31 22:11:19 -07:00
/**
* 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 );
}
}
2020-08-30 21:55:21 -07:00
/**
* Html is a special template that can construct raw html or properties into the dom and automatically
* update the dom if the property changes.
*
* @example
* new html(`<h1>Hello world!</h1>`)
*/
2020-07-28 09:04:04 -07:00
class html extends IgniteTemplate {
2020-08-30 21:55:21 -07:00
/**
* @param {String|IgniteProperty} code HTML code to be constructed within this template. If an IgniteProperty is passed it's value will be used.
*/
2020-07-28 09:04:04 -07:00
constructor ( code ) {
2020-08-30 21:55:21 -07:00
super ();
2020-07-29 14:01:41 -07:00
if ( code instanceof IgniteProperty ) {
2020-08-22 16:15:45 -07:00
this . callbacks . push ( code . attachOnChange (( oldValue , newValue ) => this . onPropertyChanged ( oldValue , newValue )));
2020-07-29 14:01:41 -07:00
this . code = code . value ;
} else {
this . code = code ;
}
2020-07-28 22:23:49 -07:00
this . tagName = "shadow html" ;
2020-07-29 11:21:02 -07:00
this . elements = [];
2020-07-28 09:04:04 -07:00
}
2020-07-29 11:21:02 -07:00
construct ( parent , sibling ) {
//Don't construct if we have no parent, no sibling and no element.
if ( ! parent && ! sibling && ! this . element ) {
return ;
2020-07-28 09:04:04 -07:00
}
2020-07-29 11:21:02 -07:00
if ( ! this . element ) {
this . element = window . document . createTextNode ( "" );
if ( sibling ) {
sibling . parentElement . insertBefore ( this . element , sibling );
} else {
parent . appendChild ( this . element );
}
}
2020-07-29 14:01:41 -07:00
//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.
if ( this . elements . length == 0 && this . code ) {
2020-09-25 09:36:39 -07:00
//If the code is an ignite template then reder that template.
if ( this . code instanceof IgniteTemplate ) {
this . code . construct ( parent , this . element );
this . elements . push ( this . code . element );
} else {
var template = window . document . createElement ( "template" );
template . innerHTML = this . code ;
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 ();
2020-07-29 11:21:02 -07:00
}
}
2020-07-28 09:04:04 -07:00
}
2020-07-29 14:01:41 -07:00
onPropertyChanged ( oldValue , newValue ) {
//Update our code to the new value from the property.
this . code = newValue ;
//Remove any elements that already exist.
this . elements . forEach (( item ) => item . remove ());
this . elements = [];
2020-08-21 21:42:10 -07:00
//Reconstruct the html
2020-07-29 14:01:41 -07:00
this . construct ( null , null );
}
2020-07-28 09:04:04 -07:00
}
2020-08-30 21:55:21 -07:00
/**
* A special ignite template that constructs a list of items using a template
* that is dynamically created for each item.
*
* @example
* new list(["1", "2", "3"], (item) => {
* return new h1(item);
* })
*/
2020-07-28 09:04:04 -07:00
class list extends IgniteTemplate {
2020-08-30 21:55:21 -07:00
/**
* @param {Array|IgniteProperty} list The list of items to construct within this template.
* @param {Function} forEach A function that construct a template for an item from the list that is passed to it.
*/
2020-07-28 09:04:04 -07:00
constructor ( list , forEach ) {
2020-08-30 21:55:21 -07:00
super ();
2020-07-28 09:04:04 -07:00
if ( list instanceof IgniteProperty ) {
2020-08-22 16:15:45 -07:00
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 )));
2020-07-28 09:04:04 -07:00
this . list = list . value ;
} else {
this . list = list ;
}
this . forEach = forEach ;
this . elements = [];
2020-08-30 21:55:21 -07:00
this . tagName = "shadow list" ;
2020-07-28 09:04:04 -07:00
}
2020-07-28 22:23:49 -07:00
construct ( parent , sibling ) {
2020-07-29 11:21:02 -07:00
//Don't construct if we have no parent, no sibling and no element.
if ( ! parent && ! sibling && ! this . element ) {
return ;
}
2020-07-28 09:04:04 -07:00
if ( ! this . element ) {
this . element = window . document . createTextNode ( "" ); //Use a textnode as our placeholder
2020-07-29 11:21:02 -07:00
if ( sibling ) {
sibling . parentElement . insertBefore ( this . element , sibling );
} else {
parent . appendChild ( this . element );
}
2020-07-28 22:23:49 -07:00
} else {
parent = this . element . parentElement ;
2020-07-28 09:04:04 -07:00
}
//If we already have elements we created, destroy them.
if ( this . elements . length > 0 ) {
for ( var i = 0 ; i < this . elements . length ; i ++ ) {
this . elements [ i ]. remove ();
}
this . elements = [];
}
2020-07-28 22:23:49 -07:00
//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 = [];
}
2020-07-28 09:04:04 -07:00
//Construct all the items in our list and use the container
if ( this . list ) {
for ( var i = 0 ; i < this . list . length ; i ++ ) {
2020-07-28 22:23:49 -07:00
var template = this . forEach ( this . list [ i ]);
template . construct ( parent , this . element );
this . children . push ( template );
this . elements . push ( template . element );
}
}
}
2020-08-21 21:42:10 -07:00
onListChanged ( oldValue , newValue ) {
this . list = newValue ;
IgniteRenderingContext . enter ();
try {
this . construct ( null ); //The list changed, reconstruct this template.
2020-08-23 11:50:31 -07:00
} catch ( error ) {
console . error ( error );
}
2020-08-21 21:42:10 -07:00
IgniteRenderingContext . leave ();
}
2020-08-22 16:15:45 -07:00
onListPush ( oldValue , newValue ) {
IgniteRenderingContext . enter ();
try {
var template = this . forEach ( this . list [ this . list . length - 1 ]);
2020-08-22 18:11:37 -07:00
if ( this . elements . length > 0 ) {
template . construct ( this . element . parentElement , this . elements [ this . elements . length - 1 ]. nextSibling );
} else {
2020-09-25 09:36:39 -07:00
template . construct ( this . element . parentElement , this . element );
2020-08-22 18:11:37 -07:00
}
2020-08-22 16:15:45 -07:00
this . children . push ( template );
this . elements . push ( template . element );
} catch { }
IgniteRenderingContext . leave ();
}
onListPop ( oldValue , newValue ) {
2020-09-25 09:36:39 -07:00
if ( this . children . length > 0 ) {
this . children [ this . children . length - 1 ]. deconstruct ();
this . children . pop ();
this . elements . pop ();
}
2020-08-22 16:15:45 -07:00
}
2020-07-28 22:23:49 -07:00
}
2020-08-30 21:55:21 -07:00
/**
* 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,
2020-09-09 08:24:18 -07:00
* 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.
2020-08-30 21:55:21 -07:00
*
* @example
2020-09-09 08:24:18 -07:00
* //You must pass the ignite element who owns the slot of the first param.
2020-08-30 21:55:21 -07:00
* new slot(this)
2020-09-09 08:24:18 -07:00
*
* @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)
2020-08-30 21:55:21 -07:00
*/
2020-07-28 22:46:22 -07:00
class slot extends IgniteTemplate {
2020-08-30 21:55:21 -07:00
/**
* @param {IgniteElement} element The parent IgniteElement that this slot is for.
*/
2020-07-28 22:46:22 -07:00
constructor ( element ) {
2020-08-30 21:55:21 -07:00
super ();
2020-07-28 22:46:22 -07:00
this . parent = element ;
}
2020-09-09 08:24:18 -07:00
/**
* Constructs this slot from this template.
* @param {HTMLElement} parent
* @param {HTMLElement} sibling
* @ignore
*/
2020-07-28 22:46:22 -07:00
construct ( parent , sibling ) {
2020-07-29 11:21:02 -07:00
//Don't construct if we have no parent, no sibling and no element.
if ( ! parent && ! sibling && ! this . element ) {
return ;
}
2020-07-28 22:46:22 -07:00
if ( ! this . element ) {
this . element = window . document . createTextNode ( "" );
if ( sibling ) {
2020-07-29 11:21:02 -07:00
sibling . parentElement . insertBefore ( this . element , sibling );
2020-07-28 22:46:22 -07:00
} else {
parent . appendChild ( this . element );
}
//Add any slot elements after this element.
2020-09-09 08:24:18 -07:00
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 );
}
});
2020-07-28 22:46:22 -07:00
}
}
2020-09-09 08:24:18 -07:00
/**
* 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.
2020-09-25 09:36:39 -07:00
* @param {String} name
2020-09-09 08:24:18 -07:00
* @param {any} newValue
* @ignore
*/
2020-09-25 09:36:39 -07:00
onStyleChanged ( name , newValue ) {
2020-09-09 08:24:18 -07:00
this . parent . elements . forEach (( element ) => {
element . style . setProperty ( name , newValue , this . styles [ name ]. priority );
});
this . styles [ name ]. value = newValue ;
}
2020-07-28 22:46:22 -07:00
}
2020-07-28 09:04:04 -07:00
export {
IgniteTemplate ,
div ,
html ,
list ,
2020-07-28 22:46:22 -07:00
a ,
2020-08-21 22:13:06 -07:00
input ,
2020-08-30 21:55:21 -07:00
button ,
2020-08-23 11:50:31 -07:00
h1 ,
h2 ,
h3 ,
h4 ,
h5 ,
2020-09-25 09:36:39 -07:00
h6 ,
2020-08-23 11:50:31 -07:00
p ,
span ,
i ,
2020-09-09 08:24:18 -07:00
ul ,
li ,
2020-08-30 21:55:21 -07:00
br ,
img ,
2020-08-31 22:11:19 -07:00
label ,
2020-07-28 22:46:22 -07:00
slot
2020-07-28 09:04:04 -07:00
};