1// Copyright (c) 2012 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5cr.define('cr.ui', function() { 6 /** @const */ var EventTarget = cr.EventTarget; 7 8 /** 9 * Creates a new selection model that is to be used with lists. This only 10 * allows a single index to be selected. 11 * 12 * @param {number=} opt_length The number items in the selection. 13 * 14 * @constructor 15 * @extends {!cr.EventTarget} 16 */ 17 function ListSingleSelectionModel(opt_length) { 18 this.length_ = opt_length || 0; 19 this.selectedIndex = -1; 20 21 // True if any item could be lead or anchor. False if only selected ones. 22 this.independentLeadItem_ = !cr.isMac && !cr.isChromeOS; 23 } 24 25 ListSingleSelectionModel.prototype = { 26 __proto__: EventTarget.prototype, 27 28 /** 29 * The number of items in the model. 30 * @type {number} 31 */ 32 get length() { 33 return this.length_; 34 }, 35 36 /** 37 * @type {!Array} The selected indexes. 38 */ 39 get selectedIndexes() { 40 var i = this.selectedIndex; 41 return i != -1 ? [this.selectedIndex] : []; 42 }, 43 set selectedIndexes(indexes) { 44 this.selectedIndex = indexes.length ? indexes[0] : -1; 45 }, 46 47 /** 48 * Convenience getter which returns the first selected index. 49 * Setter also changes lead and anchor indexes if value is nonegative. 50 * @type {number} 51 */ 52 get selectedIndex() { 53 return this.selectedIndex_; 54 }, 55 set selectedIndex(selectedIndex) { 56 var oldSelectedIndex = this.selectedIndex; 57 var i = Math.max(-1, Math.min(this.length_ - 1, selectedIndex)); 58 59 if (i != oldSelectedIndex) { 60 this.beginChange(); 61 this.selectedIndex_ = i; 62 this.leadIndex = i >= 0 ? i : this.leadIndex; 63 this.endChange(); 64 } 65 }, 66 67 /** 68 * Selects a range of indexes, starting with {@code start} and ends with 69 * {@code end}. 70 * @param {number} start The first index to select. 71 * @param {number} end The last index to select. 72 */ 73 selectRange: function(start, end) { 74 // Only select first index. 75 this.selectedIndex = Math.min(start, end); 76 }, 77 78 /** 79 * Selects all indexes. 80 */ 81 selectAll: function() { 82 // Select all is not allowed on a single selection model 83 }, 84 85 /** 86 * Clears the selection 87 */ 88 clear: function() { 89 this.beginChange(); 90 this.length_ = 0; 91 this.selectedIndex = this.anchorIndex = this.leadIndex = -1; 92 this.endChange(); 93 }, 94 95 /** 96 * Unselects all selected items. 97 */ 98 unselectAll: function() { 99 this.selectedIndex = -1; 100 }, 101 102 /** 103 * Sets the selected state for an index. 104 * @param {number} index The index to set the selected state for. 105 * @param {boolean} b Whether to select the index or not. 106 */ 107 setIndexSelected: function(index, b) { 108 // Only allow selection 109 var oldSelected = index == this.selectedIndex_; 110 if (oldSelected == b) 111 return; 112 113 if (b) 114 this.selectedIndex = index; 115 else if (index == this.selectedIndex_) 116 this.selectedIndex = -1; 117 }, 118 119 /** 120 * Whether a given index is selected or not. 121 * @param {number} index The index to check. 122 * @return {boolean} Whether an index is selected. 123 */ 124 getIndexSelected: function(index) { 125 return index == this.selectedIndex_; 126 }, 127 128 /** 129 * This is used to begin batching changes. Call {@code endChange} when you 130 * are done making changes. 131 */ 132 beginChange: function() { 133 if (!this.changeCount_) { 134 this.changeCount_ = 0; 135 this.selectedIndexBefore_ = this.selectedIndex_; 136 } 137 this.changeCount_++; 138 }, 139 140 /** 141 * Call this after changes are done and it will dispatch a change event if 142 * any changes were actually done. 143 */ 144 endChange: function() { 145 this.changeCount_--; 146 if (!this.changeCount_) { 147 if (this.selectedIndexBefore_ != this.selectedIndex_) { 148 var beforeChange = this.createChangeEvent('beforeChange'); 149 if (this.dispatchEvent(beforeChange)) 150 this.dispatchEvent(this.createChangeEvent('change')); 151 else 152 this.selectedIndex_ = this.selectedIndexBefore_; 153 } 154 } 155 }, 156 157 /** 158 * Creates event with specified name and fills its {changes} property. 159 * @param {string} name Event name. 160 */ 161 createChangeEvent: function(eventName) { 162 var e = new Event(eventName); 163 var indexes = [this.selectedIndexBefore_, this.selectedIndex_]; 164 e.changes = indexes.filter(function(index) { 165 return index != -1; 166 }).map(function(index) { 167 return { 168 index: index, 169 selected: index == this.selectedIndex_ 170 }; 171 }, this); 172 173 return e; 174 }, 175 176 leadIndex_: -1, 177 178 /** 179 * The leadIndex is used with multiple selection and it is the index that 180 * the user is moving using the arrow keys. 181 * @type {number} 182 */ 183 get leadIndex() { 184 return this.leadIndex_; 185 }, 186 set leadIndex(leadIndex) { 187 var li = this.adjustIndex_(leadIndex); 188 if (li != this.leadIndex_) { 189 var oldLeadIndex = this.leadIndex_; 190 this.leadIndex_ = li; 191 cr.dispatchPropertyChange(this, 'leadIndex', li, oldLeadIndex); 192 cr.dispatchPropertyChange(this, 'anchorIndex', li, oldLeadIndex); 193 } 194 }, 195 196 adjustIndex_: function(index) { 197 index = Math.max(-1, Math.min(this.length_ - 1, index)); 198 if (!this.independentLeadItem_) 199 index = this.selectedIndex; 200 return index; 201 }, 202 203 /** 204 * The anchorIndex is used with multiple selection. 205 * @type {number} 206 */ 207 get anchorIndex() { 208 return this.leadIndex; 209 }, 210 set anchorIndex(anchorIndex) { 211 this.leadIndex = anchorIndex; 212 }, 213 214 /** 215 * Whether the selection model supports multiple selected items. 216 * @type {boolean} 217 */ 218 get multiple() { 219 return false; 220 }, 221 222 /** 223 * Adjusts the selection after reordering of items in the table. 224 * @param {!Array.<number>} permutation The reordering permutation. 225 */ 226 adjustToReordering: function(permutation) { 227 if (this.leadIndex != -1) 228 this.leadIndex = permutation[this.leadIndex]; 229 230 var oldSelectedIndex = this.selectedIndex; 231 if (oldSelectedIndex != -1) { 232 this.selectedIndex = permutation[oldSelectedIndex]; 233 } 234 }, 235 236 /** 237 * Adjusts selection model length. 238 * @param {number} length New selection model length. 239 */ 240 adjustLength: function(length) { 241 this.length_ = length; 242 } 243 }; 244 245 return { 246 ListSingleSelectionModel: ListSingleSelectionModel 247 }; 248}); 249