PageRenderTime 61ms CodeModel.GetById 35ms RepoModel.GetById 1ms app.codeStats 0ms

/source/class/qx/ui/form/RadioGroup.js

https://github.com/fjakobs/qooxdoo
JavaScript | 524 lines | 254 code | 70 blank | 200 comment | 42 complexity | 3ea47066ddf93d5f09e31a6a0f87c7a4 MD5 | raw file
  1. /* ************************************************************************
  2. qooxdoo - the new era of web development
  3. http://qooxdoo.org
  4. Copyright:
  5. 2004-2008 1&1 Internet AG, Germany, http://www.1und1.de
  6. License:
  7. MIT: https://opensource.org/licenses/MIT
  8. See the LICENSE file in the project's top-level directory for details.
  9. Authors:
  10. * Sebastian Werner (wpbasti)
  11. * Andreas Ecker (ecker)
  12. * Christian Hagendorn (chris_schmidt)
  13. * Martin Wittemann (martinwittemann)
  14. ************************************************************************ */
  15. /**
  16. * The radio group handles a collection of items from which only one item
  17. * can be selected. Selection another item will deselect the previously selected
  18. * item.
  19. *
  20. * This class is e.g. used to create radio groups or {@link qx.ui.form.RadioButton}
  21. * or {@link qx.ui.toolbar.RadioButton} instances.
  22. *
  23. * We also offer a widget for the same purpose which uses this class. So if
  24. * you like to act with a widget instead of a pure logic coupling of the
  25. * widgets, take a look at the {@link qx.ui.form.RadioButtonGroup} widget.
  26. */
  27. qx.Class.define("qx.ui.form.RadioGroup", {
  28. extend: qx.core.Object,
  29. implement: [
  30. qx.ui.core.ISingleSelection,
  31. qx.ui.form.IField,
  32. qx.ui.form.IForm,
  33. qx.ui.form.IModelSelection
  34. ],
  35. include: [qx.ui.core.MSingleSelectionHandling, qx.ui.form.MModelSelection],
  36. /*
  37. *****************************************************************************
  38. CONSTRUCTOR
  39. *****************************************************************************
  40. */
  41. /**
  42. * @param varargs {qx.core.Object} A variable number of items, which are
  43. * initially added to the radio group, the first item will be selected.
  44. */
  45. construct(varargs) {
  46. super();
  47. // create item array
  48. this.__items = [];
  49. // add listener before call add!!!
  50. this.addListener("changeSelection", this.__onChangeSelection, this);
  51. if (varargs != null) {
  52. this.add.apply(this, arguments);
  53. }
  54. },
  55. /*
  56. *****************************************************************************
  57. PROPERTIES
  58. *****************************************************************************
  59. */
  60. properties: {
  61. /**
  62. * The property name in each of the added widgets that is grouped
  63. */
  64. groupedProperty: {
  65. check: "String",
  66. apply: "_applyGroupedProperty",
  67. event: "changeGroupedProperty",
  68. init: "value"
  69. },
  70. /**
  71. * The property name in each of the added widgets that is informed of the
  72. * RadioGroup object it is a member of
  73. */
  74. groupProperty: {
  75. check: "String",
  76. event: "changeGroupProperty",
  77. init: "group"
  78. },
  79. /**
  80. * Whether the radio group is enabled
  81. */
  82. enabled: {
  83. check: "Boolean",
  84. apply: "_applyEnabled",
  85. event: "changeEnabled",
  86. init: true
  87. },
  88. /**
  89. * Whether the selection should wrap around. This means that the successor of
  90. * the last item is the first item.
  91. */
  92. wrap: {
  93. check: "Boolean",
  94. init: true
  95. },
  96. /**
  97. * If is set to <code>true</code> the selection could be empty,
  98. * otherwise is always one <code>RadioButton</code> selected.
  99. */
  100. allowEmptySelection: {
  101. check: "Boolean",
  102. init: false,
  103. apply: "_applyAllowEmptySelection"
  104. },
  105. /**
  106. * Flag signaling if the group at all is valid. All children will have the
  107. * same state.
  108. */
  109. valid: {
  110. check: "Boolean",
  111. init: true,
  112. apply: "_applyValid",
  113. event: "changeValid"
  114. },
  115. /**
  116. * Flag signaling if the group is required.
  117. */
  118. required: {
  119. check: "Boolean",
  120. init: false,
  121. event: "changeRequired"
  122. },
  123. /**
  124. * Message which is shown in an invalid tooltip.
  125. */
  126. invalidMessage: {
  127. check: "String",
  128. init: "",
  129. event: "changeInvalidMessage",
  130. apply: "_applyInvalidMessage"
  131. },
  132. /**
  133. * Message which is shown in an invalid tooltip if the {@link #required} is
  134. * set to true.
  135. */
  136. requiredInvalidMessage: {
  137. check: "String",
  138. nullable: true,
  139. event: "changeInvalidMessage"
  140. }
  141. },
  142. /*
  143. *****************************************************************************
  144. MEMBERS
  145. *****************************************************************************
  146. */
  147. members: {
  148. /** @type {qx.ui.form.IRadioItem[]} The items of the radio group */
  149. __items: null,
  150. /*
  151. ---------------------------------------------------------------------------
  152. UTILITIES
  153. ---------------------------------------------------------------------------
  154. */
  155. /**
  156. * Get all managed items
  157. *
  158. * @return {qx.ui.form.IRadioItem[]} All managed items.
  159. */
  160. getItems() {
  161. return this.__items;
  162. },
  163. /*
  164. ---------------------------------------------------------------------------
  165. REGISTRY
  166. ---------------------------------------------------------------------------
  167. */
  168. /**
  169. * Add the passed items to the radio group.
  170. *
  171. * @param varargs {qx.ui.form.IRadioItem} A variable number of items to add.
  172. */
  173. add(varargs) {
  174. var items = this.__items;
  175. var item;
  176. var groupedProperty = this.getGroupedProperty();
  177. var groupedPropertyUp = qx.lang.String.firstUp(groupedProperty);
  178. for (var i = 0, l = arguments.length; i < l; i++) {
  179. item = arguments[i];
  180. if (items.includes(item)) {
  181. continue;
  182. }
  183. // Register listeners
  184. item.addListener(
  185. "change" + groupedPropertyUp,
  186. this._onItemChangeChecked,
  187. this
  188. );
  189. // Push RadioButton to array
  190. items.push(item);
  191. // Inform radio button about new group
  192. item.set(this.getGroupProperty(), this);
  193. // Need to update internal value?
  194. if (item.get(groupedProperty)) {
  195. this.setSelection([item]);
  196. }
  197. }
  198. // Select first item when only one is registered
  199. if (
  200. !this.isAllowEmptySelection() &&
  201. items.length > 0 &&
  202. !this.getSelection()[0]
  203. ) {
  204. this.setSelection([items[0]]);
  205. }
  206. },
  207. /**
  208. * Remove an item from the radio group.
  209. *
  210. * @param item {qx.ui.form.IRadioItem} The item to remove.
  211. */
  212. remove(item) {
  213. var items = this.__items;
  214. var groupedProperty = this.getGroupedProperty();
  215. var groupedPropertyUp = qx.lang.String.firstUp(groupedProperty);
  216. if (items.includes(item)) {
  217. // Remove RadioButton from array
  218. qx.lang.Array.remove(items, item);
  219. // Inform radio button about new group
  220. if (item.get(this.getGroupProperty()) === this) {
  221. item.reset(this.getGroupProperty());
  222. }
  223. // Deregister listeners
  224. item.removeListener(
  225. "change" + groupedPropertyUp,
  226. this._onItemChangeChecked,
  227. this
  228. );
  229. // if the radio was checked, set internal selection to null
  230. if (item.get(groupedProperty)) {
  231. this.resetSelection();
  232. }
  233. }
  234. },
  235. /**
  236. * Returns an array containing the group's items.
  237. *
  238. * @return {qx.ui.form.IRadioItem[]} The item array
  239. */
  240. getChildren() {
  241. return this.__items;
  242. },
  243. /*
  244. ---------------------------------------------------------------------------
  245. LISTENER FOR ITEM CHANGES
  246. ---------------------------------------------------------------------------
  247. */
  248. /**
  249. * Event listener for <code>changeValue</code> event of every managed item.
  250. *
  251. * @param e {qx.event.type.Data} Data event
  252. */
  253. _onItemChangeChecked(e) {
  254. var item = e.getTarget();
  255. var groupedProperty = this.getGroupedProperty();
  256. if (item.get(groupedProperty)) {
  257. this.setSelection([item]);
  258. } else if (this.getSelection()[0] == item) {
  259. this.resetSelection();
  260. }
  261. },
  262. /*
  263. ---------------------------------------------------------------------------
  264. APPLY ROUTINES
  265. ---------------------------------------------------------------------------
  266. */
  267. // property apply
  268. _applyGroupedProperty(value, old) {
  269. var item;
  270. var oldFirstUp = qx.lang.String.firstUp(old);
  271. var newFirstUp = qx.lang.String.firstUp(value);
  272. for (var i = 0; i < this.__items.length; i++) {
  273. item = this.__items[i];
  274. // remove the listener for the old change event
  275. item.removeListener(
  276. "change" + oldFirstUp,
  277. this._onItemChangeChecked,
  278. this
  279. );
  280. // add the listener for the new change event
  281. item.removeListener(
  282. "change" + newFirstUp,
  283. this._onItemChangeChecked,
  284. this
  285. );
  286. }
  287. },
  288. // property apply
  289. _applyInvalidMessage(value, old) {
  290. for (var i = 0; i < this.__items.length; i++) {
  291. this.__items[i].setInvalidMessage(value);
  292. }
  293. },
  294. // property apply
  295. _applyValid(value, old) {
  296. for (var i = 0; i < this.__items.length; i++) {
  297. this.__items[i].setValid(value);
  298. }
  299. },
  300. // property apply
  301. _applyEnabled(value, old) {
  302. var items = this.__items;
  303. if (value == null) {
  304. for (var i = 0, l = items.length; i < l; i++) {
  305. items[i].resetEnabled();
  306. }
  307. } else {
  308. for (var i = 0, l = items.length; i < l; i++) {
  309. items[i].setEnabled(value);
  310. }
  311. }
  312. },
  313. // property apply
  314. _applyAllowEmptySelection(value, old) {
  315. if (!value && this.isSelectionEmpty()) {
  316. this.resetSelection();
  317. }
  318. },
  319. /*
  320. ---------------------------------------------------------------------------
  321. SELECTION
  322. ---------------------------------------------------------------------------
  323. */
  324. /**
  325. * Select the item following the given item.
  326. */
  327. selectNext() {
  328. var item = this.getSelection()[0];
  329. var items = this.__items;
  330. var index = items.indexOf(item);
  331. if (index == -1) {
  332. return;
  333. }
  334. var i = 0;
  335. var length = items.length;
  336. // Find next enabled item
  337. if (this.getWrap()) {
  338. index = (index + 1) % length;
  339. } else {
  340. index = Math.min(index + 1, length - 1);
  341. }
  342. while (i < length && !items[index].getEnabled()) {
  343. index = (index + 1) % length;
  344. i++;
  345. }
  346. this.setSelection([items[index]]);
  347. },
  348. /**
  349. * Select the item previous the given item.
  350. */
  351. selectPrevious() {
  352. var item = this.getSelection()[0];
  353. var items = this.__items;
  354. var index = items.indexOf(item);
  355. if (index == -1) {
  356. return;
  357. }
  358. var i = 0;
  359. var length = items.length;
  360. // Find previous enabled item
  361. if (this.getWrap()) {
  362. index = (index - 1 + length) % length;
  363. } else {
  364. index = Math.max(index - 1, 0);
  365. }
  366. while (i < length && !items[index].getEnabled()) {
  367. index = (index - 1 + length) % length;
  368. i++;
  369. }
  370. this.setSelection([items[index]]);
  371. },
  372. /*
  373. ---------------------------------------------------------------------------
  374. HELPER METHODS FOR SELECTION API
  375. ---------------------------------------------------------------------------
  376. */
  377. /**
  378. * Returns the items for the selection.
  379. *
  380. * @return {qx.ui.form.IRadioItem[]} Items to select.
  381. */
  382. _getItems() {
  383. return this.getItems();
  384. },
  385. /**
  386. * Returns if the selection could be empty or not.
  387. *
  388. * @return {Boolean} <code>true</code> If selection could be empty,
  389. * <code>false</code> otherwise.
  390. */
  391. _isAllowEmptySelection() {
  392. return this.isAllowEmptySelection();
  393. },
  394. /**
  395. * Returns whether the item is selectable. In opposite to the default
  396. * implementation (which checks for visible items) every radio button
  397. * which is part of the group is selected even if it is currently not visible.
  398. *
  399. * @param item {qx.ui.form.IRadioItem} The item to check if its selectable.
  400. * @return {Boolean} <code>true</code> if the item is part of the radio group
  401. * <code>false</code> otherwise.
  402. */
  403. _isItemSelectable(item) {
  404. return this.__items.indexOf(item) != -1;
  405. },
  406. /**
  407. * Event handler for <code>changeSelection</code>.
  408. *
  409. * @param e {qx.event.type.Data} Data event.
  410. */
  411. __onChangeSelection(e) {
  412. var value = e.getData()[0];
  413. var old = e.getOldData()[0];
  414. var groupedProperty = this.getGroupedProperty();
  415. if (old) {
  416. old.set(groupedProperty, false);
  417. }
  418. if (value) {
  419. value.set(groupedProperty, true);
  420. // If Group is focused, the selection was changed by keyboard. Switch focus to new value
  421. if (this.__isGroupFocused() && value.isFocusable()) {
  422. value.focus();
  423. }
  424. }
  425. },
  426. /**
  427. * Checks if this group is focused by checking focused state of each item
  428. * @returns {Boolean} result
  429. */
  430. __isGroupFocused() {
  431. const focusHandler = qx.ui.core.FocusHandler.getInstance();
  432. for (const item of this._getItems()) {
  433. if (focusHandler.isFocused(item)) {
  434. return true;
  435. }
  436. }
  437. return false;
  438. }
  439. },
  440. /*
  441. *****************************************************************************
  442. DESTRUCTOR
  443. *****************************************************************************
  444. */
  445. destruct() {
  446. this._disposeArray("__items");
  447. }
  448. });