Chromium Code Reviews| Index: chrome/browser/resources/shared/js/cr/ui/array_data_model.js |
| diff --git a/chrome/browser/resources/shared/js/cr/ui/array_data_model.js b/chrome/browser/resources/shared/js/cr/ui/array_data_model.js |
| index 64da3be85d82e5ca08367b8a2e98ccaf40f42857..0621819bbfabb0915b9b573d2f1ba6e09f416de0 100644 |
| --- a/chrome/browser/resources/shared/js/cr/ui/array_data_model.js |
| +++ b/chrome/browser/resources/shared/js/cr/ui/array_data_model.js |
| @@ -11,13 +11,18 @@ cr.define('cr.ui', function() { |
| const Event = cr.Event; |
| /** |
| - * A data model that wraps a simple array. |
| + * A data model that wraps a simple array and supports sorting by storing |
| + * initial indexes of elements for each position in sorted array. |
| * @param {!Array} array The underlying array. |
| * @constructor |
| * @extends {EventTarget} |
| */ |
| function ArrayDataModel(array) { |
| this.array_ = array; |
| + this.indexes_ = []; |
| + for (var i = 0; i < array.length; i++) { |
| + this.indexes_.push(i); |
| + } |
| } |
| ArrayDataModel.prototype = { |
| @@ -33,13 +38,50 @@ cr.define('cr.ui', function() { |
| /** |
| * Returns the item at the given index. |
| + * This implementation returns the item at the given index in the sorted |
| + * array. |
| * @param {number} index The index of the element to get. |
| * @return {*} The element at the given index. |
| */ |
| item: function(index) { |
| - return this.array_[index]; |
| + if (index >= 0 && index < this.length) |
| + return this.array_[this.indexes_[index]]; |
| + return undefined; |
| + }, |
| + |
| + /** |
| + * Returns compare function set for given field. |
| + * @param {string} field The field to get compare function for. |
| + * @return {function(*, *): number} Compare function set for given field. |
| + */ |
| + compareFunction: function(field) { |
| + return this.compareFunctions_[field]; |
| + }, |
| + |
| + /** |
| + * Sets compare function for given field. |
| + * @param {string} field The field to set compare function. |
| + * @param {function(*, *): number} Compare function to set for given field. |
| + */ |
| + setCompareFunction: function(field, compareFunction) { |
| + this.compareFunctions_[field] = compareFunction; |
| + }, |
| + |
| + /** |
| + * Returns current sort status. |
| + * @return {!Object} Current sort status. |
| + */ |
| + get sortStatus() { |
| + if (this.sortStatus_) { |
| + return this.createSortStatus( |
| + this.sortStatus_.field, this.sortStatus_.direction); |
| + } else { |
| + return this.createSortStatus(null, null); |
| + } |
| }, |
|
dhollowa
2011/05/24 15:45:49
nit: extra spaces here.
vsevik
2011/05/25 18:12:04
Done.
|
| + |
| + |
| /** |
| * Returns the first matching item. |
| * @param {*} item The item to find. |
| @@ -63,27 +105,60 @@ cr.define('cr.ui', function() { |
| /** |
| * This removes and adds items to the model. |
| - * |
| * This dispatches a splice event. |
| - * |
| + * This implementation runs sort after splice and creates permutation for |
| + * the whole change. |
| * @param {number} index The index of the item to update. |
| * @param {number} deleteCount The number of items to remove. |
| * @param {...*} The items to add. |
| * @return {!Array} An array with the removed items. |
| */ |
| splice: function(index, deleteCount, var_args) { |
| + var addCount = arguments.length - 2; |
| + var newIndexes = []; |
| + var deletePermutation = []; |
| + var deleted = 0; |
| + for (var i = 0; i < this.indexes_.length; i++) { |
| + var oldIndex = this.indexes_[i]; |
| + if (oldIndex < index) { |
| + newIndexes.push(oldIndex); |
| + deletePermutation.push(i - deleted); |
| + } else if (oldIndex >= index + deleteCount) { |
| + newIndexes.push(oldIndex - deleteCount + addCount); |
| + deletePermutation.push(i - deleted); |
| + } else { |
| + deletePermutation.push(-1); |
| + deleted++; |
| + } |
| + } |
| + for (var i = 0; i < addCount; i++) { |
| + newIndexes.push(index + i); |
| + } |
| + this.indexes_ = newIndexes; |
| + |
| var arr = this.array_; |
| // TODO(arv): Maybe unify splice and change events? |
| - var e = new Event('splice'); |
| - e.index = index; |
| - e.removed = arr.slice(index, index + deleteCount); |
| - e.added = Array.prototype.slice.call(arguments, 2); |
| + var spliceEvent = new Event('splice'); |
| + spliceEvent.index = index; |
| + spliceEvent.removed = arr.slice(index, index + deleteCount); |
| + spliceEvent.added = Array.prototype.slice.call(arguments, 2); |
| var rv = arr.splice.apply(arr, arguments); |
| - this.dispatchEvent(e); |
| + // if sortStatus.field is null, this restores original order. |
| + var sortPermutation = this.doSort_(this.sortStatus.field, |
| + this.sortStatus.direction); |
| + if (sortPermutation) { |
| + var splicePermutation = deletePermutation.map(function(element) { |
| + return element != -1 ? sortPermutation[element] : -1; |
| + }); |
| + this.dispatchPermutedEvent_(splicePermutation); |
| + } else { |
| + this.dispatchPermutedEvent_(deletePermutation); |
| + } |
| + this.dispatchEvent(spliceEvent); |
| return rv; |
| }, |
| @@ -105,9 +180,8 @@ cr.define('cr.ui', function() { |
| /** |
| * Use this to update a given item in the array. This does not remove and |
| * reinsert a new item. |
| - * |
| * This dispatches a change event. |
| - * |
| + * This runs sort after updating. |
| * @param {number} index The index of the item to update. |
| */ |
| updateIndex: function(index) { |
| @@ -118,6 +192,151 @@ cr.define('cr.ui', function() { |
| var e = new Event('change'); |
| e.index = index; |
| this.dispatchEvent(e); |
| + |
| + if (this.sortStatus.field) { |
| + var sortPermutation = this.doSort_(this.sortStatus.field, |
| + this.sortStatus.direction); |
| + if (sortPermutation) |
| + this.dispatchPermutedEvent_(sortPermutation); |
| + } |
| + }, |
| + |
| + /** |
| + * Creates sort status with given field and direction. |
| + * @param {string} field Sort field. |
| + * @param {string} direction Sort direction. |
| + * @return {!Object} Created sort status. |
| + */ |
| + createSortStatus: function(field, direction) { |
| + return { |
| + field: field, |
| + direction: direction |
| + }; |
| + }, |
| + |
| + /** |
| + * Called before a sort happens so that you may fetch additional data |
| + * required for the sort. |
| + * |
| + * @param {string} field Sort field. |
| + * @param {function()} callback The function to invoke when preparation |
| + * is complete. |
| + */ |
| + prepareSort: function(field, callback) { |
| + callback(); |
| + }, |
| + |
| + /** |
| + * Sorts data model according to given field and direction and dispathes |
| + * sorted event. |
| + * @param {string} field Sort field. |
| + * @param {string} direction Sort direction. |
| + */ |
| + sort: function(field, direction) { |
| + var self = this; |
| + |
| + this.prepareSort(field, function() { |
| + var sortPermutation = self.doSort_(field, direction); |
| + if (sortPermutation) |
| + self.dispatchPermutedEvent_(sortPermutation); |
| + self.dispatchSortEvent_(); |
| + }); |
| + }, |
| + |
| + /** |
| + * Sorts data model according to given field and direction. |
| + * @param {string} field Sort field. |
| + * @param {string} direction Sort direction. |
| + */ |
| + doSort_: function(field, direction) { |
| + var compareFunction = this.sortFunction_(field, direction); |
| + var positions = []; |
| + for (var i = 0; i < this.length; i++) { |
| + positions[this.indexes_[i]] = i; |
| + } |
| + this.indexes_.sort(compareFunction); |
| + this.sortStatus_ = this.createSortStatus(field, direction); |
| + var sortPermutation = []; |
| + var changed = false; |
| + for (var i = 0; i < this.length; i++) { |
| + if (positions[this.indexes_[i]] != i) |
| + changed = true; |
| + sortPermutation[positions[this.indexes_[i]]] = i; |
| + } |
| + if (changed) |
| + return sortPermutation; |
| + return null; |
| + }, |
| + |
| + dispatchSortEvent_: function() { |
| + var e = new Event('sorted'); |
| + this.dispatchEvent(e); |
| + }, |
| + |
| + dispatchPermutedEvent_: function(permutation) { |
| + var e = new Event('permuted'); |
| + e.permutation = permutation; |
| + e.newLength = this.length; |
| + this.dispatchEvent(e); |
| + }, |
| + |
| + /** |
| + * Creates compare function for the field. |
| + * Returns the function set as sortFunction for given field |
| + * or default compare function |
| + * @param {string} field Sort field. |
| + * @param {function(*, *): number} Compare function. |
| + */ |
| + createCompareFunction_: function(field) { |
| + var compareFunction = |
| + this.compareFunctions_ ? this.compareFunctions_[field] : null; |
| + var defaultValuesCompareFunction = this.defaultValuesCompareFunction; |
| + if (compareFunction) { |
| + return compareFunction; |
| + } else { |
| + return function(a, b) { |
| + return defaultValuesCompareFunction.call(null, a[field], b[field]); |
| + } |
| + } |
| + return compareFunction; |
| + }, |
| + |
| + /** |
| + * Creates compare function for given field and direction. |
| + * @param {string} field Sort field. |
| + * @param {string} direction Sort direction. |
| + * @param {function(*, *): number} Compare function. |
| + */ |
| + sortFunction_: function(field, direction) { |
| + var compareFunction = null; |
| + if (field !== null) |
| + compareFunction = this.createCompareFunction_(field); |
| + var dirMultiplier = direction == 'desc' ? -1 : 1; |
| + |
| + return function(index1, index2) { |
| + var item1 = this.array_[index1]; |
| + var item2 = this.array_[index2]; |
| + |
| + var compareResult = 0; |
| + if (typeof(compareFunction) === 'function') |
| + compareResult = compareFunction.call(null, item1, item2); |
| + if (compareResult != 0) |
| + return dirMultiplier * compareResult; |
| + return dirMultiplier * this.defaultValuesCompareFunction(index1, |
| + index2); |
| + }.bind(this); |
| + }, |
| + |
| + /** |
| + * Default compare function. |
| + */ |
| + defaultValuesCompareFunction: function(a, b) { |
| + // We could insert i18n comparisons here. |
| + if (a < b) |
| + return -1; |
| + if (a > b) |
| + return 1; |
| + return 0; |
| } |
| }; |