1// Copyright (c) 2011 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 5/** 6 * @fileoverview This is a table data model 7 */ 8cr.define('cr.ui.table', function() { 9 const EventTarget = cr.EventTarget; 10 const Event = cr.Event; 11 const ArrayDataModel = cr.ui.ArrayDataModel; 12 13 /** 14 * A table data model that supports sorting by storing initial indexes of 15 * elements for each position in sorted array. 16 * @param {!Array} items The underlying array. 17 * @constructor 18 * @extends {ArrayDataModel} 19 */ 20 function TableDataModel(items) { 21 ArrayDataModel.apply(this, arguments); 22 this.indexes_ = []; 23 for (var i = 0; i < items.length; i++) { 24 this.indexes_.push(i); 25 } 26 } 27 28 TableDataModel.prototype = { 29 __proto__: ArrayDataModel.prototype, 30 31 /** 32 * Returns the item at the given index. 33 * This implementation returns the item at the given index in the source 34 * array before sort. 35 * @param {number} index The index of the element to get. 36 * @return {*} The element at the given index. 37 */ 38 getItemByUnsortedIndex_: function(unsortedIndex) { 39 return ArrayDataModel.prototype.item.call(this, unsortedIndex); 40 }, 41 42 /** 43 * Returns the item at the given index. 44 * This implementation returns the item at the given index in the sorted 45 * array. 46 * @param {number} index The index of the element to get. 47 * @return {*} The element at the given index. 48 */ 49 item: function(index) { 50 if (index >= 0 && index < this.length) 51 return this.getItemByUnsortedIndex_(this.indexes_[index]); 52 return undefined; 53 }, 54 55 /** 56 * Returns compare function set for given field. 57 * @param {string} field The field to get compare function for. 58 * @return {Function(*, *): number} Compare function set for given field. 59 */ 60 compareFunction: function(field) { 61 return this.compareFunctions_[field]; 62 }, 63 64 /** 65 * Sets compare function for given field. 66 * @param {string} field The field to set compare function. 67 * @param {Function(*, *): number} Compare function to set for given field. 68 */ 69 setCompareFunction: function(field, compareFunction) { 70 this.compareFunctions_[field] = compareFunction; 71 }, 72 73 /** 74 * Returns current sort status. 75 * @return {!Object} Current sort status. 76 */ 77 get sortStatus() { 78 if (this.sortStatus_) { 79 return this.createSortStatus( 80 this.sortStatus_.field, this.sortStatus_.direction); 81 } else { 82 return this.createSortStatus(null, null); 83 } 84 }, 85 86 /** 87 * This removes and adds items to the model. 88 * This dispatches a splice event. 89 * This implementation runs sort after splice and creates permutation for 90 * the whole change. 91 * @param {number} index The index of the item to update. 92 * @param {number} deleteCount The number of items to remove. 93 * @param {...*} The items to add. 94 * @return {!Array} An array with the removed items. 95 */ 96 splice: function(index, deleteCount, var_args) { 97 var addCount = arguments.length - 2; 98 var newIndexes = []; 99 var deletePermutation = []; 100 var deleted = 0; 101 for (var i = 0; i < this.indexes_.length; i++) { 102 var oldIndex = this.indexes_[i]; 103 if (oldIndex < index) { 104 newIndexes.push(oldIndex); 105 deletePermutation.push(i - deleted); 106 } else if (oldIndex >= index + deleteCount) { 107 newIndexes.push(oldIndex - deleteCount + addCount); 108 deletePermutation.push(i - deleted); 109 } else { 110 deletePermutation.push(-1); 111 deleted++; 112 } 113 } 114 for (var i = 0; i < addCount; i++) { 115 newIndexes.push(index + i); 116 } 117 this.indexes_ = newIndexes; 118 119 var rv = ArrayDataModel.prototype.splice.apply(this, arguments); 120 121 var splicePermutation; 122 if (this.sortStatus.field) { 123 var sortPermutation = this.doSort_(this.sortStatus.field, 124 this.sortStatus.direction); 125 splicePermutation = deletePermutation.map(function(element) { 126 return element != -1 ? sortPermutation[element] : -1; 127 }); 128 } else { 129 splicePermutation = deletePermutation; 130 } 131 this.dispatchSortEvent_(splicePermutation); 132 133 return rv; 134 }, 135 136 /** 137 * Use this to update a given item in the array. This does not remove and 138 * reinsert a new item. 139 * This dispatches a change event. 140 * This implementation runs sort after updating. 141 * @param {number} index The index of the item to update. 142 */ 143 updateIndex: function(index) { 144 ArrayDataModel.prototype.updateIndex.apply(this, arguments); 145 146 if (this.sortStatus.field) 147 this.sort(this.sortStatus.field, this.sortStatus.direction); 148 }, 149 150 /** 151 * Creates sort status with given field and direction. 152 * @param {string} field Sort field. 153 * @param {string} direction Sort direction. 154 * @return {!Object} Created sort status. 155 */ 156 createSortStatus: function(field, direction) { 157 return { 158 field: field, 159 direction: direction 160 }; 161 }, 162 163 /** 164 * Called before a sort happens so that you may fetch additional data 165 * required for the sort. 166 * 167 * @param {string} field Sort field. 168 * @param {function()} callback The function to invoke when preparation 169 * is complete. 170 */ 171 prepareSort: function(field, callback) { 172 callback(); 173 }, 174 175 /** 176 * Sorts data model according to given field and direction and dispathes 177 * sorted event. 178 * @param {string} field Sort field. 179 * @param {string} direction Sort direction. 180 */ 181 sort: function(field, direction) { 182 var self = this; 183 184 this.prepareSort(field, function() { 185 var sortPermutation = self.doSort_(field, direction); 186 self.dispatchSortEvent_(sortPermutation); 187 }); 188 }, 189 190 /** 191 * Sorts data model according to given field and direction. 192 * @param {string} field Sort field. 193 * @param {string} direction Sort direction. 194 */ 195 doSort_: function(field, direction) { 196 var compareFunction = this.sortFunction_(field, direction); 197 var positions = []; 198 for (var i = 0; i < this.length; i++) { 199 positions[this.indexes_[i]] = i; 200 } 201 this.indexes_.sort(compareFunction); 202 this.sortStatus_ = this.createSortStatus(field, direction); 203 var sortPermutation = []; 204 for (var i = 0; i < this.length; i++) { 205 sortPermutation[positions[this.indexes_[i]]] = i; 206 } 207 return sortPermutation; 208 }, 209 210 dispatchSortEvent_: function(sortPermutation) { 211 var e = new Event('sorted'); 212 e.sortPermutation = sortPermutation; 213 this.dispatchEvent(e); 214 }, 215 216 /** 217 * Creates compare function for the field. 218 * Returns the function set as sortFunction for given field 219 * or default compare function 220 * @param {string} field Sort field. 221 * @param {Function(*, *): number} Compare function. 222 */ 223 createCompareFunction_: function(field) { 224 var compareFunction = 225 this.compareFunctions_ ? this.compareFunctions_[field] : null; 226 var defaultValuesCompareFunction = this.defaultValuesCompareFunction; 227 if (compareFunction) { 228 return compareFunction; 229 } else { 230 return function(a, b) { 231 return defaultValuesCompareFunction.call(null, a[field], b[field]); 232 } 233 } 234 return compareFunction; 235 }, 236 237 /** 238 * Creates compare function for given field and direction. 239 * @param {string} field Sort field. 240 * @param {string} direction Sort direction. 241 * @param {Function(*, *): number} Compare function. 242 */ 243 sortFunction_: function(field, direction) { 244 var compareFunction = this.createCompareFunction_(field); 245 var dirMultiplier = direction == 'desc' ? -1 : 1; 246 247 return function(index1, index2) { 248 var item1 = this.getItemByUnsortedIndex_(index1); 249 var item2 = this.getItemByUnsortedIndex_(index2); 250 251 var compareResult = compareFunction.call(null, item1, item2); 252 if (compareResult != 0) 253 return dirMultiplier * compareResult; 254 return dirMultiplier * this.defaultValuesCompareFunction(index1, 255 index2); 256 }.bind(this); 257 }, 258 259 /** 260 * Default compare function. 261 */ 262 defaultValuesCompareFunction: function(a, b) { 263 // We could insert i18n comparisons here. 264 if (a < b) 265 return -1; 266 if (a > b) 267 return 1; 268 return 0; 269 } 270 }; 271 272 return { 273 TableDataModel: TableDataModel 274 }; 275}); 276