1 /* 2 * ***** BEGIN LICENSE BLOCK ***** 3 * Zimbra Collaboration Suite Web Client 4 * Copyright (C) 2005, 2006, 2007, 2009, 2010, 2013, 2014, 2016 Synacor, Inc. 5 * 6 * The contents of this file are subject to the Common Public Attribution License Version 1.0 (the "License"); 7 * you may not use this file except in compliance with the License. 8 * You may obtain a copy of the License at: https://www.zimbra.com/license 9 * The License is based on the Mozilla Public License Version 1.1 but Sections 14 and 15 10 * have been added to cover use of software over a computer network and provide for limited attribution 11 * for the Original Developer. In addition, Exhibit A has been modified to be consistent with Exhibit B. 12 * 13 * Software distributed under the License is distributed on an "AS IS" basis, 14 * WITHOUT WARRANTY OF ANY KIND, either express or implied. 15 * See the License for the specific language governing rights and limitations under the License. 16 * The Original Code is Zimbra Open Source Web Client. 17 * The Initial Developer of the Original Code is Zimbra, Inc. All rights to the Original Code were 18 * transferred by Zimbra, Inc. to Synacor, Inc. on September 14, 2015. 19 * 20 * All portions of the code are Copyright (C) 2005, 2006, 2007, 2009, 2010, 2013, 2014, 2016 Synacor, Inc. All Rights Reserved. 21 * ***** END LICENSE BLOCK ***** 22 */ 23 24 /** 25 * This requires an "owner" which is the object that owns the full set of items, implementing: 26 * getItemCount() to return the number of items 27 * getItem(index) to return the item at a given index. 28 * 29 * And optionally implementing 30 * itemSelectionChanged(item, index, isSelected) which is called 31 * for each item that is selected or deselected 32 * selectionChanged() which is called after a batch of items have 33 * been selected or deselected with select() 34 * 35 * @private 36 */ 37 AjxSelectionManager = function(anOwner) { 38 this._owner = anOwner; 39 }; 40 41 // ----------------------------------------------------------- 42 // Constants 43 // ----------------------------------------------------------- 44 45 // Actions for select() 46 AjxSelectionManager.SELECT_ONE_CLEAR_OTHERS = 0; 47 AjxSelectionManager.TOGGLE_ONE_LEAVE_OTHERS = 1; 48 AjxSelectionManager.SELECT_TO_ANCHOR = 2; 49 AjxSelectionManager.DESELECT_ALL = 3; 50 AjxSelectionManager.SELECT_ALL = 4; 51 52 // ----------------------------------------------------------- 53 // API Methods 54 // ----------------------------------------------------------- 55 56 /** 57 * returns an AjxVector 58 */ 59 AjxSelectionManager.prototype.getItems = function() { 60 if (this._selectedItems == null) { 61 this._selectedItems = this._createItemsCollection(); 62 } 63 return this._selectedItems; 64 }; 65 66 /** 67 * returns the number of selected items 68 */ 69 AjxSelectionManager.prototype.getLength = function() { 70 return this.getItems().length; 71 }; 72 73 /** 74 * returns the anchor, unless nothing is selected 75 */ 76 AjxSelectionManager.prototype.getAnchor = function() { 77 if (this._anchor == null) { 78 var items = this.getItems(); 79 if (items.length > 0) { 80 this._anchor = items[0]; 81 } 82 } 83 return this._anchor; 84 }; 85 86 /** 87 * The cursor probably changes when the users navigates with 88 * the keyboard. This returns the item that is currently the cursor, 89 * and null if nothing is selected. 90 */ 91 AjxSelectionManager.prototype.getCursor = function() { 92 if (this._cursor == null) { 93 this._cursor = this.getAnchor(); 94 } 95 return this._cursor; 96 }; 97 98 99 /** 100 * Returns true if the given item is selected. 101 */ 102 AjxSelectionManager.prototype.isSelected = function(item) { 103 return this.getItems().binarySearch(item) != -1; 104 }; 105 106 AjxSelectionManager.prototype.selectOneItem = function(item) { 107 this.select(item, AjxSelectionManager.SELECT_ONE_CLEAR_OTHERS); 108 }; 109 110 AjxSelectionManager.prototype.toggleItem = function(item) { 111 this.select(item, AjxSelectionManager.TOGGLE_ONE_LEAVE_OTHERS); 112 }; 113 114 AjxSelectionManager.prototype.selectFromAnchorToItem = function(item) { 115 this.select(item, AjxSelectionManager.SELECT_TO_ANCHOR); 116 }; 117 118 AjxSelectionManager.prototype.deselectAll = function() { 119 this.select(null, AjxSelectionManager.DESELECT_ALL); 120 }; 121 122 AjxSelectionManager.prototype.selectAll = function() { 123 this.select(null, AjxSelectionManager.SELECT_ALL); 124 }; 125 126 127 /** 128 * This method will notify the owner of any changes by calling 129 * itemSelectionChanged() (if the owner defines it) for each item whose 130 * selection changes and also by calling selectionChanged() (if the 131 * owner defines it) once at the end, if anything changed selection. 132 * 133 */ 134 AjxSelectionManager.prototype.select = function(item, action) { 135 136 // Update the anchor and cursor, if necessary 137 this._setAnchorAndCursor(item, action); 138 139 // save off the old set of selected items 140 var oldItems = this._selectedItems; 141 var oldItemsCount = (oldItems == null) ? 0 : oldItems.length; 142 143 // create a fresh set of selected items 144 this._selectedItems = null; 145 this._selectedItems = this._createItemsCollection(); 146 147 // Now update the selection 148 var itemCount = this._owner.getItemCount(); 149 var needsSort = false; 150 var selectionChanged = false; 151 var selecting = false; 152 for (var i = 0; i < itemCount; ++i) { 153 var testItem = this._owner.getItem(i); 154 var oldSelectionExists = this._isItemOldSelection(testItem, oldItems); 155 var newSelectionExists = oldSelectionExists; 156 157 switch (action) { 158 case AjxSelectionManager.SELECT_TO_ANCHOR: 159 if (this._anchor == null) { 160 // If we have no anchor, let it be the first item 161 // in the list 162 this._anchor = testItem; 163 } 164 var atEdge = (testItem == this._anchor || testItem == item); 165 var changed = false; 166 // mark the beginning of the selection for the iteration 167 if (!selecting && atEdge) { 168 selecting = true; 169 changed = true; 170 } 171 newSelectionExists = selecting; 172 // mark the end of the selection if we're there 173 if ((!changed || this._anchor == item) 174 && selecting && atEdge) { 175 selecting = false; 176 } 177 178 break; 179 case AjxSelectionManager.SELECT_ONE_CLEAR_OTHERS: 180 newSelectionExists = (testItem == item); 181 break; 182 case AjxSelectionManager.TOGGLE_ONE_LEAVE_OTHERS: 183 if (testItem == item) { 184 newSelectionExists = !oldSelectionExists ; 185 } 186 break; 187 case AjxSelectionManager.DESELECT_ALL: 188 newSelectionExists = false; 189 break; 190 case AjxSelectionManager.SELECT_ALL: 191 newSelectionExists = true; 192 break; 193 } 194 195 if (newSelectionExists) { 196 this._selectedItems.add(testItem); 197 needsSort = (this._selectedItems.length > 1); 198 } 199 200 if ( newSelectionExists != oldSelectionExists) { 201 // Something changed so notify the owner. 202 if (this._owner.itemSelectionChanged != null) { 203 this._owner.itemSelectionChanged(testItem, 204 i, newSelectionExists); 205 } 206 selectionChanged = true; 207 } 208 } 209 selectionChanged = selectionChanged || (oldItemsCount != 210 this._selectedItems.length); 211 212 if (needsSort) this._selectedItems.sort(); 213 214 if (selectionChanged && this._owner.selectionChanged != null) { 215 this._owner.selectionChanged(item); 216 } 217 }; 218 219 /** 220 * Remove an item from the selection managers selected items 221 * collection if it exists. 222 */ 223 AjxSelectionManager.prototype.removeItem = function(item) { 224 if (this._selectedItems) { 225 var index = this._selectedItems.binarySearch(item); 226 if (index > -1) this._selectedItems.removeAt(index); 227 } 228 }; 229 230 // ----------------------------------------------------------- 231 // Internal Methods 232 // ----------------------------------------------------------- 233 234 /** 235 * Creates an array suitable for use as the sorted list of selected 236 * items and returns it. 237 */ 238 AjxSelectionManager.prototype._createItemsCollection = function() { 239 return new AjxVector(); 240 }; 241 242 AjxSelectionManager.prototype._isItemOldSelection = function (testItem, oldItems) { 243 var ret = false; 244 if (oldItems) { 245 var oldSelectionIndex = oldItems.binarySearch(testItem); 246 if (oldSelectionIndex > -1) { 247 oldItems.removeAt(oldSelectionIndex); 248 } 249 ret = (oldSelectionIndex != -1); 250 } 251 return ret; 252 }; 253 254 AjxSelectionManager.prototype._setAnchorAndCursor = function (item, action) { 255 switch (action) { 256 case AjxSelectionManager.SELECT_TO_ANCHOR: 257 this._cursor = item; 258 break; 259 case AjxSelectionManager.SELECT_ONE_CLEAR_OTHERS: 260 this._anchor = item; 261 this._cursor = item; 262 break; 263 case AjxSelectionManager.TOGGLE_ONE_LEAVE_OTHERS: 264 this._anchor = item; 265 this._cursor = item; 266 break; 267 case AjxSelectionManager.DESELECT_ALL: 268 this._anchor = null; 269 this._cursor = null; 270 break; 271 case AjxSelectionManager.SELECT_ALL: 272 return; 273 } 274 }; 275