src/provider/LayerProvider.js

goog.module('gep.provider.LayerProvider');

const {Event,EventTarget} = goog.require('goog.events');

const classlist = goog.require('goog.dom.classlist');

/**
 * This provider (singleton) controls the display of all layers
 * @extends {EventTarget}
 */
class LayerProvider extends EventTarget
{
    constructor()
    {
        super();

        /**
         * List of {@Link LayerModel} in use
         * If a new layer is added it must be added here with type and name.
         * @example
         * this.layers_.push(new LayerModel(LayerType.NEWS, 'news'));
         * @type {Array<LayerModel>}
         * @private
         */
        this.layers_ = [];
        this.layers_.push(new LayerModel(LayerType.EXHIBIT, 'exhibit'));
        this.layers_.push(new LayerModel(LayerType.MEDIA, 'media'));
        this.layers_.push(new LayerModel(LayerType.VISITOR, 'visitor'));
        this.layers_.push(new LayerModel(LayerType.INSTALLATION, 'installation'));
        this.layers_.push(new LayerModel(LayerType.GUIDING, 'guiding'));
        this.layers_.push(new LayerModel(LayerType.BLOCKING, 'blocking'));

        /**
         * List of open/shown layer types
         * @type {Map<string,string|null>}
         * @private
         */
        this.activeLayers_ = new Map();
        this.layers_.forEach((layerModel) => {
            if(!this.activeLayers_.has(layerModel.type))
                this.activeLayers_.set(layerModel.type, null);
        });
    }

    /**
     * Displays the layer whose model name corresponds to the specified name.
     * Optionally, a data object can be passed to the open layer by events.
     * When the layer is reopened {@Link LayerEventType} `SHOW_LAYER` is fired.
     * If this layer is already open {@Link LayerEventType} `UPDATE_LAYER`.
     * A class is also added to the document element specifying which {@Link LayerType}
     * is currently open. (e.g. `has-open-visitor-layer`)
     * @param {string} name
     * @param {*=} data
     */
    show(name, data = null)
    {
        let layerModel = this.getLayerByName(name);
        if(layerModel)
        {
            if(this.activeLayers_.get(layerModel.type) != name)
            {
                this.activeLayers_.set(layerModel.type, name);
                classlist.add(document.documentElement, 'has-open-'+layerModel.type);
                this.dispatchEvent(new LayerEvent(LayerEventType.SHOW_LAYER, layerModel, data));
            }
            else
            {
                this.dispatchEvent(new LayerEvent(LayerEventType.UPDATE_LAYER, layerModel, data));
            }
        }
    }

    /**
     * Hide a specific layer. If no name is specified, all active layers are hidden.
     * The {@Link LayerEventType} `HIDE_LAYER` will be fired.
     * @param {string|null=} name
     */
    hide(name = null)
    {
        if(name)
        {
            let layerModel = this.getLayerByName(name);
            if(layerModel)
            {
                if(this.activeLayers_.get(layerModel.type) == name)
                {
                    this.activeLayers_.set(layerModel.type, null);
                    classlist.remove(document.documentElement, 'has-open-' + layerModel.type);
                    this.dispatchEvent(new LayerEvent(LayerEventType.HIDE_LAYER, layerModel));
                }
            }
        }
        else
        {
            this.activeLayers_.forEach((name) => {
                if(name)
                {
                    let layerModel = this.getLayerByName(name);
                    if(this.activeLayers_.get(layerModel.type) == name)
                    {
                        this.activeLayers_.set(layerModel.type, null);
                        classlist.remove(document.documentElement, 'has-open-'+layerModel.type);
                        this.dispatchEvent(new LayerEvent(LayerEventType.HIDE_LAYER, layerModel));
                    }
                }
            });
        }
    }

    /**
     * Finds and returns a LayerModel with a given name.
     * @param {string} name
     * @return {LayerModel}
     */
    getLayerByName(name)
    {
        for(let i=0; i<this.layers_.length; i++)
        {
            if (this.layers_[i].name == name)
                return this.layers_[i];
        }
        return null;
    }

    /**
     * Checked if a specific layer with the name is open.
     * If no name is specified, it is checked whether any layer is open in general.
     * @param {string|null=} name
     * @return {boolean}
     */
    isOpen(name = null)
    {
        if(name)
        {
            let layerModel = this.getLayerByName(name);
            return layerModel != null && this.activeLayers_.get(layerModel.type) != null;
        }
        else
        {
            let isOpen = false;
            this.activeLayers_.forEach((name) => {
                if(name)
                {
                    let layerModel = this.getLayerByName(name);
                    if(layerModel && this.activeLayers_.get(layerModel.type) != null)
                        isOpen = true;
                }
            });
            return isOpen
        }
    }

    /**
     * Getter
     * @return {Map<string,string|null>}
     */
    get activeLayers()
    {
        return this.activeLayers_;
    }
}

goog.addSingletonGetter(LayerProvider);

/**
 * @enum {string}
 */
const LayerType = {
    /** exhibit-layer */        EXHIBIT: 'exhibit-layer',
    /** guiding-layer */        GUIDING: 'guiding-layer',
    /** installation-frame */   INSTALLATION: 'installation-frame',
    /** media-layer */          MEDIA: 'media-layer',
    /** visitor-layer */        VISITOR: 'visitor-layer',
    /** blocking-layer */       BLOCKING: 'blocking-layer'
};

/**
 * Model for a layer consists of its type {@Link LayerType} and
 * a unique name that can be used later for an exact assignment.
 */
class LayerModel
{
    /**
     * @param {string} type Use {@Link LayerType}
     * @param {string} name Unique name for an exact assignment
     */
    constructor(type, name)
    {
        /**
         * Use {@Link LayerType}
         * @type {string}
         */
        this.type = type;

        /**
         * Unique name for an exact assignment
         * @type {string}
         */
        this.name = name;
    }
}

/**
 * Own event that is used to open, update and close the layer by the {@Link LayerProvider}
 * and can also be caught by it. The {@Link LayerModel} is always sent along for an exact assignment.
 * In addition, the optional data parameter can be used to pass further values to the layer via the event.
 * @extends {Event}
 */
class LayerEvent extends Event
{
    /**
     * @param {string} type
     * @param {LayerModel} layer
     * @param {*=} data Optional data which should be passed to the layer by this event
     */
    constructor(type, layer, data = null)
    {
        super(type);

        /**
         * @type {LayerModel}
         */
        this.layer = layer;

        /**
         * Optional data which should be passed to the layer by this event.
         * @type {*}
         */
        this.data = data;
    }
};

/**
 * @enum {string}
 */
const LayerEventType = {
    /** show-layer */   SHOW_LAYER: 'show-layer',
    /** hide-layer */   HIDE_LAYER: 'hide-layer',
    /** update-layer */ UPDATE_LAYER: 'update-layer',
};

exports = {LayerProvider,LayerType,LayerModel,LayerEvent,LayerEventType};