Reference Source

js/base/container.js

// NPM IMPORTS
import assert from 'assert'
import { fromJS } from 'immutable'
import _ from 'lodash'

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

// BROWSER IMPORTS
import Component from './component'


const context = 'browser/base/container'



/**
 * @file UI component class.
 * @author Luc BORIES
 * @license Apache-2.0
 */
export default class Container extends Component
{
	
	/**
	 * Creates an instance of Container. A Container contains other Components through the 'children' property.
	 * @extends Component
	 * 
	 * Container state attributes:
	 * 	- strategy: Immutable.Map (how to manage UI updates with items changes)
	 * 		- update_mode: 'append' | 'prepend' | 'replace' | 'insert at'
	 * 		- update_index: integer (position for 'insert at' mode)
	 * 		- resize_mode: 'remove_last' | 'remove_first'
	 * 		- resize_max: integer (max items count)
	 * 
	 * 	API
	 * 		->load(arg_state):nothing - load configuration
	 * 
	 * 	API: DEFINE ACTION WRAPPERS (only dispatch an action to update the state)
	 * 		->do_action_set_resize_max(arg_value):nothing - Set max items value for the container.
	 * 		->do_action_clear_items():nothing - Clear container items.
	 * 
	 * 		->do_action_append(arg_items_array, arg_items_count):nothing  - Append items to the container (an item can be a string or an array or an object or ...).
	 * 		->do_action_prepend(arg_items_array, arg_items_count):nothing - Prepend an item (an item can be a string or an array or an object or ...).
	 * 		->do_action_replace(arg_items_array, arg_items_count):nothing - Replace items to the container (an item can be a string or an array or an object or ...).
	 * 		->do_action_insert_at(arg_index, arg_items_array, arg_items_count):nothing - Insert items at container position index (an item can be a string or an array or an object or ...).
	 * 
	 * 		->do_action_remove_at_index(arg_index):nothing - Remove an item at given position.
	 * 		->do_action_remove_first():nothing             - Remove an item at first position.
	 * 		->do_action_remove_last(arg_count):nothing     - Remove an item at last position.
	 * 
	 * 		->reduce_action(arg_previous_state, arg_action):Immutable.Map    - Store actions reducer pure function.
	 * 		->handle_state_change(arg_previous_state, arg_new_state):nothing - Handle component state changes.
	 * 
	 * 
	 * API: DEFINE UI HANDLERS FOR STATE UPDATE (only update UI)
	 * 		->ui_items_get_count():nothing - Get container items count.
	 * 		->ui_items_clear():nothing     - Erase container items.
	 * 
	 * 		->ui_items_append(arg_items_array):nothing  - Append tems to the container.
	 * 		->ui_items_prepend(arg_items_array):nothing - Prepend tems to the container.
	 * 		->ui_items_replace(arg_items_array, arg_items_count):nothing - Replace container items.
	 * 		->ui_items_insert_at(arg_index, arg_items_array, arg_items_count):nothing - Insert items at container position index.
	 * 
	 * 		->ui_items_remove_at_index(arg_index):nothing - Remove an item at given position.
	 * 		->ui_items_remove_first():nothing             - Remove a item at first position.
	 * 		->ui_items_remove_last():nothing              - Remove an item at last position.
	 * 
	 * API: state store actions
	 * 		* 'set_resize_max'
	 * 		* 'clear_items'
	 * 
	 * 		* 'append'
	 * 		* 'prepend'
	 * 		* 'replace'
	 * 		* 'insert_at'
	 * 
	 * 		* 'remove_at_index'
	 * 		* 'remove_first'
	 * 		* 'remove_last'
	 * 
	 * @param {object} arg_runtime - client runtime.
	 * @param {object} arg_state - component 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_container = true
		
		this.strategy = undefined
		
		this.strategy_update = undefined
		this.strategy_resize_ui = undefined
		this.strategy_resize_state = undefined
		this.max_size = undefined

		this._state_action_index = 0
	}
	
	
	
	/**
	 * Load and apply a container component configuration.
	 * 
	 * @param {Immutable.Map|undefined} arg_state - component state to load (optional).
	 * 
	 * @returns {nothing} 
	 */
	load(arg_state)
	{
		this.enter_group('load')

		const state = arg_state ? arg_state : this.get_state()
		
		// console.info(context + ':load:loading ' + this.get_name())
		
		if (! state)
		{
			return
		}
		
		if (this.is_loaded)
		{
			// console.info(context + ':load:already loaded component ' + this.get_name())
			this.leave_group('load:already loaded')
			return
		}

		if (! this.store_unsubscribe)
		{
			this.store_unsubscribe = this.get_runtime().create_store_observer(this)
		}


		// GET CONTAINER STRATEGY: HOW TO UPDATE UI
		if (state.has('strategy'))
		{
			this.strategy = state.get('strategy')
			
			
			// DEFINE UPDATE FUNCTION
			
			this.strategy_update = undefined
			
			if (this.strategy.has('update_mode'))
			{
				const mode = this.strategy.get('update_mode')
				if (mode == 'append')
				{
					this.strategy_update = this.ui_items_append
				}
				else if (mode == 'prepend')
				{
					this.strategy_update = this.ui_items_prepend
				}
				else if (mode == 'replace')
				{
					this.strategy_update = this.ui_items_replace
				}
				else if (mode == 'insert at')
				{
					if ( this.strategy.has('update_index') )
					{
						const index = this.strategy.get('update_index')
						this.strategy_update = () => { this.ui_items_insert_at_index(index) }
					}
				}
			}
			
			if ( ! T.isFunction(this.strategy_update) )
			{
				this.strategy_update = this.ui_items_replace
			}
			
			
			// DEFINE RESIZE FUNCTION
			
			this.strategy_resize_ui = undefined
			this.strategy_resize_state = undefined
			this.max_size = undefined
			
			if (this.strategy.has('resize_mode'))
			{
				const mode = this.strategy.get('resize_mode')
				
				this.max_size = 100
				if ( this.strategy.has('resize_max') )
				{
					this.max_size = this.strategy.get('resize_max')
				}
				
				if (mode == 'remove_last')
				{
					this.strategy_resize_ui = () => {
						const count = this.ui_items_get_count() - this.max_size
						if (count > 0)
						{
							this.ui_items_remove_last(count)
						}
					}
				}
				else /*if (mode == 'remove_first')*/
				{
					this.strategy_resize_ui = () => {
						const count = this.ui_items_get_count() - this.max_size
						this.ui_items_remove_first(count)
					}
				}
					
				this.strategy_resize_state = (arg_items, arg_next_state) => {
					if (arg_items && arg_items.count && this.max_size)
					{
						const count = arg_items.count() -  this.max_size
						if (count > 0)
						{
							const action = { type:mode, count:count }
							return this.reduce_action(arg_next_state, action)
						}
					}
					return arg_next_state
				}
				
				// console.log(context + ':load:resize_mode', mode)
				// console.log(context + ':load:resize_max', max_size)
			}
		}

		// SET DEFAULT METHOD
		if (! this.strategy_resize_state)
		{
			this.strategy_resize_state = (items, next_state) => next_state
		}
		if (! this.strategy_resize_ui)
		{
			this.strategy_resize_ui = () => {}
		}

		// this.init_bindings()

		// this.update()
		
		this.is_loaded = true
		this.leave_group('load')
	}
	
	
	// -----------------------------------------------------------------------------------------------------
	// DEFINE ACTION WRAPPERS (only dispatch an action to update the state)
	// -----------------------------------------------------------------------------------------------------
	
	/**
	 * Set max items value for the container.
	 * 
	 * @param {integer} arg_value - max items value.
	 * 
	 * @returns {nothing}
	 */
	do_action_set_resize_max(arg_value)
	{
		let value = T.isNumber(arg_value) ? arg_value : undefined
		if (!value)
		{
			if ( T.isObject(arg_value) )
			{
				if ( T.isNumber(arg_value.resize_max) )
				{
					value = arg_value.resize_max
				}
				else if ( T.isString(arg_value.resize_max) )
				{
					try {
						value = parseInt(arg_value.resize_max)
					}
					catch(e)
					{
						console.error(context + ':do_action_set_resize_max:bad integer value from string', arg_value)
					}
				}
			}
		}
		
		assert( T.isNumber(value), context + ':do_action_set_resize_max:bad value number')
		
		this.dispatch_action('set_resize_max', {resize_max:value})
	}
	
	
	
	/**
	 * Clear container items.
	 * 
	 * @returns {nothing}
	 */
	do_action_clear_items()
	{
		this.dispatch_action('clear_items')
	}
	
	
	
	/**
	 * Append items to the container (an item can be a string or an array or an object or ...).
	 * 
	 * @param {array}  arg_items_array - items array.
	 * @param {intege} arg_items_count - items count.
	 * 
	 * @returns {nothing}
	 */
	do_action_append(arg_items_array, arg_items_count)
	{
		if (! arg_items_array)
		{
			console.warn(context + ':do_action_append:%s:bad arguments', this.get_name())
			return
		}

		if ( arguments.length == 2 && T.isNumber(arg_items_array) )
		{
			const tmp = arg_items_array
			arg_items_array = arg_items_count
			arg_items_count = tmp
		}
		if (! arg_items_count && T.isArray(arg_items_array) )
		{
			arg_items_count = arg_items_array.length
		}
		
		// DEBUG
		// console.log(context + ':do_action_append:%s:items,count:', this.get_name(), arg_items_array, arg_items_count)
		
		assert( T.isNumber(arg_items_count), context + ':do_action_append:bad items count')
		assert( T.isArray(arg_items_array), context + ':do_action_append:bad items array')

		this.dispatch_action('append', {values:arg_items_array, count:arg_items_count } )
	}
	
	
	
