Skip to content

Instantly share code, notes, and snippets.

@telesma
Forked from amcdnl/diffList.js
Last active August 29, 2015 14:18
Show Gist options
  • Save telesma/1ec05396bfb0640e297c to your computer and use it in GitHub Desktop.
Save telesma/1ec05396bfb0640e297c to your computer and use it in GitHub Desktop.
define(['angular'], function(angular) {
// add a few helpers
Array.prototype.union = function(a){
var r = this.slice(0);
a.forEach(function(i) { if (r.indexOf(i) < 0) r.push(i); });
return r;
};
Array.prototype.diff = function(a){
return this.filter(function(i) {return a.indexOf(i) < 0;});
};
// Inspired from: https://gist.github.com/katowulf/6193808
var module = angular.module('utils.diff', []);
/**
* A diff utility that compares arrays and returns a list of added, removed, and updated items
*
* Returns an object with two methods:
* diff: do a one-time diff of two arrays
* watch: observe a variable on scope and report any changes to a callback
*
* Invoking the factory is done like so:
* <code>
* function(listDiff) {
* // watch an object
* var watcher = listDiff();
*
* // a function that creates a unique hash from each object (e.g. it's key or id)
* function hashFn(obj) { return obj.id; }
*
* // a function to read the changeList when a change occurs
* function processChanges(changeList) { console.log('changes detected', changeList); }
*
* // attach a listener to the watcher
* listDiff.watch($scope, 'variableName', processChanges, hashFn);
*
* // directly process two arrays
* var changeList = watcher.diff( oldArray, newArray, hashFn );
* }
* </code>
*/
module.factory('listDiff', [
function() {
function diff(old, curr, hashFn) {
var out = {
count: 0,
added: [],
removed: []
};
if (!old && curr) {
out.added = curr.slice(0);
} else if (!curr && old) {
out.removed = old.slice(0);
} else if (hashFn) {
//todo this could be more efficient (it's possibly worse than o(n) right now)
var oldMap = old.map(hashFn),
newMap = curr.map(hashFn);
out.removed = oldMap.filter(function(x, k) {
return !newMap.hasOwnProperty(k);
});
out.added = newMap.filter(function(x, k) {
return !oldMap.hasOwnProperty(k);
});
} else {
// these don't work for angularFire because it returns
// different objects in each set and === is used to compare
out.removed = old.diff(curr);
out.added = curr.diff(old);
}
out.count = out.removed.length + out.added.length;
return out;
}
return {
diff: diff,
watch: function($scope, varName, callback, hashFn) {
//todo add a dispose method
return $scope.$watch(varName, function(newVal, oldVal) {
var out = diff(oldVal, newVal, hashFn);
if (out.count) {
callback(out);
}
}, true);
}
};
}
]);
/**
* A diff utility that watches a variable in scope, which is assumed to be an object.
* Any time it changes, the objects are compared (only one level deep) and a list
* of added, removed, and updated elements is returned to anybody listening on watch().
*
* Invoking the factory is done like so:
* <code>
* function(treeDiff) {
* // watch an object
* var watcher = treeDiff($scope, 'variableName');
*
* // attach a listener to the watcher
* watcher.watch(function(changeList) {
* console.log('changes detected', changeList);
* });
*
* // manually process an object
* var changeList = watcher.diff( oldObject, newObject );
* }
* </code>
*
* The return value of that call contains an object with three methods:
* diff: do a one-time diff of two arrays
* watch: add an observer to be notified of any changes
* orig: when using watch() this method returns the old object before it changed
*/
module.factory('treeDiff', function() {
return function($scope, variableName) {
var orig = angular.copy($scope[variableName]);
var listeners = [];
function update(newVal) {
newVal || (newVal = {});
var changes = diff(orig, newVal);
if (changes.count) {
notify(changes, newVal, orig);
orig = angular.copy(newVal);
}
}
function diff(orig, updated) {
var newKeys = Object.keys(updated),
oldKeys = Object.keys(orig);
var removed = oldKeys.diff(newKeys);
var added = newKeys.diff(oldKeys);
var union = newKeys.union(oldKeys);
var changes = {
count: removed.length + added.length,
added: added,
removed: removed,
updated: []
};
union.forEach(function(k) {
if (!angular.equals(orig[k], updated[k])) {
changes.updated.push(k);
changes.count++;
}
});
return changes;
}
function notify(changes, newVal, orig) {
listeners.each(function(fn) {
fn(changes, newVal, orig);
});
}
$scope.$watch(variableName, update, true);
return {
orig: function() {
return orig;
},
diff: diff,
watch: function(callback) {
listeners.push(callback);
}
}
}
});
return module;
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment