@qooxdoo/framework
Version:
The JS Framework for Coders
525 lines (448 loc) • 13.6 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:
* Sebastian Werner (wpbasti)
* Andreas Ecker (ecker)
* Christian Hagendorn (chris_schmidt)
* Martin Wittemann (martinwittemann)
************************************************************************ */
/**
* The radio group handles a collection of items from which only one item
* can be selected. Selection another item will deselect the previously selected
* item.
*
* This class is e.g. used to create radio groups or {@link qx.ui.form.RadioButton}
* or {@link qx.ui.toolbar.RadioButton} instances.
*
* We also offer a widget for the same purpose which uses this class. So if
* you like to act with a widget instead of a pure logic coupling of the
* widgets, take a look at the {@link qx.ui.form.RadioButtonGroup} widget.
*/
qx.Class.define("qx.ui.form.RadioGroup", {
extend: qx.core.Object,
implement: [
qx.ui.core.ISingleSelection,
qx.ui.form.IField,
qx.ui.form.IForm,
qx.ui.form.IModelSelection
],
include: [qx.ui.core.MSingleSelectionHandling, qx.ui.form.MModelSelection],
/*
*****************************************************************************
CONSTRUCTOR
*****************************************************************************
*/
/**
* @param varargs {qx.core.Object} A variable number of items, which are
* initially added to the radio group, the first item will be selected.
*/
construct(varargs) {
super();
// create item array
this.__items = [];
// add listener before call add!!!
this.addListener("changeSelection", this.__onChangeSelection, this);
if (varargs != null) {
this.add.apply(this, arguments);
}
},
/*
*****************************************************************************
PROPERTIES
*****************************************************************************
*/
properties: {
/**
* The property name in each of the added widgets that is grouped
*/
groupedProperty: {
check: "String",
apply: "_applyGroupedProperty",
event: "changeGroupedProperty",
init: "value"
},
/**
* The property name in each of the added widgets that is informed of the
* RadioGroup object it is a member of
*/
groupProperty: {
check: "String",
event: "changeGroupProperty",
init: "group"
},
/**
* Whether the radio group is enabled
*/
enabled: {
check: "Boolean",
apply: "_applyEnabled",
event: "changeEnabled",
init: true
},
/**
* Whether the selection should wrap around. This means that the successor of
* the last item is the first item.
*/
wrap: {
check: "Boolean",
init: true
},
/**
* If is set to <code>true</code> the selection could be empty,
* otherwise is always one <code>RadioButton</code> selected.
*/
allowEmptySelection: {
check: "Boolean",
init: false,
apply: "_applyAllowEmptySelection"
},
/**
* Flag signaling if the group at all is valid. All children will have the
* same state.
*/
valid: {
check: "Boolean",
init: true,
apply: "_applyValid",
event: "changeValid"
},
/**
* Flag signaling if the group is required.
*/
required: {
check: "Boolean",
init: false,
event: "changeRequired"
},
/**
* Message which is shown in an invalid tooltip.
*/
invalidMessage: {
check: "String",
init: "",
event: "changeInvalidMessage",
apply: "_applyInvalidMessage"
},
/**
* Message which is shown in an invalid tooltip if the {@link #required} is
* set to true.
*/
requiredInvalidMessage: {
check: "String",
nullable: true,
event: "changeInvalidMessage"
}
},
/*
*****************************************************************************
MEMBERS
*****************************************************************************
*/
members: {
/** @type {qx.ui.form.IRadioItem[]} The items of the radio group */
__items: null,
/*
---------------------------------------------------------------------------
UTILITIES
---------------------------------------------------------------------------
*/
/**
* Get all managed items
*
* @return {qx.ui.form.IRadioItem[]} All managed items.
*/
getItems() {
return this.__items;
},
/*
---------------------------------------------------------------------------
REGISTRY
---------------------------------------------------------------------------
*/
/**
* Add the passed items to the radio group.
*
* @param varargs {qx.ui.form.IRadioItem} A variable number of items to add.
*/
add(varargs) {
var items = this.__items;
var item;
var groupedProperty = this.getGroupedProperty();
var groupedPropertyUp = qx.lang.String.firstUp(groupedProperty);
for (var i = 0, l = arguments.length; i < l; i++) {
item = arguments[i];
if (items.includes(item)) {
continue;
}
// Register listeners
item.addListener(
"change" + groupedPropertyUp,
this._onItemChangeChecked,
this
);
// Push RadioButton to array
items.push(item);
// Inform radio button about new group
item.set(this.getGroupProperty(), this);
// Need to update internal value?
if (item.get(groupedProperty)) {
this.setSelection([item]);
}
}
// Select first item when only one is registered
if (
!this.isAllowEmptySelection() &&
items.length > 0 &&
!this.getSelection()[0]
) {
this.setSelection([items[0]]);
}
},
/**
* Remove an item from the radio group.
*
* @param item {qx.ui.form.IRadioItem} The item to remove.
*/
remove(item) {
var items = this.__items;
var groupedProperty = this.getGroupedProperty();
var groupedPropertyUp = qx.lang.String.firstUp(groupedProperty);
if (items.includes(item)) {
// Remove RadioButton from array
qx.lang.Array.remove(items, item);
// Inform radio button about new group
if (item.get(this.getGroupProperty()) === this) {
item.reset(this.getGroupProperty());
}
// Deregister listeners
item.removeListener(
"change" + groupedPropertyUp,
this._onItemChangeChecked,
this
);
// if the radio was checked, set internal selection to null
if (item.get(groupedProperty)) {
this.resetSelection();
}
}
},
/**
* Returns an array containing the group's items.
*
* @return {qx.ui.form.IRadioItem[]} The item array
*/
getChildren() {
return this.__items;
},
/*
---------------------------------------------------------------------------
LISTENER FOR ITEM CHANGES
---------------------------------------------------------------------------
*/
/**
* Event listener for <code>changeValue</code> event of every managed item.
*
* @param e {qx.event.type.Data} Data event
*/
_onItemChangeChecked(e) {
var item = e.getTarget();
var groupedProperty = this.getGroupedProperty();
if (item.get(groupedProperty)) {
this.setSelection([item]);
} else if (this.getSelection()[0] == item) {
this.resetSelection();
}
},
/*
---------------------------------------------------------------------------
APPLY ROUTINES
---------------------------------------------------------------------------
*/
// property apply
_applyGroupedProperty(value, old) {
var item;
var oldFirstUp = qx.lang.String.firstUp(old);
var newFirstUp = qx.lang.String.firstUp(value);
for (var i = 0; i < this.__items.length; i++) {
item = this.__items[i];
// remove the listener for the old change event
item.removeListener(
"change" + oldFirstUp,
this._onItemChangeChecked,
this
);
// add the listener for the new change event
item.removeListener(
"change" + newFirstUp,
this._onItemChangeChecked,
this
);
}
},
// property apply
_applyInvalidMessage(value, old) {
for (var i = 0; i < this.__items.length; i++) {
this.__items[i].setInvalidMessage(value);
}
},
// property apply
_applyValid(value, old) {
for (var i = 0; i < this.__items.length; i++) {
this.__items[i].setValid(value);
}
},
// property apply
_applyEnabled(value, old) {
var items = this.__items;
if (value == null) {
for (var i = 0, l = items.length; i < l; i++) {
items[i].resetEnabled();
}
} else {
for (var i = 0, l = items.length; i < l; i++) {
items[i].setEnabled(value);
}
}
},
// property apply
_applyAllowEmptySelection(value, old) {
if (!value && this.isSelectionEmpty()) {
this.resetSelection();
}
},
/*
---------------------------------------------------------------------------
SELECTION
---------------------------------------------------------------------------
*/
/**
* Select the item following the given item.
*/
selectNext() {
var item = this.getSelection()[0];
var items = this.__items;
var index = items.indexOf(item);
if (index == -1) {
return;
}
var i = 0;
var length = items.length;
// Find next enabled item
if (this.getWrap()) {
index = (index + 1) % length;
} else {
index = Math.min(index + 1, length - 1);
}
while (i < length && !items[index].getEnabled()) {
index = (index + 1) % length;
i++;
}
this.setSelection([items[index]]);
},
/**
* Select the item previous the given item.
*/
selectPrevious() {
var item = this.getSelection()[0];
var items = this.__items;
var index = items.indexOf(item);
if (index == -1) {
return;
}
var i = 0;
var length = items.length;
// Find previous enabled item
if (this.getWrap()) {
index = (index - 1 + length) % length;
} else {
index = Math.max(index - 1, 0);
}
while (i < length && !items[index].getEnabled()) {
index = (index - 1 + length) % length;
i++;
}
this.setSelection([items[index]]);
},
/*
---------------------------------------------------------------------------
HELPER METHODS FOR SELECTION API
---------------------------------------------------------------------------
*/
/**
* Returns the items for the selection.
*
* @return {qx.ui.form.IRadioItem[]} Items to select.
*/
_getItems() {
return this.getItems();
},
/**
* Returns if the selection could be empty or not.
*
* @return {Boolean} <code>true</code> If selection could be empty,
* <code>false</code> otherwise.
*/
_isAllowEmptySelection() {
return this.isAllowEmptySelection();
},
/**
* Returns whether the item is selectable. In opposite to the default
* implementation (which checks for visible items) every radio button
* which is part of the group is selected even if it is currently not visible.
*
* @param item {qx.ui.form.IRadioItem} The item to check if its selectable.
* @return {Boolean} <code>true</code> if the item is part of the radio group
* <code>false</code> otherwise.
*/
_isItemSelectable(item) {
return this.__items.indexOf(item) != -1;
},
/**
* Event handler for <code>changeSelection</code>.
*
* @param e {qx.event.type.Data} Data event.
*/
__onChangeSelection(e) {
var value = e.getData()[0];
var old = e.getOldData()[0];
var groupedProperty = this.getGroupedProperty();
if (old) {
old.set(groupedProperty, false);
}
if (value) {
value.set(groupedProperty, true);
// If Group is focused, the selection was changed by keyboard. Switch focus to new value
if (this.__isGroupFocused() && value.isFocusable()) {
value.focus();
}
}
},
/**
* Checks if this group is focused by checking focused state of each item
* @returns {Boolean} result
*/
__isGroupFocused() {
const focusHandler = qx.ui.core.FocusHandler.getInstance();
for (const item of this._getItems()) {
if (focusHandler.isFocused(item)) {
return true;
}
}
return false;
}
},
/*
*****************************************************************************
DESTRUCTOR
*****************************************************************************
*/
destruct() {
this._disposeArray("__items");
}
});