	/**
	 * Prepend items to the container (an item can be a string or an array or an object or ...).
	 * 
	 * @param {array}  arg_items_array - items array.
	 * @param {intege} arg_items_count - items count.
	 * 
	 * @returns {nothing}
	 */
	do_action_prepend(arg_items_array, arg_items_count)
	{
		if (! arg_items_array)
		{
			console.warn(context + ':do_action_prepend:%s:bad arguments', this.get_name())
			return
		}

		if ( arguments.length == 2 && T.isNumber(arg_items_array) )
		{
			const tmp = arg_items_array
			arg_items_array = arg_items_count
			arg_items_count = tmp
		}
		if (! arg_items_count && T.isArray(arg_items_array))
		{
			arg_items_count = arg_items_array.length
		}

		// DEBUG
		// console.log(context + ':do_action_prepend:%s:items,count:', this.get_name(), arg_items_array, arg_items_count)

		assert( T.isNumber(arg_items_count), context + ':do_action_prepend:bad items count')
		assert( T.isArray(arg_items_array), context + ':do_action_prepend:bad items array')
		
		// DEBUG
		// console.log(context + ':do_action_prepend:arg_items_array', arg_items_array)

		this.dispatch_action('prepend', { values:arg_items_array, count:arg_items_count } )
	}
	
	
	
	/**
	 * Replace items to the container (an item can be a string or an array or an object or ...).
	 * 
	 * @param {array}  arg_items_array - items array.
	 * @param {intege} arg_items_count - items count.
	 * 
	 * @returns {nothing}
	 */
	do_action_replace(arg_items_array, arg_items_count)
	{
		if (! arg_items_array)
		{
			console.warn(context + ':do_action_replace:%s:bad arguments', this.get_name())
			return
		}

		if ( arguments.length == 2 && T.isNumber(arg_items_array) )
		{
			const tmp = arg_items_array
			arg_items_array = arg_items_count
			arg_items_count = tmp
		}
		
		// DEBUG
		// console.log(context + ':do_action_replace:%s:items,count:', this.get_name(), arg_items_array, arg_items_count)
		
		assert( T.isNumber(arg_items_count), context + ':do_action_replace:bad items count')
		assert( T.isArray(arg_items_array), context + ':do_action_replace:bad items array')

		this.dispatch_action('replace', {values:arg_items_array, count:arg_items_count } )
	}
	
	
	
	/**
	 * Insert items at container position index (an item can be a string or an array or an object or ...).
	 * 
	 * @param {intege} arg_index       - position index.
	 * @param {array}  arg_items_array - items array.
	 * @param {intege} arg_items_count - items count.
	 * 
	 * @returns {nothing}
	 */
	do_action_insert_at(arg_index, arg_items_array, arg_items_count)
	{
		assert( T.isNumber(arg_index), context + ':do_action_insert_at:bad index number')
		assert( T.isNumber(arg_items_count), context + ':do_action_insert_at:bad items count')
		
		if (arg_items_count == 1)
		{
			arg_items_array = [arg_items_array]
		}
		assert( T.isArray(arg_items_array), context + ':do_action_insert_at:bad items array')

		this.dispatch_action('insert_at', {index:arg_index, values:arg_items_array, count:arg_items_count } )
	}
	
	
	
	/**
	 * Remove an item at given position.
	 * 
	 * @param {number} arg_index - item index.
	 * 
	 * @returns {nothing}
	 */
	do_action_remove_at_index(arg_index)
	{
		assert( T.isNumber(arg_index), context + ':remove_at_index:bad index number' )
		
		this.dispatch_action( { type:'remove_at_index', index:arg_index } )
	}
	
	
	
	/**
	 * Remove an item at first position.
	 * 
	 * @returns {nothing}
	 */
	do_action_remove_first()
	{
		this.dispatch_action('remove_first')
	}
	
	
	
	/**
	 * Remove an item at last position.
	 *
	 * @param {number} arg_count - items count.
	 * 
	 * @returns {nothing}
	 */
	do_action_remove_last(arg_count)
	{
		this.dispatch_action('remove_last', { count:arg_count })
	}
	
	
	
	/**
	 * Store actions reducer pure function.
	 * 
	 * @param {Immutable.Map} arg_previous_state - previous state.
	 * @param {object} arg_action - store action: { type:'', component:'', ...}
	 * 
	 * @returns {Immutable.Map} - new state
	 */
	reduce_action(arg_previous_state, arg_action)
	{
		// console.log(context + ':reduce_action:prev state', arg_previous_state.toJS())
		
		if (! arg_previous_state)
		{
			console.error(context + ':reduce_action:%s:bad previous state for action=', this.get_name(), arg_action)
			return undefined
		}

		// PUSH ACTION ON UI ACTIONS STACK
		let stack =  arg_previous_state.get('ui_actions_stack')
		stack = stack ? stack.push(arg_action) : fromJS([arg_action])

		if (this._state_action_index >= stack.size - 1)
		{
			this._state_action_index = 0
			stack = fromJS([arg_action])
			// console.log(context + ':reduce_action:this._state_action_index(%s) >= stack.size(%s)', this._state_action_index, stack.size - 1)
		}
		
		let next_state = arg_previous_state.set('ui_actions_stack', stack)
		// console.log(context + ':reduce_action:stack', stack)

		switch(arg_action.type)
		{
			case 'set_resize_max': {
				if ( ! T.isNumber(arg_action.resize_max) && arg_action.resize_max > 0 )
				{
					return arg_previous_state
				}
				
				this.max_size = arg_action.resize_max
				const items = arg_previous_state.get('items')
				next_state = next_state.setIn(['strategy', 'resize_max'], arg_action.resize_max)
				return this.strategy_resize_state(items, next_state)
			}
			
			case 'clear_items': {
				next_state = next_state.set('items', fromJS([]))
				return next_state
			}
			
			case 'append': {
				const values = arg_action.values

				// DEBUG
				// console.log('container:reduce_action:append:values', values)
				// console.log('container:reduce_action:append:prev items', arg_previous_state.get('items').toJS())
				
				// NOTHING TO APPEND
				if (values.length == 0)
				{
					return arg_previous_state
				}

				let items = arg_previous_state.get('items').concat(values)
				next_state = next_state.set('items', items)
				
				return this.strategy_resize_state(items, next_state)
			}
			
			case 'prepend': {
				const values = arg_action.values
				// console.log(context + ':reduce_action:prepend:values', values)
				
				let items = arg_previous_state.get('items')
				values.forEach(
					(value) => items = items.splice(0, 0, value)
				)
				next_state = next_state.set('items', items)
				
				return this.strategy_resize_state(items, next_state)
			}
			
			case 'replace': {
				const values = arg_action.values
				console.log(context + ':reduce_action:replace:values', values)
				
				next_state = next_state.set('items', fromJS(values))
				
				return this.strategy_resize_state(fromJS(values), next_state)
			}
			
			case 'insert_at': {
				const index = arg_action.index
				const value = arg_action.value
				
				const items = arg_previous_state.get('items').insert(index, value)
				next_state = next_state.set('items', items)
				
				return this.strategy_resize_state(items, next_state)
			}
			
			case 'remove_at_index': {
				const index = arg_action.index
				const items = arg_previous_state.get('items').delete(index)
				next_state = next_state.set('items', items)
				return next_state
			}
			
			case 'remove_first': {
				const items = arg_previous_state.get('items').delete(0)
				next_state = next_state.set('items', items)
				return next_state
			}
			
			case 'remove_last': {
				if ( T.isNumber(arg_action.count) )
				{
					const count = arg_action.count
					
					if (count <= 0)
					{
						return arg_previous_state
					}
					
					if ( ! arg_previous_state.has('items') )
					{
						return arg_previous_state
					}
					
					let items = arg_previous_state.get('items')
					const last = items.size
					if ( (last - count) < 0 )
					{
						return arg_previous_state.clear()
					}
					
					items = items.splice(last - count, count)
					return next_state.set('items', items)
				}
				
				let items = arg_previous_state.get('items')
				const index = items.size
				items = items.delete(index)
				
				next_state = next_state.set('items', items)
				return next_state
			}
		}
		
		return arg_previous_state
	}
	
	
	
