API Docs for: 1.0.0
Show:

File: Resources/public/js/views/fields/ez-selection-editview.js

/*
 * Copyright (C) eZ Systems AS. All rights reserved.
 * For full copyright and license information view LICENSE file distributed with this source code.
 */
YUI.add('ez-selection-editview', function (Y) {
    "use strict";
    /**
     * Provides the field edit view for the Selection (ezselection) fields
     *
     * @module ez-selection-editview
     */
    Y.namespace('eZ');

    var FIELDTYPE_IDENTIFIER = 'ezselection',
        IS_LIST_HIDDEN = 'is-list-hidden',
        IS_TOP_LIST = 'is-top-list';

    /**
     * Selection edit view. It uses the SelectionFilterView to provide a rich UI
     * to help selecting one or several items over a long list.
     *
     * @namespace eZ
     * @class SelectionEditView
     * @constructor
     * @extends eZ.FieldEditView
     */
    Y.eZ.SelectionEditView = Y.Base.create('selectionEditView', Y.eZ.FieldEditView, [], {
        VALUES_TPL: '<li class="ez-selection-value" />',

        events: {
            '.ez-selection-values': {
                'tap': '_toggleShowSelectionUI',
            },
            '.ez-selection-value': {
                'tap': '_removeValue'
            },
        },

        /**
         * Initializes the selection edit view.
         *
         * @method initializer
         */
        initializer: function () {
            /**
             * The selection filter view instance. It is created when the view
             * is set as active.
             *
             * @property _selectionFilter
             * @protected
             * @type eZ.SelectionFilterView
             */
            this._selectionFilter = null;

            /**
             * Holds the clickoutside event handle.
             *
             * @property _clickoutsideHandler
             * @protected
             * @type {EventHandle}
             */
            this._clickoutsideHandler = null;
            this._useStandardFieldDefinitionDescription = false;

            this.after('activeChange', function (e) {
                if ( e.newVal ) {
                    this._initSelectionFilter();
                }
            });

            this._set('values', this._getSelectedTextValues());

            this.after('fieldChange', function (e) {
                this._set('values', this._getSelectedTextValues());
            });
            this.after('valuesChange', this._uiSyncSelectionValues);
            this.after('valuesChange', this._validateAddedRemovedValues);
            this.after('showSelectionUIChange', this._uiShowSelectionList);
        },

        /**
         * Renders the selection edit view.
         *
         * @method render
         */
        render: function () {
            this.get('container').addClass(IS_LIST_HIDDEN);
            return Y.eZ.FieldEditView.prototype.render.apply(this, arguments);
        },

        /**
         * Recreates the selection filter when the view is rerendered.
         *
         * @method _afterActiveReRender
         * @protected
         */
        _afterActiveReRender: function () {
            this._selectionFilter.destroy();
            this._clickoutsideHandler.detach();
            this._initSelectionFilter();
        },

        /**
         * Initializes and the selection filter view for the current
         * field/fieldDefinition. Once created, it is render right away.
         *
         * @method _initSelectionFilter
         * @protected
         */
        _initSelectionFilter: function () {
            this._selectionFilter = this._getSelectionFilter();

            this._selectionFilter.on('select', Y.bind(function (e) {

                if ( !this.get('isMultiple') ) {
                    this.set('showSelectionUI', false);
                }

                if ( Y.Object.keys(e.attributes).length === 1 ) {
                    this._addSelection(e.attributes.text);
                } else {
                    this._addSelection(e.attributes);
                }

            }, this));

            this._selectionFilter.on('unselect', Y.bind(function (e) {
                this._removeSelection(e.text);
            }, this));

            this._selectionFilter.render();
            this._clickoutsideHandler = this.get('container').one('.ez-selection-input-ui').on(
                'clickoutside', Y.bind(function (e) {
                    this.set('showSelectionUI', false);
                }, this)
            );
            this._attachedViewEvents.push(this._clickoutsideHandler);
        },

        /**
         * Returns the selection filter
         *
         * @method _getSelectionFilter
         * @protected
         * @return Y.eZ.SelectionFilterView
         */
        _getSelectionFilter: function () {
            var container = this.get('container'),
                selected = this._getSelectedTextValues(),
                input = container.one('.ez-selection-filter-input'),
                source = this._getSource();

            return new Y.eZ.SelectionFilterView({
                container: input.get('parentNode'),
                inputNode: input,
                listNode: this.get('container').one('.ez-selection-options'),
                selected: selected,
                source: source,
                filter: (source.length > 5),
                resultFilters: 'startsWith',
                resultHighlighter: 'startsWith',
                isMultiple: this.get('isMultiple'),
            });
        },

        /**
         * Returns the selection source
         *
         * @method _getSource
         * @protected
         * @return {Array}
         */
        _getSource: function () {
            return this.get('fieldDefinition').fieldSettings.options;
        },

        /**
         * Returns an array of the values stored by the field being edited.
         *
         * @method _getSelectedTextValues
         * @protected
         * @return {Array}
         */
        _getSelectedTextValues: function () {
            var field = this.get('field'),
                fieldDefinition = this.get('fieldDefinition'),
                valueIndexes = [],
                options = [],
                res = [];

            if ( field && field.fieldValue ) {
                valueIndexes = field.fieldValue;
            }
            if ( fieldDefinition && fieldDefinition.fieldSettings.options ) {
                options = fieldDefinition.fieldSettings.options;
            }

            Y.Array.each(valueIndexes, function (index) {
                res.push(options[index]);
            });
            return res;
        },

        /**
         * Adds the value to list of the currently selected values
         *
         * @method _addSelection
         * @protected
         * @param {String|Object}  value
         */
        _addSelection: function (value) {
            var values = this.get('values');

            values.push(value);
            this._set('values', values, {"added": value});
        },

        /**
         * Removes the value from the list of the currently selected values
         *
         * @method _removeSelection
         * @protected
         * @param {String} value
         * @param {Node} [node] the node storing the value in the list
         */
        _removeSelection: function (value, node) {
            var values = this.get('values');

            values = Y.Array.reject(values, function (val) {
                return (typeof val === 'object' ? val.text === value : val === value);
            });

            this._selectionFilter.unselect(value);
            this._set('values', values, {
                "removed": value,
                "node": node || null
            });
        },

        /**
         * Event handler for the tap event on a selected value. It removes the
         * tapped value from the selected list
         *
         * @param {Object} e event facade
         * @method _removeValue
         * @protected
         */
        _removeValue: function (e) {
            if (this.get('isNotTranslatable')) {
                return;
            }

            this._removeSelection(e.target.getAttribute('data-text'), e.target);
        },

        destructor: function () {
            if ( this._selectionFilter ) {
                this._selectionFilter.destroy();
                delete this._selectionFilter;
            }
        },

        /**
         * Event handler for the tap event on selection UI to display/hide the
         * selection filter
         *
         * @param {Object} e event facade
         * @method _toggleShowSelectionUI
         * @protected
         */
        _toggleShowSelectionUI: function (e) {
            if (this.get('isNotTranslatable')) {
                return;
            }

            if ( !e.target || !e.target.hasClass('ez-selection-value') ) {
                this.set('showSelectionUI', !this.get('showSelectionUI'));
            }
        },

        /**
         * Returns true if the selection filter list should appear on top of the
         * selection
         *
         * @method _isTopList
         * @private
         * @return Boolean
         */
        _isTopList: function () {
            var c = this._selectionFilter.get('container'),
                bottomSpace;

            bottomSpace = c.get('winHeight') + c.get('docScrollY') - Math.round(c.getY());
            return c.get('offsetHeight') > bottomSpace;
        },

        /**
         * Event handler for the showSelectionUIChange event. It displays/hides
         * the selection fitler depending on the new value of the
         * showSelectionUI attribute.
         *
         * @method _uiShowSelectionList
         * @protected
         * @param {Object} event facade
         */
        _uiShowSelectionList: function (e) {
            var container = this.get('container');

            if ( e.newVal ) {
                if ( this._isTopList() ) {
                    container.addClass(IS_TOP_LIST);
                } else {
                    container.removeClass(IS_TOP_LIST);
                }
                container.removeClass(IS_LIST_HIDDEN);
                this._selectionFilter.resetFilter();
                this._selectionFilter.focus();
            } else {
                this._selectionFilter.resetFilter();
                container.addClass(IS_LIST_HIDDEN);
            }
            this.validate();
        },

        /**
         * Event handler for the valuesChange event. It makes sure the displayed
         * selected values are in sync where the actual selection.
         *
         * @method _uiSyncSelectionValues
         * @protected
         * @param {Object} e event facade
         */
        _uiSyncSelectionValues: function (e) {
            var selectionValues = this.get('container').one('.ez-selection-values'),
                node;

            if ( e.added ) {
                if ( typeof e.added === "object" ) {
                    node = Y.Node.create(this.VALUES_TPL).setContent(e.added.text);
                    Y.Object.each(e.added, function (value, key) {
                        node.setAttribute('data-' + key, value);
                    });
                } else {
                    node = Y.Node.create(this.VALUES_TPL).setContent(e.added);
                    node.setAttribute('data-text', e.added);
                }
                selectionValues.append(node);
            } else if ( e.removed ) {
                if ( e.node ) {
                    node = e.node;
                } else {
                    node = selectionValues.one('.ez-selection-value[data-text="' + e.removed + '"]');
                }
                node.remove(true);
            }
        },

        /**
         * Check if there is a value that has been added or removed with the event facade.
         * If it is, then call validate().
         *
         * @method _validateAddedRemovedValues
         * @protected
         * @param {Object} e event facade
         */
        _validateAddedRemovedValues: function (e) {
            if ( e.added || e.removed ) {
                this.validate();
            }
        },

        /**
         * Validates the current state of the selection. A selection is invalid
         * if it's required and empty.
         *
         * @method validate
         */
        validate: function () {
            if (
                this.get('fieldDefinition').isRequired
                && this.get('values').length === 0
            ) {
                this.set('errorStatus', 'This field is required');
            } else {
                this.set('errorStatus', false);
            }
        },

        /**
         * Defines the variables to imported in the field edit template.
         *
         * @protected
         * @method _variables
         * @return {Object} containing isRequired, isMultiple and selected
         * entries
         */
        _variables: function () {
            var def = this.get('fieldDefinition');

            return {
                "isRequired": def.isRequired,
                "selected": this.get('values'),
                "isMultiple": this.get('isMultiple'),
            };
        },

        /**
         * Returns the field value. The selection field value is an array
         * containing the indexes of the selected values in the field definition
         * options field settings.
         *
         * @protected
         * @method _getFieldValue
         * @return Array
         */
        _getFieldValue: function () {
            var values = this.get('values'),
                options =  this.get('fieldDefinition').fieldSettings.options,
                res = [];

            Y.Array.each(values, function (val) {
                res.push(options.indexOf(val));
            }, this);

            return res;
        }
    }, {
        ATTRS: {
            /**
             * The text values of the selected options
             *
             * @attribute values
             * @readonly
             * @default []
             * @type Array
             */
            values: {
                readOnly: true,
                value: []
            },

            /**
             * Whether the selection filter should be shown
             *
             * @attribute showSelectionUI
             * @type boolean
             * @default false
             */
            showSelectionUI: {
                value: false,
            },

            /**
             * Whether the field accepts several values
             *
             * @attribute isMultiple
             * @readonly
             * @type boolean
             */
            isMultiple: {
                readOnly: true,
                getter: function () {
                    return this.get('fieldDefinition').fieldSettings.isMultiple;
                },
            }
        }
    });

    Y.eZ.FieldEditView.registerFieldEditView(
        FIELDTYPE_IDENTIFIER, Y.eZ.SelectionEditView
    );
});