Reference Source

js/rendering/page.js

// NPM IMPORTS
import _ from 'lodash'
import h from 'virtual-dom/h'

// COMMON IMPORTS
import T                   from '../utils/types'
import rendering_normalize from './rendering_normalize'


let context = 'common/rendering/page'



// DEFAULT STATE
const default_state = {
	title:undefined,
	metas:undefined, 	 // array of head tags

	body_headers:undefined,   // array of tags
	body_contents:undefined,   // array of tags
	body_footers:undefined,   // array of tags
	
	head_styles_tags:[],
	head_styles_urls:[],
	
	head_scripts_tags:[],
	head_scripts_urls:[],

	body_styles_tags:[],
	body_styles_urls:[],

	body_scripts_tags:[],
	body_scripts_urls:[]
}

// DEFAULT SETTINGS
const default_settings = {
	html_lang:undefined,
	html_class:undefined,
	html_prefix:undefined,

	head_charset:'utf-8',
	head_viewport:undefined,
	head_description:undefined,
	head_robots:undefined,

	body_class:undefined,

	assets_urls_templates:{
		script:'{{url}}',
		style:'{{url}}',
		image:'{{url}}',
		html:'{{url}}'
	}
}


const get_url = (arg_template, arg_url, arg_absolute)=>arg_absolute ? arg_url : arg_template.replace('{{url}}', arg_url)



/**
 * Page rendering with given state, produce a rendering result.
 * 
 * @param {object} arg_settings - rendering item settings.
 * @param {object} arg_state - component state.
 * @param {object} arg_rendering_context - rendering context: { trace_fn:..., resolver:..., credentials:..., rendering_factory:... }.
 * @param {RenderingResult} arg_rendering_result - rendering result to update.
 * 
 * @returns {RenderingResult} - updated Rendering result: VNode or Html text, headers.
 */
