Reference Source

js/rendering/rendering_result.js

// NPM IMPORTS
import assert from 'assert'
import _ from 'lodash'
import VNode from 'virtual-dom/vnode/vnode'
import VText from 'virtual-dom/vnode/vtext'
import html_to_vdom from 'html-to-vdom'
import vdom_as_json from 'vdom-as-json'
import create_element from 'virtual-dom/create-element'
const vdom_to_json = vdom_as_json.toJson
const vdom_from_json = vdom_as_json.fromJson

// COMMON IMPORTS
import T            from '../utils/types'
import {is_browser} from '../utils/is_browser'
import uid          from '../utils/uid'


let context = 'common/rendering/rendering_result'

const convertHTML = html_to_vdom(
	{
		VNode: VNode,
		VText: VText
	}
)



/**
 * @file RenderingResult class.
 * 
 * @author Luc BORIES
 * 
 * @license Apache-2.0
 */
export default class RenderingResult
{
	/**
	 * Create RenderingResult instance.
	 * 
	 * API:
	 * 		->constructor():nothing - create an empty rendering result instance.
	 * 		
	 * 		->add_result(arg_result, arg_merge_vnodes=true):nothing - merge RenderingResult instances.
	 * 
	 * 		->add_html(arg_tag_id, arg_html):nothing - take Html text and convert it to a virtual tree.
	 * 		->add_vtree(arg_tag_id, arg_vtree):nothing - add a virtual tree.
	 * 		->remove_vtree(arg_tag_id):nothing - remove a virtual tree.
	 * 
	 * 		->get_vtree_json(arg_tag_id):VTree - get an existing VTree as Json.
	 * 		->get_vtree(arg_id):VTree - get an existing VTree instance.
	 * 		->get_html(arg_id)
	 * 		
	 * 		->get_final_vtree(arg_final_id, arg_parent_result):VNode - get final VTree.
	 * 		->get_final_html(arg_final_id, arg_parent_result):string - get Html code for an existing vtree.
	 * 
	 * 		->set_headers(arg_headers)
	 * 
	 * 		->add_head_scripts_urls(arg_urls)
	 * 		->add_head_scripts_tags(arg_tags)
	 * 
	 * 		->add_body_scripts_urls(arg_urls)
	 * 		->add_body_scripts_tags(arg_tags)
	 * 
	 * 		->add_head_styles_urls(arg_tags)
	 * 		->add_head_styles_tags(arg_tags)
	 * 
     * @returns {nothing}
	 */
	constructor()
	{
		this.is_rendering_result = true

		this.uid = 'RR_' + uid()
		this.vtrees = {}

		this.headers = []

		this.head_scripts_urls = []
		this.head_scripts_tags = []
		this.body_scripts_urls = []
		this.body_scripts_tags = []

		this.head_styles_urls = []
		this.head_styles_tags = []
		this.body_styles_urls = []
		this.body_styles_tags = []

		this.head_links_urls = []
	}



	/**
	 * Merge RenderingResult instances.
	 * 
	 * @param {RenderingResult} arg_result - rendering result to add to this result.
	 * @param {boolean} arg_merge_vnodes - flag to merge or not vnodes (default:true) (optional)
	 * 
	 * @returns {nothing}
	 */
	add_result(arg_result, arg_merge_vnodes=true)
	{
		// console.log(context + ':add_result [' + this.uid + ']:merge=' + (arg_merge_vnodes ? 'y' : 'n'), this.body_scripts_urls.length, arg_result.body_scripts_urls.length)

		if (arg_merge_vnodes)
		{
			this.vtrees = Object.assign(this.vtrees, arg_result.vtrees)
		}
		
		const asset_compare = _.isEqual

		this.head_scripts_urls = _.uniqWith( _.concat(this.head_scripts_urls, arg_result.head_scripts_urls), asset_compare )
		this.head_scripts_tags = _.uniqWith( _.concat(this.head_scripts_tags, arg_result.head_scripts_tags), asset_compare )
		this.body_scripts_urls = _.uniqWith( _.concat(this.body_scripts_urls, arg_result.body_scripts_urls), asset_compare )
		this.body_scripts_tags = _.uniqWith( _.concat(this.body_scripts_tags, arg_result.body_scripts_tags), asset_compare )

		this.head_styles_urls  = _.uniqWith( _.concat(this.head_styles_urls,  arg_result.head_styles_urls),  asset_compare )
		this.head_styles_tags  = _.uniqWith( _.concat(this.head_styles_tags,  arg_result.head_styles_tags),  asset_compare )
		this.body_styles_urls  = _.uniqWith( _.concat(this.body_styles_urls,  arg_result.body_styles_urls),  asset_compare )
		this.body_styles_tags  = _.uniqWith( _.concat(this.body_styles_tags,  arg_result.body_styles_tags),  asset_compare )

		this.head_links_urls   = _.uniqWith( _.concat(this.head_links_urls,   arg_result.head_links_urls),   asset_compare )
	}



