@squirrel-forge/ui-core
Version:
A collection of interface, classes, functions and abstracts made for the browser and babel compatible.
268 lines (243 loc) • 7.75 kB
JavaScript
/**
* Requires
*/
import { Exception, isPojo, mergeObject, str2node, strCreate } from '@squirrel-forge/ui-util';
/**
* Ui template renderer exception
* @class
* @extends Exception
*/
class UiTemplateRendererException extends Exception {}
/**
* @typedef {string|UiTemplateRendererTemplateData|Array<UiTemplateRendererTemplateData>|Function} UiTemplateRendererData
*/
/**
* @typedef {Object} UiTemplateRendererTemplateData
* @property {string} template - Template reference name
* @property {UiTemplateData} data - Template data
* @property {UiTemplateRendererCompileData} as - Precompile template data
*/
/**
* @typedef {Object} UiTemplateRendererCompileData
* @property {UiTemplateRendererData} * - Any template data property that needs to be compiled
*/
/**
* Example nested data
* {
* template : 'name',
* data : {},
* as : {
* id : () => { return 'foo'; },
* content : [
* {
* template : 'name',
* data : {...},
* as : {...},
* },
* 'foo',
* ],
* 'header.controls.custom' => 'foo',
* };
*/
/**
* Ui template renderer
* @class
*/
export class UiTemplateRenderer {
/**
* Check for render data structure
* @public
* @static
* @param {UiTemplateRendererTemplateData} data - Render data
* @return {boolean} - True if render data
*/
static isRenderData( data ) {
return isPojo( data ) && typeof data.template === 'string' && isPojo( data.data );
}
/**
* Debug object
* @public
* @static
* @property
* @type {null|console|Object}
*/
static debug = null;
/**
* Templates reference
* @public
* @static
* @type {Object}
*/
static tmpl = {};
/**
* Keep marker
* @public
* @static
* @property
* @type {string}
*/
static keepMarker = '__keep__';
/**
* Set template
* @public
* @static
* @param {string} name - Template reference
* @param {UiTemplate} tmpl - Template instance
* @return {void}
*/
static add( name, tmpl ) {
if ( typeof tmpl !== 'object' ) throw new UiTemplateRendererException( 'Must be a template object: ' + name );
if ( this.tmpl[ name ] ) throw new UiTemplateRendererException( 'Template already defined: ' + name );
this.tmpl[ name ] = tmpl;
}
/**
* Require template
* @public
* @static
* @param {string} name - Template reference
* @param {UiTemplate} tmpl - Template instance
* @return {void}
*/
static require( name, tmpl ) {
try {
this.add( name, tmpl );
} catch ( e ) {
if ( typeof tmpl !== 'object' ) throw e;
}
}
/**
* Get template class
* @public
* @static
* @param {string} name - Template reference
* @return {UiTemplate} - Template instance
*/
static get( name ) {
if ( this.tmpl && this.tmpl[ name ] ) return this.tmpl[ name ];
throw new UiTemplateRendererException( 'Unknown template: ' + name );
}
/**
* Render data.as block
* @public
* @static
* @param {Object} data - As data block
* @param {string} trace - Trace string
* @return {Object} - Rendered object data
*/
static as( data, trace ) {
const result = {};
const entries = Object.entries( data );
for ( let i = 0; i < entries.length; i++ ) {
const [ item_path, item_data ] = entries[ i ];
const rendered = this.recursive( item_data, trace + '.' + item_path );
strCreate( item_path, rendered, result, true, false, this.debug );
}
return result;
}
/**
* Render data
* @public
* @static
* @param {UiTemplateRendererTemplateData} data - Render object
* @param {string} trace - Trace string
* @return {string} - Rendered template
*/
static data( data, trace ) {
const tmpl = this.get( data.template );
if ( this.debug ) this.debug.log( this.name + '::data template:', data.template, '[' + trace + ']' );
if ( isPojo( data.as ) ) {
const as = this.as( data.as, trace + '.as' );
if ( this.debug ) this.debug.log( this.name + '::data as:', as, '[' + trace + ']' );
mergeObject( data.data, as, true, true );
}
return tmpl.render( data.data );
}
/**
* Render recursive
* @public
* @static
* @param {UiTemplateRendererData} data - Render object or array
* @param {string} trace - Trace string
* @return {string|*[]} - Rendered data
*/
static recursive( data, trace ) {
const to = typeof data;
if ( to === 'string' ) {
return data;
} else if ( to === 'function' ) {
// Render custom function
let result;
try {
result = data( trace );
} catch ( e ) {
throw new UiTemplateRendererException( 'Failed to render custom callback [' + trace + ']', e );
}
if ( typeof result !== 'string' ) {
throw new UiTemplateRendererException( 'A custom callback must always return a string [' + trace + ']' );
}
return result;
} else if ( data instanceof Array ) {
// Render array of unknowns
const result = [];
for ( let i = 0; i < data.length; i++ ) {
result.push( this.recursive( data[ i ], trace + `[${i}]` ) );
}
// Keep array structure for processing inside template
if ( result[ 0 ] === this.keepMarker ) {
result.shift();
return result;
}
return result.join( '' );
} else if ( this.isRenderData( data ) ) {
return this.data( data, trace );
} else if ( isPojo( data ) && data[ this.keepMarker ] === true ) {
return data;
} else if ( typeof data.toString === 'function' ) {
return data.toString();
} else {
if ( this.debug ) this.debug.error( this.name + '::render', data );
throw new UiTemplateRendererException( 'Unknown data type: ' + typeof data + ' [' + trace + ']' );
}
}
/**
* Render
* @public
* @static
* @param {UiTemplateRendererData} data - Render object or array
* @return {string} - Rendered data
*/
static render( data ) {
if ( this.debug ) this.debug.groupCollapsed( this.name + '::render' );
const result = this.recursive( data, 'data' );
if ( this.debug ) this.debug.groupEnd();
return result;
}
/**
* Render as node
* @public
* @static
* @param {null|UiTemplateData|Object|Array<UiTemplateData|Object>} data - Template data /list
* @return {NodeList|Array} - Rendered nodes or empty array
*/
static node( data = null ) {
const rendered = this.render( data );
if ( rendered ) return str2node( rendered );
return [];
}
/**
* Append rendered data
* @public
* @static
* @param {HTMLElement} to - Element to append to
* @param {null|Object|Array} data - Template data /list
* @return {NodeList|Array} - Rendered nodes or empty array
*/
static append( to, data = null ) {
if ( !( to instanceof HTMLElement ) ) throw new UiTemplateRendererException( 'Requires a HTMLElement to append to' );
const nodes = this.node( data );
for ( let i = 0; i < nodes.length; i++ ) {
to.appendChild( nodes[ i ] );
}
return nodes;
}
}