node-json2html
Version:
json2html - HTML templating in pure javascript
1,442 lines (1,078 loc) • 50.5 kB
JavaScript
// json2html.js 3.3.3
// https://www.json2html.com
// (c) 2006-2025 Crystalline Technologies
// json2html may be freely distributed under the MIT license.
(function() {
"use strict";
// Baseline setup
// --------------
// Establish the root object, `window` (`self`) in the browser, `global`
// on the server, or `this` in some virtual machines. We use `self`
// instead of `window` for `WebWorker` support.
let root = typeof self == 'object' && self.self === self && self ||
typeof global == 'object' && global.global === global && global ||
this ||
{};
//Components {name:template}
let COMPONENTS = {};
//Triggers {name:[{obj,template,ele}]} for updates
let TRIGGERS = {};
//Constants
const CONST = {
"tokenization":{
//Regex for tokenization
"regex":/\${([\w\-\.\,\$\@\s]+)}/
}
};
/* ---------------------------------------- Interactive HTML Object (iHTML) ------------------------------------------------ */
function iHTML(html){
//Object type
this.type = "iHTML";
//html
this.html = html || "";
//associated events
this.events = {};
//associated update triggers
this.triggers = {};
}
//Append an ihtml object
// obj = ihtml OR html string
iHTML.prototype.append = function(obj){
if(obj)
if(obj.type === "iHTML") {
//Append the html
this.html += obj.html;
//Append the events
Object.assign(this.events, obj.events);
//Append the update triggers
Object.assign(this.triggers, obj.triggers);
}
//Added for chaining
return(this);
};
//Append HTML to this object
iHTML.prototype.appendHTML = function(html){
this.html += html;
};
//Spit out the object as json
iHTML.prototype.toJSON = function(){
return({
"html":this.html,
"events":this.events,
"triggers":this.triggers
});
};
//Seal the object
// don't allow any more properties or methods
Object.seal(iHTML);
/* ---------------------------------------- Tokenizer ------------------------------------------------ */
function Tokenizer( tokenizers, doBuild ){
if( !(this instanceof Tokenizer ) )
return new Tokenizer( tokenizers, onEnd, onFound );
this.tokenizers = tokenizers.splice ? tokenizers : [tokenizers];
if( doBuild )
this.doBuild = doBuild;
}
Tokenizer.prototype.parse = function( src ){
this.src = src;
this.ended = false;
this.tokens = [ ];
do this.next(); while( !this.ended );
return this.tokens;
};
Tokenizer.prototype.build = function( src, real ){
if( src )
this.tokens.push(
!this.doBuild ? src :
this.doBuild(src,real,this.tkn)
);
};
Tokenizer.prototype.next = function(){
let self = this,
plain;
self.findMin();
plain = self.src.slice(0, self.min);
self.build( plain, false );
self.src = self.src.slice(self.min).replace(self.tkn,function( all ){
self.build(all, true);
return '';
});
if( !self.src )
self.ended = true;
};
Tokenizer.prototype.findMin = function(){
let self = this, i=0, tkn, idx;
self.min = -1;
self.tkn = '';
while(( tkn = self.tokenizers[i++]) !== undefined ){
idx = self.src[tkn.test?'search':'indexOf'](tkn);
if( idx != -1 && (self.min == -1 || idx < self.min )){
self.tkn = tkn;
self.min = idx;
}
}
if( self.min == -1 )
self.min = self.src.length;
};
/* ---------------------------------------- Public Methods ------------------------------------------------ */
//Create the new json2html with prototype polution protection
if(!root.json2html) root.json2html = Object.create(null);
//Current Version
root.json2html.version = "3.3.3";
//Render a json2html template to html string
// obj (requried) : json object to render, or json string
// template (required): json2html template (array / json object / json string)
// options (optional) : {
// props : {name:value,...}
// components : {name:template,...}
// output : ihtml / html (default)
// }
root.json2html.render = function(obj,template,options) {
//Allow for a json string of json object
let parsed = obj;
//Check for a string (JSON string or literal)
if(typeof(obj) === "string") {
try {
parsed = JSON.parse(obj);
} catch(e) {
//Assume that this is a literal string
parsed = obj;
}
}
//Set the object to the parsed value
// allows for JSON object or a string value of a JSON object or literal
obj = parsed;
//Set the options
if(!options) options = {};
//Set the default to html output
if(!options.output) options.output = "html";
//Check to make sure we have a template and object
if(_typeof(template) !== "object" || _typeof(obj) !== "object") {
//Check what type of output we're looking for
switch(options.output) {
case "ihtml":
return(new iHTML(""));
break;
default:
return("");
break;
}
}
//Get the properties
if(!options.props) options.props = {};
//Check what type of output we're looking for
switch(options.output) {
case "ihtml":
return(_render({
"obj":obj,
"props":options.props,
"template":template,
"options":options}));
break;
default:
return(_render({
"obj":obj,
"props":options.props,
"template":template,
"options":options}).html);
break;
}
};
//json2html component methods
// use Object.create to prevent prototype polution
root.json2html.component = Object.create(null);
//Add a component (name = string, template = json2html template)
//OR function(components) where component is obj with name:template property eg {"name":template,...}
root.json2html.component.add = function(name,template){
//Determine what we're adding
switch(_typeof(name,true)) {
//Multiple components
case "object":
//Components
COMPONENTS = Object.assign(COMPONENTS,name);
break;
//One component
case "string":
COMPONENTS[name] = template;
break;
//Not supported
default:
break;
}
};
//Get a component
root.json2html.component.get = function(name) {
return(COMPONENTS[name]);
};
//Trigger a component to be updated
// id (required) : id of the component that needs to be updated
// obj (optional) : object we want to use, will overwrite the original object used for this rendering
root.json2html.refresh = function(id,obj) {
//Make sure we have a id
if(!id) return;
//Get the triggers (always an array)
let arry = TRIGGERS[id];
if(!arry) return;
//Create a list of all triggers that we need to render
let all = [];
//Itterate over all elements to trigger an update for
for(let i=0; i < arry.length; i++) {
//Get the trigger
let trigger = arry[i];
//Get the object
// default to the original trigger object
let _obj = trigger.obj;
if(obj) _obj = obj;
//Add the trigger object
all.push({
"index":i,
"obj":_obj,
"trigger":trigger
});
}
//Perform all the updates
// this needs to be done AFTER triggers are read otherwise we'll have an infinite loop
// render() is called which adds triggers
for(let a=0; a < all.length; a++) {
//Get the trigger object
let o = all[a];
//Render the update if we can find the element in the dom
if( document.contains(o.trigger.ele) ) o.trigger.ele.json2html(o.obj,o.trigger.template,{"method":"replace","props":o.trigger.props});
else {
//Otherwise remove the trigger as it's stale
arry.splice(o.trigger.index,1);
}
}
//Finally save the trigger
// as we might have removed some
TRIGGERS[id] = arry;
};
//DEPRECATED
root.json2html.trigger = root.json2html.refresh;
//Encode the html string to text
root.json2html.toText = function(html) {
//Check for undefined or null
if(html === undefined || html === null) return("");
//Otherwise convert to a string and encode HTML components
return html.toString()
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/\"/g, """)
.replace(/\'/g, "'")
.replace(/\//g, "/");
};
//Hydrate elements with their events & update triggers
root.json2html.hydrate = function(parent,events,triggers) {
let arry = parent;
//Convert the parent to an array of elements
if(!Array.isArray(parent)) arry = [parent];
//For each parent complete the following
for(let i=0; i < arry.length; i++) {
let element = arry[i];
//Attach events and get the elements that need ready to be triggered
let ready = _attachEvents(element,events);
//Trigger all the json2html.ready events
for(let i=0; i < ready.length; i++)
_triggerEvent(ready[i],"j2h-ready");
//Set the upgrade triggers
if(triggers) _setTriggers(element,triggers);
}
return(this);
};
/* ---------------------------------------- JS DOM Methods --------------------------------------------------- */
//ONLY for the browser
// and we have Element defined
if(typeof(window) === "object" && typeof(Element) === "function") {
//Render a json2html template & append to dom element
// obj : json object to render, or json string
// template: json2html template (array / object / json string)
// options : {}
// components : {name:template,...}
// method : prepend, replace, append (default)
Element.prototype.json2html = function(obj,template,options) {
//Create the optional options if required
if(!options) options = {};
//Default to ihtml output
options.output = "ihtml";
//Render using the master render function
let ihtml = json2html.render(obj,template,options);
//Convert the html into a dom object using innerHTML
// return the childNodes (Node List)
let dom = document.createElement(this.tagName);
dom.innerHTML = ihtml.html;
//Set the element that we want to hydrate with
let ele = this;
//Determine how we should add the new content
switch(options.method) {
//Replace
case "replace":
//Convert to an array
// we'll use this for hydration
ele = Array.from(dom.childNodes);
//Replace
this.replaceWith(...dom.childNodes);
break;
//Prepend
case "prepend":
this.prepend(...dom.childNodes);
break;
//Default to append
default:
this.append(...dom.childNodes);
break;
}
//Rehydrate the object
// this will add the events, trigger ready events and setup trigger updates
json2html.hydrate(ele, ihtml.events, ihtml.triggers);
//Return this for chaining
return(this);
};
}
/* ---------------------------------------- jQuery Methods (if jquery is present) --------------------------------------------------- */
//ONLY for the browser
// and we have jQuery defined
if(typeof(window) === "object")
if(window.jQuery) {
(function($){
//jQuery render template via chaining
// obj : json object to render or json string
// template: json2html template (array / object / json string)
// options : {}
// components : {name:template,...}
// method : prepend, replace, append (default)
$.fn.json2html = function(obj, template, options) {
//Set the options
if(!options) options = {};
//Make sure we set the output to ihtml
options.output = "ihtml";
//Render each object
return($(this).each(function(){
//Render the template
// use the render function with iHTML output
// then we'll hydrate with events after it's added to the dom
let ihtml = json2html.render(obj,template,options);
//Set the element that we want to hydrate with
let $ele = $(this);
//Determine how we should add the new content
switch(options.method) {
//Replace
case "replace":
//Convert the html into a dom object using innerHTML
// return the childNodes (Node List)
let $dom = $("<" + $ele[0].tagName + ">");
$dom.html(ihtml.html);
//Set the element to the dom object children
$ele = $dom.children();
//Repace with these dom children
$.fn.replaceWith.call($(this),$ele);
break;
//Prepend
case "prepend":
$.fn.prepend.call($(this),ihtml.html);
break;
//Default to append
default:
$.fn.append.call($(this),ihtml.html);
break;
}
//Hydrate with events
$ele.j2hHydrate(ihtml.events,ihtml.triggers);
}));
};
//Hydrate the json2html elements with these events
$.fn.j2hHydrate = function(events,triggers) {
//Attach the events for each element
return($(this).each(function(){
//Hydrate this element with these events
json2html.hydrate(this,events,triggers);
}));
};
})(window.jQuery);
}
/* ---------------------------------------- Prviate Methods ------------------------------------------------ */
//Trigger the event type for this element
function _triggerEvent(element,type,props) {
let event; // The custom event that will be created
//TODO switch to use new CustomEvent instead
// removes support for IE
//Check to see if we have the createEvent function
if(document.createEvent){
event = document.createEvent("HTMLEvents");
event.initEvent(type, true, true);
event.eventName = type;
//Add custom properties to event
if(props)
for(let prop in props)
event[prop] = props[prop];
element.dispatchEvent(event);
} else {
event = document.createEventObject();
event.eventName = type;
event.eventType = type;
//Add custom properties to event
if(props)
for(let prop in props)
event[prop] = props[prop];
element.fireEvent("on" + event.eventType, event);
}
}
//Attach the events to the parent & children of this element
// we need to check the parent as well to ensure that events get added after a trigger event
function _attachEvents(parent,events) {
//Record json2html specific ready events
let ready = [];
//Get the elements that need to be triggered
let elements = Array.from( parent.querySelectorAll("[-j2h-e]") );
//Also check to see if the parent element has any triggers
if(parent.getAttribute("-j2h-e")) elements.push(parent);
//Itterate over the elements with events
for(let e=0; e < elements.length; e++) {
let element = elements[e];
//Get the events we should attach to this element
let attach = element.getAttribute("-j2h-e");
//remove the event attribute
element.removeAttribute("-j2h-e");
//Make sure we have some events to attach
if(attach) {
//split by " " (can contain multiple events per element)
let _events = attach.split(" ");
//Add each event
for(let i = 0; i < _events.length; i++) {
//Process each event and keep the context for the event listener
((event)=>{
//Don't have this event then just skip
if(!event) return;
//Add the ready event
// json2html specific event
if(event.type === "ready") {
//Sepcify that we'll need to trigger these later
ready.push(element);
//rename the event to j2h-ready
event.type = "j2h-ready";
}
//Attach the events to the element
element.addEventListener(event.type,function(e){
//Disable j2h-ready events from being propagated
if(event.type === "j2h-ready") e.stopPropagation();
//attach the javascript event
event.data.event = e;
//call the appropriate method
if(_typeof(event.action) === "function") event.action.call(this,event.data);
});
})(events[_events[i]]);
}
}
}
//Return the ready events
return(ready);
}
//Set the update triggers
function _setTriggers(parent,triggers) {
//Get the elements that need to be triggered
let elements = Array.from( parent.querySelectorAll("[-j2h-t]") );
//Also check to see if the parent element has any triggers
if(parent.getAttribute("-j2h-t")) elements.push(parent);
//Itterate over the elements with triggers
for(let e=0; e < elements.length; e++) {
let element = elements[e];
//Get the triggers that we need to listen to
let id = element.getAttribute("-j2h-t");
//Make sure we have some triggers
if(!id) return;
//split by " " (can contain multiple triggers per element)
let _triggers = id.split(" ");
//Add each trigger
for(let i = 0; i < _triggers.length; i++) {
let trigger = triggers[_triggers[i]];
//Don't have a trigger then just skip
if(!trigger) continue;
//Add the element to the trigger
// we need this later to make sure we update the right element
trigger.ele = element;
//Create a new array of triggers for this trigger name
if(!TRIGGERS[trigger.name]) TRIGGERS[trigger.name] = [];
//Add the trigger
TRIGGERS[trigger.name].push(trigger);
}
//remove the event attribute
element.removeAttribute("-j2h-t");
}
}
//Render the object using the template to ihtml (html + events)
// rendering {
// obj : state object used for rendering
// parent : parent object to the state we're rendering (if we have one)
// index : index of the array that we're rendering (if the parent is an array)
// props : properties used for rendering
// template : json2html template (array / object / json string)
// options : {}
// components : {name:template,...}
// method : prepend, replace, append (default)
// output : html / ihtml (although we always output iHTML needed to determine if we bother with events)
//function _render(obj, template, options, index, pobj) {
function _render(rendering) {
//obj, props, template, options, index, parent
//Create a new ihtml object
let ihtml = new iHTML();
//Check to see what type of object we're rending
switch(_typeof(rendering.obj,true)) {
case "array":
//Itterrate through the array and render each object
let len=rendering.obj.length;
////Render the object using this template depending on the type of object
for(let j=0;j<len;++j)
ihtml.append( _renderObj({...rendering,"obj":rendering.obj[j],"index":j}) );
break;
//Don't render for undefined or null objects
case "undefined":
case "null":
break;
//Make sure to allow for literals as well
default:
//Render the object using this template depending on the type of object
ihtml.append( _renderObj(rendering) );
break;
}
return(ihtml);
}
//Render an object using this template to ithml
// takes the rendering {obj,parent,index,props,template,options}
// obj, template, options, index, pobj
function _renderObj(rendering) {
let ihtml = new iHTML();
//Check the type of template we want to apply
switch(_typeof(rendering.template,true)) {
//Array of templates
case "array":
//Itterate through each template
let t_len = rendering.template.length;
//Render the template and append
// using a new rendering object
for(let t=0; t < t_len; ++t)
ihtml.append( _renderObj({...rendering,"template":rendering.template[t],"parent":undefined}) );
break;
//single template & single object
case "object":
let obj = rendering.template["{}"],
func;
//Check what type of object we're looking to use for the data state
switch(_typeof(obj)) {
case "function":
//Set to use the function
func = obj;
case "object":
//If we have a rendering parent
// then process the function OR object
// this will allow for arrays to be processed correctly (as they have parents)
if(!rendering.parent) {
//Run the function if we have one
if(func) obj = func.call(rendering.obj,rendering.obj,rendering.index,rendering.props);
//Render the object
ihtml.append( _render({...rendering,"obj":obj,"parent":rendering.obj}) );
break;
}
default:
//Render the component
// or html
if(rendering.template["[]"]) ihtml.append( _component(rendering) );
else ihtml.append( _html(rendering) );
break;
}
break;
}
return(ihtml);
}
//Get the html value of the object
// using the key
function _getValue(key, rendering,allowObjects) {
let out = "";
//Get the template property
let prop = rendering.template[key];
//Check the type of this template property
switch(_typeof(prop)) {
//Get the value from the function
case "function":
//Check what typeof value is for the object we're rendering
switch(_typeof(rendering.obj)) {
//If this is a json object or array then get the component that we want
case "object":
//Otherwise get the value
return( prop.call(rendering.obj,rendering.obj,rendering.index,rendering.props) );
break;
//NOT SUPPORTED
case "function":
case "undefined":
case "null":
return("");
break;
//BOOLEAN, NUMBER, BIGINT, STRING, SYMBOL
default:
//Create a new object with the properties (value & index)
let _obj = {"value":rendering.obj,"index":rendering.index,"props":rendering.props};
return(prop.call(_obj,_obj,rendering.index,rendering.props));
break;
}
break;
//Check for short hand ${..} (state) and ${%...} (props)
case "string":
//Parse the string and replace ${..} with object values
out = _parse(prop,(all,path)=>_getByType(path,rendering));
break;
//Spit out blank
case "null":
case "undefined":
out = "";
break;
//Check for objects and arrays
case "object":
//Use the object (if we allow it)
if(allowObjects) out = prop;
else out = "";
break;
//Arrays, and other literals
default:
//Get the string representation for this property
out = prop.toString();
break;
}
return(out);
}
/* ---------------------------------------- Safe Object Methods -------------------------------------------- */
//Get the value by path from the state object or properties
// using the object type
function _getByType(path,rendering) {
//By default use the state object
let obj = rendering.obj,
useIndex = true;
//Check the first part of the path
// to see if we should use the properties object instead
if(path.indexOf("@") === 0) {
//Set the object to the rendering properties
obj = rendering.props;
//Remove the @ from the path
path = path.slice(1);
}
//Check what typeof value is for the object we're rendering
switch(_typeof(obj)) {
//If this is an json object then get the value we're looking for
// properties will always be an object
case "object":
return(_get(path,obj));
break;
//NOT SUPPORTED
case "function":
case "undefined":
case "null":
return("");
break;
//For literal arrays (and single objects) of type
//BOOLEAN, NUMBER, BIGINT, STRING, SYMBOL
default:
//Check the path of the shorthand
switch(path) {
//RESERVED word for literal array value
case "value":
return(obj);
break;
//RESERVED word for literal array value index
case "index":
//Return empty string if we don't have an index
// for objects
if(rendering.index === undefined || rendering.index === null) return("");
return(rendering.index);
break;
}
break;
}
}
//Get the value by the path from this object
function _get(path,obj){
//Split the path into it's seperate components
let _path = path.split(".");
//Set the object we use to query for this name to be the original object
let subObj = obj;
//Parse the object properties
let c_len = _path.length;
for(let i=0;i<c_len;++i) {
//Skip if we don't have this part of the path
if( _path[i].length > 0 ) {
//Get the sub object using the path
subObj = subObj[_path[i]];
//Break if we don't have this sub object
if(subObj === null || subObj === undefined) break;
}
}
//Return an empty string if we don't have a value
if(subObj === null || subObj === undefined) return("");
return(subObj);
}
//Set object value
function _set(obj, path, val) {
path.split && (path=path.split('.'));
var i=0, l=path.length, t=obj, x, k;
while (i < l) {
k = path[i++];
if (k === '__proto__' || k === 'constructor' || k === 'prototype') break;
t = t[k] = (i === l) ? val : (typeof(x=t[k])===typeof(path)) ? x : (path[i]*0 !== 0 || !!~(''+path[i]).indexOf('.')) ? {} : [];
}
}
/* ---------------------------------------- Interpolate (Template Literals) -------------------------------------------- */
//Typeof helper
function _typeof(obj,checkArray) {
const type = typeof obj;
//Check what kind of object this is
if(type === "object") {
//Check for null
if(obj === null) return("null");
//Check for array
if(checkArray)
if(Array.isArray(obj)) return("array");
}
return(type);
}
//Get a new random id
function _id() {
return (_random()+_random());
}
//Random string (4 characters)
function _random() {
return (((1+Math.random())*0x10000)|0).toString(16).substring(1);
}
//Determines if we have a void element
// (No end tag, and must not contain any contents)
function _isVoidElement(element) {
//Determine if we match any of the void elements
// as specified by https://www.w3.org/TR/html5/syntax.html#void-elements
switch(element) {
//Allow these void elements
case "area":
case "base":
case "br":
case "col":
case "command":
case "embed":
case "hr":
case "img":
case "input":
case "keygen":
case "link":
case "meta":
case "param":
case "source":
case "track":
case "wbr":
return(true);
break;
//Otherwise we're not void
default:
return(false);
break;
}
}
//Use the tokenizer to parse the str
function _parse(str, method) {
const tokenizer = new Tokenizer([
CONST.tokenization.regex
],( src, real, re )=>real ? src.replace(re,method) : src);
return(tokenizer.parse(str).join(""));
}
/* ---------------------------------------- Template Types ------------------------------------------------ */
//default html type
// supports <>
// returns iHTML
// pobj, obj, template, options, index
function _html(rendering){
//Create a new ihtml object for the parent and it's children
let parent = new iHTML(),
children = new iHTML();
//Set the default html element key
// and initialize the events arrau
let ele = "<>",
events = [],
triggers = [];
//Itterate through the properties of this template
for(let prop in rendering.template) {
switch(prop) {
//HTML element
case "<>":
//Get the element name (this can be tokenized)
parent.name = _getValue(ele, {...rendering,"obj":rendering.parent || rendering.obj});
//Create a new element
parent.appendHTML("<" + parent.name);
break;
//Object we want to render
case "{}":
break;
//Assign
case ">>":
//Add in onchange event
//if so then setup the event data
let aData = {
"obj":rendering.obj,
"props":rendering.props,
"index":rendering.index,
//Unique for assign
"var":rendering.template[prop]
};
//create a new id for this event
let aId = _id();
//Check for the type of element this is
switch(rendering.template["<>"]) {
//Partial Support
case "input":
//Check the type of input
switch(rendering.template["type"]) {
//These types of inputs aren't supported
case "button":
case "submit":
case "reset":
case "image":
case "radio":
continue;
break;
//All others are supported
default:
break;
}
break;
//Supported
case "select":
case "textarea":
break;
//All others not supported
default:
continue;
break;
}
//Add to the events for this element
// we'll add these later into the DOM
parent.events[aId] = {"type":"change","data":aData,"action":e=>{
let target = e.event.target,
val;
//Check the type of input
switch(target.type) {
//Special types
// we need to check these to see if we un-selected them
case "checkbox":
if(target.checked) val = true;
break;
//Otherwise set the value to the targets value
default:
val = target.value;
break;
}
//Set the value
_set( e.obj, e.var, val);
}};
//Add the event to the list of events for this element
events.push(aId);
break;
//Refresh Id
case "#":
//create a new refresh id for this event
let tid = _id();
//Add to the triggers for this element
// we'll add these later in the DOM
parent.triggers[tid] = {"name":_getValue(prop,rendering),"obj":rendering.obj,"props":rendering.props,"template":rendering.template};
//Add the trigger to the list of triggers for this element
triggers.push(tid);
break;
//Encode text
case "text":
//Determine what kind of object this is
// array => NOT SUPPORTED
// other => text
// Encode the value as text and add it to the children
if(!Array.isArray(rendering.template[prop])) children.appendHTML( json2html.toText( _getValue(prop,rendering) ) );
break;
//Encode as HTML
// accepts array of children, functions, string, number, boolean
case "html":
//Determine if we have more than one template
// array & function => children
// other => html
switch(_typeof(rendering.template[prop],true)) {
case "array":
//Append the rendered children
children.append( _render({...rendering,"template":rendering.template[prop],"parent":undefined}) );
break;
case "function":
//Get the result from the function
// HTML is the inner html of the component (if it had any)
let temp = rendering.template[prop].call(rendering.obj, rendering.obj, rendering.index, rendering.props, rendering.html);
//Determine what type of result we have
switch(_typeof(temp,true)) {
//Only returned by json2html.render ()
case "object":
//Check the type of object
switch(temp.type) {
//Add the object as a template
case "iHTML":
children.append(temp);
break;
//Otherwise don't render
default:
break;
}
break;
//Not supported
case "function":
case "undefined":
case "null":
break;
//Render the array as a string
// append to html
case "array":
children.appendHTML(temp.toString());
break;
//string, number, boolean, etc..
// append to html
default:
children.appendHTML(temp);
break;
}
break;
default:
//Get the HTML associated with this element
children.appendHTML( _getValue(prop,rendering) );
break;
}
break;
default:
//Add the property as a attribute if it's not a key one
let isEvent = false;
//Check if the first two characters are 'on' then this is an event
if( prop.length > 2 )
if( prop.substring(0,2).toLowerCase() === "on" ) {
//Determine if we should add events
if(rendering.options.output === "ihtml") {
//if so then setup the event data
let data = {
"obj":rendering.obj,
"props":rendering.props
};
//Check to see what type of object we're trying to render
switch(_typeof(rendering.obj)) {
//Do nothing for json object
case "function":
case "undefined":
case "null":
case "object":
break;
//BOOLEAN, NUMBER, BIGINT, STRING, SYMBOL
default:
//Create a new object with the properties (value & index)
data.obj = {"value":rendering.obj,"index":rendering.index};
break;
}
//create a new id for this event
let id = _id();
//Add to the events for this element
// we'll add these later into the DOM
parent.events[id] = {"type":prop.substring(2),"data":data,"action":rendering.template[prop]};
//Add the event to the list of events for this element
events.push(id);
}
//this is an event
isEvent = true;
}
//If this wasn't an event AND we actually have a value then add it as a property
if(!isEvent){
//Get the value
let val = _getValue(prop,rendering);
//Make sure we have a value
if(val !== undefined) {
let out;
//Determine the output type of this value (wrap with quotes)
if(typeof val === "string") out = '"' + val.replace(/"/g, '"') + '"';
else out = val;
//create the name value pair
parent.appendHTML(" " + prop + "=" + out);
}
}
break;
}
}
//Insert temporary event property -j2h-e
// with events seperated by a space
// if needed
if(events.length) parent.appendHTML(" -j2h-e='" + events.join(" ") + "'");
//Insert temporary event property -j2h-t
// with events seperated by a space
// if needed
if(triggers.length) parent.appendHTML(" -j2h-t='" + triggers.join(" ") + "'");
//Check to see if the parent is an html element
// or just a container
if(parent.name) {
//Determine if this is a void element
// shouldn't have any contents
if(_isVoidElement(parent.name)) parent.appendHTML("/>");
else {
//Close the opening tag
parent.appendHTML(">");
//add the children
parent.append(children);
//add the closing tag
parent.appendHTML("</" + parent.name + ">");
}
} else {
//Otherwise we don't have a parent html element
// so just add the children to the empty parent
parent.append(children);
}
return(parent);
}
//component type
// supports []
// returns iHTML
// pobj, obj, template, options, index
function _component(rendering) {
//Create a new ihtml object for the parent
let ihtml = new iHTML();
let component = {
"template":undefined,
"name":undefined
};
//Create a new props object for this component
let props = {};
//Set the innner html for this component
let html;
//Itterate over the template properties
for(let prop in rendering.template) {
//Check the property
switch(prop) {
//REQUIRED
case "[]":
//Get the component name
// these can be dynamic
let name = _getValue(prop,{...rendering,"obj":rendering.parent || rendering.obj,"parent":undefined});
//Check for a local component first
if(rendering.options.components) component.template = rendering.options.components[name];
//Otherwise check the global components (if we didn't have a local template)
if(!component.template) component.template = COMPONENTS[name];
//Otherwise ignore
break;
//Embed this template within the component
// if needed
case "html":
//Check what object type of template we allow
switch(_typeof(rendering.template.html)) {
//Make sure we have an object or array
case "object":
//Render the children
// make sure to clear the parent
// children don't have access to the parent
html = _render({...rendering,"template":rendering.template.html,"parent":undefined});
break;
}
break;
//Set the others as properties of this template
default:
//Set the property
// allow an object to be used
props[prop] = _getValue(prop,{...rendering,"obj":rendering.parent || rendering.obj,"parent":undefined},true);
break;
}
}
//If we don't hav