427 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			427 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| import { IgniteProperty, IgniteCallback, IgniteRendering } from './ignite-html.js';
 | |
| import { IgniteTemplate } from './ignite-template.js';
 | |
| 
 | |
| /**
 | |
|  * The outline of a Ignite Element that extends the html element
 | |
|  * and can be used to create custom components. Ignite Element's use an Ignite Template
 | |
|  * for the render function.
 | |
|  * 
 | |
|  * @example
 | |
|  * 
 | |
|  * class MainApp extends IgniteElement {
 | |
|  *   constructor() {
 | |
|  *       super();
 | |
|  *   }
 | |
|  *
 | |
|  *   get properties() {
 | |
|  *       return {
 | |
|  *       };
 | |
|  *   }
 | |
|  *
 | |
|  *   render() {
 | |
|  *       return this.template
 | |
|  *           .child(
 | |
|  *               new h1(`<i class="fad fa-fire-alt" style="--fa-primary-color: #FFC107; --fa-secondary-color: #FF5722; --fa-secondary-opacity: 1.0;"></i> Ignite HTML`),
 | |
|  *               new h4(`Adding more fire to the web.`)
 | |
|  *           );
 | |
|  *    }
 | |
|  * }
 | |
|  * 
 | |
|  * customElements.define("main-app", MainApp);
 | |
|  * 
 | |
|  * @example
 | |
|  * //If you want to easily use an Ignite Element with templates see the following which can be added
 | |
|  * //to any component file
 | |
|  * class MainAppTemplate extends IgniteTemplate {
 | |
|  *   constructor(...children) {
 | |
|  *       super("main-app", children);
 | |
|  *   }
 | |
|  * }
 | |
|  * 
 | |
|  * export {
 | |
|  *  MainAppTemplate as MainApp
 | |
|  * }
 | |
|  * 
 | |
|  * @example
 | |
|  * //If a template was created for a Ignite Element (like the previous example) it can be used just like any other template:
 | |
|  * new MainApp()
 | |
|  */
 | |
