245 lines
6.9 KiB
JavaScript
245 lines
6.9 KiB
JavaScript
|
"use strict";
|
||
|
|
||
|
// Clickable button
|
||
|
Toolkit.Button = class Button extends Toolkit.Component {
|
||
|
|
||
|
// Object constructor
|
||
|
constructor(application, options) {
|
||
|
super(application, "div");
|
||
|
options = options || {};
|
||
|
|
||
|
// Configure instance fields
|
||
|
this.blurListeners = [];
|
||
|
this.clickListeners = [];
|
||
|
this.enabled = "enabled" in options ? options.enabled : true;
|
||
|
this.focusListeners = [];
|
||
|
this.pressed = "pressed" in options ? options.pressed : false;
|
||
|
this.tabStop = true;
|
||
|
this.text = options.text || "";
|
||
|
this.toggleable = "toggleable"in options?options.toggleable:false;
|
||
|
|
||
|
// Configure element
|
||
|
this.element.type = "button";
|
||
|
this.element.setAttribute("role", "button");
|
||
|
this.element.style.cursor = "default";
|
||
|
this.element.style.position = "relative";
|
||
|
this.element.style.userSelect = "none";
|
||
|
this.element.addEventListener("blur" , e=>this.onblur (e));
|
||
|
this.element.addEventListener("focus" , e=>this.onfocus (e));
|
||
|
this.element.addEventListener("keydown" , e=>this.onkeydown (e));
|
||
|
this.element.addEventListener("pointerdown", e=>this.onpointerdown(e));
|
||
|
this.element.addEventListener("pointermove", e=>this.onpointermove(e));
|
||
|
this.element.addEventListener("pointerup" , e=>this.onpointerup (e));
|
||
|
|
||
|
// Configure properties
|
||
|
this.setEnabled (this.enabled );
|
||
|
this.setPressed (this.pressed );
|
||
|
this.setTabStop (this.tabStop );
|
||
|
this.setText (this.text );
|
||
|
this.setToggleable(this.toggleable);
|
||
|
application.addComponent(this);
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
///////////////////////////// Public Methods //////////////////////////////
|
||
|
|
||
|
// Add a callback for blur events
|
||
|
addBlurListener(listener) {
|
||
|
if (this.blurListeners.indexOf(listener) == -1)
|
||
|
this.blurListeners.push(listener);
|
||
|
}
|
||
|
|
||
|
// Add a callback for click events
|
||
|
addClickListener(listener) {
|
||
|
if (this.clickListeners.indexOf(listener) == -1)
|
||
|
this.clickListeners.push(listener);
|
||
|
}
|
||
|
|
||
|
// Add a callback for focus events
|
||
|
addFocusListener(listener) {
|
||
|
if (this.focusListeners.indexOf(listener) == -1)
|
||
|
this.focusListeners.push(listener);
|
||
|
}
|
||
|
|
||
|
// Determine whether the component participates in the tab sequence
|
||
|
getTabStop() {
|
||
|
return this.tabStop;
|
||
|
}
|
||
|
|
||
|
// Retrieve the control's text
|
||
|
getText() {
|
||
|
return this.text;
|
||
|
}
|
||
|
|
||
|
// Determine whether the control is enabled
|
||
|
isEnabled() {
|
||
|
return this.enabled;
|
||
|
}
|
||
|
|
||
|
// Determine the toggle button's active state
|
||
|
isPressed() {
|
||
|
return this.pressed;
|
||
|
}
|
||
|
|
||
|
// Determine whether the button is a toggle button
|
||
|
isToggleable() {
|
||
|
return this.toggleable;
|
||
|
}
|
||
|
|
||
|
// Specify whether the control is enabled
|
||
|
setEnabled(enabled) {
|
||
|
this.enabled = enabled = !!enabled;
|
||
|
if (enabled)
|
||
|
this.element.removeAttribute("disabled");
|
||
|
else this.element.setAttribute("disabled", "");
|
||
|
}
|
||
|
|
||
|
// Specify whether the component participates in the regular tab sequence
|
||
|
setTabStop(tabStop) {
|
||
|
this.tabStop = tabStop = !!tabStop;
|
||
|
this.element.setAttribute("tabindex", tabStop ? "0" : "-1");
|
||
|
}
|
||
|
|
||
|
// Specify the toggle button's active state
|
||
|
setPressed(pressed) {
|
||
|
this.pressed = pressed = !!pressed;
|
||
|
if (this.toggleable)
|
||
|
this.element.setAttribute("aria-pressed", pressed);
|
||
|
}
|
||
|
|
||
|
// Specify the control's text
|
||
|
setText(text) {
|
||
|
this.text = text || "";
|
||
|
this.localize();
|
||
|
}
|
||
|
|
||
|
// Specify whether the button is a toggle button
|
||
|
setToggleable(toggleable) {
|
||
|
this.toggleable = toggleable = !!toggleable;
|
||
|
if (toggleable)
|
||
|
this.element.setAttribute("aria-pressed", this.pressed);
|
||
|
else this.element.removeAttribute("aria-pressed");
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
///////////////////////////// Private Methods /////////////////////////////
|
||
|
|
||
|
// Actions when the button is activated
|
||
|
activate(e) {
|
||
|
if (this.toggleable)
|
||
|
this.setPressed(!this.pressed);
|
||
|
for (let listener of this.clickListeners)
|
||
|
listener(e, this);
|
||
|
}
|
||
|
|
||
|
// Update display text with localized strings
|
||
|
localize() {
|
||
|
let text = this.text;
|
||
|
if (this.application)
|
||
|
text = this.application.translate(text, this);
|
||
|
this.element.innerText = text;
|
||
|
this.element.setAttribute("aria-label", text);
|
||
|
}
|
||
|
|
||
|
// Blur event handler
|
||
|
onblur(e) {
|
||
|
for (let listener of this.blurListeners)
|
||
|
listener(e, this);
|
||
|
this.focusChanged(
|
||
|
this, e.relatedTarget ? e.relatedTarget.component : null);
|
||
|
}
|
||
|
|
||
|
// Focus event handler
|
||
|
onfocus(e) {
|
||
|
for (let listener of this.focusListeners)
|
||
|
listener(e, this);
|
||
|
this.focusChanged(
|
||
|
e.relatedTarget ? e.relatedTarget.component : null, this);
|
||
|
}
|
||
|
|
||
|
// Key press event handler
|
||
|
onkeydown(e) {
|
||
|
|
||
|
// Error checking
|
||
|
if (e.key != " " && e.key != "Enter")
|
||
|
return;
|
||
|
|
||
|
// Configure event
|
||
|
e.preventDefault();
|
||
|
e.stopPropagation();
|
||
|
|
||
|
// The button was activated
|
||
|
this.activate(e);
|
||
|
}
|
||
|
|
||
|
// Pointer down event handler
|
||
|
onpointerdown(e) {
|
||
|
|
||
|
// Error checking
|
||
|
if (e.button != 0 || this.element.hasPointerCapture(e.pointerId))
|
||
|
return;
|
||
|
|
||
|
// Configure event
|
||
|
e.preventDefault();
|
||
|
e.stopPropagation();
|
||
|
|
||
|
// Configure element
|
||
|
this.element.focus();
|
||
|
this.element.setPointerCapture(e.pointerId);
|
||
|
this.element.setAttribute("active", "");
|
||
|
}
|
||
|
|
||
|
// Pointer move event handler
|
||
|
onpointermove(e) {
|
||
|
|
||
|
// Error checking
|
||
|
if (!this.element.hasPointerCapture(e.pointerId))
|
||
|
return;
|
||
|
|
||
|
// Configure event
|
||
|
e.preventDefault();
|
||
|
e.stopPropagation();
|
||
|
|
||
|
// Working variables
|
||
|
let bounds = this.element.getBoundingClientRect();
|
||
|
let active =
|
||
|
e.x >= bounds.x && e.x < bounds.x + bounds.width &&
|
||
|
e.y >= bounds.y && e.y < bounds.y + bounds.height
|
||
|
;
|
||
|
|
||
|
// Configure event
|
||
|
if (active)
|
||
|
this.element.setAttribute("active", "");
|
||
|
else this.element.removeAttribute("active");
|
||
|
}
|
||
|
|
||
|
// Pointer up event handler
|
||
|
onpointerup(e) {
|
||
|
|
||
|
// Error checking
|
||
|
if (!this.element.hasPointerCapture(e.pointerId))
|
||
|
return;
|
||
|
|
||
|
// Configure event
|
||
|
e.preventDefault();
|
||
|
e.stopPropagation();
|
||
|
|
||
|
// Working variables
|
||
|
let active = this.element.hasAttribute("active");
|
||
|
|
||
|
// Configure element
|
||
|
this.element.releasePointerCapture(e.pointerId);
|
||
|
this.element.removeAttribute("active");
|
||
|
|
||
|
// The pointer was released without activating the button
|
||
|
if (!active)
|
||
|
return;
|
||
|
|
||
|
// The button was activated
|
||
|
this.activate(e);
|
||
|
}
|
||
|
|
||
|
};
|