UNPKG

zombiebox

Version:

ZombieBox is a JavaScript framework for development of Smart TV and STB applications

392 lines (325 loc) 7.42 kB
/* * This file is part of the ZombieBox package. * * Copyright © 2012-2021, Interfaced * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ import {div, hide, show} from '../html'; import {debug} from '../console/console'; import Block from '../block'; import IFocusable from '../interfaces/i-focusable'; import IStateful from '../history/interfaces/i-stateful'; import Container from '../widgets/container'; /** * Layer is a base abstraction for Application to manipulate its visible/hidden layers * It could be a scene, popup or whatever * @implements {IStateful} * @implements {IFocusable} */ export default class Layer extends Container { /** */ constructor() { super(); /** * Layer root element * @type {HTMLDivElement} * @protected */ this._root = null; /** * Layer's children layers container element * @type {HTMLDivElement} * @protected */ this._childLayersContainer = null; /** * Layer content container element * @type {HTMLDivElement} * @protected */ this._container = null; /** * Used for block pointer events * @type {HTMLDivElement} * @protected */ this._pointerBlock = null; /** * @type {boolean} * @protected */ this._isVisible = false; /** * @type {Array<Layer>} * @protected */ this._childLayers = []; /** * @type {Block} * @protected */ this._block = new Block(); /** * Fired with: nothing * @const {string} */ this.EVENT_NEED_TO_BE_HIDDEN = 'need-to-be-hidden'; /** * Fired with: Layer * @const {string} */ this.EVENT_CHILD_LAYER_SHOWN = 'child-layer-shown'; /** * Fired with: Layer * @const {string} */ this.EVENT_CHILD_LAYER_HIDDEN = 'child-layer-hidden'; this._block.on(this._block.EVENT_BLOCK, this._blockPointer.bind(this)); this._block.on(this._block.EVENT_UNBLOCK, this._unblockPointer.bind(this)); this._createDOM(); } /** * @override */ focus() { debug('Layer.focus: ' + this.getCSSClassName(), this._root.style.display || 'block'); super.focus(); } /** * @override */ blur() { debug('Layer.blur: ' + this.getCSSClassName(), this._root.style.display || 'block'); super.blur(); } /** * @override */ processKey(zbKey, event) { if (this._block.isBlocked()) { return false; } const topLayer = this.getTopChildLayer(); if (topLayer) { return topLayer.processKey(zbKey, event); } return super.processKey(zbKey, event); } /** * @override */ toString() { return this.getCSSClassName(); } /** * @param {Promise} promise * @return {Promise} */ wait(promise) { return this._block.block(promise); } /** * @return {boolean} */ isBlocked() { return this._block.isBlocked(); } /** * @return {string} */ getCSSClassName() { return this._container ? this._container.className : 'none'; } /** * @return {HTMLElement} */ getRoot() { return this._root; } /** * @return {HTMLElement} */ getContainer() { return this._container; } /** * Called before container of the layer was shown */ beforeDOMShow() { debug('Layer.beforeDOMShow: ' + this.getCSSClassName()); if (!this.getActiveWidget() && this._defaultWidget) { this.activateWidget(this._defaultWidget); } this._widgets.forEach((widget) => { if (widget.isVisible()) { widget.beforeDOMShow(); } }); } /** * Called before layer was hidden */ beforeDOMHide() { debug('Layer.beforeDOMHide: ' + this.getCSSClassName()); this._widgets.forEach((widget) => { if (widget.isVisible()) { widget.beforeDOMHide(); } }); } /** */ afterDOMShow() { debug('Layer.afterDOMShow: ' + this.getCSSClassName()); this._isVisible = true; this._widgets.forEach((widget) => { widget.setParentDOMVisible(true); if (widget.isVisible()) { widget.afterDOMShow(); } }); } /** */ afterDOMHide() { debug('Layer.afterDOMHide: ' + this.getCSSClassName()); this._isVisible = false; this._widgets.forEach((widget) => { widget.setParentDOMVisible(false); if (widget.isVisible()) { widget.afterDOMHide(); } }); } /** * @return {boolean} */ isVisible() { return this._isVisible; } /** * Create new instance of layerClassName and append it to the container * @param {Function} LayerClass * @param {*=} params * @return {Layer} */ showChildLayer(LayerClass, params) { const layer = /** @type {Layer} */ (new LayerClass(params)); this.showChildLayerInstance(layer); return layer; } /** * Show an existing instance of layer and append it to the container * @param {Layer} layer */ showChildLayerInstance(layer) { const layerContainer = layer.getRoot(); const closeLayer = this.closeChildLayer.bind(this, layer); const waitClose = (eventName, childLayer) => { if (childLayer === layer) { layer.off(layer.EVENT_NEED_TO_BE_HIDDEN, closeLayer); this.off(this.EVENT_CHILD_LAYER_HIDDEN, waitClose); } }; layer.on(layer.EVENT_NEED_TO_BE_HIDDEN, closeLayer); this.on(this.EVENT_CHILD_LAYER_HIDDEN, waitClose); show(this._childLayersContainer); layer.beforeDOMShow(); this._childLayersContainer.appendChild(layerContainer); this._childLayers.push(layer); layer.afterDOMShow(); layer.focus(); this._fireEvent(this.EVENT_CHILD_LAYER_SHOWN, layer); if (this.isFocused()) { this.blur(); } } /** * @param {Layer} layer */ closeChildLayer(layer) { const layerIndex = this._childLayers.indexOf(layer); const layerContainer = layer.getRoot(); this._childLayers.splice(layerIndex, 1); layer.blur(); layer.beforeDOMHide(); this._childLayersContainer.removeChild(layerContainer); layer.afterDOMHide(); if (!this.hasChildLayers()) { hide(this._childLayersContainer); } this._fireEvent(this.EVENT_CHILD_LAYER_HIDDEN, layer); if (!this.hasChildLayers()) { this.focus(); } } /** * @return {number} */ getChildLayersCount() { return this._childLayers.length; } /** * @return {boolean} */ hasChildLayers() { return this.getChildLayersCount() !== 0; } /** * @return {Array<Layer>} */ getChildLayers() { return this._childLayers; } /** * @return {?Layer} */ getTopChildLayer() { return this._childLayers[this._childLayers.length - 1] || null; } /** * Add CSS class name to layer container * @param {string} cssClass * @protected */ _addContainerClass(cssClass) { this._container.classList.add(cssClass); } /** * Remove CSS class name to layer container * @param {string} cssClass * @protected */ _removeContainerClass(cssClass) { this._container.classList.remove(cssClass); } /** * @private */ _blockPointer() { show(this._pointerBlock); } /** * @private */ _unblockPointer() { hide(this._pointerBlock); } /** * @private */ _createDOM() { this._root = div('zb-layer zb-fullscreen'); this._container = div('zb-layer__container zb-fullscreen'); this._childLayersContainer = div('zb-layer__children zb-fullscreen'); this._pointerBlock = div('zb-layer__pointer-block zb-fullscreen'); this._root.appendChild(this._container); this._root.appendChild(this._childLayersContainer); this._root.appendChild(this._pointerBlock); hide(this._childLayersContainer); hide(this._pointerBlock); } }