@qooxdoo/framework
Version:
The JS Framework for Coders
361 lines (312 loc) • 9.3 kB
JavaScript
/* ************************************************************************
qooxdoo - the new era of web development
http://qooxdoo.org
Copyright:
2004-2008 1&1 Internet AG, Germany, http://www.1und1.de
License:
MIT: https://opensource.org/licenses/MIT
See the LICENSE file in the project's top-level directory for details.
Authors:
* Martin Wittemann (martinwittemann)
************************************************************************ */
/**
* A toggle Button widget
*
* If the user presses the button by tapping on it pressing the enter or
* space key, the button toggles between the pressed an not pressed states.
*/
qx.Class.define("qx.ui.form.ToggleButton", {
extend: qx.ui.basic.Atom,
include: [qx.ui.core.MExecutable],
implement: [
qx.ui.form.IBooleanForm,
qx.ui.form.IExecutable,
qx.ui.form.IRadioItem
],
/*
*****************************************************************************
CONSTRUCTOR
*****************************************************************************
*/
/**
* Creates a ToggleButton.
*
* @param label {String} The text on the button.
* @param icon {String} An URI to the icon of the button.
*/
construct(label, icon) {
super(label, icon);
// register pointer events
this.addListener("pointerover", this._onPointerOver);
this.addListener("pointerout", this._onPointerOut);
this.addListener("pointerdown", this._onPointerDown);
this.addListener("pointerup", this._onPointerUp);
// register keyboard events
this.addListener("keydown", this._onKeyDown);
this.addListener("keyup", this._onKeyUp);
// register execute event
this.addListener("execute", this._onExecute, this);
// ARIA attrs
const contentEl = this.getContentElement();
contentEl.setAttribute("role", "button");
contentEl.setAttribute("aria-pressed", false);
},
/*
*****************************************************************************
PROPERTIES
*****************************************************************************
*/
properties: {
// overridden
appearance: {
refine: true,
init: "toggle-button"
},
// overridden
focusable: {
refine: true,
init: true
},
/** The value of the widget. True, if the widget is checked. */
value: {
check: "Boolean",
nullable: true,
event: "changeValue",
apply: "_applyValue",
init: false
},
/** The assigned qx.ui.form.RadioGroup which handles the switching between registered buttons. */
group: {
check: "qx.ui.form.RadioGroup",
nullable: true,
apply: "_applyGroup"
},
/**
* Whether the button has a third state. Use this for tri-state checkboxes.
*
* When enabled, the value null of the property value stands for "undetermined",
* while true is mapped to "enabled" and false to "disabled" as usual. Note
* that the value property is set to false initially.
*
*/
triState: {
check: "Boolean",
apply: "_applyTriState",
nullable: true,
init: null
},
/**
* The behavior when the button is executed (e.g. clicked). Only useful for tri-state checkboxes.
* If "cycle" is set, the button cycles through the states "disabled", "undetermined", and "enabled"
* If "toggle" is set, the button toggles between "disabled" and "enabled".
*/
executeBehavior: {
check: ["cycle", "toggle"],
init: "toggle"
},
/**
* Whether the field is read only
*/
readOnly: {
check: "Boolean",
event: "changeReadOnly",
init: false
}
},
/*
*****************************************************************************
MEMBERS
*****************************************************************************
*/
members: {
/** The assigned {@link qx.ui.form.RadioGroup} which handles the switching between registered buttons */
_applyGroup(value, old) {
if (old) {
old.remove(this);
}
if (value) {
value.add(this);
}
},
/**
* Changes the state of the button dependent on the checked value.
*
* @param value {Boolean} Current value
* @param old {Boolean} Previous value
*/
_applyValue(value, old) {
value ? this.addState("checked") : this.removeState("checked");
let ariaPressed = Boolean(value);
if (this.isTriState()) {
if (value === null) {
ariaPressed = "mixed";
this.addState("undetermined");
} else if (old === null) {
this.removeState("undetermined");
}
}
this.getContentElement().setAttribute("aria-pressed", ariaPressed);
},
/**
* Apply value property when triState property is modified.
*
* @param value {Boolean} Current value
* @param old {Boolean} Previous value
*/
_applyTriState(value, old) {
this._applyValue(this.getValue());
},
/**
* Handler for the execute event.
*
* @param e {qx.event.type.Event} The execute event.
*/
_onExecute(e) {
if (this.getReadOnly()) {
return;
}
if (this.isTriState() && this.getExecuteBehavior() === "cycle") {
let newValue;
let currentValue = this.getValue();
if (currentValue === null) {
newValue = true;
} else if (currentValue === true) {
newValue = false;
} else {
newValue = null;
}
this.setValue(newValue);
} else {
this.toggleValue();
}
},
/**
* Listener method for "pointerover" event.
* <ul>
* <li>Adds state "hovered"</li>
* <li>Removes "abandoned" and adds "pressed" state (if "abandoned" state is set)</li>
* </ul>
*
* @param e {qx.event.type.Pointer} Pointer event
*/
_onPointerOver(e) {
if (e.getTarget() !== this) {
return;
}
if (this.getReadOnly()) {
return;
}
this.addState("hovered");
if (this.hasState("abandoned")) {
this.removeState("abandoned");
this.addState("pressed");
}
},
/**
* Listener method for "pointerout" event.
* <ul>
* <li>Removes "hovered" state</li>
* <li>Adds "abandoned" state (if "pressed" state is set)</li>
* <li>Removes "pressed" state (if "pressed" state is set and button is not checked)
* </ul>
*
* @param e {qx.event.type.Pointer} pointer event
*/
_onPointerOut(e) {
if (e.getTarget() !== this) {
return;
}
this.removeState("hovered");
if (this.hasState("pressed")) {
if (!this.getValue()) {
this.removeState("pressed");
}
this.addState("abandoned");
}
},
/**
* Listener method for "pointerdown" event.
* <ul>
* <li>Activates capturing</li>
* <li>Removes "abandoned" state</li>
* <li>Adds "pressed" state</li>
* </ul>
*
* @param e {qx.event.type.Pointer} pointer event
*/
_onPointerDown(e) {
if (!e.isLeftPressed()) {
return;
}
if (this.getReadOnly()) {
return;
}
// Activate capturing if the button get a pointerout while
// the button is pressed.
this.capture();
this.removeState("abandoned");
this.addState("pressed");
e.stopPropagation();
},
/**
* Listener method for "pointerup" event.
* <ul>
* <li>Releases capturing</li>
* <li>Removes "pressed" state (if not "abandoned" state is set and "pressed" state is set)</li>
* <li>Removes "abandoned" state (if set)</li>
* <li>Toggles {@link #value} (if state "abandoned" is not set and state "pressed" is set)</li>
* </ul>
*
* @param e {qx.event.type.Pointer} pointer event
*/
_onPointerUp(e) {
this.releaseCapture();
if (this.hasState("abandoned")) {
this.removeState("abandoned");
} else if (this.hasState("pressed")) {
this.execute();
}
this.removeState("pressed");
e.stopPropagation();
},
/**
* Listener method for "keydown" event.<br/>
* Removes "abandoned" and adds "pressed" state
* for the keys "Enter" or "Space"
*
* @param e {Event} Key event
*/
_onKeyDown(e) {
if (this.getReadOnly()) {
return;
}
switch (e.getKeyIdentifier()) {
case "Enter":
case "Space":
this.removeState("abandoned");
this.addState("pressed");
e.stopPropagation();
}
},
/**
* Listener method for "keyup" event.<br/>
* Removes "abandoned" and "pressed" state (if "pressed" state is set)
* for the keys "Enter" or "Space". It also toggles the {@link #value} property.
*
* @param e {Event} Key event
*/
_onKeyUp(e) {
if (!this.hasState("pressed")) {
return;
}
switch (e.getKeyIdentifier()) {
case "Enter":
case "Space":
this.removeState("abandoned");
this.execute();
this.removeState("pressed");
e.stopPropagation();
}
}
}
});