# Source: ui/base/abstract-button.js

     /*global require, exports*/

    /**
     * @module montage/ui/base/abstract-button
     * @requires montage/ui/base/abstract-control
     * @requires montage/composer/press-composer
     */
    var AbstractControl = require("./abstract-control").AbstractControl,
        PressComposer = require("../../composer/press-composer").PressComposer;

    /**
     * @class AbstractButton
     * @extends AbstractControl
     */
    var AbstractButton = exports.AbstractButton = AbstractControl.specialize( /** @lends AbstractButton.prototype # */ {

        /**
         * Dispatched when the button is activated through a mouse click, finger
         * tap, or when focused and the spacebar is pressed.
         *
         * @event AbstractButton#action
         * @property {Map} detail - The detail object as defined in {@link AbstractControl#detail}
         */

        /**
         * Dispatched when the button is pressed for a period of time, set by
         * {@link AbstractButton#holdThreshold}.
         *
         * @event AbstractButton#longAction
         * @property {Map detail - The detail object as defined in {@link AbstractControl#detail}
         */

        /**
         * @constructs
         */
        constructor: {
            value: function AbstractButton() {
                if(this.constructor ===  AbstractButton) {
                    throw new Error("AbstractButton cannot be instantiated.");
                }
                this._pressComposer = new PressComposer();
                this.addComposer(this._pressComposer);
                this._pressComposer.defineBinding("longPressThreshold ", {"<-": "holdThreshold", source: this});

                //classList management
                this.defineBinding("classList.has('montage--disabled')", {"<-": "!enabled"});
                this.defineBinding("classList.has('montage--active')", {"<-": "active"});
            }
        },

        /**
         * Enables or disables the Button from user input. When this property is
         * set to `false`, the "montage--disabled" CSS style is applied to the
         * button's DOM element during the next draw cycle. When set to `true` the
         * "montage--disabled" CSS class is removed from the element's class
         * list.
         * @property {boolean} value
         */
        enabled: {
            value: true
        },

        /**
         * @private
         */
        _preventFocus: {
            value: false
        },

        /**
         * Specifies whether the button should receive focus or not.
         *
         * @property {boolean}
         * @default false
         */
        preventFocus: {
            get: function () {
                return this._preventFocus;
            },
            set: function (value) {
                this._preventFocus = !!value;
                this.needsDraw = true;
            }
        },

        acceptsActiveTarget: {
            value: function () {
                return ! this._preventFocus;
            }
        },

        willBecomeActiveTarget: {
            value: function (previousActiveTarget) {

            }
        },

        /**
         * Stores the node that contains this button's value. Only used for
         * non-`<input>` elements.
         * @private
         */
        _labelNode: {value:undefined, enumerable: false},

        _label: {value: undefined, enumerable: false},

        /**
         * The displayed text on the button. In an `input` element this is taken from the element's `value` attribute.
         * On any other element (including `button`) this is the first child node which is a text node.
         * If one isn't found then it will be created. If the button has a non-null `converter` property,
         * the converter object's `convert()` method is called on the value before being assigned to the button instance.
         *
         * @property {string}
         * @default undefined
         */
        label: {
            get: function () {
                return this._label;
            },
            set: function (value) {
                if (typeof value !== "undefined" && this.converter) {
                    try {
                        value = this.converter.convert(value);
                        if (this.error) {
                            this.error = null;
                        }
                    } catch(e) {
                        // unable to convert - maybe error
                        this.error = e;
                    }
                }
                this._label = "" + value;
                this.needsDraw = true;
            }
        },

        /**
         * The amount of time in milliseconds the user must press and hold the
         * button a `longAction` event is dispatched. The default is 1 second.
         * @property {number} value
         * @default 1000
         */
        holdThreshold: {
            value: 1000
        },

        /**
         * @property {PressComposer} value
         * @default null
         * @private
         */
        _pressComposer: {
            value: null
        },

        /**
         * @private
         */
        _active: {
            value: false
        },

        /**
         * This property is true when the button is being interacted with, either
         * through mouse click or touch event, otherwise false.
         *
         * @property {boolean}
         * @default false
         */
        active: {
            get: function () {
                return this._active;
            },
            set: function (value) {
                this._active = value;
                this.needsDraw = true;
            }
        },

        prepareForActivationEvents: {
            value: function () {
                this._pressComposer.addEventListener("pressStart", this, false);
                this._pressComposer.addEventListener("press", this, false);
                this._pressComposer.addEventListener("pressCancel", this, false);
            }
        },

        // Optimisation
        addEventListener: {
            value: function (type, listener, useCapture) {
                AbstractControl.prototype.addEventListener.call(this, type, listener, useCapture);
                if (type === "longAction") {
                    this._pressComposer.addEventListener("longPress", this, false);
                }
            }
        },

        // Handlers

        /**
         * Called when the user starts interacting with the component.
         *
         * @private
         */
        handlePressStart: {
            value: function (event) {
                this.active = true;

                if (event.touch) {
                    // Prevent default on touchmove so that if we are inside a scroller,
                    // it scrolls and not the webpage
                    document.addEventListener("touchmove", this, false);
                }

                if (!this._preventFocus) {
                    this._element.focus();
                }
            }
        },

        /**
         * Called when the user has interacted with the button.
         *
         * @private
         */
        handlePress: {
            value: function (event) {
                this.active = false;
                this.dispatchActionEvent();
                document.removeEventListener("touchmove", this, false);
            }
        },

        handleKeyup: {
            value: function (event) {
                // action event on spacebar
                if (event.keyCode === 32) {
                    this.active = false;
                    this.dispatchActionEvent();
                }
            }
        },

        handleLongPress: {
            value: function (event) {
                // When we fire the "longAction" event we don't want to fire the
                // "action" event as well.
                this._pressComposer.cancelPress();

                var longActionEvent = document.createEvent("CustomEvent");
                longActionEvent.initCustomEvent("longAction", true, true, null);
                this.dispatchEvent(longActionEvent);
            }
        },

        /**
         * Called when all interaction is over.
         * @private
         */
        handlePressCancel: {
            value: function (event) {
                this.active = false;
                document.removeEventListener("touchmove", this, false);
            }
        },

        /**
         * @private
         */
        handleTouchmove: {
            value: function (event) {
                event.preventDefault();
            }
        },

        /**
         * If this is an input element then the label is handled differently.
         * @private
         */
        isInputElement: {
            value: false
        },

        enterDocument: {
            value: function (firstDraw) {
                if(firstDraw) {
                    this.isInputElement = (this.originalElement.tagName === "INPUT");
                    // Only take the value from the element if it hasn't been set
                    // elsewhere (i.e. in the serialization)
                    if (this.isInputElement) {
                        if (this._label === undefined) {
                            this.label = this.originalElement.value;
                        }
                    } else {
                        if (!this.originalElement.firstChild) {
                            this.originalElement.appendChild(document.createTextNode(""));
                        }
                        this._labelNode = this.originalElement.firstChild;
                        if (this._label === undefined) {
                            this.label = this._labelNode.data;
                        }
                    }

                    //this.classList.add("montage-Button");
                    this.element.setAttribute("role", "button");
                    this.element.addEventListener("keyup", this, false);
                }
            }
        },

        /**
         * Draws the label to the DOM.
         * @function
         * @private
         */
        _drawLabel: {
            enumerable: false,
            value: function (value) {
                if (this.isInputElement) {
                    this._element.value = value;
                } else if (this._labelNode) {
                    this._labelNode.data = value;
                }
            }
        },

        draw: {
            value: function () {
                if (this._elementNeedsTabIndex()) {
                    if (this._preventFocus) {
                        this.element.removeAttribute("tabindex");
                    } else {
                        this.element.setAttribute("tabindex", "-1");
                    }
                }

                if (this.isInputElement) {
                    this.element.disabled = !this.enabled;
                }

                this._drawLabel(this.label);
            }
        },

        _elementNeedsTabIndexRegex: {
            value: /INPUT|TEXTAREA|A|SELECT|BUTTON|LABEL/
        },

        _elementNeedsTabIndex: {
            value: function () {
                return this.element.tagName.match(this._elementNeedsTabIndexRegex) === null;
            }
        }

    });