	/**
	 * Add Html tag.
	 * 
	 * @param {string} arg_tag_id - tag id string.
	 * @param {string} arg_html - Html string.
	 * 
	 * @returns {nothing}.
	 */
	add_html(arg_tag_id, arg_html)
	{
		assert( T.isString(arg_tag_id) && arg_tag_id.length > 0, context + ':add_html:bad tag id string')
		assert( T.isString(arg_html), context + ':set_html:bad html string')
		this.vtrees[arg_tag_id] = vdom_to_json( convertHTML(arg_html) )
	}



	/**
	 * Add a VTree instance.
	 * 
	 * @param {string} arg_tag_id - tag id string.
	 * @param {VNode} arg_vtree - virtual-dom virtual tree.
	 * 
	 * @returns {nothing}.
	 */
	add_vtree(arg_tag_id, arg_vtree)
	{
		assert( T.isString(arg_tag_id) && arg_tag_id.length > 0, context + ':add_vtree:bad tag id string')
		assert( T.isObject(arg_vtree), context + ':add_vtree:bad vtree object')
		this.vtrees[arg_tag_id] = arg_vtree
	}



	/**
	 * Remove a VTree instance.
	 * 
	 * @param {string} arg_tag_id - tag id string.
	 * 
	 * @returns {nothing}.
	 */
	remove_vtree(arg_tag_id)
	{
		assert( T.isString(arg_tag_id) && arg_tag_id.length > 0, context + ':add_vtree:bad tag id string')
		delete this.vtrees[arg_tag_id]
	}



	/**
	 * Convert all VNode to json objects.
	 * 
	 * @returns {RenderingResult}
	 */
	convert_to_json()
	{
		let i = 0
		const keys = Object.keys(this.vtrees)
		const l = keys.length
		for( ; i < l ; i++)
		{
			const key = keys[i]
			this.vtrees[key] = vdom_to_json( this.vtrees[key] ) 
		}
		return this
	}



	/**
	 * Convert all VNode to json objects.
	 * 
	 * @returns {RenderingResult}
	 */
	convert_from_json()
	{
		let i = 0
		const keys = Object.keys(this.vtrees)
		const l = keys.length
		for( ; i < l ; i++)
		{
			const key = keys[i]
			this.vtrees[key] = vdom_from_json( this.vtrees[key] ) 
		}
		return this
	}



	/**
	 * Get an existing VTree as Json.
	 * 
	 * @param {string} arg_tag_id - tag id string.
	 * @param {VNode} arg_vtree - virtual-dom virtual tree.
	 * 
	 * @returns {nothing}.
	 */
	get_vtree_json(arg_tag_id)
	{
		const vtree = this.get_vtree(arg_tag_id)
		if (vtree)
		{
			return vdom_to_json(vtree)
		}
		return undefined
	}



	/**
	 * Get existing VTree instance.
	 * 
	 * @param {string} arg_id - element id.
	 * 
	 * @returns {VTree}
	 */
	get_vtree(arg_id)
	{
		return this.vtrees[arg_id]
	}



	/**
	 * Get Html code for an existing vtree.
	 * 
	 * @param {string} arg_id - element id.
	 * 
	 * @returns {string}
	 */
	get_html(arg_id)
	{
		const vtree = this.get_vtree(arg_id)
		if (! vtree)
		{
			return undefined
		}

		try
		{
			const e = create_element(vtree)
			if (e)
			{
				return is_browser() ? e.innerHTML : e.toString()
			}
		}
		catch(e)
		{
			console.error(context + ':', e)
		}
		return undefined
	}



