userinterface.js

(function() {

	const PROPERTIES_PROCESSED = ["tagName", "children"]
	const APPEND_CHILD = "appendChild"
	const INSERT_BEFORE = "insertBefore"
	const REMOVE_ELEMENT = "removeElement"
	const UPDATE_ELEMENT = "updateElement"
	const REPLACE_ELEMENT = "replaceElement"
	const WRAP_ELEMENT = "wrapElement"
	const CLEAR_LISTENERS = "clearListeners"
	const METHODS_CREATE = [
		APPEND_CHILD,
		INSERT_BEFORE,
		REPLACE_ELEMENT,
		WRAP_ELEMENT
	]

	const _models = []
	let _listeners = []

	/**
	 * @type {string}
	 */
	this.appendChild = APPEND_CHILD
	/**
	 * @type {string}
	 */
	this.insertBefore = INSERT_BEFORE
	/**
	 * @type {string}
	 */
	this.removeElement = REMOVE_ELEMENT
	/**
	 * @type {string}
	 */
	this.replaceElement = REPLACE_ELEMENT
	/**
	 * @type {string}
	 */
	this.updateElement = UPDATE_ELEMENT
	/**
	 * @type {string}
	 */
	this.wrapElement = WRAP_ELEMENT
	/**
	 * @type {string}
	 */
	this.clearListeners = CLEAR_LISTENERS

	/**
	 * Load a model
	 * @param {object} 							model
	 * @param {string} 							model.name                  The name of the model
	 * @param {string} 							model.method                One of the following methods name: appendChild, insertBefore, removeElement, updateElement, replaceElement, wrapElement, clearListeners
	 * @param {Object}							model.properties  					Processed properties along with any properties an ElementĀ¹ can have
	 * @param {function}						[model.callback]  					Callback of processed properties along with any properties an ElementĀ¹ can have
	 * @param {Object[]} 						[model.properties.children]	An array of the "properties" object
	 */
	this.model = function(model) {
		_models.push(model)
	}

	/**
	 * Link a model to a "binding", that is a callback function
	 * @param {string} 	 name 	  The name of the model
	 * @param {function} callback The function binding the model
	*/
	this.bind = function(name, callback) {
		_models.find(model => model.name === name).binding = {name, callback}
	}

	/**
	 * Update the DOM accordingly to a model
	 * @param {string}  name 		    						 The name of the model
	 * @param {Object}  parameters						 The parameters of the model
	 * @param {Object}  [parameters.data] 			 The data that will be echoed on the model
	 * @param {Element} parameters.parentNode	 The target Node
	 * @param {Array}   [parameters.bindingArgs] The arguments that go along with the binding
	 */
	this.runModel = async function(name, parameters = {}) {
		const model = _models.find(model => model.name === name)
		let { method, properties } = model
		if (model.hasOwnProperty("callback") === true) {
			properties = model.callback(parameters.data)
		}
		let element
		if (METHODS_CREATE.includes(method) === true) {
			element = await UserInterface.createElement(properties)
		}
		if (method === APPEND_CHILD) {
			parameters.parentNode.appendChild(element)
		} else if (method === INSERT_BEFORE) {
			parameters.parentNode.parentNode.insertBefore(element, parameters.parentNode)
		} else if (method === REMOVE_ELEMENT) {
			parameters.parentNode.parentNode.removeChild(parameters.parentNode)
		} else if (method === REPLACE_ELEMENT) {
			parameters.parentNode.parentNode.replaceChild(element, parameters.parentNode)
		} else if (method === UPDATE_ELEMENT) {
			Object.assign(parameters.parentNode, properties)
		} else if (method === WRAP_ELEMENT) {
			element.appendChild(parameters.parentNode.cloneNode(true))
			parameters.parentNode.parentNode.replaceChild(element, parameters.parentNode)
		} else if (method === CLEAR_LISTENERS) {
			parameters.parentNode.parentNode.replaceChild(parameters.parentNode.cloneNode(true), parameters.parentNode)
		}
		if (element && model.hasOwnProperty("binding") === true) {
			await	model.binding.callback.apply(null, [element].concat(parameters.bindingArgs))
		}
	}

	/**
	 * Transform a model into an Element
	 * @param   {object}   properties	Processed properties along with any properties a Element can have
	 * @param   {function} [callback]	Callback version of properties
	 * @returns {Element}             An array of ElementsĀ¹
	 */
	this.createElement = async function(properties) {
		const { tagName, children = [] } = properties
		const element = document.createElement(tagName)
		Object.keys(properties).filter(property => PROPERTIES_PROCESSED.includes(property) === false).forEach(function(property) {
			element[property] = properties[property]
		})
		for (const child of children) {
			const childNode = await UserInterface.createElement(child)
			element.appendChild(childNode)
		}
		return element
	}

	/**
	 * Returns the properties of a model
	 * @param  {string} name   The name of the model
	 * @param  {Object} [data] The data that will be echoed on the model
	 * @returns {Object}	       The "properties" object of the model
	 */
	this.getModelProperties = function(name, data) {
		const model = _models.find(model => model.name === name)
		if (model.hasOwnProperty("callback")) {
			return model.callback(data)
		} else if (model.hasOwnProperty("properties")) {
			return model.properties
		}
	}

	/**
	 * Load a listener
	 * @param  {*} 		  	context	 Where the announce will be broadcasted
	 * @param  {string}   title 	 The content of the message
	 * @param  {function} callback
	 */
	this.listen = function(context, title, callback) {
		const listener = {context, title, callback}
		_listeners.push(listener)
		return listener
	}

	/**
	 * Remove a listener
	 * @param  {Object} listener
	 */
	this.removeListener = function(listener) {
		_listeners = _listeners.filter(listener_ => listener_ !== listener)
	}

	/**
	 * Message one or many listeners
	 * @param  {*} 			context Where the announce will be broadcasted
	 * @param  {string} title 	The title of the announce
	 * @param  {*} 			content The content of the announce
	 */
	this.announce = async function(context, title, content) {
		const listeners = _listeners.filter(listener => listener.context === context && listener.title === title)
		for (const listener of listeners) {
			await listener.callback(content)
		}
	}

}).call(UserInterface = {})