это немного долго ответа, но я не опубликовать его еще.
function monitor(obj, callBack){
var api={
patch: patchObjectWithDiff,
init: init,
resolve: resolve,
snapshot: snapshot,
diff: diff,
update: changeMonitor
};
function merge2(o, ob) {
for (var z in ob) {
if (ob.hasOwnProperty(z)) {
if(typeof ob[z]=="object"){
if(ob[z]==null){
delete o[z];
}else{
merge2(o[z] || {}, ob[z]);
}
}else{
o[z] = ob[z];
}
}
}
return o;
}
function snapshot(obj) {
var out = [];
function merge3(ob, path) {
path = path || [];
var tp;
for(var z in ob) {
if(ob.hasOwnProperty(z)) {
if(ob[z] && typeof ob[z] == "object" && [Date, RegExp].indexOf(ob[z].constructor) == -1) {
tp=path.concat(z);
out.push({
path: tp.join("`"),
path2: tp,
dt: "set",
date: +new Date,
v: Array.isArray(ob[z]) ? "[]" : "{}"
});
merge3(ob[z], path.concat(z));
} else {
tp=path.concat(z);
out.push({
path: tp.join("`"),
path2: tp,
type: "set",
dt: +new Date,
v: JSON.stringify(ob[z])
});
}
}
}
}
merge3(obj);
return out;
};
function diff(d1, d2){
var out=d2.filter(function(a,b,c){
var ov=JSON.stringify(a.v);
return d1.some(function(aa,bb){ return aa.path==a.path && JSON.stringify(aa.v) != ov; });
}),
// find deletions
dels=d1.filter(function(a,b,c){
return !d2.some(function(aa,bb){ if(aa.path==a.path){ return true; }; });
}),
allPaths=dels.map(function(a){return a.path}).sort(),
dels2=dels.filter(function eliminateUnneededSubBranches(a){
var pos=allPaths.indexOf(a.path2.slice(0,-1).join("`"));
return pos==-1 || pos >= allPaths.indexOf(a.path);
}).map(function(a){a.type="del"; delete a.v; return a;});
[].push.apply(out, dels2);
//find inserts
var outNew=d2.filter(function(a,b,c){
var ov=JSON.stringify(a.v);
return !d1.some(function(aa,bb){ return aa.path==a.path });
});
[].push.apply(out, outNew);
return out.map(function(a){
var x= {
dt: a.dt,
k: a.path2
};
if(a.hasOwnProperty("v")){ x.v=a.v; }
return x;
a.k=a.path2;
delete a.path;
delete a.path2;
delete a.type;
return a;
});
}
function resolve(path, object){
var tob=object;
path.map(function(a){ return (tob=tob[a])||tob; })
return tob;
}
function patchObjectWithDiff(diff, object){
diff.forEach(function(a,b,c){
var p= resolve(a.k.slice(0,-1), object),
k= a.k.slice(-1)[0];
if(a.hasOwnProperty("v")){ //set:
p[k]=JSON.parse(a.v);
if(String(p[k]).match(/Z$/)){ p[k]=new Date(''+p[k]) || p[k]; }
}else{ // del:
if(Array.isArray(p)){ p.splice(k,1); }else{ delete p[k]; }
}
});
return object;
}
var init=snapshot(JSON.parse(JSON.stringify(obj))),
id=Math.random()+ Number(new Date());
var init=snapshot(obj);
function changeMonitor(){
var thisTime=snapshot(obj),
diffs=diff(init, thisTime);
if(diffs.length){
api.diffs=diffs;
(callBack||console.log.bind(console))("objectUpdate", diffs);
init=thisTime;
}//end if change?
}
setInterval(changeMonitor, 2500);
return api;
}
демо/пример использования:
var obj={a:1, b:[1,2,3], c: false}; // a model object
var dupe=JSON.parse(JSON.stringify(obj)); // a cheap clone of the data for demo use
//subscribe this object to updates
var mon=monitor(obj, function(type, changes){console.log(type, changes); });
// make some changes to the object:
obj.e="cool!";
obj.b.push(5);
obj.a=7;
// manually call update instead of waiting for the bundler:
// (this is needed for this demo so we can reconcile the changes in sync and view the output)
mon.update();
// now apply stored changes to the clone of the orig data:
var updatedDupe= mon.patch(mon.diffs, dupe);
// use a cheap and easy but not production-reliable to compare the objects:
JSON.stringify(updatedDupe)==JSON.stringify(obj); // should be true
испытаны в хром и Firefox.
Помните, что использование данного JOON конкретного демонстрационного примера зависит от некоторой удачи и последовательного заказа клавиш, что не гарантируется спецификацией JS. Порядок клавиш не имеет большого значения, но это может привести к сбою сравнения JSON.stringify() ==, хотя свойства объекта действительно синхронизированы. Это просто для демонстрации, чтобы получить истинный/ложный ответ, если он работает, не бить меня ...
вы можете дать ему специальный обратный вызов для отправки («diff», }) изменения по мере их возникновения, а затем использовать подписанное событие из pusher et al. («diff», function (e) {mon.patch (e.diffs, obj);}); применить ваши изменения и запустить обновление просмотра в вашем MVC.
Я оставлю это вам, чтобы работать с localStorage и в режиме онлайн/офлайн там, где вам нужно, это должно быть очень легко после получения этого.
Все в файлах изменений списка изменений поставляются с тремя ключами:
{"dt":1392348959730,"k":["b","3"],"v":"5"}
dt: a timestamp of when the change was discovered
k: the key path where the change was detected
v: what the discovered changed value is as of dt
Этот сценарий горячий от прессы и я не имел время, чтобы написать надлежащую документацию, но я полагаю, что это может помочь или, по крайней мере, вдохновляйте решение, которое работает для вас.