2014-02-14 82 views
0

我有一个带有REST API的后端服务器。在前端,我使用Angular.js。为了处理实时部分,我想使用Pusher等第三方服务。用于实时和离线网络应用程序的Javascript库

我正在寻找一个简单的库,可以处理前端的mvc部分的M。更具体地说,我想要一个模型接口来抽象出离线和实时方面的复杂性。

例如,从Angular方面来说,我想订阅我的Model,并在它发生变化时得到通知。我还想有一个.save()方法来处理与服务器和其他客户端的同步。

这个库应该:

  • 脱机工作:将数据保存在LOCAL_STORAGE和同步回来的服务器时,它会重新联机。

  • 听取实时更改,更新其模型并将更改传播给听众。

  • 与标准REST接口配合良好。

所以,仅仅作为一个快速伪例如,在角我想这样做:

​​

用户是模型抽象。当它得到实时更新,我的$范围用户应该相应地改变。

$scope.users[0].set('name', 'testing) 

这应该将模型保存到服务器。或者,如果离线,应将其保存到本地,并在稍后联机时进行同步。

我知道有线上服务试图完成这项服务,例如Firebase和kinvey。这些工具的问题在于它只提供托管解决方案。我需要控制REST服务器和数据库。所以,基本上,我正在寻找一个“Firebase”库 - 没有所有的认证和授权 - 可以用于REST服务器和pubsub第三方。

谢谢!

回答

1

这个有点长的答案,但我没有它尚未公布。

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测试。

请注意,此特定演示对JSON的使用取决于一些运气和一致的按键顺序,这不受JS规范保证。按键顺序并不重要,但它可能会导致JSON.stringify()==比较失败,即使该对象的属性确实同步了。这只是为了示范的目的,如果它的真实/错误的答案,如果它的工作,不要打我...

你可以给它一个自定义回调send(“diff”,{diffs:mon.diffs }),然后使用来自pusher等人的订阅事件(“diff”,function(e){mon.patch(e.diffs,obj);});在您的MVC中应用您的更改并触发视图更新。

我会让你在localStorage和在线/离线工作,如果你需要的话,在得到这些之后应该很容易。

在变化列表中的所有的diff配有三个按键:

{"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 

这个脚本是热关闭新闻,我还没有来得及写正确的文件,但我想它可能会帮助或至少激发出适合您的解决方案。

相关问题