export default (arg_settings, arg_state={}, arg_rendering_context, arg_rendering_result)=>{
	// NORMALIZE ARGS
	const { settings, state, rendering_context, rendering_result } = rendering_normalize(default_settings, default_state, arg_settings, arg_state, arg_rendering_context, arg_rendering_result, context)
	const rendering_factory = rendering_context ? rendering_context.rendering_factory : undefined


	// GET SETTINGS ATTRIBUTES
	const html_lang_value    = T.isString(settings.html_lang)    ? settings.html_lang : undefined
	const html_class_value   = T.isString(settings.html_class)   ? settings.html_class : undefined
	const html_prefix_value  = T.isString(settings.html_prefix)  ? settings.html_prefix : undefined
	const head_charset_value = T.isString(settings.head_charset) ? settings.head_charset : 'UTF-8'
	const head_viewport_value= T.isString(settings.head_viewport)? settings.head_viewport : 'width=device-width'
	const head_description_value= T.isString(settings.head_description)? settings.head_description : undefined
	const head_robots_value  = T.isString(settings.head_robots)  ? settings.head_robots : undefined
	const body_class_value   = T.isString(settings.body_class)   ? settings.body_class : undefined

	// GET STATE ATTRIBUTES
	const title_value         = T.isString(state.title)       ? state.title : 'Devapt'
	const metas_value         = T.isArray(state.metas)        ? state.metas : []
	const body_headers_value  = T.isArray(state.body_headers) ? state.body_headers : undefined
	const body_contents_value = T.isArray(state.body_contents)? state.body_contents : undefined
	const body_footers_value  = T.isArray(state.body_footers) ? state.body_footers : undefined


	// BUILD CONTENT
	const render = (item) => {
		// console.log(item, context + ':render:item')
		return T.isFunction(rendering_factory) ? rendering_factory(item, rendering_context, settings.children).get_final_vtree(undefined, rendering_result) : item.toString()
	}


	// BUILD HEAD SCRIPTS TAGS
	const head_script_tag = (item)=>{
		if ( T.isObject(item) && T.isString(item.id) && item.id.length > 0 && T.isString(item.content) && item.content.length > 0 )
		{
			const type = T.isString(item.type)  && item.type.length  > 0 ? item.type  : 'text/javascript'

			return h('script', { id:item.id, type:type }, item.content)
		}
	}

	// BUILD BODY SCRIPTS TAGS
	const body_script_tag = (item)=>{
		if ( T.isObject(item) && T.isString(item.id) && item.id.length > 0 && T.isString(item.content) && item.content.length > 0 )
		{
			const type = T.isString(item.type)  && item.type.length  > 0 ? item.type  : 'text/javascript'

			const content = `
				window.devapt_bootstrap_promise.then(
					function()
					{
						${item.content}
					}
				)
			`
			return h('script', { id:item.id, type:type }, content)
		}
	}

	// BUILD STYLES TAGS
	const style = (item)=>{
		if ( T.isObject(item) && T.isString(item.id) && item.id.length > 0 && T.isString(item.content) && item.content.length > 0 )
		{
			const type  = T.isString(item.type)  && item.type.length  > 0 ? item.type  : 'text/css'
			const media = T.isString(item.media) && item.media.length > 0 ? item.media : 'all'

			return h('style', { id:item.id, type:type, media:media }, item.content)
		}
	}


	// BUILD HEAD STYLES/LINKS URLS
	const link = (item)=>{
		if ( T.isObject(item) && T.isString(item.href) && item.href.length > 0 && T.isString(item.id) && item.id.length > 0 )
		{
			const rel   = T.isString(item.rel)   && item.rel.length   > 0 ? item.rel   : 'stylesheet'
			const type  = T.isString(item.type)  && item.type.length  > 0 ? item.type  : 'text/css'
			const media = T.isString(item.media) && item.media.length > 0 ? item.media : 'all'
			const title = T.isString(item.title) && item.title.length > 0 ? item.title : undefined
			const url_template = rel == 'stylesheet' ? settings.assets_urls_templates.style : settings.assets_urls_templates.image

			return h('link', { id:item.id, href:get_url(url_template, item.href, item.absolute), type:type, media:media, rel:rel, title:title })
		}
	}


	// BUILD SCRIPTS URLS
	const script_url = (item)=>{
		if ( T.isObject(item) && T.isString(item.src) && item.src.length > 0 && T.isString(item.id) && item.id.length > 0 )
		{
			const type = T.isString(item.type)  && item.type.length  > 0 ? item.type  : 'text/javascript'

			return h('script', { id:item.id, src:get_url(settings.assets_urls_templates.script, item.src, item.absolute), type:type })
		}
	}

	const body_script_url = (item)=>{
		if ( T.isObject(item) && T.isString(item.src) && item.src.length > 0 && T.isString(item.id) && item.id.length > 0 )
		{
			const type = T.isString(item.type)  && item.type.length  > 0 ? item.type  : 'text/javascript'

			const url = get_url(settings.assets_urls_templates.script, item.src, item.absolute)
			switch(item.id){
				case 'js-socketio':
				case 'js-browser':
				case 'js-initial-state':
				case 'js-initial-content':
					return h('script', { id:item.id, src:url, type:type })
				
				case 'js-devapt-core-browser': {
					const content = `
						var required_element = document.getElementById("js-browser")
						var required_loaded = window.babel ? true : false

						var core_browser_promise_cb = function(resolve, reject) {
							if (window.DEVAPT.TRACE_RUNTIME)
							{
								console.log("page loading:loading of [${item.id}] begins")
							}

							try{
								var load_cb = function() {
									// CREATE CORE BROWSER SCRIPT ELEMENT
									var js_element = document.createElement("script")
									js_element.setAttribute("id", "${item.id}")
									js_element.src = "${url}"
									document.body.appendChild(js_element)
									
									var js_load_cb = function() {
										if (window.DEVAPT.TRACE_RUNTIME)
										{
											console.log("page loading:loading of [${item.id}] ends")
										}
										resolve()
									}

									js_element.addEventListener ("load", js_load_cb, false)
								}

								if (! required_loaded)
								{
									required_element.addEventListener ("load", load_cb, false)
								} else {
									load_cb()
								}

								if (window.DEVAPT.TRACE_RUNTIME)
								{
									console.log("page loading:loading of [${item.id}] waits dependancies")
								}
							}
							catch(e) {
								console.error("page loading:loading [${item.id}]:error=" + e.toString())
							}
						}

						window.devapt_core_browser_promise = new Promise(core_browser_promise_cb)
					`
					return h('script', { id:item.id + '-load', type:type }, content)
				}

				case 'js-devapt-bootstrap': {
					const content = `
						var bootstrap_promise_cb = function(resolve, reject) {
							window.DEVAPT.TRACE_RUNTIME && console.log("page loading:loading of [${item.id}] begins")

							window.devapt_core_browser_promise.then(
								function()
								{
									if (window.DEVAPT.TRACE_RUNTIME)
									{
										console.log("page loading:loading of [${item.id}] dependancies resolved")
									}

									try{
										// CREATE BOOTSTRAP SCRIPT ELEMENT
										var js_element = document.createElement("script")
										js_element.setAttribute("id", "${item.id}")
										js_element.src = "${url}"
										document.body.appendChild(js_element)

										if (window.DEVAPT.TRACE_RUNTIME)
										{
											console.log("page loading:loading of [${item.id}] element", js_element)
										}

										var js_load_cb = function() {
											window.devapt().register_asset_loading("script", "${item.id}", "${url}")
											window.DEVAPT.TRACE_RUNTIME && console.log("page loading:loading of [${item.id}] ends")
											resolve()
										}

										js_element.addEventListener ("load", js_load_cb, false)
										
									}
									catch(e) {
										reject("page loading:loading [${item.id}]:error=" + e.toString())
									}
								}
							)
							if (window.DEVAPT.TRACE_RUNTIME)
							{
								console.log("page loading:loading of [${item.id}] waits dependancies")
							}
						}

						window.devapt_bootstrap_promise = new Promise(bootstrap_promise_cb)
					`
					return h('script', { id:item.id + '-load', type:type }, content)
				}
			}
			
			let options = undefined
			if ( T.isNotEmptyArray(item.required) )
			{
				options = { required:item.required }
			}
			const options_str = JSON.stringify(options)

			const content = `
				window.devapt_bootstrap_promise.then(
					function()
					{
						window.devapt().script_promise("${item.id}", "${url}", ${options_str})
					}
				)
			`
			return h('script', { id:item.id, type:type }, content)
		}
	}

	const do_body_scripts_urls = (items)=>{
		let socketio_item = undefined
		let browser_item = undefined
		let core_browser_item = undefined
		let bootstrap_item = undefined
		const ordered_items = []
		_.forEach(items,
			(item)=>{
				if (item && item.id && item.id == 'js-socketio')
				{
					socketio_item = item
					return
				}
				if (item && item.id && item.id == 'js-browser')
				{
					browser_item = item
					return
				}
				if (item && item.id && item.id == 'js-devapt-core-browser')
				{
					core_browser_item = item
					return
				}
				if (item && item.id && item.id == 'js-devapt-bootstrap')
				{
					bootstrap_item = item
					return
				}
				ordered_items.push(item)
			}
		)
		return [socketio_item, browser_item, core_browser_item, bootstrap_item].concat(ordered_items).map(body_script_url)
	}

	// BUILD BODY SCRIPTS ELEMENTS
	const do_body_scripts_tags = (items)=>{
		return items.map(body_script_tag)
	}

	// BUILD TITLE TAG
	const title = h('title', undefined, title_value)


	// BUILD METAS TAG
	const meta_charset     = T.isString(head_charset_value)     ? h('meta', { charSet:head_charset_value }, undefined) : undefined
	const meta_viewport    = T.isString(head_viewport_value)    ? h('meta', { name:'viewport', content:head_viewport_value }) : undefined
	const meta_description = T.isString(head_description_value) ? h('meta', { name:'description', content:head_description_value }) : undefined
	const meta_robots      = T.isString(head_robots_value)      ? h('meta', { name:'robots', content:head_robots_value }) : undefined
	const meta = (item)=>{
		if ( T.isObject(item) && T.isString(item.name) && item.name.length > 0 && T.isString(item.content) )
		{
			return h('meta', { name:item.name, content:item.content})
		}
	}
	const metas = [meta_charset, meta_viewport, meta_description, meta_robots].concat( metas_value.map(meta) )


	// BUILD BODY TAG
	const headers_children = T.isArray(body_headers_value) ? body_headers_value.map(render) : undefined
	const headers = h('header', undefined, headers_children)
	
	const contents_children = T.isArray(body_contents_value) ? body_contents_value.map(render) : undefined
	const contents = h('div', { id:'content'}, contents_children)
	
	const footers_children = T.isArray(body_footers_value) ? body_footers_value.map(render) : undefined
	const footers = h('footer', undefined, footers_children)

	const body_styles_tags  = ( T.isArray(state.body_styles_tags)  ? _.concat(rendering_result.body_styles_tags,  state.body_styles_tags)  : rendering_result.body_styles_tags ).map(style)
	const body_styles_urls  = ( T.isArray(state.body_styles_urls)  ? _.concat(rendering_result.body_styles_urls,  state.body_styles_urls)  : rendering_result.body_styles_urls ).map(link)
	const body_scripts_tags = do_body_scripts_tags( T.isArray(state.body_scripts_tags) ? _.concat(rendering_result.body_scripts_tags, state.body_scripts_tags) : rendering_result.body_scripts_tags )
	const body_scripts_urls = do_body_scripts_urls( T.isArray(state.body_scripts_urls) ? _.concat(rendering_result.body_scripts_urls, state.body_scripts_urls) : rendering_result.body_scripts_urls )

	const body_children = [headers, contents, footers, body_styles_urls, body_styles_tags, body_scripts_urls, body_scripts_tags]
	const body_props = { class:body_class_value }
	const body = h('body', body_props, body_children)
	

	// BUILD ASSETS TAGS
	const head_styles_urls  = ( T.isArray(state.head_styles_urls)  ? _.concat(rendering_result.head_styles_urls,  state.head_styles_urls) : rendering_result.head_styles_urls  ).map(link)
	const head_links_urls   = ( T.isArray(state.head_links_urls)   ? _.concat(rendering_result.head_links_urls,   state.head_links_urls)  : rendering_result.head_links_urls   ).map(link)
	const head_scripts_urls = ( T.isArray(state.head_scripts_urls) ? _.concat(rendering_result.head_scripts_urls, state.head_scripts_urls): rendering_result.head_scripts_urls ).map(script_url)
	const head_scripts_tags = ( T.isArray(state.head_scripts_tags) ? _.concat(rendering_result.head_scripts_tags, state.head_scripts_tags): rendering_result.head_scripts_tags ).map(head_script_tag)
	const head_styles_tags  = ( T.isArray(state.head_styles_tags)  ? _.concat(rendering_result.head_styles_tags,  state.head_styles_tags) : rendering_result.head_styles_tag   ).map(style)


	// BUILD HEAD TAG
	const head_children = [title].concat(metas, head_styles_urls, head_links_urls, head_scripts_urls, head_styles_tags, head_scripts_tags)
	const head_props = undefined
	const head = h('head', head_props, head_children)


	// BUILD HTML TAG
	const page_children = [head, body]
	const page_props = { lang:html_lang_value, class:html_class_value, prefix:html_prefix_value }
	const page = h('html', page_props, page_children)
	
	rendering_result.add_vtree('page', page)

	return rendering_result
}