Reference Source

js/base/component/bound_dom.js

// NPM IMPORTS
import assert from 'assert'
import _ from 'lodash'

// COMMON IMPORTS
import T   from 'devapt-core-common/dist/js/utils/types'
import uid from 'devapt-core-common/dist/js/utils/uid.js'

// BROWSER IMPORTS
import BindingsLoader from '../binding/bindings_loader'
import RenderedDom    from './rendered_dom'


const context = 'browser/base/component/bound_dom'



/**
 * @file UI dom bindings class.
 * 
 * @author Luc BORIES
 * @license Apache-2.0
 * 
 * @example:
 * 	API
 * 		->load(arg_state):nothing - Load and apply a component configuration.
 * 		->init_bindings():nothing - Init bindings.
 * 		->unload():nothing - Unload a component configuration.
 * 
 */
export default class BoundDom extends RenderedDom
{
	/**
	 * Creates an instance of Component.
	 * 
	 * @param {RuntimeBase}   arg_runtime     - client runtime.
	 * @param {Immutable.Map} arg_state       - component initial state.
	 * @param {string}        arg_log_context - context of traces of this instance (optional).
	 * 
	 * @returns {nothing}
	 */
	constructor(arg_runtime, arg_state, arg_log_context)
	{
		const log_context = arg_log_context ? arg_log_context : context
		super(arg_runtime, arg_state, log_context)
		
		this.is_bound_dom = true

		this._is_loaded = false
		this._bindings = {}

		// DEBUG
		// console.info(context + ':constructor:creating component ' + this.get_name())
		// this.enable_trace()
	}
	
	
	
	/**
	 * Load and apply a component configuration.
	 * 
	 * @param {Immutable.Map|undefined} arg_state - component state to load (optional).
	 * 
	 * @returns {nothing|Promise} 
	 */
	load(arg_state)
	{
		this.enter_group('load')

		if (this._is_loaded)
		{
			// console.info(context + ':load:already loaded component ' + this.get_name())
			this.leave_group('load:already loaded')
			return
		}

		// const self = this
		// console.info(context + ':load:loading component ' + this.get_name())
		
		if (! this.store_unsubscribe)
		{
			this.store_unsubscribe = this.get_runtime().create_store_observer(this)
		}
		
		const state = arg_state ? arg_state : this.get_state()
		// console.log(state, 'load bindinds')
		
		if (! state)
		{
			this.leave_group('load:no state found')
			return
		}

		this.init_assets()

		// this.update()

		this._is_loaded = true
		this.leave_group('load')
	}
	

	
	/**
	 * Init bindings.
	 * 
	 * @returns {nothing} 
	 */
	init_bindings()
	{
		this.enter_group('init_bindings')

		const state = this.get_state()
		const bindings = state.has('bindings') ? state.get('bindings').toJS() : undefined
		if ( T.isObject(bindings) )
		{
			if ( T.isArray(bindings.services) )
			{
				bindings.services.forEach(
					(bind_cfg) => {
						bind_cfg.type = bind_cfg.timeline ? 'timeline' : (bind_cfg.dom_event ? 'emitter_jquery' : 'service')
						const id = 'binding_' + uid()
						this._bindings[id] = BindingsLoader.load(id, this._runtime, this, bind_cfg)
					}
				)
			}

			if ( T.isArray(bindings.streams) )
			{
				bindings.streams.forEach(
					(bind_cfg) => {
						// console.log(context + ':load:stream binding:', bind_cfg)
						let stream = bind_cfg.source_stream ? bind_cfg.source_stream : undefined
						
						if ( T.isString(stream) )
						{
							let source_component = this
							if ( bind_cfg.source_type == 'views' && T.isNotEmptyString(bind_cfg.source_selector) )
							{
								source_component = window.devapt().ui(bind_cfg.source_selector)
							}
							stream = source_component.get_named_stream(stream)
						}

						if ( T.isObject(stream) && stream.is_stream )
						{
							bind_cfg.type = 'stream'
							bind_cfg.source_stream = stream
							const id = 'binding_' + uid()
							this._bindings[id] = BindingsLoader.load(id, this._runtime, this, bind_cfg)

							// console.log(context + ':load:stream bound:', id, bind_cfg)
						}
					}
				)
			}
			
			if ( T.isArray(bindings.emitter_jquery) )
			{
				bindings.emitter_jquery.forEach(
					(bind_cfg) => {
						bind_cfg.type = 'emitter_jquery'
						const id = 'binding_' + uid()
						const binding_streams = BindingsLoader.load(id, this._runtime, this, bind_cfg)
						if ( T.isArray(binding_streams) )
						{
							_.forEach(binding_streams,
								(binding, index)=>{
									this._bindings[id + '_' + index] = binding
								}
							)
						} else {
							this._bindings[id] = binding_streams
						}
					}
				)
			}
			
			if ( T.isArray(bindings.emitter_dom) )
			{
				bindings.emitter_dom.forEach(
					(bind_cfg) => {
						bind_cfg.type = 'emitter_dom'
						const id = 'binding_' + uid()
						const binding_streams = BindingsLoader.load(id, this._runtime, this, bind_cfg)
						if ( T.isArray(binding_streams) )
						{
							_.forEach(binding_streams,
								(binding, index)=>{
									this._bindings[id + '_' + index] = binding
								}
							)
						} else {
							this._bindings[id] = binding_streams
						}
					}
				)
			}
		}

		this.leave_group('init_bindings')
	}
	
	
	
	/**
	 * Unload a component configuration.
	 * 
	 * @returns {nothing} 
	 */
	unload()
	{
		this.enter_group('unload')

		assert( T.isFunction(this.store_unsubscribe), context + ':unload:bad store_unsubscribe function')
		
		// UNBIND ALL BINDINGS
		_.forEach(this._bindings,
			(binding/*, id*/)=>{
				binding._unsubscribe()
				if (binding._unsubscribe_state_update)
				{
					binding._unsubscribe_state_update()
				}
			}
		)

		// DETACH STORE CHANGE LISTENER
		this.store_unsubscribe()

		this.leave_group('unload')
	}
}