	/**
	 * Handle component state changes.
	 * 
	 * @param {Immutable.Map} arg_previous_state - previous state map.
	 * @param {Immutable.Map} arg_new_state - new state map.
	 * 
	 * @returns {nothing}
	 */
	handle_state_change(arg_previous_state, arg_new_state)
	{
		// console.log(context + ':handle_state_change')

		// GET WAITING UI STATE CHANGE ACTIONS
		let ui_actions_stack = arg_new_state.get('ui_actions_stack', [])
		ui_actions_stack = ui_actions_stack.toJS ? ui_actions_stack.toJS() : ui_actions_stack
		
		// LOOP ON WAITING ACTIONS
		let action = undefined
		while(this._state_action_index < ui_actions_stack.length)
		{
			action = ui_actions_stack[this._state_action_index]
			// console.log(context + ':handle_state_change:action', action, ui_actions_stack)
			this._state_action_index++

			this.handle_state_change_action(action, arg_previous_state, arg_new_state)
		}
	}



	/**
	 * Handle component state changes action.
	 * 
	 * @param {object}        arg_action         - state change action
	 * @param {Immutable.Map} arg_previous_state - previous state map.
	 * @param {Immutable.Map} arg_new_state      - new state map.
	 * 
	 * @returns {nothing}
	 */
	handle_state_change_action(arg_action, arg_previous_state, arg_new_state)
	{
		// console.log(context + ':handle_state_change_action:action', arg_action)

		switch(arg_action.type)
		{
			case 'clear_items': {
				this.ui_items_clear()
				break
			}
			
			case 'append': {
				this.ui_items_append(arg_action.values, arg_action.count)
				break
			}
			
			case 'prepend': {
				this.ui_items_prepend(arg_action.values, arg_action.count)
				return
			}
			
			case 'replace': {
				this.ui_items_replace(arg_action.values, arg_action.count)
				break
			}
			
			case 'insert_at': {
				this.ui_items_insert_at(arg_action.index, arg_action.values, arg_action.count)
				break
			}
			
			case 'remove_at_index': {
				this.ui_items_remove_at(arg_action.index)
				break
			}
			
			case 'remove_first': {
				this.ui_items_remove_first()
				break
			}
			
			case 'remove_last': {
				this.ui_items_remove_last(arg_action.count)
				break
			}
		}

		if ( T.isFunction(this.strategy_resize_ui) )
		{
			this.strategy_resize_ui()
		}
	}
	
	
	
	// -----------------------------------------------------------------------------------------------------
	// DEFINE UI HANDLERS FOR STATE UPDATE
	// -----------------------------------------------------------------------------------------------------
	
	
	
	
	/**
	 * Get container items count.
	 * 
	 * @returns {integer}
	 */
	ui_items_get_count()
	{
		const dom_elem = this.get_dom_element()
		return dom_elem.children.length
	}
	
	
	
	/**
	 * Erase container items.
	 * 
	 * @returns {nothing}
	 */
	ui_items_clear()
	{
		const dom_elem = this.get_dom_element()

		while(dom_elem.hasChildNodes())
		{
			const child_elem = dom_elem.lastChild
			dom_elem.removeChild(child_elem)
		}
	}
	
	
	
	/**
	 * Append items to the container.
	 * 
	 * @param {array} arg_items_array - items array.
	 * @param {intege} arg_items_count - items count.
	 * 
	 * @returns {nothing}
	 */
	ui_items_append(arg_items_array, arg_items_count)
	{
		assert( T.isArray(arg_items_array), context + ':ui_items_append:bad items array')
		assert( T.isNumber(arg_items_count), context + ':ui_items_append:bad items count')
		
		// NOT YET IMPLEMENTED

		const force_rendering = true
		this.render(force_rendering)
	}
	
	
	