| class IgniteElement extends HTMLElement {
 | |
|     constructor() {
 | |
|         super();
 | |
| 
 | |
|         /** 
 | |
|          * @ignore
 | |
|          * @type {Boolean} 
 | |
|          * */
 | |
|         this.elementConnected = false; //Used to know if the connectedCallback was already called.
 | |
| 
 | |
|         /** 
 | |
|          * The ignite html template used to construct this element.
 | |
|          * @type {IgniteTemplate} 
 | |
|          * */
 | |
|         this.template = null;
 | |
| 
 | |
|         /** 
 | |
|          * The child elements within this element upon creation.
 | |
|          * @type {HTMLElement[]} 
 | |
|          * */
 | |
|         this.elements = [];
 | |
| 
 | |
|         /** 
 | |
|          * @ignore
 | |
|          * @type {IgniteCallback} 
 | |
|          * */
 | |
|         this.readyCallback = new IgniteCallback(() => this.ready());
 | |
| 
 | |
|         /** 
 | |
|          * @ignore
 | |
|          * @type {IgniteCallback[]} 
 | |
|          * */
 | |
|         this.onDisconnectCallbacks = [];
 | |
| 
 | |
|         //Create the variables for this element.
 | |
|         this.createVariables();
 | |
| 
 | |
|         //Create the properties for this element.
 | |
|         this.createProperties();
 | |
| 
 | |
|         //Init the element before connected callback is fired
 | |
|         //Create a new rendering context so the init method can access properties correctly.
 | |
|         IgniteRendering.push();
 | |
|         this.init();
 | |
|         IgniteRendering.pop();
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Returns an object with all the variables for this ignite element. Variables
 | |
|      * unlike properties have no callbacks or events, they are just data.
 | |
|      * 
 | |
|      * @returns {Object} An object with properties that will be assigned to this ignite element as variables.
 | |
|      * 
 | |
|      * @example
 | |
|      * get variables() {
 | |
|      *  return {
 | |
|      *      dialog: null,
 | |
|      *      clickCallback: null
 | |
|      *  };
 | |
|      * }
 | |
|      */
 | |
|     get variables() {
 | |
|         return {};
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Returns an object with all the properties for this ignite element. If null or empty
 | |
|      * then no properties will be created. To change a property and read it's current value see below.
 | |
|      * 
 | |
|      * @returns {Object} An object with properties that will be assigned to this ignite element.
 | |
|      * 
 | |
|      * @example
 | |
|      * get properties() {
 | |
|      *  return {
 | |
|      *      show: false,
 | |
|      *      title: "This is a title",
 | |
|      *      items: [1, 2, 3]
 | |
|      *  };
 | |
|      * }
 | |
|      * 
 | |
|      * @example
 | |
|      * //To change a property access it via this.PROP_NAME
 | |
|      * this.show = false;
 | |
|      * 
 | |
|      * //To get a properties value access if via this.PROP_NAME
 | |
|      * console.log(this.title);
 | |
|      */
 | |
|     get properties() {
 | |
|         return {};
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Returns any CSS styling code for this ignite element. If this returns a non null
 | |
|      * value the CSS will auto be injected into the current HTML page once and reused for the life
 | |
|      * of the ignite element.
 | |
|      * 
 | |
|      * @returns {String} A string containing CSS code to be used with this ignite element.
 | |
|      *  
 | |
|      * @example
 | |
|      * get styles() {
 | |
|      *  return `
 | |
|      *      h1 {
 | |
|      *          color: black;
 | |
|      *      }
 | |
|      *  `;
 | |
|      * }
 | |
|      */
 | |
|     get styles() {
 | |
|         return null;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Creates the variables for this ignite element and initializes them.
 | |
|      * @ignore
 | |
|      */
 | |
|     createVariables() {
 | |
|         var vars = this.variables;
 | |
|         if (vars != null) {
 | |
|             Object.keys(vars).forEach(name => this[name] = vars[name]);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Resets the variables for this element back to their original default
 | |
|      * values.
 | |
|      */
 | |
|     resetVariables() {
 | |
|         var vars = this.variables;
 | |
|         if (vars != null) {
 | |
|             Object.keys(vars).forEach(name => this[name] = vars[name]);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Gets all the property values from this element and returns it.
 | |
|      * @returns {Object} An object with all of the property values for this ignite element.
 | |
|      */
 | |
|     getProperties() {
 | |
|         var ret = {};
 | |
|         var props = this.properties;
 | |
|         IgniteRendering.push();
 | |
|         Object.keys(props).forEach(name => ret[name] = this[name]);
 | |
|         IgniteRendering.pop();
 | |
|         return ret;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Sets all the property values on this element.
 | |
|      */
 | |
|     setProperties(props) {
 | |
|         if (props == null || props == undefined) {
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         if (props instanceof IgniteObject) {
 | |
|             props.update();
 | |
| 
 | |
|             Object.getOwnPropertyNames(props).forEach(name => this[name] = props[name]);
 | |
|         } else {
 | |
|             Object.keys(props).forEach(name => this[name] = props[name]);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Creates the getters/setters for properties in this
 | |
|      * ignite element and initializes everything.
 | |
|      * @ignore
 | |
|      */
 | |
|     createProperties() {
 | |
|         var props = this.properties;
 | |
|         if (props != null) {
 | |
|             Object.keys(props).forEach(name => {
 | |
|                 var prop = (props[name] instanceof IgniteProperty ? props[name] : new IgniteProperty(props[name]));
 | |
|                 Object.defineProperty(this, name, {
 | |
|                     get: () => { return (IgniteRendering.rendering ? prop : prop.value); },
 | |
|                     set: (value) => { prop.value = value; }
 | |
|                 });
 | |
|             });
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Resets the properties for this element back to their original default
 | |
|      * values.
 | |
|      */
 | |
|     resetProperties() {
 | |
|         var props = this.properties;
 | |
|         if (props != null) {
 | |
|             Object.keys(props).forEach(name => this[name] = props[name]);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Setups this ignite element and constructs it's template when
 | |
|      * this function is called by the DOM upon this element being created somewhere.
 | |
|      * @ignore
 | |
|      */
 | |
|     connectedCallback() {
 | |
|         //Only run this if we haven't been connected before.
 | |
|         //If this element is moved the instance will be the same but it will call this again
 | |
|         //and we want to trap this and not run the code below twice.
 | |
|         if (this.elementConnected) {
 | |
|             return;
 | |
|         } else {
 | |
|             this.elementConnected = true;
 | |
|         }
 | |
| 
 | |
|         //See if a styling sheet has been created for this element if it's needed.
 | |
|         if (this.styles !== null && this.styles !== "") {
 | |
|             if (document.getElementsByClassName(`ignite-html-${this.tagName.toLowerCase()}-css`).length == 0) {
 | |
|                 var styleEl = document.createElement("style");
 | |
|                 styleEl.classList.add(`ignite-html-${this.tagName.toLowerCase()}-css`);
 | |
|                 styleEl.innerHTML = this.styles;
 | |
|                 document.body.prepend(styleEl);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         //If we don't already have a template, make sure we create one,
 | |
|         //this can happen if this element was constructed in the DOM instead of within a template.
 | |
|         if (!this.template) {
 | |
|             this.template = new IgniteTemplate();
 | |
|             this.template.element = this;
 | |
|             this.template.tagName = this.tagName;
 | |
|         }
 | |
| 
 | |
|         //Add any childNodes we have to the elements list within this
 | |
|         this.childNodes.forEach((item) => this.elements.push(item));
 | |
| 
 | |
|         //Enter a rendering context so properties don't expose their values until we are done.
 | |
|         IgniteRendering.enter();
 | |
| 
 | |
|         //Make sure the render template is our template, if not, add it as a child.
 | |
|         var renderTemplate = this.render();
 | |
|         if (renderTemplate !== this.template && renderTemplate) {
 | |
|             this.template.child(renderTemplate);
 | |
|         } else if (!renderTemplate) {
 | |
|             throw `RenderTemplate was null for template: ${this.tagName}. Is render() returning null or not returning a template?`;
 | |
|         }
 | |
| 
 | |
|         //Construct our template.
 | |
|         try {
 | |
|             this.template.construct(this.parentElement);
 | |
|         } catch (error) {
 | |
|             console.error(error);
 | |
|         }
 | |
| 
 | |
|         //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);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Cleanups this element and deconstructs everything when this element is removed from
 | |
|      * the DOM.
 | |
|      * @ignore
 | |
|      */
 | |
|     disconnectedCallback() {
 | |
|         //Run a test here, if the element was moved but not removed it will have a disconnect
 | |
|         //get called but then be reconnected, so use a timer to see if this is what happened.
 | |
|         setTimeout(() => {
 | |
|             //If the element is connected then don't do this, more than likely
 | |
|             //the element was moved or something.
 | |
|             if (this.isConnected) {
 | |
|                 return;
 | |
|             }
 | |
| 
 | |
|             //If we still have a reference to our template, deconstruct it.
 | |
|             if (this.template) {
 | |
|                 this.template.deconstruct();
 | |
|             }
 | |
| 
 | |
|             //Detach the after render callback
 | |
|             this.readyCallback.disconnect();
 | |
| 
 | |
|             //Call any on disconnected callbacks
 | |
|             if (this.onDisconnectCallbacks) {
 | |
|                 this.onDisconnectCallbacks.forEach(callback => callback.invoke(this));
 | |
|             }
 | |
| 
 | |
|             //Cleanup this element if we need to.
 | |
|             this.cleanup();
 | |
|         }, 1);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Attaches a function to the on disconnect event for this element and returns a callback.
 | |
|      * @param {Function} onDisconnect Disconnect function to be called when on disconnect is raised.
 | |
|      * @returns IgniteCallback created for this callback.
 | |
|      */
 | |
|     attachOnDisconnect(onDisconnect) {
 | |
|         var callback = new IgniteCallback(onDisconnect, detach => this.detachOnDisconnect(detach));
 | |
|         this.onDisconnectCallbacks.push(callback);
 | |
|         return callback;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Removes an ignite callback from the on disconnect event.
 | |
|      * @param {IgniteCallback} callback The ignite callback to disconnect.
 | |
|      * @ignore
 | |
|      */
 | |
|     detachOnDisconnect(callback) {
 | |
|         this.onDisconnectCallbacks = this.onDisconnectCallbacks.filter(item => item != callback);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Returns the template to be rendered for this element.
 | |
|      * 
 | |
|      * @returns An ignite template to be used to construct this ignite element.
 | |
|      * 
 | |
|      * @see this.template is automatically created for each IgniteElement and must be used in the render() function.
 | |
|      * 
 | |
|      * @example
 | |
|      * render() {
 | |
|      *  return this.template
 | |
|      *       .child(
 | |
|      *           new h1(`<i class="fad fa-fire-alt" style="--fa-primary-color: #FFC107; --fa-secondary-color: #FF5722; --fa-secondary-opacity: 1.0;"></i> Ignite HTML`),
 | |
|      *           new h4(`Adding more fire to the web.`)
 | |
|      *       );
 | |
|      * }
 | |
|      */
 | |
|     render() {
 | |
|         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.
 | |
|      */
 | |
|     init() {
 | |
| 
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Called when this ignite element is ready. This will invoke once all
 | |
|      * ignite elements on the current page have been rendered and setup. You can safely know
 | |
|      * all elements have be initialized and are ready once this is called.
 | |
|      */
 | |
|     ready() {
 | |
| 
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Called when this ignite element should cleanup
 | |
|      * any resources like events, timers, ect. This is called when the element
 | |
|      * is being destroyed.
 | |
|      */
 | |
|     cleanup() {
 | |
| 
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Generates a uuid and returns it.
 | |
|      * @returns {String} A unique string, for example: '1b23ec67-4d90-4992-9c5a-b5c0844deaef'
 | |
|      */
 | |
|     uuid() {
 | |
|         return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, c =>
 | |
|             (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
 | |
|         );
 | |
|     }
 | |
| }
 | |
| 
 | |
| export {
 | |
|     IgniteElement
 | |
| }; |