Reference Source

js/base/binding/binding_service_timeline.js

// NPM IMPORTS
import assert from 'assert'
import { format } from 'util'

// COMMON IMPORTS
import T             from 'devapt-core-common/dist/js/utils/types'
import { transform } from 'devapt-core-common/dist/js/utils/transform'
import Stream        from 'devapt-core-common/dist/js/messaging/stream'

// BROWSER IMPORTS
import BindingStream from './binding_stream'


const context = 'browser/base/binding/binding_service_timeline'



/**
 * @file UI binding class for service based timeline.
 * 
 * @author Luc BORIES
 * 
 * @license Apache-2.0
 */
export default class BindingServiceTimeline extends BindingStream
{
	/**
	 * Creates an instance of Binding.
	 * @extends Bindable
	 *
	 * @param {string} arg_id - binding identifier.
	 * @param {RuntimeBase} arg_runtime - client runtime.
	 * @param {Component} arg_component - component instance.
	 * 
	 * @returns {nothing}
	 */
	constructor(arg_id, arg_runtime, arg_component)
	{
		super(arg_id, arg_runtime, arg_component)

		this.is_binding_service_timeline = true
	}

	
	/**
	 * Build binding.
	 * 
	 * @returns {Promise} 
	 */
	build()
	{
		this._component.enter_group('build')

		console.info(context + ':build:loading binding for component ' + this._component.get_name(), this._stream)
		
		assert( T.isString(this._source_timeline),       context + format(':build:component=%s:bad timeline name=%s',            this._component.get_name(), this._source_timeline) )
		assert( T.isString(this._source_svc_name),       context + format(':build:component=%s:bad service name=%s,timeline=%s', this._component.get_name(), this._source_timeline, this._source_svc_name) )
		assert( T.isString(this._source_svc_method),     context + format(':build:component=%s:bad service name=%s,timeline=%s', this._component.get_name(), this._source_timeline, this._source_svc_method) )
		assert( T.isArray(this._targets) && this._targets.length > 0, context + format(':build:component=%s,timeline=%s:bad targets',  this._component.get_name(), this._source_timeline, this._source_svc_method) )
		assert( T.isString(this._target_method),         context + format(':build:component=%s,timeline=%s:bad target method=%s', this._component.get_name(), this._source_timeline, this._target_method) )
		
		let promise = this.bind_timeline()
		promise = promise.then(
			(unsubscribe)=>{
				this._unsubscribe = unsubscribe

				if ( T.isArray(this._state_path) && this._state_path.length > 0 )
				{
					const opds = { method:{ operands:[this._state_path]}}
					this.set_targets_instances_array([this._component])
					this.set_target_method_name('dispatch_update_state_value_action')
					this.set_options(opds)
					return this.bind_timeline().then(
						(unsubscribe)=>{
							this._unsubscribe_state_update = unsubscribe
						}
					)
				}
			}
		)
		
		this._component.leave_group('build:async')
		return promise
	}
	
	
	
	/**
	 * Bind a service timeline stream event on object method.
	 * 
	 * @returns {Promise}
	 */
	bind_timeline()
	{
		// console.info(context + ':bind_timeline:loading binding for component ' + this._component.get_name(), this._source_timeline)
		
		return this._runtime.register_service(this._source_svc_name).then(
			(svc) => {
				// console.log(context + ':bind_timeline:svc', svc)
				assert( (this._source_svc_method in svc) && T.isFunction(svc[this._source_svc_method]), context + ':bind_timeline:bad bound method function')
				
				if (this._source_svc_method == 'post')
				{
					svc.subscribe() // TODO ???????
				}
				
				const method_cfg = T.isObject(this._options) ? this._options.method : undefined
				const operands   = T.isObject(method_cfg) ? method_cfg.operands : undefined
				const format_cfg = T.isObject(this._options) ? this._options.format  : undefined

				assert( this._source_timeline in svc[this._source_svc_method].timelines, context + ':bind_timeline:timeline name [' + this._source_timeline + '] not found')
				assert( T.isObject( svc[this._source_svc_method].timelines[this._source_timeline] ), context + ':bind_timeline:bad timeline object')
				
				const stream = svc[this._source_svc_method].timelines[this._source_timeline].stream
				// stream.subscribe( (datas) => {console.log(context + ':bind_timeline:timeline=%s datas=', arg_timeline_name, datas) } )
				
				const unsubscribes = []
				this._targets.forEach(
					(target, index)=>{
						// const method = this._target_method
						// console.info(context + ':bind_timeline:component=%s timeline=%s target at %s method=%s', this._component.get_name(), this._source_timeline, index, method)
						
						const values_xform = {
							"result_type":"single",
							"fields":[
								{
									"name":'value at ' + index,
									"path":'value'
								}
							]
						}
						
						const datas_xform = (arg_stream_record) => {
							if (arg_stream_record.datas)
							{
								// console.log(context + ':bind_stream:datas', arg_stream_record.datas)
								return arg_stream_record.datas
							}
							return arg_stream_record
						}

						let extracted_stream = stream.map(datas_xform).map( transform(values_xform) )
						if (this._options && T.isBoolean(this._options.dispatch) && this._options.dispatch)
						{
							const at_index_xform = (arg_stream_record) => {
								if ( T.isArray(arg_stream_record) && arg_stream_record.length > index )
								{
									return arg_stream_record[index]
								}
								return undefined
							}

							extracted_stream = extracted_stream.map(at_index_xform)
						}

						// INIT STARTING VALUE
						this._stream = new Stream( extracted_stream.startWith(this._starting_value) )
						
						// DEBUG
						// this._stream.get_source_stream().onValue(
						// 	(extracted_value) => {
						// 		console.log(context + ':bind_timeline:timeline=%s method=%s stream value=', this._source_timeline, method, extracted_value)
						// 	}
						// )

						const unbind = this.bind_stream(this._stream, this._source_xform, target, this._target_method, operands, format_cfg, this._starting_value)
						unsubscribes.push(unbind)

						// if ( T.isArray(this._state_path) && this._state_path.length > 0 )
						// {
						// 	const opds = { method:{ operands:[this._state_path]}}
						// 	this._unsubscribe_state_update = this.bind_stream(this._stream, this._source_xform, [this._component], 'dispatch_update_state_value_action', opds)
						// }
					}
				)

				return ()=>{
					unsubscribes.forEach(
						(unsubscribe)=>{
							unsubscribe()
						}
					)
				}
			}
		)
	}
}