can-util
Version:
Common utilities for CanJS projects
151 lines (130 loc) • 4.19 kB
JavaScript
var namespace = require("can-namespace");
var slice = [].slice;
// a b c
// a b c d
// [[2,0, d]]
var defaultIdentity = function(a, b){ return a === b; };
function reverseDiff(oldDiffStopIndex, newDiffStopIndex, oldList, newList, identity) {
var oldIndex = oldList.length - 1,
newIndex = newList.length - 1;
while( oldIndex > oldDiffStopIndex && newIndex > newDiffStopIndex) {
var oldItem = oldList[oldIndex],
newItem = newList[newIndex];
if( identity( oldItem, newItem ) ) {
oldIndex--;
newIndex--;
continue;
} else {
// use newIndex because it reflects any deletions
return [{
index: newDiffStopIndex,
deleteCount: (oldIndex-oldDiffStopIndex+1),
insert: slice.call(newList, newDiffStopIndex,newIndex+1)
}];
}
}
// if we've reached of either the new or old list
// we simply return
return [{
index: newDiffStopIndex,
deleteCount: (oldIndex-oldDiffStopIndex+1),
insert: slice.call(newList, newDiffStopIndex,newIndex+1)
}];
}
/**
* @module {function} can-util/js/diff/diff diff
* @parent can-util/js
* @signature `diff( oldList, newList, [identity] )`
*
* @param {ArrayLike} oldList the array to diff from
* @param {ArrayLike} newList the array to diff to
* @param {function} identity an optional identity function for comparing elements
* @return {Array} a list of Patch objects representing the differences
*
* Returns the difference between two ArrayLike objects (that have nonnegative
* integer keys and the `length` property) as an array of patch objects.
*
* A patch object returned by this function has the following properties:
* - **index**: the index of newList where the patch begins
* - **deleteCount**: the number of items deleted from that index in newList
* - **insert**: an Array of items newly inserted at that index in newList
*
* ```js
* var diff = require("can-util/js/diff/diff");
*
* console.log(diff([1], [1, 2])); // -> [{index: 1, deleteCount: 0, insert: [2]}]
* console.log(diff([1, 2], [1])); // -> [{index: 1, deleteCount: 1, insert: []}]
*
* // with an optional identity function:
* diff(
* [{id:1},{id:2}],
* [{id:1},{id:3}],
* (a,b) => a.id === b.id
* ); // -> [{index: 1, deleteCount: 1, insert: [{id:3}]}]
* ```
*/
// TODO: update for a better type reference. E.g.:
// @typdef {function(*,*)} can-util/diff/diff/typedefs.identity identify(a, b)
//
// @param {*} a This is something.
// @param {can-util/diff/diff/typedefs.identity} identity(a, b)
// @option {*} a
module.exports = namespace.diff = function(oldList, newList, identity){
identity = identity || defaultIdentity;
var oldIndex = 0,
newIndex = 0,
oldLength = oldList.length,
newLength = newList.length,
patches = [];
while(oldIndex < oldLength && newIndex < newLength) {
var oldItem = oldList[oldIndex],
newItem = newList[newIndex];
if( identity( oldItem, newItem ) ) {
oldIndex++;
newIndex++;
continue;
}
// look for single insert, does the next newList item equal the current oldList.
// 1 2 3
// 1 2 4 3
if( newIndex+1 < newLength && identity( oldItem, newList[newIndex+1] ) ) {
patches.push({index: newIndex, deleteCount: 0, insert: [ newList[newIndex] ]});
oldIndex++;
newIndex += 2;
continue;
}
// look for single removal, does the next item in the oldList equal the current newList item.
// 1 2 3
// 1 3
else if( oldIndex+1 < oldLength && identity( oldList[oldIndex+1], newItem ) ) {
patches.push({index: newIndex, deleteCount: 1, insert: []});
oldIndex += 2;
newIndex++;
continue;
}
// just clean up the rest and exit
// 1 2 3
// 1 2 5 6 7
else {
// iterate backwards to `newIndex`
// "a", "b", "c", "d", "e"
// "a", "x", "y", "z", "e"
// -> {}
patches.push.apply(patches, reverseDiff(oldIndex, newIndex , oldList, newList, identity) );
return patches;
}
}
if( (newIndex === newLength) && (oldIndex === oldLength) ) {
return patches;
}
// a b
// a b c d e
patches.push(
{index: newIndex,
deleteCount: oldLength-oldIndex,
insert: slice.call(newList, newIndex) } );
return patches;
};
// a b c
// a d e b c
;