	/**
	 * Prepend items to the container.
	 * 
	 * @param {array} arg_items_array - items array.
	 * @param {intege} arg_items_count - items count.
	 * 
	 * @returns {nothing}
	 */
	ui_items_prepend(arg_items_array, arg_items_count)
	{
		assert( T.isArray(arg_items_array), context + ':ui_items_prepend:bad items array')
		assert( T.isNumber(arg_items_count), context + ':ui_items_prepend:bad items count')
		
		// NOT YET IMPLEMENTED

		const force_rendering = true
		this.render(force_rendering)
	}
	
	
	
	/**
	 * Replace container items.
	 * 
	 * @param {array} arg_items_array - items array.
	 * @param {intege} arg_items_count - items count.
	 * 
	 * @returns {nothing}
	 */
	ui_items_replace(arg_items_array, arg_items_count)
	{
		assert( T.isArray(arg_items_array), context + ':ui_items_replace:bad items array')
		assert( T.isNumber(arg_items_count), context + ':ui_items_replace:bad items count')
		
		// NOT YET IMPLEMENTED

		const force_rendering = true
		this.render(force_rendering)
	}
	
	
	
	/**
	 * Insert items at container position index.
	 * 
	 * @param {intege} arg_index - position index.
	 * @param {array} arg_items_array - items array.
	 * @param {intege} arg_items_count - items count.
	 * 
	 * @returns {nothing}
	 */
	ui_items_insert_at(arg_index, arg_items_array, arg_items_count)
	{
		assert( T.isArray(arg_items_array), context + ':ui_items_replace:bad items array')
		assert( T.isNumber(arg_items_count), context + ':ui_items_replace:bad items count')
		
		// NOT YET IMPLEMENTED
	}
	
	
	
	/**
	 * Remove an item at given position.
	 * 
	 * @param {number} arg_index - item index.
	 * 
	 * @returns {nothing}
	 */
	ui_items_remove_at_index(arg_index)
	{
		assert( T.isNumber(arg_index), context + ':ui_items_remove_at_index:bad index number')
		
		// NOT YET IMPLEMENTED
	}
	
	
	
	/**
	 * Remove a item at first position.
	 * 
	 * @returns {nothing}
	 */
	ui_items_remove_first()
	{
		// NOT YET IMPLEMENTED
	}
	
	
	
	/**
	 * Remove an item at last position.
	 * 
	 * @returns {nothing}
	 */
	ui_items_remove_last()
	{
		// NOT YET IMPLEMENTED
	}



	/**
	 * Hide an element.
	 * 
	 * @param {Element} arg_element - DOM element to hide.
	 * 
	 * @returns {nothing}
	 */
	ui_items_hide_item(arg_element)
	{
		arg_element.style.display = 'none'
	}



	/**
	 * Show an element.
	 * 
	 * @param {Element} arg_element - DOM element to hide.
	 * 
	 * @returns {nothing}
	 */
	ui_items_show_item(arg_element)
	{
		arg_element.style.display = 'block'
	}



	/**
	 * Toggle items visibility belonging a given filter.
	 * 
	 * @param {string} arg_selector - items selector.
	 * @param {string} arg_filter_text - filter string.
	 * 
	 * @returns {nothing}
	 */
	ui_items_filter(arg_selector, arg_filter_text)
	{
		console.log(context + ':ui_items_filter:selector=%s:filter=%s',arg_selector, arg_filter_text)

		const dom_element = this.get_dom_element()
		const selected_elements = dom_element.querySelectorAll(arg_selector)
		if ( ! T.isNodeList(selected_elements) || selected_elements.length == 0)
		{
			return
		}

		_.forEach(selected_elements,
			(element)=>{
				const text = '' + this._get_dom_text(element)
				if ( text.search(arg_filter_text) >= 0 )
				{
					this.ui_items_show_item(element)
				} else {
					this.ui_items_hide_item(element)
				}
			}
		)
	}



	/**
	 * Show component.
	 * 
	 * @returns {nothing}
	 */
	show()
	{
		super.show()

		const children = this.get_children_component()
		_.forEach(children,
			(child)=>{
				child.show()
			}
		)
	}



	/**
	 * Hide component.
	 * 
	 * @returns {nothing}
	 */
	hide()
	{
		super.hide()

		const children = this.get_children_component()
		_.forEach(children,
			(child)=>{
				child.hide()
			}
		)
	}
}