	/**
	 * Get final VTree.
	 * 
	 * @param {string|undefined} arg_final_id - final tree id (optional).
	 * @param {RenderingResult} arg_parent_result - parent result to merge assets into (optional).
	 * 
	 * @returns {VNode}
	 */
	get_final_vtree(arg_final_id, arg_parent_result)
	{
		// MERGE THIS ASSETS INTO PARENT RESULT ASSETS
		if ( T.isObject(arg_parent_result) && arg_parent_result.is_rendering_result)
		{
			arg_parent_result.add_result(this, false)
		}

		// NO VNODE
		const size = _.size(this.vtrees)
		if (size == 0)
		{
			return new VText('')
		}

		// ONLY ONE VNODE
		if (size == 1)
		{
			const trees = _.toArray(this.vtrees)
			return trees[0]
		}

		// MANY VNODES
		const settings = T.isString(arg_final_id) ? { id:arg_final_id } : undefined
		return new VNode('DIV', settings, _.toArray(this.vtrees), 'id', undefined)
	}



	/**
	 * Get Html code for an existing vtree.
	 * 
	 * @param {string|undefined} arg_final_id - final tree id (optional).
	 * @param {RenderingResult} arg_parent_result - parent result to merge assets into (optional).
	 * 
	 * @returns {string}
	 */
	get_final_html(arg_final_id, arg_parent_result)
	{
		const vtree = this.get_final_vtree(arg_final_id, arg_parent_result)
		if (! vtree)
		{
			return undefined
		}

		try
		{
			const e = create_element(vtree)
			if (e)
			{
				return is_browser() ? e.innerHTML : e.toString()
			}
		}
		catch(e)
		{
			console.error(context + ':', e)
		}
		return undefined
	}



	/**
	 * Set headers.
	 * 
	 * @param {array} set_headers - headers strings array.
	 * 
	 * @returns {nothing}.
	 */
	set_headers(arg_headers)
	{
		assert( T.isArray(arg_headers), context + ':set_headers:bad headers array')
		this.headers = arg_headers
	}



	/**
	 * Add header scripts urls.
	 * 
	 * @param {array} arg_urls - scripts urls strings array.
	 * 
	 * @returns {nothing}.
	 */
	add_head_scripts_urls(arg_urls=[])
	{
		assert( T.isArray(arg_urls), context + ':add_head_scripts_urls:bad head scripts urls array')
		this.head_scripts_urls = _.concat(this.head_scripts_urls, arg_urls)
	}



	/**
	 * Add header scripts tags.
	 * 
	 * @param {array} arg_tags - scripts tags strings array.
	 * 
	 * @returns {nothing}.
	 */
	add_head_scripts_tags(arg_tags=[])
	{
		assert( T.isArray(arg_tags), context + ':add_head_scripts_tags:bad head scripts tags array')
		this.head_scripts_tags = _.concat(this.head_scripts_tag, arg_tags)
	}



	/**
	 * Add body scripts urls.
	 * 
	 * @param {array} arg_urls - scripts urls strings array.
	 * 
	 * @returns {nothing}.
	 */
	add_body_scripts_urls(arg_urls=[])
	{
		assert( T.isArray(arg_urls), context + ':add_body_scripts_urls:bad body scripts urls array')
		this.body_scripts_urls = _.concat(this.body_scripts_urls, arg_urls)
	}



	/**
	 * Add body scripts tags.
	 * 
	 * @param {array} arg_tags - scripts tags strings array.
	 * 
	 * @returns {nothing}.
	 */
	add_body_scripts_tags(arg_tags=[])
	{
		assert( T.isArray(arg_tags), context + ':add_body_scripts_tags:bad body scripts tags array')
		this.body_scripts_tags = _.concat(this.body_scripts_tags, arg_tags)
	}



	/**
	 * Add header styles urls.
	 * 
	 * @param {array} arg_urls - styles urls strings array.
	 * 
	 * @returns {nothing}.
	 */
	add_head_styles_urls(arg_urls=[])
	{
		assert( T.isArray(arg_urls), context + ':add_head_styles_urls:bad head styles urls array')
		this.head_styles_urls = _.concat(this.head_styles_urls, arg_urls)
	}



	/**
	 * Add header styles tags.
	 * 
	 * @param {array} arg_tags - styles tags strings array.
	 * 
	 * @returns {nothing}.
	 */
	add_head_styles_tags(arg_tags=[])
	{
		assert( T.isArray(arg_tags), context + ':add_head_styles_tags:bad head styles tags array')
		this.head_styles_tags = _.concat(this.head_styles_tags, arg_tags)
	}
}