mediawiki-extensions-Echo/modules/viewmodel/mw.echo.dm.SortedList.js

165 lines
4.6 KiB
JavaScript
Raw Normal View History

( function ( mw, oo ) {
/**
* @class mw.echo.dm.SortedList
* Contains and a sorted mw.echo.dm.List
*
* @constructor
*/
mw.echo.dm.SortedList = function OoSortedEmitterList() {
// Mixin constructors
mw.echo.dm.List.call( this );
this.sortingCallback = function ( a, b ) {
return a.getTimestamp() - b.getTimestamp();
};
// Listen to sortChange event and make sure
// we re-sort the changed item when that happens
this.aggregate( {
sortChange: 'itemSortChange'
} );
this.connect( this, {
itemSortChange: 'onItemSortChange'
} );
};
oo.mixinClass( mw.echo.dm.SortedList, mw.echo.dm.List );
/**
* Handle a case where an item changed a property that relates
* to its sorted order
*
* @param {mw.echo.dm.NotificationItem} item Item in the list
*/
mw.echo.dm.SortedList.prototype.onItemSortChange = function ( item ) {
// Remove the item
this.removeItems( item );
// Re-add the item so it is in the correct place
this.addItems( item );
};
/**
* Set the sorting callback for this sorted list.
*
* @param {Function} sortingCallback Sorting callback
*/
mw.echo.dm.SortedList.prototype.setSortingCallback = function ( sortingCallback ) {
var items = this.getItems();
this.sortingCallback = sortingCallback;
// Empty the list
this.clearItems();
// Re-add the items in the new order
this.addItems( items );
};
/**
* Add items to the sorted list.
*
* @param {OO.EventEmitter|OO.EventEmitter[]} items Item to add or
* an array of items to add
*/
mw.echo.dm.SortedList.prototype.addItems = function ( items ) {
var index, i, insertionIndex;
if ( !Array.isArray( items ) ) {
items = [ items ];
}
if ( items.length === 0 ) {
return this;
}
// Call parent mixin
for ( i = 0; i < items.length; i++ ) {
// Find insertion index
insertionIndex = this.findInsertionIndex( items[ i ] );
// Check if the item exists using the sorting callback
// and remove it first if it exists
if (
// First make sure the insertion index is not at the end
// of the list (which means it does not point to any actual
// items)
insertionIndex <= this.items.length &&
// Make sure there actually is an item in this index
this.items[ insertionIndex ] &&
// The callback returns 0 if the items are equal
this.sortingCallback( this.items[ insertionIndex ], items[ i ] ) === 0
) {
// Remove the existing item
this.removeItems( this.items[ insertionIndex ] );
}
// Insert item at the insertion index
index = this.insertItem( items[ i ], insertionIndex );
this.emit( 'add', items[ i ], insertionIndex );
}
return this;
};
/**
* Normalize requested index to fit into the array.
* In the case of a sorted list, the index
*
* @param {OO.EventEmitter} item Items to insert
* @return {number} The index the item should be inserted into
*/
mw.echo.dm.SortedList.prototype.findInsertionIndex = function ( item ) {
var list = this;
return this.binarySearchIndex(
this.items,
// Fake a this.sortingCallback.bind( null, item ) call here
// otherwise this doesn't pass tests in phantomJS
function ( otherItem ) {
return list.sortingCallback( item, otherItem );
},
true
);
};
/**
* Use binary search to locate an element in a sorted array.
*
* searchFunc is given an element from the array. `searchFunc(elem)` must return a number
* above 0 if the element we're searching for is to the right of (has a higher index than) elem,
* below 0 if it is to the left of elem, or zero if it's equal to elem.
*
* To search for a specific value with a comparator function (a `function cmp(a,b)` that returns
* above 0 if `a > b`, below 0 if `a < b`, and 0 if `a == b`), you can use
* `searchFunc = cmp.bind( null, value )`.
*
* @param {Array} arr Array to search in
* @param {Function} searchFunc Search function
* @param {boolean} [forInsertion] If not found, return index where val could be inserted
* @return {number|null} Index where val was found, or null if not found
*/
mw.echo.dm.SortedList.prototype.binarySearchIndex = function ( arr, searchFunc, forInsertion ) {
// TODO: Replace this with OO.binarySearch
// See https://gerrit.wikimedia.org/r/#/c/246813/
var mid, cmpResult,
left = 0,
right = arr.length;
while ( left < right ) {
// Equivalent to Math.floor( ( left + right ) / 2 ) but much faster
/*jshint bitwise:false */
mid = ( left + right ) >> 1;
cmpResult = searchFunc( arr[ mid ] );
if ( cmpResult < 0 ) {
right = mid;
} else if ( cmpResult > 0 ) {
left = mid + 1;
} else {
return mid;
}
}
return forInsertion ? right : null;
};
}( mediaWiki, OO ) );