/*global require, exports*/
/**
* @module montage/ui/base/abstract-toggle-button.reel
*/
var AbstractControl = require("./abstract-control").AbstractControl,
PressComposer = require("../../composer/press-composer").PressComposer,
KeyComposer = require("../../composer/key-composer").KeyComposer;
/**
* @class AbstractToggleButton
* @extends AbstractControl
* @fires action
* @fires longAction
*/
var AbstractToggleButton = exports.AbstractToggleButton = AbstractControl.specialize( /** @lends AbstractToggleButton# */ {
/**
* Dispatched when the toggle button is activated through a mouse click,
* finger tap, or when focused and the spacebar is pressed.
* @event action
* @memberof AbstractToggleButton
* @param {Event} event
*/
/**
* Dispatched when the toggle button is pressed for a period of time, set
* by {@link holdThreshold}.
* @event longAction
* @memberof AbstractToggleButton
* @param {Event} event
*/
/**
* @private
*/
constructor: {
value: function () {
if(this.constructor === AbstractToggleButton) {
throw new Error("AbstractToggleButton cannot be instantiated.");
}
this._pressComposer = new PressComposer();
this._pressComposer.defineBinding("longPressThreshold ", {
"<-": "holdThreshold",
source: this
});
this.addComposer(this._pressComposer);
this._keyComposer = new KeyComposer();
this._keyComposer.component = this;
this._keyComposer.keys = "space";
this.addComposer(this._keyComposer);
this.classList.add("montage-ToggleButton");
this.defineBindings({
// classList management
"classList.has('montage--disabled')": {
"<-": "!enabled"
},
"classList.has('montage--active')": {
"<-": "active"
},
"classList.has('montage-ToggleButton--pressed')": {
"<-": "pressed"
}
});
}
},
/**
* 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.
* @type {boolean}
*/
enabled: {
value: true
},
acceptsActiveTarget: {
value: function () {
return true;
}
},
/**
Stores the node that contains this button's value. Only used for
non-`<input>` elements.
@private
*/
_labelNode: {
value: null
},
_pressedLabel: {
value: "on"
},
/**
* The displayed text on the button when it is pressed.
*
* @type {string}
* @default undefined
*/
pressedLabel: {
get: function () {
return this._pressedLabel;
},
set: function (value) {
this._pressedLabel = "" + value;
this.needsDraw = true;
}
},
_unpressedLabel: {
value: "off"
},
/**
* The displayed text on the button when it is unpressed.
* @type {string}
* @default undefined
*/
unpressedLabel: {
get: function () {
return this._unpressedLabel;
},
set: function (value) {
this._unpressedLabel = "" + 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.
* @type {number}
* @default 1000
*/
holdThreshold: {
value: 1000
},
_pressComposer: {
value: null
},
_keyComposer: {
value: null
},
_active: {
value: false
},
/**
* This property is true when the button is being interacted with, either
* through mouse click or touch event, otherwise false.
* @type {boolean}
* @default false
*/
active: {
get: function () {
return this._active;
},
set: function (value) {
this._active = value;
this.needsDraw = true;
}
},
_pressed: {
value: false
},
pressed: {
set: function (value) {
if (value !== this._pressed) {
this._pressed = value;
this.needsDraw = true;
}
},
get: function () {
return this._pressed;
}
},
prepareForActivationEvents: {
value: function () {
this._pressComposer.addEventListener("pressStart", this, false);
this._pressComposer.addEventListener("press", this, false);
this._pressComposer.addEventListener("pressCancel", this, false);
this._keyComposer.addEventListener("keyPress", 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);
}
}
},
/**
* Called when the user has interacted with the button.
* @private
*/
handlePress: {
value: function (event) {
this.active = false;
if (!this.enabled) {
return;
}
this.pressed = !this.pressed;
this.dispatchActionEvent();
document.removeEventListener("touchmove", this, false);
}
},
handleKeyPress: {
value: function (event) {
this.active = false;
if (!this.enabled) {
return;
}
this.pressed = !this.pressed;
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);
}
},
handleTouchmove: {
value: function (event) {
event.preventDefault();
}
},
_toggle: {
value: function () {
this.pressed = !this.pressed;
}
},
/**
* 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");
if (!this.isInputElement) {
if (!this.originalElement.firstChild) {
this.originalElement.appendChild(document.createTextNode(""));
}
this._labelNode = this.originalElement.firstChild;
}
this.element.setAttribute("role", "button");
}
}
},
/**
* Draws the label to the DOM.
* @function
* @private
*/
_drawLabel: {
enumerable: false,
value: function (value) {
if (this.isInputElement) {
this._element.setAttribute("value", value);
} else if (this._labelNode) {
this._labelNode.data = value;
}
}
},
draw: {
value: function () {
if (this.pressed) {
this._drawLabel(this.pressedLabel);
} else {
this._drawLabel(this.unpressedLabel);
}
this.element.setAttribute("aria-pressed", this.pressed);
}
}
});