Katochimoto/xblocks

View on GitHub
src/blocks/select/index.js

Summary

Maintainability
A
1 hr
Test Coverage
import './style';
import './index.jsx';

import _ from 'lodash';
import { xb } from 'context';
import { create, event as xevent } from 'xblocks-core';
import ConstantMenu from 'constants/menu';
import ConstantSelect from 'constants/select';
import initialDefinitionSelected from 'utils/initialDefinitionSelected';
import removeChild from 'dom/removeChild';
import mixinElementDisabled from 'mixin/element/disabled';
import mixinFocusComponent from 'mixin/element/focusComponent';

const MENU_ATTRS = {
    'attachment': 'top left',
    'target-attachment': 'bottom left',
    'target-modifier': 'initial',
    'constraints': encodeURIComponent(JSON.stringify([
        {
            'to': 'window',
            'attachment': 'element together'
        }
    ]))
};

/**
 * xb-select html element
 *
 * @class xb.Select
 * @memberof xb
 * @augments HTMLElement
 */
export default xb.Select = create('xb-select', [
    mixinElementDisabled,
    mixinFocusComponent,

    {
        prototype: Object.create(HTMLElement.prototype),

        lifecycle: {
            created: function () {
                this[ ConstantSelect.SELECTED ] = initialDefinitionSelected(this, true);
            }
        },

        events: {
            'xb-destroy': function () {
                this._menuRemove();
                this[ ConstantSelect.SELECTED ] = {};
            },

            'xb-update': function () {
                this._menuRemove();
            },

            'click': function () {
                this.selectMenuInstance.open();
            },

            'keydown:keypass(32)': function () {
                this.selectMenuInstance.open();
            },

            'change': function () {
                this.getComponent().setState({ selected: this.selectedObjects });
            }
        },

        /**
         * @lends xb.Select.prototype
         */
        accessors: {
            componentStyle: {
                get: function () {
                    return {
                        [ this.xtagName ]: require('!!raw!postcss!stylus!./style/inline.styl')
                    };
                }
            },

            /**
             * @prop {xb.Menu} selectMenuInstance Menu instance
             * @readonly
             */
            selectMenuInstance: {
                get: function () {
                    let menu = this[ ConstantSelect.MENU ];

                    if (!menu) {
                        menu = this[ ConstantSelect.MENU ] = this._menuCreate();
                    }

                    return menu;
                }
            },

            multiple: {
                attribute: {
                    boolean: true
                }
            },

            /**
             * @prop {string[]} value the values of the selected item
             * @readonly
             */
            value: {
                get: function () {
                    // если меню ещё не открывалось
                    // вызывать selectMenuInstance не нужно, чтобы не создавать меню без необходимости
                    if (!this[ ConstantSelect.MENU ]) {
                        return _.map(this[ ConstantSelect.SELECTED ], 'value');
                    }

                    return this.selectMenuInstance.value;
                }
            },

            /**
             * @prop {HTMLElement[]} selectedItems the selected item
             * @readonly
             */
            selectedItems: {
                get: function () {
                    // если меню ещё не открывалось
                    // вызывать selectMenuInstance не нужно, чтобы не создавать меню без необходимости
                    if (!this[ ConstantSelect.MENU ]) {
                        return _.values(this[ ConstantSelect.SELECTED ]);
                    }

                    return this.selectMenuInstance.selectedItems;
                }
            },

            /**
             * @prop {Object[]} selectedObjects the selected item
             * @readonly
             */
            selectedObjects: {
                get: function () {
                    // если меню ещё не открывалось
                    // вызывать selectMenuInstance не нужно, чтобы не создавать меню без необходимости
                    if (!this[ ConstantSelect.MENU ]) {
                        return _.map(this[ ConstantSelect.SELECTED ], item => {
                            return {
                                label: item.getAttribute('label'),
                                value: item.value
                            };
                        });
                    }

                    return this.selectMenuInstance.selectedObjects;
                }
            }
        },

        methods: {
            /**
             * @private
             */
            _menuRemove: function () {
                const menu = this[ ConstantSelect.MENU ];
                if (!menu) {
                    return;
                }

                this[ ConstantSelect.MENU ] = undefined;
                menu.close();
                removeChild(menu);
            },

            /**
             * @returns {xb.Menu}
             * @private
             */
            _menuCreate: function () {
                const targetClassName = `_select-target-${this.xuid}`;
                const menu = this.ownerDocument.createElement('xb-menu');
                const attrs = _.merge({ target: `.${targetClassName}` }, MENU_ATTRS);
                const size = Number(this.getAttribute('size') || 0);

                if (size) {
                    attrs.size = size;
                }

                for (let attrName in attrs) {
                    menu.setAttribute(attrName, attrs[ attrName ]);
                }

                menu[ ConstantMenu.SELECTED ] = this[ ConstantSelect.SELECTED ];

                menu.selectable = true;
                menu.alwaysselected = true;
                menu.multiple = this.multiple;
                menu.autoclose = !this.multiple;
                menu.innerHTML = this.content;
                menu.addEventListener('xb-destroy', ::this._menuRemove, false);

                xevent.forwardingEvents('change', menu, this);

                this.classList.add(targetClassName);

                return this.ownerDocument.body.appendChild(menu);
            }
        }
    }
]);