UNPKG

@qooxdoo/framework

Version:

The JS Framework for Coders

449 lines (377 loc) 14.2 kB
/* ************************************************************************ qooxdoo - the new era of web development http://qooxdoo.org Copyright: 2004-2009 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) ************************************************************************ */ /** * Mixin for the selection in the data binding controller. * It contains an selection property which can be manipulated. * Remember to call the method {@link #_addChangeTargetListener} on every * change of the target. * It is also important that the elements stored in the target e.g. ListItems * do have the corresponding model stored as user data under the "model" key. */ qx.Mixin.define("qx.data.controller.MSelection", { /* ***************************************************************************** CONSTRUCTOR ***************************************************************************** */ construct : function() { // check for a target property if (!qx.Class.hasProperty(this.constructor, "target")) { throw new Error("Target property is needed."); } // create a default selection array if (this.getSelection() == null) { this.__ownSelection = new qx.data.Array(); this.setSelection(this.__ownSelection); } }, /* ***************************************************************************** PROPERTIES ***************************************************************************** */ properties : { /** * Data array containing the selected model objects. This property can be * manipulated directly which means that a push to the selection will also * select the corresponding element in the target. */ selection : { check: "qx.data.Array", event: "changeSelection", apply: "_applySelection", init: null } }, events : { /** * This event is fired as soon as the content of the selection property changes, but * this is not equal to the change of the selection of the widget. If the selection * of the widget changes, the content of the array stored in the selection property * changes. This means you have to listen to the change event of the selection array * to get an event as soon as the user changes the selected item. * <pre class="javascript">obj.getSelection().addListener("change", listener, this);</pre> */ "changeSelection" : "qx.event.type.Data", /** Fires after the value was modified */ "changeValue" : "qx.event.type.Data" }, /* ***************************************************************************** MEMBERS ***************************************************************************** */ members : { // private members // // set the semaphore-like variable for the selection change _modifingSelection : 0, __selectionListenerId : null, __selectionArrayListenerId : null, __ownSelection : null, /** * setValue implements part of the {@link qx.ui.form.IField} interface. * * @param selection {qx.data.IListData|null} List data to select as value. * @return {null} The status of this operation. */ setValue : function(selection) { if (null === selection) { this.resetSelection(); } else { this.setSelection(selection); } return null; }, /** * getValue implements part of the {@link qx.ui.form.IField} interface. * * @return {qx.data.IListData} The current selection. */ getValue : function() { return this.getSelection(); }, /** * resetValue implements part of the {@link qx.ui.form.IField} interface. */ resetValue : function() { this.resetSelection(); }, /* --------------------------------------------------------------------------- APPLY METHODS --------------------------------------------------------------------------- */ /** * Apply-method for setting a new selection array. Only the change listener * will be removed from the old array and added to the new. * * @param value {qx.data.Array} The new data array for the selection. * @param old {qx.data.Array|null} The old data array for the selection. */ _applySelection: function(value, old) { // remove the old listener if necessary if (this.__selectionArrayListenerId != undefined && old != undefined) { old.removeListenerById(this.__selectionArrayListenerId); this.__selectionArrayListenerId = null; } // add a new change listener to the changeArray if (value) { this.__selectionArrayListenerId = value.addListener( "change", this.__changeSelectionArray, this ); } // apply the new selection this._updateSelection(); }, /* --------------------------------------------------------------------------- EVENT HANDLER --------------------------------------------------------------------------- */ /** * Event handler for the change of the data array holding the selection. * If a change is in the selection array, the selection update will be * invoked. */ __changeSelectionArray: function() { this._updateSelection(); }, /** * Event handler for a change in the target selection. * If the selection in the target has changed, the selected model objects * will be found and added to the selection array. */ _changeTargetSelection: function() { // dont do anything without a target if (this.getTarget() == null) { return; } // if a selection API is supported if (!this.__targetSupportsMultiSelection() && !this.__targetSupportsSingleSelection()) { return; } // if __changeSelectionArray is currently working, do nothing if (this._inSelectionModification()) { return; } // get both selections var targetSelection = this.getTarget().getSelection(); var selection = this.getSelection(); if (selection == null) { selection = new qx.data.Array(); this.__ownSelection = selection; this.setSelection(selection); } // go through the target selection var spliceArgs = [0, selection.getLength()]; for (var i = 0; i < targetSelection.length; i++) { var model = targetSelection[i].getModel(); if (model !== null) { spliceArgs.push(model); } } // use splice to ensure a correct change event [BUG #4728] selection.splice.apply(selection, spliceArgs).dispose(); // fire the change event manually this.fireDataEvent("changeSelection", this.getSelection()); }, /* --------------------------------------------------------------------------- SELECTION --------------------------------------------------------------------------- */ /** * Helper method which should be called by the classes including this * Mixin when the target changes. * * @param value {qx.ui.core.Widget|null} The new target. * @param old {qx.ui.core.Widget|null} The old target. */ _addChangeTargetListener: function(value, old) { // remove the old selection listener if (this.__selectionListenerId != undefined && old != undefined) { old.removeListenerById(this.__selectionListenerId); } if (value != null) { // if a selection API is supported if ( this.__targetSupportsMultiSelection() || this.__targetSupportsSingleSelection() ) { // add a new selection listener this.__selectionListenerId = value.addListener( "changeSelection", this._changeTargetSelection, this ); } } }, /** * Method for updating the selection. It checks for the case of single or * multi selection and after that checks if the selection in the selection * array is the same as in the target widget. */ _updateSelection: function() { // do not update if no target is given if (!this.getTarget() || !this.getSelection()) { return; } // mark the change process in a flag this._startSelectionModification(); // if its a multi selection target if (this.__targetSupportsMultiSelection()) { var targetSelection = []; // go through the selection array for (var i = 0; i < this.getSelection().length; i++) { // store each item var model = this.getSelection().getItem(i); var selectable = this.__getSelectableForModel(model); if (selectable != null) { targetSelection.push(selectable); } } this.getTarget().setSelection(targetSelection); // get the selection of the target targetSelection = this.getTarget().getSelection(); // get all items selected in the list var targetSelectionItems = []; for (var i = 0; i < targetSelection.length; i++) { targetSelectionItems[i] = targetSelection[i].getModel(); } // go through the controller selection for (var i = this.getSelection().length - 1; i >= 0; i--) { // if the item in the controller selection is not selected in the list if (!targetSelectionItems.includes(this.getSelection().getItem(i))) { // remove the current element and get rid of the return array this.getSelection().splice(i, 1).dispose(); } } // if its a single selection target } else if (this.__targetSupportsSingleSelection()) { // get the model which should be selected var item = this.getSelection().getItem(this.getSelection().length - 1); if (item !== undefined) { // select the last selected item (old selection will be removed anyway) this.__selectItem(item); // remove the other items from the selection data array and get // rid of the return array this.getSelection().splice( 0, this.getSelection().getLength() - 1 ).dispose(); } else { // if there is no item to select (e.g. new model set [BUG #4125]), // reset the selection this.getTarget().resetSelection(); } } // reset the changing flag this._endSelectionModification(); this.fireDataEvent("changeValue", this.getSelection()); }, /** * Helper-method returning true, if the target supports multi selection. * @return {Boolean} true, if the target supports multi selection. */ __targetSupportsMultiSelection: function() { var targetClass = this.getTarget().constructor; return qx.Class.implementsInterface(targetClass, qx.ui.core.IMultiSelection); }, /** * Helper-method returning true, if the target supports single selection. * @return {Boolean} true, if the target supports single selection. */ __targetSupportsSingleSelection: function() { var targetClass = this.getTarget().constructor; return qx.Class.implementsInterface(targetClass, qx.ui.core.ISingleSelection); }, /** * Internal helper for selecting an item in the target. The item to select * is defined by a given model item. * * @param item {qx.core.Object} A model element. */ __selectItem: function(item) { var selectable = this.__getSelectableForModel(item); // if no selectable could be found, just return if (selectable == null) { return; } // if the target is multi selection able if (this.__targetSupportsMultiSelection()) { // select the item in the target this.getTarget().addToSelection(selectable); // if the target is single selection able } else if (this.__targetSupportsSingleSelection()) { this.getTarget().setSelection([selectable]); } }, /** * Returns the list item storing the given model in its model property. * * @param model {var} The representing model of a selectable. * @return {Object|null} List item or <code>null</code> if none was found */ __getSelectableForModel : function(model) { // get all list items var children = this.getTarget().getSelectables(true); // go through all children and search for the child to select for (var i = 0; i < children.length; i++) { if (children[i].getModel() == model) { return children[i]; } } // if no selectable was found return null; }, /** * Helper-Method signaling that currently the selection of the target is * in change. That will block the change of the internal selection. * {@link #_endSelectionModification} */ _startSelectionModification: function() { this._modifingSelection++; }, /** * Helper-Method signaling that the internal changing of the targets * selection is over. * {@link #_startSelectionModification} */ _endSelectionModification: function() { this._modifingSelection > 0 ? this._modifingSelection-- : null; }, /** * Helper-Method for checking the state of the selection modification. * {@link #_startSelectionModification} * {@link #_endSelectionModification} * @return {Boolean} <code>true</code> if selection modification is active */ _inSelectionModification: function() { return this._modifingSelection > 0; } }, /* ***************************************************************************** DESTRUCTOR ***************************************************************************** */ destruct : function() { if (this.__ownSelection) { this.__ownSelection.dispose(); } } });