API Docs for: 1.8.0
Show:

File: src/composite/select.js

/*
 * Copyright (c) 2014-2015, Wanadev <http://www.wanadev.fr/>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *   * Redistributions of source code must retain the above copyright notice, this
 *     list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above copyright notice,
 *     this list of conditions and the following disclaimer in the documentation
 *     and/or other materials provided with the distribution.
 *   * Neither the name of Wanadev nor the names of its contributors may be used
 *     to endorse or promote products derived from this software without specific
 *     prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * Authored by: Fabien LOISON <http://flozz.fr/>
 */

/**
 * PhotonUI - Javascript Web User Interface.
 *
 * @module PhotonUI
 * @submodule Composite
 * @namespace photonui
 */

var Stone  = require("stonejs");
var Helpers = require("../helpers.js");
var Widget = require("../widget.js");
var PopupMenu = require("./popupmenu.js");
var MenuItem = require("../container/menuitem.js");

/**
 * Select input.
 *
 * wEvents:
 *
 *   * value-changed:
 *     - description: called when the value was modified.
 *     - callback:    function(widget, value)
 *
 * @class Select
 * @constructor
 * @extends photonui.Widget
 */
var Select = Widget.$extend({

    // Constructor
    __init__: function (params) {
        params = params || {};

        // Attach popup & special mixin
        this.__popupMenu = new PopupMenu({
            maxHeight: 300,
            className: "photonui-select-popup",
            iconVisible: false
        });

        this._registerWEvents(["value-changed"]);
        this.$super(params);

        this._bindEvent("popup", this.html, "click", this.__onClick.bind(this));
        this._bindEvent("mouse-down", this.html, "mousedown", this.__onMouseDown.bind(this));

        this.setValue(params.value || this.value, true);
    },

    //////////////////////////////////////////
    // Properties and Accessors             //
    //////////////////////////////////////////

    // ====== Public properties ======

    /**
     * The field value.
     *
     * @property value
     * @type String (maybe)
     * @default ""
     */
    _value: "",

    getValue: function () {
        "@photonui-update";
        return this._value;
    },

    setValue: function (value, force) {
        if (this.value == value && !force) {
            return;
        }

        var items = this.__popupMenu.children;

        for (var i = 0 ; i < items.length ; i++) {
            if (items[i] instanceof MenuItem && items[i].value == value) {
                this._value = value;
                Helpers.cleanNode(this.__html.select);
                this.__html.select.appendChild(items[i].html.cloneNode(true));
                return;
            }
        }

        this._value = "";
        if (this.__displayValue) {
            this.__displayValue.destroy();
        }
        this.__displayValue = new MenuItem({text: this.placeholder, className: "photonui-select-placeholder"});
        Helpers.cleanNode(this.__html.select);
        this.__html.select.appendChild(this.__displayValue.html);
    },

    /**
     * The placeholder displayed if nothing is selected.
     *
     * @property placeholder
     * @type String
     * @default "Select..."
     */
    _placeholder: Stone.lazyGettext("Select..."),

    getPlaceholder: function () {
        return this._placeholder;
    },

    setPlaceholder: function (placeholder) {
        this._placeholder = placeholder;
    },

    /**
     * Layout children widgets.
     *
     * @property children
     * @type Array
     * @default []
     */
    getChildren: function () { return this.__popupMenu.getChildren(); },
    setChildren: function (p) {
        this.__popupMenu.setChildren(p);
        this._updateItemsBinding();
    },

    /**
     * Layout children widgets name.
     *
     * @property childrenNames
     * @type Array
     * @default []
     */
    getChildrenNames: function () { return this.__popupMenu.getChildrenNames(); },
    setChildrenNames: function (p) {
        this.__popupMenu.setChildrenNames(p);
        this._updateItemsBinding();
    },

    /**
     * Width of the container node.
     *
     * @property popupWidth
     * @type Number
     * @default: null (auto)
     */
    getPopupWidth: function () { return this.__popupMenu.getWidth(); },
    setPopupWidth: function (p) { this.__popupMenu.setWidth(p); },

    /**
     * Height of the popup container node.
     *
     * @property popupHeight
     * @type Number
     * @default: null (auto)
     */
    getPopupHeight: function () { return this.__popupMenu.getHeight(); },
    setPopupHeight: function (p) { this.__popupMenu.setHeight(p); },

    /**
     * Maximum width of the popup container node.
     *
     * @property popupMaxWidth
     * @type Number
     * @default: null (no maximum)
     */
    getPopupMaxWidth: function () { return this.__popupMenu.getMaxWidth(); },
    setPopupMaxWidth: function (p) { this.__popupMenu.setMaxWidth(p); },

    /**
     * Minimum width of the popup container node.
     *
     * @property popupMinWidth
     * @type Number
     * @default: null (no minimum)
     */
    _minWidthDefined: false,
    getPopupMinWidth: function () { return this.__popupMenu.getMinWidth(); },
    setPopupMinWidth: function (p) { this._minWidthDefined = true ; this.__popupMenu.setMinWidth(p); },

    /**
     * Maximum height of the popup container node.
     *
     * @property popupMaxHeight
     * @type Number
     * @default: 300
     */
    getPopupMaxHeight: function () { return this.__popupMenu.getMaxHeight(); },
    setPopupMaxHeight: function (p) { this.__popupMenu.setMaxHeight(p); },

    /**
     * Minimum height of the popup container node.
     *
     * @property popupMinHeight
     * @type Number
     * @default: null (no minimum)
     */
    getPopupMinHeight: function () { return this.__popupMenu.getMinHeight(); },
    setPopupMinHeight: function (p) { this.__popupMenu.setMinHeight(p); },

    /**
     * Popup width (outer HTML element).
     *
     * @property popupOffsetWidth
     * @type Number
     * @readOnly
     */
    getPopupOffsetWidth: function () { return this.__popupMenu.getOffsetWidth(); },

    /**
     * Popup height (outer HTML element).
     *
     * @property popupOffsetHeight
     * @type Number
     * @readOnly
     */
    getPopupOffsetHeight: function () { return this.__popupMenu.getOffsetHeight(); },

    /**
     * Window container node padding.
     *
     * @property popupPadding
     * @type Number
     * @default 0
     */
    getPopupPadding: function () { return this.__popupMenu.getPadding(); },
    setPopupPadding: function (p) { this.__popupMenu.setPadding(p); },

    /**
     * Define if icon on menu items are visible.
     *
     * @property iconVisible
     * @type Boolean
     * @default: false
     */
    isIconVisible: function () {
        "@photonui-update";
        return this.__popupMenu.isIconVisible();
    },
    setIconVisible: function (p) {
        if (!p) {
            this.addClass("photonui-select-noicon");
        } else {
            this.removeClass("photonui-select-noicon");
        }
        this.__popupMenu.setIconVisible(p);
    },

    setVisible: function (visible) {
        this.$super(visible);
        if (!visible) {
            this.__popupMenu.hide();
        }
    },

    /**
     * Html outer element of the widget (if any).
     *
     * @property html
     * @type HTMLElement
     * @default null
     * @readOnly
     */
    getHtml: function () {
        return this.__html.select;
    },

    // ====== Private properties ======

    /**
     * The popupMenu.
     *
     * @property __popupMenu
     * @private
     * @type photonui.PopupMenu
     */
    __popupMenu: null,

    //////////////////////////////////////////
    // Methods                              //
    //////////////////////////////////////////

    // ====== Public methods ======

    /**
     * Add a widget to the layout.
     *
     * @method addChild
     * @param {photonui.Widget} widget The widget to add.
     * @param {Object} layoutOption Specific option for the layout (optional).
     */
    addChild: function (w, l) {
        this.__popupMenu.addChild(w, l);
        this._updateItemsBinding();
    },

    /**
     * Destroy the widget.
     *
     * @method destroy
     */
    destroy: function () {
        if (this.__displayValue) {
            this.__displayValue.destroy();
        }
        if (this.__popupMenu) {
            this.__popupMenu.destroy();
        }
        this.$super();
    },

    // ====== Private methods ======

    /**
     * Build the widget HTML.
     *
     * @method _buildHtml
     * @private
     */
    _buildHtml: function () {
        this.__html.select = document.createElement("div");
        this.__html.select.className = "photonui-widget photonui-select";
        this.__html.select.tabIndex = "0";
    },

    /**
     * Update the popup items binding.
     *
     * @method _updateItemsBinding
     * @private
     */
    _updateItemsBinding: function () {
        var items = this.__popupMenu.children;

        for (var i = 0 ; i < items.length ; i++) {
            if (items[i] instanceof MenuItem) {
                items[i].registerCallback(this.name + "-click",
                        "click", this.__onItemClicked, this);
            }
        }
    },

    //////////////////////////////////////////
    // Internal Events Callbacks            //
    //////////////////////////////////////////

    /**
     * @method __onClick
     * @private
     * @param event
     */
    __onClick: function (event) {
        if (!this._minWidthDefined) {
            this.popupMinWidth = this.offsetWidth;
        }
        if (this.__popupMenu.visible) {
            this.__popupMenu.hide();
        } else {
            this.__popupMenu.popupWidget(this);
        }
    },

    /**
     * @method __onMouseDown
     * @private
     * @param event
     */
    __onMouseDown: function (event) {
        if (this.__popupMenu.visible) {
            event.stopPropagation();
        }
    },

    /**
     * @method __onItemClicked
     * @private
     * @param {photonui.MenuItem} widget
     */
    __onItemClicked: function (widget) {
        this.value = widget.value;
        this._callCallbacks("value-changed", [this.value]);
    },

    /**
     * @method __onLocaleChanged
     * @private
     */
    __onLocaleChanged: function () {
        this.$super();
        this.setValue(this.value, true);
    }

});

module.exports = Select;