UNPKG

@qooxdoo/framework

Version:

The JS Framework for Coders

485 lines (417 loc) 12.1 kB
/* ************************************************************************ qooxdoo - the new era of web development http://qooxdoo.org Copyright: 2021-2021 Zenesis Limited https://www.zenesis.com License: MIT: https://opensource.org/licenses/MIT See the LICENSE file in the project's top-level directory for details. Authors: * John Spackman (github.com/johnspackman) ************************************************************************ */ /** * A form widget which allows multiple selection with a drop down checked list. * * @childControl spacer {qx.ui.core.Spacer} flexible spacer widget * @childControl atom {qx.ui.basic.Atom} shows the text and icon of the content * @childControl arrow {qx.ui.basic.Image} shows the arrow to open the popup */ qx.Class.define("qx.ui.form.CheckedSelectBox", { extend: qx.ui.form.AbstractSelectBox, implement: [ qx.ui.core.IMultiSelection, qx.ui.form.IModelSelection, qx.ui.form.IField ], construct() { super(); this.__modelSelection = new qx.data.Array(); this.__modelSelection.addListener( "change", this.__onModelSelectionChange, this ); this.__atomsOnDisplay = []; this._add(this._createChildControl("tags"), { flex: 1, flexShrink: true }); this._add(this._createChildControl("spacer")); this._add(this._createChildControl("arrow")); // Register listener this.addListener("pointerover", this._onPointerOver, this); this.addListener("pointerout", this._onPointerOut, this); this.addListener("tap", this._onTap, this); }, properties: { appearance: { refine: true, init: "checked-selectbox" } }, events: { /** Event for psuedo property selection */ changeSelection: "qx.event.type.Data", /** Event for psuedo property checked */ changeChecked: "qx.event.type.Data", /** Event for psuedo property value */ changeValue: "qx.event.type.Data", /** Event for psuedo property modelSelection */ changeModelSelection: "qx.event.type.Data", /** Fired when a tag widget is added to the results; data is a map containing: * `tagWidget` - the tag widget being added * `item` - the item in the list that is checked * `itemModel` - the model item that backs the item */ attachResultsTag: "qx.event.type.Data", /** Fired when a tag widget is removed from the results; data is a map containing: * `tagWidget` - the tag widget being added * `item` - the item in the list that is checked * `itemModel` - the model item that backs the item */ detachResultsTag: "qx.event.type.Data" }, members: { /** @type {qx.data.Array} the modelSelection psuedo property */ __modelSelection: null, /** @type {qx.ui.basic.Atom[]} atoms used to show the selection */ __atomsOnDisplay: null, /** * @Override * @lint ignoreReferenceField(_forwardStates) */ _forwardStates: { focused: true }, /** * @Override */ _createChildControlImpl(id, hash) { switch (id) { case "popup": var control = new qx.ui.popup.Popup(new qx.ui.layout.VBox()).set({ autoHide: false, keepActive: false }); control.add(this.getChildControl("allNone")); control.add(this.getChildControl("list")); control.addListener( "changeVisibility", this._onPopupChangeVisibility, this ); return control; case "allNone": var control = new qx.ui.form.Button("All / None").set({ allowGrowX: false }); control.addListener("execute", this._onAllNoneExecute, this); return control; case "list": var control = new qx.ui.form.CheckedList().set({ focusable: false, keepFocus: true, height: null, width: null, maxHeight: this.getMaxListHeight() }); control.addListener("changeChecked", this._onListChangeChecked, this); return control; case "spacer": return new qx.ui.core.Spacer(); case "tags": return new qx.ui.container.Composite(new qx.ui.layout.HBox()).set({ allowGrowX: false }); case "tag": return new qx.ui.form.Tag(); case "arrow": return new qx.ui.basic.Image().set({ anonymous: true }); } return super._createChildControlImpl(id); }, /** * @Override * @see qx.ui.form.IField */ getValue() { return this.getSelection(); }, /** * @Override * @see qx.ui.form.IField */ setValue(value) { this.setSelection(value); }, /** * @Override * @see qx.ui.form.IField */ resetValue() { this.setSelection([]); }, /** * Getter for psuedo property "checked" * * @return {qx.ui.form.IListItem[]} */ getChecked() { return this.getChildControl("list").getChecked(); }, /** * Setter for psuedo property "checked" * * @param checked {qx.ui.form.IListItem[]} */ setChecked(checked) { this.getChildControl("list").setChecked(checked); }, /** * Reset for psuedo property "checked" */ resetChecked() { this.getChildControl("list").resetChecked(); }, /** * @Override * @see qx.ui.core.ISingleSelection */ getSelection() { return this.getChildControl("list").getChecked(); }, /** * @Override * @see qx.ui.core.ISingleSelection */ setSelection(items) { this.getChildControl("list").setChecked(items); }, /** * @Override * @see qx.ui.core.ISingleSelection */ resetSelection() { this.getChildControl("list").setChecked([]); }, /** * @Override * @see qx.ui.core.ISingleSelection */ isSelected(item) { return qx.lang.Array.contains( this.getChildControl("list").getChecked(), item ); }, /** * @Override * @see qx.ui.core.ISingleSelection */ isSelectionEmpty() { return this.getChildControl("list").getChecked().length == 0; }, /** * @Override * @see qx.ui.core.ISingleSelection */ getSelectables() { return this.getChildControl("list").getChildren(); }, /** * @Override * @see qx.ui.core.IMultiSelection */ selectAll() { let lst = this.getChildControl("list"); lst.setChecked(lst.getChildren()); }, /** * @Override * @see qx.ui.core.IMultiSelection */ addToSelection(item) { let lst = this.getChildControl("list"); let checked = lst.getChecked(); if (!qx.lang.Array.contain(checked, item)) { checked.push(item); lst.setChecked(checked); } }, /** * @Override * @see qx.ui.core.IMultiSelection */ removeFromSelection(item) { let lst = this.getChildControl("list"); let checked = lst.getChecked(); if (qx.lang.Array.remove(checked, item)) { lst.setChecked(checked); } }, /** * @Override * @see qx.ui.form.IModelSelection */ setModelSelection(value) { this.__onModelSelectionChange.replace(value ? value : []); }, /** * @Override * @see qx.ui.form.IModelSelection */ getModelSelection() { return this.__modelSelection; }, /** * Event handler for changes to the modelSelection array */ __onModelSelectionChange(evt) { let checked = []; let selected = {}; this.getModelSelection().forEach( itemModel => (selected[itemModel.toHashCode()] = itemModel) ); this.getChildren().forEach(item => { let itemModel = item.getModel(); if (selected[itemModel.toHashCode()]) { checked.push(item); } }); let lst = this.getChildControl("list"); if (!qx.lang.Array.equals(checked, lst.getChecked())) { lst.setChecked(checked); } }, /** * Event handler for the All/None button */ _onAllNoneExecute() { let lst = this.getChildControl("list"); let checked = lst.getChecked(); if (checked.length == 0) { lst.setChecked(lst.getChildren()); } else { lst.setChecked([]); } }, /** * Event handler for changes to the list's checked array */ _onListChangeChecked(evt) { let lst = this.getChildControl("list"); let modelSelection = lst.getChecked().map(item => item.getModel()); if ( !qx.lang.Array.equals( modelSelection, this.getModelSelection().toArray() ) ) { this.__modelSelection.replace(modelSelection); this.fireDataEvent("changeValue", this.getValue()); let children = {}; this.getChildren().forEach(item => { let itemModel = item.getModel(); children[itemModel.toHashCode()] = item; }); let tags = this.getChildControl("tags"); const attachTag = (tag, itemModel) => { tags.add(tag); let item = children[itemModel.toHashCode()]; tag.set({ model: itemModel, label: item.getLabel() }); this.fireDataEvent("attachResultsTag", { tagWidget: tag, item: item, itemModel: itemModel }); }; const detachTag = tag => { let itemModel = tag.getModel(); tag.setModel(null); tags.remove(tag); this.fireDataEvent("detachResultsTag", { tagWidget: tag, item: children[itemModel.toHashCode()], itemModel: itemModel }); }; while (this.__atomsOnDisplay.length > modelSelection.length) { let tag = this.__atomsOnDisplay.pop(); detachTag(tag); } modelSelection.forEach((itemModel, index) => { let tag = this.getChildControl("tag#" + index); if (this.__atomsOnDisplay.length <= index) { this.__atomsOnDisplay.push(tag); } else { this.__atomsOnDisplay[index] = tag; } attachTag(tag, itemModel); }); } }, /** * 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 (!this.isEnabled() || e.getTarget() !== this) { return; } if (this.hasState("abandoned")) { this.removeState("abandoned"); this.addState("pressed"); } this.addState("hovered"); }, /** * Listener method for "pointerout" event * <ul> * <li>Removes "hovered" state</li> * <li>Adds "abandoned" and removes "pressed" state (if "pressed" state is set)</li> * </ul> * * @param e {qx.event.type.Pointer} Pointer event */ _onPointerOut(e) { if (!this.isEnabled() || e.getTarget() !== this) { return; } this.removeState("hovered"); if (this.hasState("pressed")) { this.removeState("pressed"); this.addState("abandoned"); } }, /** * Toggles the popup's visibility. * * @param e {qx.event.type.Pointer} Pointer event */ _onTap(e) { this.open(); }, /** * @Override */ _onBlur(evt) { let popup = this.getChildControl("popup"); for ( let widget = evt.getRelatedTarget(); widget; widget = widget.getLayoutParent() ) { if (widget == popup) { evt.getRelatedTarget().addListenerOnce("blur", this._onBlur, this); return; } } this.close(); } } });