@squirrel-forge/ui-core
Version:
A collection of interface, classes, functions and abstracts made for the browser and babel compatible.
272 lines (247 loc) • 7.51 kB
JavaScript
/**
* Requires
*/
import { Exception, isPojo, mergeObject, str2node } from '@squirrel-forge/ui-util';
/**
* Ui template exception
* @class
* @extends Exception
*/
class UiTemplateException extends Exception {}
/**
* @typedef {Object} UiTemplateData
* @property {*} * - Any required template value
*/
/**
* @typedef {Object} UiDefaultsTemplateData
* @property {null|string[]} classes - List of classes
* @property {null|string|string[]} attributes - List of attributes
*/
/**
* @typedef {Object} UiProcessedDefaultsTemplateData
* @property {string[]} classes - List of classes
* @property {string[]} attributes - List of attributes
*/
/**
* Ui template
* @abstract
* @class
*/
export class UiTemplate {
/**
* Load template from dom
* @public
* @static
* @param {string} id - Element id
* @return {string} - Template string
*/
static dom( id ) {
const template = document.getElementById( id );
if ( !template ) throw new UiTemplateException( 'Template not found: ' + id );
return template.innerHTML.trim();
}
/**
* Template data
* @private
* @property
* @type {null|Object}
*/
#data = null;
/**
* Extend data
* @private
* @property
* @type {boolean}
*/
#extend = true;
/**
* Debug object
* @public
* @property
* @type {null|console|Object}
*/
debug = null;
/**
* Template render error output
* @public
* @property
* @type {null|string}
*/
errorMessage = null;
/**
* Template default data
* @protected
* @property
* @type {null|Object}
*/
_defaults = null;
/**
* Constructor
* @constructor
* @param {null|UiTemplateData|Object} data - Template data
* @param {null|console} debug - Debug object
*/
constructor( data = null, debug = null ) {
this.debug = debug;
if ( data ) this.data = data;
}
/**
* Template render method
* @abstract
* @protected
* @param {UiTemplateData|Object} data - Template data
* @return {string} - Rendered template
*/
_render( data ) {
if ( this.debug ) this.debug.warn( this.constructor.name + '::_render', data );
throw new UiTemplateException( 'Template requires a render method' );
}
/**
* Template validate method
* @abstract
* @protected
* @param {UiTemplateData|Object} data - Template data
* @throws UiTemplateException
* @return {void}
*/
_validate( data ) {
if ( this.debug ) this.debug.warn( this.constructor.name + '::_validate', data );
throw new UiTemplateException( 'Template requires a validate method' );
}
/**
* Extend getter
* @public
* @return {boolean} - Extendable state
*/
get extend() {
return this.#extend;
}
/**
* Extend setter
* @public
* @param {boolean} state - Extendable state
* @return {void}
*/
set extend( state ) {
if ( typeof state !== 'boolean' ) throw new UiTemplateException( 'Extend must be a boolean value' );
this.#extend = state;
}
/**
* Data getter
* @public
* @return {Object|null} - Template data
*/
get data() {
return this.#data;
}
/**
* Data setter
* @public
* @param {UiTemplateData|Object} data - Template data
* @return {void}
*/
set data( data ) {
if ( !isPojo( data ) ) throw new UiTemplateException( 'Cannot set invalid template data, must be a plain object' );
this.#data = data;
}
/**
* Process default data
* @protected
* @param {UiTemplateData|Object} data - Data object
* @param {UiDefaultsTemplateData|Object} defaults - Default data
* @param {null|string} addId - Add id attribute
* @return {UiProcessedDefaultsTemplateData} - Processed defaults
*/
_process_defaults( data, defaults, addId = true ) {
const classes = defaults?.classes || [];
if ( data.classes instanceof Array ) {
classes.push( ...data.classes );
} else if ( typeof data.classes === 'string' ) {
classes.push( data.classes );
}
const attributes = defaults?.attributes || [];
if ( addId && data.id ) attributes.push( `id="${data.id}"` );
if ( data.attributes instanceof Array ) {
attributes.push( ...data.attributes );
} else if ( typeof data.attributes === 'string' ) {
attributes.push( data.attributes );
}
return { classes, attributes };
}
/**
* Render template
* @public
* @param {null|UiTemplateData|Object} data - Template data
* @return {string} - Rendered template
*/
render( data = null ) {
if ( !isPojo( this._defaults ) ) throw new UiTemplateException( 'Invalid template defaults data' );
if ( data ) this.data = data;
if ( !isPojo( this.#data ) ) throw new UiTemplateException( 'Invalid template data, must be a plain object' );
const compiled = {};
mergeObject( compiled, this._defaults, true, true );
mergeObject( compiled, this.#data, this.#extend, true );
this._validate( compiled );
if ( this.debug ) this.debug.log( this.constructor.name + '::render', compiled );
return this._render( compiled );
}
/**
* Render multiple templates
* @public
* @param {Array<UiTemplateData|Object>} data - Template data list
* @param {boolean} asArray - Return result as array
* @return {string|Array<string>} - Rendered templates
*/
loop( data, asArray = false ) {
const result = [];
for ( let i = 0; i < data.length; i++ ) {
result.push( this.render( data ) );
}
return asArray ? result : result.join( '' );
}
/**
* Render as node
* @public
* @param {null|UiTemplateData|Object|Array<UiTemplateData|Object>} data - Template data /list
* @return {NodeList|Array} - Rendered nodes or empty array
*/
node( data = null ) {
const rendered = data instanceof Array ?
this.loop( data ) : this.render( data );
if ( rendered ) return str2node( rendered );
return [];
}
/**
* Append rendered template
* @public
* @param {HTMLElement} to - Element to append to
* @param {null|Object|Array} data - Template data /list
* @return {NodeList|Array} - Rendered nodes or empty array
*/
append( to, data = null ) {
if ( !( to instanceof HTMLElement ) ) throw new UiTemplateException( '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;
}
/**
* To string conversion
* @public
* @return {string} - rendered template
*/
toString() {
let rendered;
try {
rendered = this.render();
} catch ( e ) {
if ( this.debug ) this.debug.error( this.constructor.name + '::toString', e );
rendered = this.errorMessage;
}
if ( typeof rendered !== 'string' ) {
rendered = `<!-- render error: ${this.constructor.name} with: ${JSON.stringify( this.#data )} -->`;
}
return rendered;
}
}