Compare commits

...

34 Commits

Author SHA1 Message Date
Kevin Jahns
ee516a6271 url change 2014-10-17 13:13:53 +00:00
Kevin Jahns
84601c2b94 links 2014-10-17 13:04:06 +00:00
Kevin Jahns
bab06e9f3d links 2014-10-17 13:02:12 +00:00
Kevin Jahns
be814f8f8c updated md_s 2014-10-06 16:59:18 +02:00
Kevin Jahns
ee7c5e4bad examples 2014-10-06 16:56:15 +02:00
Kevin Jahns
590c5ea900 switched to self hosted peerjs server 2014-10-06 16:53:09 +02:00
Kevin Jahns
3c21fd08b7 switch to self hosted peerjs server 2014-10-06 15:55:10 +02:00
Kevin Jahns
d2f16eaa87 merge 2014-10-06 14:19:56 +02:00
Kevin Jahns
98e582c413 finishing xml 2014-10-06 14:16:32 +02:00
Kevin Jahns
693f9bab67 update 2014-10-06 10:35:02 +02:00
Kevin Jahns
6caab5e853 docs 2014-10-06 10:17:42 +02:00
Kevin Jahns
530210ca63 small fixes 2014-10-06 10:17:05 +02:00
Kevin Jahns
3415d0ee5e tests run fine. but it needs some more user testing 2014-10-02 21:14:37 +02:00
Kevin Jahns
968e07a8ff fixed jquery.replaceWith 2014-09-30 20:10:34 +02:00
Kevin Jahns
1455933abb more jquery methods work 2014-09-29 19:26:25 +02:00
Kevin Jahns
eb3afe9106 many jquery methods work 2014-09-29 18:52:05 +02:00
Kevin Jahns
1e73381863 added counter for golovin 2014-09-29 15:32:09 +02:00
Kevin Jahns
5cb7951d44 more xml 2014-09-29 15:28:44 +02:00
Kevin Jahns
631bf47a96 merge 2014-09-29 13:12:02 +02:00
Kevin Jahns
51b5294e3c a 2014-09-29 13:10:22 +02:00
Kevin Jahns
607d442abc decreased sync timeout 2014-09-29 12:26:04 +02:00
Kevin Jahns
6623bd6134 increased gc timeout 2014-09-29 12:10:33 +02:00
Kevin Jahns
c337f37665 sync process 2014-09-29 11:58:30 +02:00
Kevin Jahns
8fcd8f6809 Added PeerJs error handlers 2014-09-29 11:06:05 +02:00
Kevin Jahns
03d652f70b insertBefore (xml), and prevent bug for addProperty-listener 2014-09-29 09:58:44 +02:00
Kevin Jahns
98b1a8f660 created xml branch 2014-09-26 12:07:44 +02:00
Kevin Jahns
09cd9f3394 created xml branch 2014-09-26 12:02:14 +02:00
Kevin Jahns
12e8768c31 resoved issue #7, empty value when addProperty fires on late join 2014-09-26 11:58:42 +02:00
Kevin Jahns
93a04c8e23 merge xml support and bugfixes 2014-09-26 10:57:17 +02:00
Kevin Jahns
977e4d2a7c begin implementing xml support 2014-09-26 10:48:21 +02:00
Kevin Jahns
4da6efd634 fixed late join issues, and null on event 2014-09-25 19:38:33 +02:00
Kevin Jahns
b750d95b98 Issue #6: Events carry creator information 2014-09-17 18:13:05 +02:00
Kevin Jahns
68c17f1876 Operations are now Garbage Collected! 2014-09-17 16:10:41 +02:00
Kevin Jahns
b03f477a3f Issue #5, handle null in json 2014-08-26 03:05:43 +02:00
102 changed files with 42029 additions and 2681 deletions

View File

@@ -6,14 +6,15 @@ Yatta! provides similar functionality as [ShareJs](https://github.com/share/Shar
but does not require you to understand how the internals work. The predefined data structures provide a simple API to access your shared data structures.
Predefined data structures:
* Text - [Collaborative Text Editing Example](http://dadamonad.github.io/Yatta/examples/TextEditing/) and [Source](./examples/TextEditing/)
* Json - [Tutorial](./examples/PeerJs-Json/)
* XML (coming soon)
* Text - [Collaborative Text Editing Example](http://dadamonad.github.io/Yatta/examples/TextEditing/)
* Json - [Tutorial](http://dadamonad.github.io/Yatta/examples/PeerJs-Json/)
* XML - [XML Example](http://dadamonad.github.io/Yatta/examples/XmlExample/) Collaboratively manipulate the dom with native dom-features and jQuery.
Unlike other frameworks, Yatta! supports P2P message propagation and is not bound to a specific communication protocol.
It is possible to add any communication protocol to Yatta. Currently it supports:
* [PeerJs](http://peerjs.com/) - A WebRTC Framework
* [SimpleWebRTC](http://simplewebrtc.com/) - Another WebRTC Framework (coming soon)
* [IWC](http://dbis.rwth-aachen.de/cms/projects/the-xmpp-experience#interwidget-communication) - Inter-widget Communication
## Use it!
@@ -75,13 +76,16 @@ Yatta! is still in an early development phase. Don't expect that everything is w
But I would become really motivated if you gave me some feedback :) ([github](https://github.com/DadaMonad/Yatta/issues)).
### Current Issues
Currently, I don't perform Garbage Collection. Therefore, the space requirement will never decrease.
* Garbage Collection
* XML support
* HTML editable tag
* More efficient representation of text.
* Use a better data structure for the History Buffer - it should be possible to use Arrays.
* SimpleRTC support
## Support
Please report any issues to the [Github issue page](https://github.com/DadaMonad/Yatta/issues)!
Please report _any_ issues to the [Github issue page](https://github.com/DadaMonad/Yatta/issues)!
I would appreciate if developers gave me feedback on how _convenient_ the framework is, and if it is easy to use. Particularly the XML-support may not support every DOM-methods - if you encounter a method that does not cause any change on other peers,
please state function name, and sample parameters. However, there are browser-specific features, that Yatta won't support.
## License
Yatta! is licensed under the [MIT License](./LICENSE.txt).

View File

@@ -5,7 +5,7 @@
"authors": [
"Kevin Jahns <kevin.jahns@rwth-aachen.de>"
],
"description": "A Framework that enables Real-Time Collaboration on arbitrary data structures.",
"description": "A Framework that enables Real-Time collaboration on arbitrary data structures.",
"main": "./build/**",
"keywords": [
"OT",

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
!function t(n,e,r){function i(u,a){if(!e[u]){if(!n[u]){var s="function"==typeof require&&require;if(!a&&s)return s(u,!0);if(o)return o(u,!0);throw new Error("Cannot find module '"+u+"'")}var c=e[u]={exports:{}};n[u][0].call(c.exports,function(t){var e=n[u][1][t];return i(e?e:t)},c,c.exports,t,n,e,r)}return e[u].exports}for(var o="function"==typeof require&&require,u=0;u<r.length;u++)i(r[u]);return i}({1:[function(t,n){var e;e=function(t,n){var e,r,i,o,u,a;return a=null,null!=n&&(a=n.iwcHandler),o={},r=new DUIClient,r.connect(function(t){var n;return null!=(n=o[t.action])&&n.map(function(n){return setTimeout(function(){return n(t)},0)}),null!=a?a(t):void 0}),r.initOK(),u=null,e=function(){function t(t,n,e,i){var a,s,c,l;this.engine=t,this.HB=n,this.execution_listener=e,this.yatta=i,this.duiClient=r,this.iwcHandler=o,l=function(t){return function(n){return 0!==Object.getOwnPropertyNames(t.initialized).length?t.send(n):void 0}}(this),this.execution_listener.push(l),this.initialized={},a=function(t){return function(e){var r;return n=e.extras.HB,r=e.extras.user,t.engine.applyOpsCheckDouble(n),t.initialized[r]=!0}}(this),o.Yatta_push_HB_element=[a],this.sendIwcIntent("Yatta_get_HB_element",this.HB.getOperationCounter()),s=function(t){return function(n){var e;return e=n.extras,null!=t.initialized[e.uid.creator]?t.receive(e):void 0}}(this),this.iwcHandler.Yatta_new_operation=[s],null!=u&&this.engine.applyOpsCheckDouble(u),c=function(t){return function(n){var e,r;return r=n.extras,console.log(r),e={HB:t.yatta.getHistoryBuffer()._encode(r),user:t.yatta.getUserId()},t.sendIwcIntent("Yatta_push_HB_element",e)}}(this),this.iwcHandler.Yatta_get_HB_element=[c]}return t.prototype.setIwcHandler=function(t){return a=t},t.prototype.sendIwcIntent=function(t,n){var e;return e=null,arguments.length>=2?(t=arguments[0],n=arguments[1],e={action:t,component:"",data:"",dataType:"",flags:["PUBLISH_GLOBAL"],extras:n}):e=arguments[0],this.duiClient.sendIntent(e)},t.prototype.send=function(t){return t.uid.creator===this.HB.getUserId()&&"string"!=typeof t.uid.op_number?this.sendIwcIntent("Yatta_new_operation",t):void 0},t.prototype.receive=function(t){return t.uid.creator!==this.HB.getUserId()?this.engine.applyOp(t):void 0},t}(),i=function(){var n;return n=Math.floor(1e6*Math.random()),t(e,n)},void setTimeout(i,5e3)},n.exports=e,"undefined"!=typeof window&&null!==window&&(null==window.Y&&(window.Y={}),window.Y.createIwcConnector=e)},{}]},{},[1]);
!function t(n,e,r){function i(u,a){if(!e[u]){if(!n[u]){var s="function"==typeof require&&require;if(!a&&s)return s(u,!0);if(o)return o(u,!0);throw new Error("Cannot find module '"+u+"'")}var c=e[u]={exports:{}};n[u][0].call(c.exports,function(t){var e=n[u][1][t];return i(e?e:t)},c,c.exports,t,n,e,r)}return e[u].exports}for(var o="function"==typeof require&&require,u=0;u<r.length;u++)i(r[u]);return i}({1:[function(t,n){var e;e=function(t,n){var e,r,i,o,u,a;return a=null,null!=n&&(a=n.iwcHandler),o={},r=new DUIClient,r.connect(function(t){var n;return null!=(n=o[t.action])&&n.map(function(n){return setTimeout(function(){return n(t)},0)}),null!=a?a(t):void 0}),r.initOK(),u=null,e=function(){function t(t,n,e,i){var a,s,c,l;this.engine=t,this.HB=n,this.execution_listener=e,this.yatta=i,this.duiClient=r,this.iwcHandler=o,l=function(t){return function(n){return 0!==Object.getOwnPropertyNames(t.initialized).length?t.send(n):void 0}}(this),this.execution_listener.push(l),this.initialized={},a=function(t){return function(e){var r;return n=e.extras.HB,r=e.extras.user,t.engine.applyOpsCheckDouble(n),t.initialized[r]=!0}}(this),o.Yatta_push_HB_element=[a],this.sendIwcIntent("Yatta_get_HB_element",this.HB.getOperationCounter()),s=function(t){return function(n){var e;return e=n.extras,null!=t.initialized[e.uid.creator]?t.receive(e):void 0}}(this),this.iwcHandler.Yatta_new_operation=[s],null!=u&&this.engine.applyOpsCheckDouble(u),c=function(t){return function(n){var e,r;return r=n.extras,console.log(r),e={HB:t.yatta.getHistoryBuffer()._encode(r),user:t.yatta.getUserId()},t.sendIwcIntent("Yatta_push_HB_element",e)}}(this),this.iwcHandler.Yatta_get_HB_element=[c]}return t.prototype.setIwcHandler=function(t){return a=t},t.prototype.sendIwcIntent=function(t,n){var e;return e=null,arguments.length>=2?(t=arguments[0],n=arguments[1],e={action:t,component:"",data:"",dataType:"",flags:["PUBLISH_GLOBAL"],extras:n}):e=arguments[0],this.duiClient.sendIntent(e)},t.prototype.send=function(t){return t.uid.creator===this.HB.getUserId()&&"string"!=typeof t.uid.op_number?this.sendIwcIntent("Yatta_new_operation",t):void 0},t.prototype.receive=function(t){return t.uid.creator!==this.HB.getUserId()?this.engine.applyOp(t):void 0},t}(),i=function(){var n;return n=Math.floor(1e6*Math.random()),t(e,n)},setTimeout(i,5e3),void 0},n.exports=e,"undefined"!=typeof window&&null!==window&&(null==window.Y&&(window.Y={}),window.Y.createIwcConnector=e)},{}]},{},[1]);

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
!function n(e,t,r){function o(u,s){if(!t[u]){if(!e[u]){var c="function"==typeof require&&require;if(!s&&c)return c(u,!0);if(i)return i(u,!0);throw new Error("Cannot find module '"+u+"'")}var f=t[u]={exports:{}};e[u][0].call(f.exports,function(n){var t=e[u][1][n];return o(t?t:n)},f,f.exports,n,e,t,r)}return t[u].exports}for(var i="function"==typeof require&&require,u=0;u<r.length;u++)o(r[u]);return o}({1:[function(n,e){var t;t=function(){var n,e,t;return t=null,2===arguments.length?(t=new Peer(arguments[0]),e=arguments[1]):(t=new Peer(arguments[0],arguments[1]),e=arguments[2]),n=function(){function n(n,e,r,o){var i;this.engine=n,this.HB=e,this.execution_listener=r,this.yatta=o,this.peer=t,this.connections={},this.peer.on("connection",function(n){return function(e){return n.addConnection(e)}}(this)),i=function(n){return function(e){var t,r,o,i;if(e.uid.creator===n.HB.getUserId()&&"string"!=typeof e.uid.op_number){o=n.connections,i=[];for(r in o)t=o[r],i.push(t.send({op:e}));return i}}}(this),this.execution_listener.push(i)}return n.prototype.connectToPeer=function(n){return null==this.connections[n]&&n!==this.yatta.getUserId()?this.addConnection(t.connect(n)):void 0},n.prototype.getAllConnectionIds=function(){var n,e;e=[];for(n in this.connections)e.push(n);return e},n.prototype.addConnection=function(n){var e,t,r;return this.connections[n.peer]=n,t=!1,e=!1,n.on("data",function(r){return function(o){var i,u,s,c,f;if("empty_message"===o);else{if(null!=o.HB)return t=!0,r.engine.applyOpsCheckDouble(o.HB),n.send({conns:r.getAllConnectionIds()});if(null!=o.op)return r.engine.applyOp(o.op);if(null!=o.conns){for(c=o.conns,f=[],u=0,s=c.length;s>u;u++)i=c[u],f.push(r.connectToPeer(i));return f}if(null==o.state_vector)throw new Error("Can't parse this operation");if(!e)return n.send({HB:r.yatta.getHistoryBuffer()._encode(o.state_vector)}),e=!0}}}(this)),(r=function(e){return function(){return n.send({state_vector:e.HB.getOperationCounter()}),t?void 0:setTimeout(r,100)}}(this))()},n}(),t.on("open",function(t){return e(n,t)})},e.exports=t,"undefined"!=typeof window&&null!==window&&(null==window.Y&&(window.Y={}),window.Y.createPeerJsConnector=t)},{}]},{},[1]);
!function n(e,t,r){function o(s,u){if(!t[s]){if(!e[s]){var c="function"==typeof require&&require;if(!u&&c)return c(s,!0);if(i)return i(s,!0);throw new Error("Cannot find module '"+s+"'")}var a=t[s]={exports:{}};e[s][0].call(a.exports,function(n){var t=e[s][1][n];return o(t?t:n)},a,a.exports,n,e,t,r)}return t[s].exports}for(var i="function"==typeof require&&require,s=0;s<r.length;s++)o(r[s]);return o}({1:[function(n,e){var t;t=function(){var n,e,t;return t=null,2===arguments.length?(t=new Peer(arguments[0]),e=arguments[1]):(t=new Peer(arguments[0],arguments[1]),t.on("error",function(n){throw new Error("Peerjs connector: "+n)}),t.on("disconnected",function(){throw new Error("Peerjs connector disconnected from signalling server. Cannot accept new connections. Not fatal, but not so good either..")}),e=arguments[2]),n=function(){function n(n,e,r,o){var i,s;this.engine=n,this.HB=e,this.execution_listener=r,this.yatta=o,this.peer=t,this.connections={},this.new_connection_listeners=[],this.peer.on("connection",function(n){return function(e){return n.addConnection(e)}}(this)),s=function(n){return function(){var e,t,r,o;r=n.connections,o=[];for(t in r)e=r[t],o.push(e.send({sync_state_vector:n.HB.getOperationCounter()}));return o}}(this),setInterval(s,4e3),i=function(n){return function(e){var t,r,o,i;if(e.uid.creator===n.HB.getUserId()&&"string"!=typeof e.uid.op_number){o=n.connections,i=[];for(r in o)t=o[r],i.push(t.send({op:e}));return i}}}(this),this.execution_listener.push(i)}return n.prototype.connectToPeer=function(n){return null==this.connections[n]&&n!==this.yatta.getUserId()?this.addConnection(t.connect(n)):void 0},n.prototype.getAllConnectionIds=function(){var n,e;e=[];for(n in this.connections)e.push(n);return e},n.prototype.onNewConnection=function(n){return this.new_connection_listeners.push(n)},n.prototype.addConnection=function(n){var e,t,r;return this.connections[n.peer]=n,t=!1,e=!1,n.on("data",function(r){return function(o){var i,s,u,c,a;if("empty_message"===o);else if(null!=o.HB){if(t=!0,r.engine.applyOpsCheckDouble(o.HB),!o.initialized)return n.send({conns:r.getAllConnectionIds()}),r.new_connection_listeners.map(function(e){return e(n)})}else{if(null!=o.op)return r.engine.applyOp(o.op);if(null!=o.conns){for(c=o.conns,a=[],s=0,u=c.length;u>s;s++)i=c[s],a.push(r.connectToPeer(i));return a}if(null!=o.sync_state_vector)return n.send({HB:r.yatta.getHistoryBuffer()._encode(o.sync_state_vector),initialized:!0});if(null==o.state_vector)throw new Error("Can't parse this operation: "+o);if(!e)return n.send({HB:r.yatta.getHistoryBuffer()._encode(o.state_vector),initialized:!1}),e=!0}}}(this)),r=function(e){return function(){return n.send({state_vector:e.HB.getOperationCounter()}),t?void 0:setTimeout(r,100)}}(this),r()},n}(),t.on("open",function(t){return e(n,t)})},e.exports=t,"undefined"!=typeof window&&null!==window&&(null==window.Y&&(window.Y={}),window.Y.createPeerJsConnector=t)},{}]},{},[1]);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -9,22 +9,44 @@
callback = arguments[1];
} else {
peer = new Peer(arguments[0], arguments[1]);
peer.on('error', function(err) {
throw new Error("Peerjs connector: " + err);
});
peer.on('disconnected', function() {
throw new Error("Peerjs connector disconnected from signalling server. Cannot accept new connections. Not fatal, but not so good either..");
});
callback = arguments[2];
}
PeerJsConnector = (function() {
function PeerJsConnector(engine, HB, execution_listener, yatta) {
var send_;
var send_, sync_every_collaborator;
this.engine = engine;
this.HB = HB;
this.execution_listener = execution_listener;
this.yatta = yatta;
this.peer = peer;
this.connections = {};
this.new_connection_listeners = [];
this.peer.on('connection', (function(_this) {
return function(conn) {
return _this.addConnection(conn);
};
})(this));
sync_every_collaborator = (function(_this) {
return function() {
var conn, conn_id, _ref, _results;
_ref = _this.connections;
_results = [];
for (conn_id in _ref) {
conn = _ref[conn_id];
_results.push(conn.send({
sync_state_vector: _this.HB.getOperationCounter()
}));
}
return _results;
};
})(this);
setInterval(sync_every_collaborator, 4000);
send_ = (function(_this) {
return function(o) {
var conn, conn_id, _ref, _results;
@@ -59,6 +81,10 @@
return _results;
};
PeerJsConnector.prototype.onNewConnection = function(f) {
return this.new_connection_listeners.push(f);
};
PeerJsConnector.prototype.addConnection = function(conn) {
var initialized_him, initialized_me, sendStateVector;
this.connections[conn.peer] = conn;
@@ -72,9 +98,14 @@
} else if (data.HB != null) {
initialized_me = true;
_this.engine.applyOpsCheckDouble(data.HB);
return conn.send({
conns: _this.getAllConnectionIds()
});
if (!data.initialized) {
conn.send({
conns: _this.getAllConnectionIds()
});
return _this.new_connection_listeners.map(function(f) {
return f(conn);
});
}
} else if (data.op != null) {
return _this.engine.applyOp(data.op);
} else if (data.conns != null) {
@@ -85,15 +116,21 @@
_results.push(_this.connectToPeer(conn_id));
}
return _results;
} else if (data.sync_state_vector != null) {
return conn.send({
HB: _this.yatta.getHistoryBuffer()._encode(data.sync_state_vector),
initialized: true
});
} else if (data.state_vector != null) {
if (!initialized_him) {
conn.send({
HB: _this.yatta.getHistoryBuffer()._encode(data.state_vector)
HB: _this.yatta.getHistoryBuffer()._encode(data.state_vector),
initialized: false
});
return initialized_him = true;
}
} else {
throw new Error("Can't parse this operation");
throw new Error("Can't parse this operation: " + data);
}
};
})(this));

File diff suppressed because one or more lines are too long

View File

@@ -27,6 +27,7 @@
if (!((user_list != null ? user_list.length : void 0) === 0)) {
this.engine.applyOps(user_list[0].getHistoryBuffer()._encode());
}
this.HB.setManualGarbageCollect();
this.unexecuted = {};
}

View File

@@ -1 +1 @@
{"version":3,"sources":["Connectors/TestConnector.coffee"],"names":[],"mappings":"AACA;AAAA,MAAA,CAAA;;AAAA,EAAA,CAAA,GAAI,OAAA,CAAQ,YAAR,CAAJ,CAAA;;AAAA,EAEA,MAAM,CAAC,OAAP,GAAiB,SAAC,SAAD,GAAA;AAMf,QAAA,aAAA;WAAM;AAQS,MAAA,uBAAE,MAAF,EAAW,EAAX,EAAgB,kBAAhB,GAAA;AACX,YAAA,gCAAA;AAAA,QADY,IAAC,CAAA,SAAA,MACb,CAAA;AAAA,QADqB,IAAC,CAAA,KAAA,EACtB,CAAA;AAAA,QAD0B,IAAC,CAAA,qBAAA,kBAC3B,CAAA;AAAA,QAAA,KAAA,GAAQ,CAAA,SAAA,KAAA,GAAA;iBAAA,SAAC,CAAD,GAAA;mBACN,KAAC,CAAA,IAAD,CAAM,CAAN,EADM;UAAA,EAAA;QAAA,CAAA,CAAA,CAAA,IAAA,CAAR,CAAA;AAAA,QAEA,IAAC,CAAA,kBAAkB,CAAC,IAApB,CAAyB,KAAzB,CAFA,CAAA;AAAA,QAIA,IAAC,CAAA,kBAAD,GAAsB,EAJtB,CAAA;AAAA,QAKA,yBAAA,GAA4B,CAAA,SAAA,KAAA,GAAA;iBAAA,SAAC,CAAD,GAAA;mBAC1B,KAAC,CAAA,kBAAkB,CAAC,IAApB,CAAyB,CAAzB,EAD0B;UAAA,EAAA;QAAA,CAAA,CAAA,CAAA,IAAA,CAL5B,CAAA;AAAA,QAOA,IAAC,CAAA,kBAAkB,CAAC,IAApB,CAAyB,yBAAzB,CAPA,CAAA;AAQA,QAAA,IAAG,CAAA,sBAAK,SAAS,CAAE,gBAAX,KAAqB,CAAtB,CAAP;AACE,UAAA,IAAC,CAAA,MAAM,CAAC,QAAR,CAAiB,SAAU,CAAA,CAAA,CAAE,CAAC,gBAAb,CAAA,CAA+B,CAAC,OAAhC,CAAA,CAAjB,CAAA,CADF;SARA;AAAA,QAWA,IAAC,CAAA,UAAD,GAAc,EAXd,CADW;MAAA,CAAb;;AAAA,8BAkBA,sBAAA,GAAwB,SAAA,GAAA;eACtB,IAAC,CAAA,mBADqB;MAAA,CAlBxB,CAAA;;AAAA,8BAyBA,IAAA,GAAM,SAAC,CAAD,GAAA;AACJ,YAAA,wBAAA;AAAA,QAAA,IAAG,CAAC,CAAC,CAAC,GAAG,CAAC,OAAN,KAAiB,IAAC,CAAA,EAAE,CAAC,SAAJ,CAAA,CAAlB,CAAA,IAAuC,CAAC,MAAA,CAAA,CAAQ,CAAC,GAAG,CAAC,SAAb,KAA4B,QAA7B,CAA1C;AACE;eAAA,gDAAA;iCAAA;AACE,YAAA,IAAG,IAAI,CAAC,SAAL,CAAA,CAAA,KAAsB,IAAC,CAAA,EAAE,CAAC,SAAJ,CAAA,CAAzB;4BACE,IAAI,CAAC,YAAL,CAAA,CAAmB,CAAC,OAApB,CAA4B,CAA5B,GADF;aAAA,MAAA;oCAAA;aADF;AAAA;0BADF;SADI;MAAA,CAzBN,CAAA;;AAAA,8BAmCA,OAAA,GAAS,SAAC,CAAD,GAAA;AACP,YAAA,YAAA;;yBAA8B;SAA9B;eACA,IAAC,CAAA,UAAW,CAAA,CAAC,CAAC,GAAG,CAAC,OAAN,CAAc,CAAC,IAA3B,CAAgC,CAAhC,EAFO;MAAA,CAnCT,CAAA;;AAAA,8BA0CA,QAAA,GAAU,SAAC,IAAD,GAAA;AACR,YAAA,IAAA;AAAA,QAAA,kDAAoB,CAAE,gBAAnB,GAA4B,CAA/B;iBACE,IAAC,CAAA,MAAM,CAAC,OAAR,CAAgB,IAAC,CAAA,UAAW,CAAA,IAAA,CAAK,CAAC,KAAlB,CAAA,CAAhB,EADF;SADQ;MAAA,CA1CV,CAAA;;AAAA,8BAiDA,cAAA,GAAgB,SAAA,GAAA;eACd,IAAC,CAAA,QAAD,CAAW,CAAC,CAAC,MAAF,CAAS,CAAT,EAAa,SAAS,CAAC,MAAV,GAAiB,CAA9B,CAAX,EADc;MAAA,CAjDhB,CAAA;;AAAA,8BAuDA,QAAA,GAAU,SAAA,GAAA;AACR,YAAA,YAAA;AAAA;AAAA,aAAA,SAAA;wBAAA;AACE,UAAA,IAAC,CAAA,MAAM,CAAC,QAAR,CAAiB,GAAjB,CAAA,CADF;AAAA,SAAA;eAEA,IAAC,CAAA,UAAD,GAAc,GAHN;MAAA,CAvDV,CAAA;;2BAAA;;SAda;EAAA,CAFjB,CAAA;AAAA","file":"Connectors/TestConnector.js","sourceRoot":"/source/","sourcesContent":["\n_ = require \"underscore\"\n\nmodule.exports = (user_list)->\n\n #\n # @nodoc\n # A trivial Connector that simulates network delay.\n #\n class TestConnector\n\n #\n # @param {Engine} engine The transformation engine\n # @param {HistoryBuffer} HB\n # @param {Array<Function>} execution_listener You must ensure that whenever an operation is executed, every function in this Array is called.\n # @param {Yatta} yatta The Yatta framework.\n #\n constructor: (@engine, @HB, @execution_listener)->\n send_ = (o)=>\n @send o\n @execution_listener.push send_\n\n @applied_operations = []\n appliedOperationsListener = (o)=>\n @applied_operations.push o\n @execution_listener.push appliedOperationsListener\n if not (user_list?.length is 0)\n @engine.applyOps user_list[0].getHistoryBuffer()._encode()\n\n @unexecuted = {}\n\n #\n # This engine applied operations in a specific order.\n # Get the ops in the right order.\n #\n getOpsInExecutionOrder: ()->\n @applied_operations\n\n #\n # This function is called whenever an operation was executed.\n # @param {Operation} o The operation that was executed.\n #\n send: (o)->\n if (o.uid.creator is @HB.getUserId()) and (typeof o.uid.op_number isnt \"string\")\n for user in user_list\n if user.getUserId() isnt @HB.getUserId()\n user.getConnector().receive(o)\n\n #\n # This function is called whenever an operation was received from another peer.\n # @param {Operation} o The operation that was received.\n #\n receive: (o)->\n @unexecuted[o.uid.creator] ?= []\n @unexecuted[o.uid.creator].push o\n\n #\n # Flush one operation from the line of a specific user.\n #\n flushOne: (user)->\n if @unexecuted[user]?.length > 0\n @engine.applyOp @unexecuted[user].shift()\n\n #\n # Flush one operation on a random line.\n #\n flushOneRandom: ()->\n @flushOne (_.random 0, (user_list.length-1))\n\n #\n # Flush all operations on every line.\n #\n flushAll: ()->\n for n,ops of @unexecuted\n @engine.applyOps ops\n @unexecuted = {}\n\n"]}
{"version":3,"sources":["Connectors/TestConnector.coffee"],"names":[],"mappings":"AACA;AAAA,MAAA,CAAA;;AAAA,EAAA,CAAA,GAAI,OAAA,CAAQ,YAAR,CAAJ,CAAA;;AAAA,EAEA,MAAM,CAAC,OAAP,GAAiB,SAAC,SAAD,GAAA;AAMf,QAAA,aAAA;WAAM;AAQS,MAAA,uBAAE,MAAF,EAAW,EAAX,EAAgB,kBAAhB,GAAA;AACX,YAAA,gCAAA;AAAA,QADY,IAAC,CAAA,SAAA,MACb,CAAA;AAAA,QADqB,IAAC,CAAA,KAAA,EACtB,CAAA;AAAA,QAD0B,IAAC,CAAA,qBAAA,kBAC3B,CAAA;AAAA,QAAA,KAAA,GAAQ,CAAA,SAAA,KAAA,GAAA;iBAAA,SAAC,CAAD,GAAA;mBACN,KAAC,CAAA,IAAD,CAAM,CAAN,EADM;UAAA,EAAA;QAAA,CAAA,CAAA,CAAA,IAAA,CAAR,CAAA;AAAA,QAEA,IAAC,CAAA,kBAAkB,CAAC,IAApB,CAAyB,KAAzB,CAFA,CAAA;AAAA,QAIA,IAAC,CAAA,kBAAD,GAAsB,EAJtB,CAAA;AAAA,QAKA,yBAAA,GAA4B,CAAA,SAAA,KAAA,GAAA;iBAAA,SAAC,CAAD,GAAA;mBAC1B,KAAC,CAAA,kBAAkB,CAAC,IAApB,CAAyB,CAAzB,EAD0B;UAAA,EAAA;QAAA,CAAA,CAAA,CAAA,IAAA,CAL5B,CAAA;AAAA,QAOA,IAAC,CAAA,kBAAkB,CAAC,IAApB,CAAyB,yBAAzB,CAPA,CAAA;AAQA,QAAA,IAAG,CAAA,sBAAK,SAAS,CAAE,gBAAX,KAAqB,CAAtB,CAAP;AACE,UAAA,IAAC,CAAA,MAAM,CAAC,QAAR,CAAiB,SAAU,CAAA,CAAA,CAAE,CAAC,gBAAb,CAAA,CAA+B,CAAC,OAAhC,CAAA,CAAjB,CAAA,CADF;SARA;AAAA,QAWA,IAAC,CAAA,EAAE,CAAC,uBAAJ,CAAA,CAXA,CAAA;AAAA,QAYA,IAAC,CAAA,UAAD,GAAc,EAZd,CADW;MAAA,CAAb;;AAAA,8BAmBA,sBAAA,GAAwB,SAAA,GAAA;eACtB,IAAC,CAAA,mBADqB;MAAA,CAnBxB,CAAA;;AAAA,8BA0BA,IAAA,GAAM,SAAC,CAAD,GAAA;AACJ,YAAA,wBAAA;AAAA,QAAA,IAAG,CAAC,CAAC,CAAC,GAAG,CAAC,OAAN,KAAiB,IAAC,CAAA,EAAE,CAAC,SAAJ,CAAA,CAAlB,CAAA,IAAuC,CAAC,MAAA,CAAA,CAAQ,CAAC,GAAG,CAAC,SAAb,KAA4B,QAA7B,CAA1C;AACE;eAAA,gDAAA;iCAAA;AACE,YAAA,IAAG,IAAI,CAAC,SAAL,CAAA,CAAA,KAAsB,IAAC,CAAA,EAAE,CAAC,SAAJ,CAAA,CAAzB;4BACE,IAAI,CAAC,YAAL,CAAA,CAAmB,CAAC,OAApB,CAA4B,CAA5B,GADF;aAAA,MAAA;oCAAA;aADF;AAAA;0BADF;SADI;MAAA,CA1BN,CAAA;;AAAA,8BAoCA,OAAA,GAAS,SAAC,CAAD,GAAA;AACP,YAAA,YAAA;;yBAA8B;SAA9B;eACA,IAAC,CAAA,UAAW,CAAA,CAAC,CAAC,GAAG,CAAC,OAAN,CAAc,CAAC,IAA3B,CAAgC,CAAhC,EAFO;MAAA,CApCT,CAAA;;AAAA,8BA2CA,QAAA,GAAU,SAAC,IAAD,GAAA;AACR,YAAA,IAAA;AAAA,QAAA,kDAAoB,CAAE,gBAAnB,GAA4B,CAA/B;iBACE,IAAC,CAAA,MAAM,CAAC,OAAR,CAAgB,IAAC,CAAA,UAAW,CAAA,IAAA,CAAK,CAAC,KAAlB,CAAA,CAAhB,EADF;SADQ;MAAA,CA3CV,CAAA;;AAAA,8BAkDA,cAAA,GAAgB,SAAA,GAAA;eACd,IAAC,CAAA,QAAD,CAAW,CAAC,CAAC,MAAF,CAAS,CAAT,EAAa,SAAS,CAAC,MAAV,GAAiB,CAA9B,CAAX,EADc;MAAA,CAlDhB,CAAA;;AAAA,8BAwDA,QAAA,GAAU,SAAA,GAAA;AACR,YAAA,YAAA;AAAA;AAAA,aAAA,SAAA;wBAAA;AACE,UAAA,IAAC,CAAA,MAAM,CAAC,QAAR,CAAiB,GAAjB,CAAA,CADF;AAAA,SAAA;eAEA,IAAC,CAAA,UAAD,GAAc,GAHN;MAAA,CAxDV,CAAA;;2BAAA;;SAda;EAAA,CAFjB,CAAA;AAAA","file":"Connectors/TestConnector.js","sourceRoot":"/source/","sourcesContent":["\n_ = require \"underscore\"\n\nmodule.exports = (user_list)->\n\n #\n # @nodoc\n # A trivial Connector that simulates network delay.\n #\n class TestConnector\n\n #\n # @param {Engine} engine The transformation engine\n # @param {HistoryBuffer} HB\n # @param {Array<Function>} execution_listener You must ensure that whenever an operation is executed, every function in this Array is called.\n # @param {Yatta} yatta The Yatta framework.\n #\n constructor: (@engine, @HB, @execution_listener)->\n send_ = (o)=>\n @send o\n @execution_listener.push send_\n\n @applied_operations = []\n appliedOperationsListener = (o)=>\n @applied_operations.push o\n @execution_listener.push appliedOperationsListener\n if not (user_list?.length is 0)\n @engine.applyOps user_list[0].getHistoryBuffer()._encode()\n\n @HB.setManualGarbageCollect()\n @unexecuted = {}\n\n #\n # This engine applied operations in a specific order.\n # Get the ops in the right order.\n #\n getOpsInExecutionOrder: ()->\n @applied_operations\n\n #\n # This function is called whenever an operation was executed.\n # @param {Operation} o The operation that was executed.\n #\n send: (o)->\n if (o.uid.creator is @HB.getUserId()) and (typeof o.uid.op_number isnt \"string\")\n for user in user_list\n if user.getUserId() isnt @HB.getUserId()\n user.getConnector().receive(o)\n\n #\n # This function is called whenever an operation was received from another peer.\n # @param {Operation} o The operation that was received.\n #\n receive: (o)->\n @unexecuted[o.uid.creator] ?= []\n @unexecuted[o.uid.creator].push o\n\n #\n # Flush one operation from the line of a specific user.\n #\n flushOne: (user)->\n if @unexecuted[user]?.length > 0\n @engine.applyOp @unexecuted[user].shift()\n\n #\n # Flush one operation on a random line.\n #\n flushOneRandom: ()->\n @flushOne (_.random 0, (user_list.length-1))\n\n #\n # Flush all operations on every line.\n #\n flushAll: ()->\n for n,ops of @unexecuted\n @engine.applyOps ops\n @unexecuted = {}\n\n"]}

View File

@@ -66,7 +66,9 @@
var o;
o = this.parseOperation(op_json);
this.HB.addToCounter(o);
if (!o.execute()) {
if (this.HB.getOperation(o) != null) {
} else if (!o.execute()) {
this.unprocessed_ops.push(o);
} else {
this.HB.addOperation(o);
@@ -83,7 +85,9 @@
_ref = this.unprocessed_ops;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
op = _ref[_i];
if (!op.execute()) {
if (this.HB.getOperation(op) != null) {
} else if (!op.execute()) {
unprocessed.push(op);
} else {
this.HB.addOperation(op);

File diff suppressed because one or more lines are too long

View File

@@ -9,19 +9,26 @@
JsonFramework = (function() {
function JsonFramework(user_id, Connector) {
var first_word, type_manager;
var beg, end, first_word, type_manager, uid_beg, uid_end;
this.HB = new HistoryBuffer(user_id);
type_manager = json_types_uninitialized(this.HB);
this.types = type_manager.types;
this.engine = new Engine(this.HB, type_manager.parser);
this.HB.engine = this.engine;
this.connector = new Connector(this.engine, this.HB, type_manager.execution_listener, this);
first_word = new this.types.JsonType(this.HB.getReservedUniqueIdentifier());
this.HB.addOperation(first_word).execute();
this.root_element = first_word;
uid_beg = this.HB.getReservedUniqueIdentifier();
uid_end = this.HB.getReservedUniqueIdentifier();
beg = this.HB.addOperation(new this.types.Delimiter(uid_beg, void 0, uid_end)).execute();
end = this.HB.addOperation(new this.types.Delimiter(uid_end, beg, void 0)).execute();
this.root_element = new this.types.ReplaceManager(void 0, this.HB.getReservedUniqueIdentifier(), beg, end);
this.HB.addOperation(this.root_element).execute();
this.root_element.replace(first_word, this.HB.getReservedUniqueIdentifier());
}
JsonFramework.prototype.getSharedObject = function() {
return this.root_element;
return this.root_element.val();
};
JsonFramework.prototype.getConnector = function() {
@@ -33,7 +40,7 @@
};
JsonFramework.prototype.setMutableDefault = function(mutable) {
return this.root_element.setMutableDefault(mutable);
return this.getSharedObject().setMutableDefault(mutable);
};
JsonFramework.prototype.getUserId = function() {
@@ -41,26 +48,27 @@
};
JsonFramework.prototype.toJson = function() {
return this.root_element.toJson();
return this.getSharedObject().toJson();
};
JsonFramework.prototype.val = function(name, content, mutable) {
return this.root_element.val(name, content, mutable);
JsonFramework.prototype.val = function() {
var _ref;
return (_ref = this.getSharedObject()).val.apply(_ref, arguments);
};
JsonFramework.prototype.on = function() {
var _ref;
return (_ref = this.root_element).on.apply(_ref, arguments);
return (_ref = this.getSharedObject()).on.apply(_ref, arguments);
};
JsonFramework.prototype.deleteListener = function() {
var _ref;
return (_ref = this.root_element).deleteListener.apply(_ref, arguments);
return (_ref = this.getSharedObject()).deleteListener.apply(_ref, arguments);
};
Object.defineProperty(JsonFramework.prototype, 'value', {
get: function() {
return this.root_element.value;
return this.getSharedObject().value;
},
set: function(o) {
var o_name, o_obj, _results;

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,85 @@
(function() {
var Engine, HistoryBuffer, XmlFramework, json_types_uninitialized;
json_types_uninitialized = require("../Types/XmlTypes");
HistoryBuffer = require("../HistoryBuffer");
Engine = require("../Engine");
XmlFramework = (function() {
function XmlFramework(user_id, Connector) {
var beg, end, type_manager, uid_beg, uid_end;
this.HB = new HistoryBuffer(user_id);
type_manager = json_types_uninitialized(this.HB);
this.types = type_manager.types;
this.engine = new Engine(this.HB, type_manager.parser);
this.HB.engine = this.engine;
this.connector = new Connector(this.engine, this.HB, type_manager.execution_listener, this);
uid_beg = this.HB.getReservedUniqueIdentifier();
uid_end = this.HB.getReservedUniqueIdentifier();
beg = this.HB.addOperation(new this.types.Delimiter(uid_beg, void 0, uid_end)).execute();
end = this.HB.addOperation(new this.types.Delimiter(uid_end, beg, void 0)).execute();
this.root_element = new this.types.ReplaceManager(void 0, this.HB.getReservedUniqueIdentifier(), beg, end);
this.HB.addOperation(this.root_element).execute();
}
XmlFramework.prototype.getSharedObject = function() {
return this.root_element.val();
};
XmlFramework.prototype.getConnector = function() {
return this.connector;
};
XmlFramework.prototype.getHistoryBuffer = function() {
return this.HB;
};
XmlFramework.prototype.setMutableDefault = function(mutable) {
return this.getSharedObject().setMutableDefault(mutable);
};
XmlFramework.prototype.getUserId = function() {
return this.HB.getUserId();
};
XmlFramework.prototype.toJson = function() {
return this.getSharedObject().toJson();
};
XmlFramework.prototype.val = function() {
var newXml;
if ((arguments.length === 0) || (typeof arguments[0] === "boolean")) {
return this.getSharedObject().val(arguments[0]);
} else if (arguments.length === 1) {
newXml = new this.types.XmlType(void 0, void 0, void 0, void 0, arguments[0]);
this.HB.addOperation(newXml).execute();
this.root_element.replace(newXml);
return newXml;
} else {
throw new Error("can only parse 0, or 1 parameter!");
}
};
XmlFramework.prototype.on = function() {
var _ref;
return (_ref = this.getSharedObject()).on.apply(_ref, arguments);
};
return XmlFramework;
})();
module.exports = XmlFramework;
if (typeof window !== "undefined" && window !== null) {
if (window.Y == null) {
window.Y = {};
}
window.Y.XmlFramework = XmlFramework;
}
}).call(this);
//# sourceMappingURL=../Frameworks/XmlFramework.js.map

File diff suppressed because one or more lines are too long

View File

@@ -1,34 +1,97 @@
(function() {
var HistoryBuffer;
var HistoryBuffer,
__bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
HistoryBuffer = (function() {
function HistoryBuffer(user_id) {
this.user_id = user_id;
this.emptyGarbage = __bind(this.emptyGarbage, this);
this.operation_counter = {};
this.buffer = {};
this.change_listeners = [];
this.garbage = [];
this.trash = [];
this.performGarbageCollection = true;
this.garbageCollectTimeout = 1000;
this.reserved_identifier_counter = 0;
setTimeout(this.emptyGarbage, this.garbageCollectTimeout);
}
HistoryBuffer.prototype.emptyGarbage = function() {
var o, _i, _len, _ref;
_ref = this.garbage;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
o = _ref[_i];
if (typeof o.cleanup === "function") {
o.cleanup();
}
}
this.garbage = this.trash;
this.trash = [];
if (this.garbageCollectTimeout !== -1) {
this.garbageCollectTimeoutId = setTimeout(this.emptyGarbage, this.garbageCollectTimeout);
}
return void 0;
};
HistoryBuffer.prototype.getUserId = function() {
return this.user_id;
};
HistoryBuffer.prototype.addToGarbageCollector = function() {
var o, _i, _len, _results;
if (this.performGarbageCollection) {
_results = [];
for (_i = 0, _len = arguments.length; _i < _len; _i++) {
o = arguments[_i];
if (o != null) {
_results.push(this.garbage.push(o));
} else {
_results.push(void 0);
}
}
return _results;
}
};
HistoryBuffer.prototype.stopGarbageCollection = function() {
this.performGarbageCollection = false;
this.setManualGarbageCollect();
this.garbage = [];
return this.trash = [];
};
HistoryBuffer.prototype.setManualGarbageCollect = function() {
this.garbageCollectTimeout = -1;
clearTimeout(this.garbageCollectTimeoutId);
return this.garbageCollectTimeoutId = void 0;
};
HistoryBuffer.prototype.setGarbageCollectTimeout = function(garbageCollectTimeout) {
this.garbageCollectTimeout = garbageCollectTimeout;
};
HistoryBuffer.prototype.getReservedUniqueIdentifier = function() {
return {
creator: '_',
op_number: '_'
op_number: "_" + (this.reserved_identifier_counter++),
doSync: false
};
};
HistoryBuffer.prototype.getOperationCounter = function() {
HistoryBuffer.prototype.getOperationCounter = function(user_id) {
var ctn, res, user, _ref;
res = {};
_ref = this.operation_counter;
for (user in _ref) {
ctn = _ref[user];
res[user] = ctn;
if (user_id == null) {
res = {};
_ref = this.operation_counter;
for (user in _ref) {
ctn = _ref[user];
res[user] = ctn;
}
return res;
} else {
return this.operation_counter[user_id];
}
return res;
};
HistoryBuffer.prototype._encode = function(state_vector) {
@@ -48,7 +111,7 @@
user = _ref[u_name];
for (o_number in user) {
o = user[o_number];
if ((!isNaN(parseInt(o_number))) && unknown(u_name, o_number)) {
if (o.doSync && unknown(u_name, o_number)) {
o_json = o._encode();
if (o.next_cl != null) {
o_next = o.next_cl;
@@ -105,9 +168,18 @@
throw new Error("You must not overwrite operations!");
}
this.buffer[o.creator][o.op_number] = o;
if (this.number_of_operations_added_to_HB == null) {
this.number_of_operations_added_to_HB = 0;
}
this.number_of_operations_added_to_HB++;
return o;
};
HistoryBuffer.prototype.removeOperation = function(o) {
var _ref;
return (_ref = this.buffer[o.creator]) != null ? delete _ref[o.op_number] : void 0;
};
HistoryBuffer.prototype.addToCounter = function(o) {
var _results;
if (this.operation_counter[o.creator] == null) {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -9,12 +9,20 @@
execution_listener = [];
Operation = (function() {
function Operation(uid) {
this.is_deleted = false;
this.doSync = true;
this.garbage_collected = false;
if (uid == null) {
uid = HB.getNextOperationIdentifier();
}
this.creator = uid['creator'], this.op_number = uid['op_number'];
if (uid.doSync == null) {
uid.doSync = !isNaN(parseInt(uid.op_number));
}
this.creator = uid['creator'], this.op_number = uid['op_number'], this.doSync = uid['doSync'];
}
Operation.prototype.type = "Insert";
Operation.prototype.on = function(events, f) {
var e, _base, _i, _len, _results;
if (this.event_listeners == null) {
@@ -71,6 +79,27 @@
}
};
Operation.prototype.isDeleted = function() {
return this.is_deleted;
};
Operation.prototype.applyDelete = function(garbagecollect) {
if (garbagecollect == null) {
garbagecollect = true;
}
if (!this.garbage_collected) {
this.is_deleted = true;
if (garbagecollect) {
this.garbage_collected = true;
return HB.addToGarbageCollector(this);
}
}
};
Operation.prototype.cleanup = function() {
return HB.removeOperation(this);
};
Operation.prototype.setParent = function(parent) {
this.parent = parent;
};
@@ -82,10 +111,15 @@
Operation.prototype.getUid = function() {
return {
'creator': this.creator,
'op_number': this.op_number
'op_number': this.op_number,
'sync': this.doSync
};
};
Operation.prototype.dontSync = function() {
return this.doSync = false;
};
Operation.prototype.execute = function() {
var l, _i, _len;
this.is_executed = true;
@@ -140,6 +174,8 @@
Delete.__super__.constructor.call(this, uid);
}
Delete.prototype.type = "Delete";
Delete.prototype._encode = function() {
return {
'type': "Delete",
@@ -179,19 +215,52 @@
Insert.__super__.constructor.call(this, uid);
}
Insert.prototype.type = "Insert";
Insert.prototype.applyDelete = function(o) {
var callLater, garbagecollect, _ref;
if (this.deleted_by == null) {
this.deleted_by = [];
}
this.deleted_by.push(o);
if ((this.parent != null) && this.deleted_by.length === 1) {
return this.parent.callEvent("delete", this);
callLater = false;
if ((this.parent != null) && !this.isDeleted()) {
callLater = true;
}
if (o != null) {
this.deleted_by.push(o);
}
garbagecollect = false;
if (!((this.prev_cl != null) && (this.next_cl != null)) || this.prev_cl.isDeleted()) {
garbagecollect = true;
}
Insert.__super__.applyDelete.call(this, garbagecollect);
if (callLater) {
this.parent.callEvent("delete", this, o);
}
if ((_ref = this.next_cl) != null ? _ref.isDeleted() : void 0) {
return this.next_cl.applyDelete();
}
};
Insert.prototype.isDeleted = function() {
var _ref;
return ((_ref = this.deleted_by) != null ? _ref.length : void 0) > 0;
Insert.prototype.cleanup = function() {
var d, o, _i, _len, _ref, _ref1;
if ((_ref = this.prev_cl) != null ? _ref.isDeleted() : void 0) {
_ref1 = this.deleted_by;
for (_i = 0, _len = _ref1.length; _i < _len; _i++) {
d = _ref1[_i];
d.cleanup();
}
o = this.next_cl;
while (o.type !== "Delimiter") {
if (o.origin === this) {
o.origin = this.prev_cl;
}
o = o.next_cl;
}
this.prev_cl.next_cl = this.next_cl;
this.next_cl.prev_cl = this.prev_cl;
return Insert.__super__.cleanup.apply(this, arguments);
}
};
Insert.prototype.getDistanceToOrigin = function() {
@@ -203,53 +272,24 @@
break;
}
d++;
if (this === this.prev_cl) {
throw new Error("this should not happen ;) ");
}
o = o.prev_cl;
}
return d;
};
Insert.prototype.update_sl = function() {
var o;
o = this.prev_cl;
({
update: function(dest_cl, dest_sl) {
var _results;
_results = [];
while (true) {
if (o.isDeleted()) {
_results.push(o = o[dest_cl]);
} else {
this[dest_sl] = o;
break;
}
}
return _results;
}
});
update("prev_cl", "prev_sl");
return update("next_cl", "prev_sl");
};
Insert.prototype.execute = function() {
var distance_to_origin, i, o, parent, _ref, _ref1, _ref2;
if (this.is_executed != null) {
return this;
Insert.prototype.execute = function(fire_event) {
var distance_to_origin, i, o, parent, _ref;
if (fire_event == null) {
fire_event = true;
}
if (!this.validateSavedOperations()) {
return false;
} else {
if (((_ref = this.prev_cl) != null ? _ref.validateSavedOperations() : void 0) && ((_ref1 = this.next_cl) != null ? _ref1.validateSavedOperations() : void 0) && this.prev_cl.next_cl !== this) {
distance_to_origin = 0;
if (this.prev_cl != null) {
distance_to_origin = this.getDistanceToOrigin();
o = this.prev_cl.next_cl;
i = 0;
i = distance_to_origin;
while (true) {
if (o == null) {
console.log(JSON.stringify(this.prev_cl.getUid()));
console.log(JSON.stringify(this.next_cl.getUid()));
}
if (o !== this.next_cl) {
if (o.getDistanceToOrigin() === i) {
if (o.creator < this.creator) {
@@ -278,8 +318,8 @@
this.prev_cl.next_cl = this;
this.next_cl.prev_cl = this;
}
parent = (_ref2 = this.prev_cl) != null ? _ref2.getParent() : void 0;
if (parent != null) {
parent = (_ref = this.prev_cl) != null ? _ref.getParent() : void 0;
if ((parent != null) && fire_event) {
this.setParent(parent);
this.parent.callEvent("insert", this);
}
@@ -295,7 +335,7 @@
if (prev instanceof Delimiter) {
break;
}
if ((prev.isDeleted != null) && !prev.isDeleted()) {
if (!prev.isDeleted()) {
position++;
}
prev = prev.prev_cl;
@@ -314,6 +354,8 @@
ImmutableObject.__super__.constructor.call(this, uid, prev, next, origin);
}
ImmutableObject.prototype.type = "ImmutableObject";
ImmutableObject.prototype.val = function() {
return this.content;
};
@@ -331,15 +373,15 @@
if (this.next_cl != null) {
json['next'] = this.next_cl.getUid();
}
if ((this.origin != null) && this.origin !== this.prev_cl) {
json["origin"] = this.origin.getUid();
if (this.origin != null) {
json["origin"] = this.origin().getUid();
}
return json;
};
return ImmutableObject;
})(Insert);
})(Operation);
parser['ImmutableObject'] = function(json) {
var content, next, origin, prev, uid;
uid = json['uid'], content = json['content'], prev = json['prev'], next = json['next'], origin = json['origin'];
@@ -355,8 +397,21 @@
Delimiter.__super__.constructor.call(this, uid);
}
Delimiter.prototype.isDeleted = function() {
return false;
Delimiter.prototype.type = "Delimiter";
Delimiter.prototype.applyDelete = function() {
var o;
Delimiter.__super__.applyDelete.call(this);
o = this.next_cl;
while (o != null) {
o.applyDelete();
o = o.next_cl;
}
return void 0;
};
Delimiter.prototype.cleanup = function() {
return Delimiter.__super__.cleanup.call(this);
};
Delimiter.prototype.execute = function() {

File diff suppressed because one or more lines are too long

View File

@@ -78,13 +78,23 @@
JsonType.prototype.type = "JsonType";
JsonType.prototype.applyDelete = function() {
return JsonType.__super__.applyDelete.call(this);
};
JsonType.prototype.cleanup = function() {
return JsonType.__super__.cleanup.call(this);
};
JsonType.prototype.toJson = function() {
var json, name, o, val;
val = this.val();
json = {};
for (name in val) {
o = val[name];
if (o.constructor === {}.constructor) {
if (o === null) {
json[name] = o;
} else if (o.constructor === {}.constructor) {
json[name] = this.val(name).toJson();
} else if (o instanceof types.Operation) {
while (o instanceof types.Operation) {
@@ -98,16 +108,18 @@
return json;
};
JsonType.prototype.setReplaceManager = function(rm) {
this.parent = rm.parent;
JsonType.prototype.setReplaceManager = function(replace_manager) {
this.replace_manager = replace_manager;
return this.on(['change', 'addProperty'], function() {
var _ref;
return (_ref = rm.parent).forwardEvent.apply(_ref, [this].concat(__slice.call(arguments)));
if (replace_manager.parent != null) {
return (_ref = replace_manager.parent).forwardEvent.apply(_ref, [this].concat(__slice.call(arguments)));
}
});
};
JsonType.prototype.getParent = function() {
return this.parent;
return this.replace_manager.parent;
};
JsonType.prototype.mutable_default = true;
@@ -124,14 +136,13 @@
};
JsonType.prototype.val = function(name, content, mutable) {
var json, o, o_name, obj, word;
var json, obj, word;
if (typeof name === 'object') {
for (o_name in name) {
o = name[o_name];
this.val(o_name, o, content);
}
json = new JsonType(void 0, name, content);
HB.addOperation(json).execute();
this.replace_manager.replace(json);
return this;
} else if ((name != null) && (content != null)) {
} else if ((name != null) && arguments.length > 1) {
if (mutable != null) {
if (mutable === true || mutable === 'mutable') {
mutable = true;
@@ -143,7 +154,7 @@
}
if (typeof content === 'function') {
return this;
} else if (((!mutable) || typeof content === 'number') && content.constructor !== Object) {
} else if ((content == null) || (((!mutable) || typeof content === 'number') && content.constructor !== Object)) {
obj = HB.addOperation(new types.ImmutableObject(void 0, content)).execute();
return JsonType.__super__.val.call(this, name, obj);
} else {

File diff suppressed because one or more lines are too long

View File

@@ -18,6 +18,22 @@
MapManager.__super__.constructor.call(this, uid);
}
MapManager.prototype.type = "MapManager";
MapManager.prototype.applyDelete = function() {
var name, p, _ref;
_ref = this.map;
for (name in _ref) {
p = _ref[name];
p.applyDelete();
}
return MapManager.__super__.applyDelete.call(this);
};
MapManager.prototype.cleanup = function() {
return MapManager.__super__.cleanup.call(this);
};
MapManager.prototype.val = function(name, content) {
var o, obj, result, _ref, _ref1;
if (content != null) {
@@ -60,8 +76,18 @@
AddName.__super__.constructor.call(this, uid);
}
AddName.prototype.type = "AddName";
AddName.prototype.applyDelete = function() {
return AddName.__super__.applyDelete.call(this);
};
AddName.prototype.cleanup = function() {
return AddName.__super__.cleanup.call(this);
};
AddName.prototype.execute = function() {
var beg, end, uid_beg, uid_end, uid_r;
var beg, end, uid_beg, uid_end, uid_r, _base;
if (!this.validateSavedOperations()) {
return false;
} else {
@@ -76,6 +102,7 @@
end = HB.addOperation(new types.Delimiter(uid_end, beg, void 0)).execute();
this.map_manager.map[this.name] = HB.addOperation(new ReplaceManager(void 0, uid_r, beg, end));
this.map_manager.map[this.name].setParent(this.map_manager, this.name);
((_base = this.map_manager.map[this.name]).add_name_ops != null ? _base.add_name_ops : _base.add_name_ops = []).push(this);
this.map_manager.map[this.name].execute();
}
return AddName.__super__.execute.apply(this, arguments);
@@ -116,6 +143,8 @@
ListManager.__super__.constructor.call(this, uid, prev, next, origin);
}
ListManager.prototype.type = "ListManager";
ListManager.prototype.execute = function() {
if (this.validateSavedOperations()) {
this.beginning.setParent(this);
@@ -170,7 +199,7 @@
return ListManager;
})(types.Insert);
})(types.Operation);
ReplaceManager = (function(_super) {
__extends(ReplaceManager, _super);
@@ -181,35 +210,54 @@
}
}
ReplaceManager.prototype.type = "ReplaceManager";
ReplaceManager.prototype.applyDelete = function() {
var o, _i, _len, _ref;
o = this.beginning;
while (o != null) {
o.applyDelete();
o = o.next_cl;
}
if (this.add_name_ops != null) {
_ref = this.add_name_ops;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
o = _ref[_i];
o.applyDelete();
}
}
return ReplaceManager.__super__.applyDelete.call(this);
};
ReplaceManager.prototype.cleanup = function() {
return ReplaceManager.__super__.cleanup.call(this);
};
ReplaceManager.prototype.replace = function(content, replaceable_uid) {
var o, op;
o = this.getLastOperation();
op = new Replaceable(content, this, replaceable_uid, o, o.next_cl);
return HB.addOperation(op).execute();
HB.addOperation(op).execute();
return void 0;
};
ReplaceManager.prototype.setParent = function(parent, property_name) {
var addPropertyListener;
this.on('insert', (function(_this) {
return function(event, op) {
if (op.next_cl instanceof types.Delimiter) {
return _this.parent.callEvent('change', property_name);
}
};
})(this));
this.on('change', (function(_this) {
return function(event) {
return _this.parent.callEvent('change', property_name);
};
})(this));
addPropertyListener = (function(_this) {
return function(event, op) {
if (op.next_cl instanceof types.Delimiter && op.prev_cl instanceof types.Delimiter) {
_this.parent.callEvent('addProperty', property_name);
}
return _this.deleteListener('addProperty', addPropertyListener);
};
})(this);
var addPropertyListener, repl_manager;
repl_manager = this;
this.on('insert', function(event, op) {
if (op.next_cl instanceof types.Delimiter) {
return repl_manager.parent.callEvent('change', property_name, op);
}
});
this.on('change', function(event, op) {
if (repl_manager !== this) {
return repl_manager.parent.callEvent('change', property_name, op);
}
});
addPropertyListener = function(event, op) {
repl_manager.deleteListener('addProperty', addPropertyListener);
return repl_manager.parent.callEvent('addProperty', property_name, op);
};
this.on('insert', addPropertyListener);
return ReplaceManager.__super__.setParent.call(this, parent);
};
@@ -232,8 +280,8 @@
json['prev'] = this.prev_cl.getUid();
json['next'] = this.next_cl.getUid();
}
if ((this.origin != null) && this.origin !== this.prev_cl) {
json["origin"] = this.origin.getUid();
if (this.origin != null) {
json["origin"] = this.origin().getUid();
}
return json;
};
@@ -252,12 +300,14 @@
function Replaceable(content, parent, uid, prev, next, origin) {
this.saveOperation('content', content);
this.saveOperation('parent', parent);
if (!((prev != null) && (next != null) && (content != null))) {
throw new Error("You must define content, prev, and next for Replaceable-types!");
if (!((prev != null) && (next != null))) {
throw new Error("You must define prev, and next for Replaceable-types!");
}
Replaceable.__super__.constructor.call(this, uid, prev, next, origin);
}
Replaceable.prototype.type = "Replaceable";
Replaceable.prototype.val = function() {
return this.content;
};
@@ -266,23 +316,46 @@
return this.parent.replace(content);
};
Replaceable.prototype.applyDelete = function() {
if (this.content != null) {
this.content.applyDelete();
this.content.dontSync();
}
this.content = null;
return Replaceable.__super__.applyDelete.apply(this, arguments);
};
Replaceable.prototype.cleanup = function() {
return Replaceable.__super__.cleanup.apply(this, arguments);
};
Replaceable.prototype.execute = function() {
var _base;
var ins_result, _ref;
if (!this.validateSavedOperations()) {
return false;
} else {
if (typeof (_base = this.content).setReplaceManager === "function") {
_base.setReplaceManager(this.parent);
if ((_ref = this.content) != null) {
if (typeof _ref.setReplaceManager === "function") {
_ref.setReplaceManager(this.parent);
}
}
return Replaceable.__super__.execute.apply(this, arguments);
ins_result = Replaceable.__super__.execute.call(this, this.content != null);
if (ins_result) {
if (this.next_cl.type === "Delimiter" && this.prev_cl.type !== "Delimiter") {
this.prev_cl.applyDelete();
} else if (this.next_cl.type !== "Delimiter") {
this.applyDelete();
}
}
return ins_result;
}
};
Replaceable.prototype._encode = function() {
var json;
var json, _ref;
json = {
'type': "Replaceable",
'content': this.content.getUid(),
'content': (_ref = this.content) != null ? _ref.getUid() : void 0,
'ReplaceManager': this.parent.getUid(),
'prev': this.prev_cl.getUid(),
'next': this.next_cl.getUid(),

File diff suppressed because one or more lines are too long

View File

@@ -25,13 +25,19 @@
__extends(TextInsert, _super);
function TextInsert(content, uid, prev, next, origin) {
this.content = content;
if ((content != null ? content.creator : void 0) != null) {
this.saveOperation('content', content);
} else {
this.content = content;
}
if (!((prev != null) && (next != null))) {
throw new Error("You must define prev, and next for TextInsert-types!");
}
TextInsert.__super__.constructor.call(this, uid, prev, next, origin);
}
TextInsert.prototype.type = "TextInsert";
TextInsert.prototype.getLength = function() {
if (this.isDeleted()) {
return 0;
@@ -40,8 +46,27 @@
}
};
TextInsert.prototype.applyDelete = function() {
TextInsert.__super__.applyDelete.apply(this, arguments);
if (this.content instanceof types.Operation) {
this.content.applyDelete();
}
return this.content = null;
};
TextInsert.prototype.execute = function() {
if (!this.validateSavedOperations()) {
return false;
} else {
if (this.content instanceof types.Operation) {
this.content.insert_parent = this;
}
return TextInsert.__super__.execute.call(this);
}
};
TextInsert.prototype.val = function(current_position) {
if (this.isDeleted()) {
if (this.isDeleted() || (this.content == null)) {
return "";
} else {
return this.content;
@@ -49,15 +74,19 @@
};
TextInsert.prototype._encode = function() {
var json;
var json, _ref;
json = {
'type': "TextInsert",
'content': this.content,
'uid': this.getUid(),
'prev': this.prev_cl.getUid(),
'next': this.next_cl.getUid()
};
if ((this.origin != null) && this.origin !== this.prev_cl) {
if (((_ref = this.content) != null ? _ref.getUid : void 0) != null) {
json['content'] = this.content.getUid();
} else {
json['content'] = this.content;
}
if (this.origin !== this.prev_cl) {
json["origin"] = this.origin.getUid();
}
return json;
@@ -80,17 +109,51 @@
WordType.prototype.type = "WordType";
WordType.prototype.insertText = function(position, content) {
var c, o, op, _i, _len;
o = this.getOperationByPosition(position);
for (_i = 0, _len = content.length; _i < _len; _i++) {
c = content[_i];
op = new TextInsert(c, void 0, o.prev_cl, o);
WordType.prototype.applyDelete = function() {
var o;
o = this.beginning;
while (o != null) {
o.applyDelete();
o = o.next_cl;
}
return WordType.__super__.applyDelete.call(this);
};
WordType.prototype.cleanup = function() {
return WordType.__super__.cleanup.call(this);
};
WordType.prototype.push = function(content) {
return this.insertAfter(this.end.prev_cl, content);
};
WordType.prototype.insertAfter = function(left, content) {
var c, op, right, _i, _len;
while (left.isDeleted()) {
left = left.prev_cl;
}
right = left.next_cl;
if (content.type != null) {
op = new TextInsert(content, void 0, left, right);
HB.addOperation(op).execute();
} else {
for (_i = 0, _len = content.length; _i < _len; _i++) {
c = content[_i];
op = new TextInsert(c, void 0, left, right);
HB.addOperation(op).execute();
left = op;
}
}
return this;
};
WordType.prototype.insertText = function(position, content) {
var ith, left;
ith = this.getOperationByPosition(position);
left = ith.prev_cl;
return this.insertAfter(left, content);
};
WordType.prototype.deleteText = function(position, length) {
var d, delete_ops, i, o, _i;
o = this.getOperationByPosition(position);
@@ -147,10 +210,16 @@
WordType.prototype.setReplaceManager = function(op) {
this.saveOperation('replace_manager', op);
this.validateSavedOperations();
return this.on(['insert', 'delete'], (function(_this) {
return function() {
this.on('insert', (function(_this) {
return function(event, ins) {
var _ref;
return (_ref = _this.replace_manager) != null ? _ref.callEvent('change') : void 0;
return (_ref = _this.replace_manager) != null ? _ref.forwardEvent(_this, 'change', ins) : void 0;
};
})(this));
return this.on('delete', (function(_this) {
return function(event, ins, del) {
var _ref;
return (_ref = _this.replace_manager) != null ? _ref.forwardEvent(_this, 'change', del) : void 0;
};
})(this));
};
@@ -278,8 +347,8 @@
if (this.next_cl != null) {
json['next'] = this.next_cl.getUid();
}
if ((this.origin != null) && this.origin !== this.prev_cl) {
json["origin"] = this.origin.getUid();
if (this.origin != null) {
json["origin"] = this.origin().getUid();
}
return json;
};

File diff suppressed because one or more lines are too long

View File

@@ -1,5 +1,408 @@
(function() {
var dont_proxy, json_types_uninitialized, proxy_token, _proxy,
__hasProp = {}.hasOwnProperty,
__extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
json_types_uninitialized = require("./JsonTypes");
proxy_token = false;
dont_proxy = function(f) {
var e;
proxy_token = true;
try {
f();
} catch (_error) {
e = _error;
proxy_token = false;
throw new Error(e);
}
return proxy_token = false;
};
_proxy = function(f_name, f) {
var old_f;
old_f = this[f_name];
if (old_f != null) {
return this[f_name] = function() {
var args, that, _ref;
if (!proxy_token && !((_ref = this._yatta) != null ? _ref.isDeleted() : void 0)) {
that = this;
args = arguments;
return dont_proxy(function() {
f.apply(that, args);
return old_f.apply(that, args);
});
} else {
return old_f.apply(this, arguments);
}
};
}
};
if (typeof Element !== "undefined" && Element !== null) {
Element.prototype._proxy = _proxy;
}
module.exports = function(HB) {
var TextNodeType, XmlType, json_types, parser, types;
json_types = json_types_uninitialized(HB);
types = json_types.types;
parser = json_types.parser;
XmlType = (function(_super) {
__extends(XmlType, _super);
function XmlType(uid, tagname, attributes, elements, xml) {
var attr, d, element, i, n, word, _i, _j, _len, _ref, _ref1, _ref2;
this.tagname = tagname;
this.xml = xml;
/* In case you make this instanceof Insert again
if prev? and (not next?) and prev.type?
* adjust what you actually mean. you want to insert after prev, then
* next is not defined. but we only insert after non-deleted elements.
* This is also handled in TextInsert.
while prev.isDeleted()
prev = prev.prev_cl
next = prev.next_cl
*/
XmlType.__super__.constructor.call(this, uid);
if (((_ref = this.xml) != null ? _ref._yatta : void 0) != null) {
d = new types.Delete(void 0, this.xml._yatta);
HB.addOperation(d).execute();
this.xml._yatta = null;
}
if ((attributes != null) && (elements != null)) {
this.saveOperation('attributes', attributes);
this.saveOperation('elements', elements);
} else if ((attributes == null) && (elements == null)) {
this.attributes = new types.JsonType();
this.attributes.setMutableDefault('immutable');
HB.addOperation(this.attributes).execute();
this.elements = new types.WordType();
this.elements.parent = this;
HB.addOperation(this.elements).execute();
} else {
throw new Error("Either define attribute and elements both, or none of them");
}
if (this.xml != null) {
this.tagname = this.xml.tagName;
for (i = _i = 0, _ref1 = this.xml.attributes.length; 0 <= _ref1 ? _i < _ref1 : _i > _ref1; i = 0 <= _ref1 ? ++_i : --_i) {
attr = xml.attributes[i];
this.attributes.val(attr.name, attr.value);
}
_ref2 = this.xml.childNodes;
for (_j = 0, _len = _ref2.length; _j < _len; _j++) {
n = _ref2[_j];
if (n.nodeType === n.TEXT_NODE) {
word = new TextNodeType(void 0, n);
HB.addOperation(word).execute();
this.elements.push(word);
} else if (n.nodeType === n.ELEMENT_NODE) {
element = new XmlType(void 0, void 0, void 0, void 0, n);
HB.addOperation(element).execute();
this.elements.push(element);
} else {
throw new Error("I don't know Node-type " + n.nodeType + "!!");
}
}
this.setXmlProxy();
}
void 0;
}
XmlType.prototype.type = "XmlType";
XmlType.prototype.applyDelete = function(op) {
if ((this.insert_parent != null) && !this.insert_parent.isDeleted()) {
return this.insert_parent.applyDelete(op);
} else {
this.attributes.applyDelete();
this.elements.applyDelete();
return XmlType.__super__.applyDelete.apply(this, arguments);
}
};
XmlType.prototype.cleanup = function() {
return XmlType.__super__.cleanup.call(this);
};
XmlType.prototype.setXmlProxy = function() {
var findNode, insertBefore, removeChild, renewClassList, that;
this.xml._yatta = this;
that = this;
this.elements.on('insert', function(event, op) {
var newNode, right, rightNode;
if (op.creator !== HB.getUserId() && this === that.elements) {
newNode = op.content.val();
right = op.next_cl;
while ((right != null) && right.isDeleted()) {
right = right.next_cl;
}
rightNode = null;
if (right.type !== 'Delimiter') {
rightNode = right.val().val();
}
return dont_proxy(function() {
return that.xml.insertBefore(newNode, rightNode);
});
}
});
this.elements.on('delete', function(event, op) {
var del_op, deleted;
del_op = op.deleted_by[0];
if ((del_op != null) && del_op.creator !== HB.getUserId() && this === that.elements) {
deleted = op.content.val();
return dont_proxy(function() {
return that.xml.removeChild(deleted);
});
}
});
this.attributes.on(['addProperty', 'change'], function(event, property_name, op) {
if (op.creator !== HB.getUserId() && this === that.attributes) {
return dont_proxy(function() {
var newval;
newval = op.val().val();
if (newval != null) {
return that.xml.setAttribute(property_name, op.val().val());
} else {
return that.xml.removeAttribute(property_name);
}
});
}
});
findNode = function(child) {
var elem;
if (child == null) {
throw new Error("you must specify a parameter!");
}
child = child._yatta;
elem = that.elements.beginning.next_cl;
while (elem.type !== 'Delimiter' && elem.content !== child) {
elem = elem.next_cl;
}
if (elem.type === 'Delimiter') {
return false;
} else {
return elem;
}
};
insertBefore = function(insertedNode_s, adjacentNode) {
var child, element, inserted_nodes, next, prev, _results;
next = null;
if (adjacentNode != null) {
next = findNode(adjacentNode);
}
prev = null;
if (next) {
prev = next.prev_cl;
} else {
prev = this._yatta.elements.end.prev_cl;
while (prev.isDeleted()) {
prev = prev.prev_cl;
}
}
inserted_nodes = null;
if (insertedNode_s.nodeType === insertedNode_s.DOCUMENT_FRAGMENT_NODE) {
child = insertedNode_s.lastChild;
_results = [];
while (child != null) {
element = new XmlType(void 0, void 0, void 0, void 0, child);
HB.addOperation(element).execute();
that.elements.insertAfter(prev, element);
_results.push(child = child.previousSibling);
}
return _results;
} else {
element = new XmlType(void 0, void 0, void 0, void 0, insertedNode_s);
HB.addOperation(element).execute();
return that.elements.insertAfter(prev, element);
}
};
this.xml._proxy('insertBefore', insertBefore);
this.xml._proxy('appendChild', insertBefore);
this.xml._proxy('removeAttribute', function(name) {
return that.attributes.val(name, void 0);
});
this.xml._proxy('setAttribute', function(name, value) {
return that.attributes.val(name, value);
});
renewClassList = function(newclass) {
var dont_do_it, elem, value, _i, _len;
dont_do_it = false;
if (newclass != null) {
for (_i = 0, _len = this.length; _i < _len; _i++) {
elem = this[_i];
if (newclass === elem) {
dont_do_it = true;
}
}
}
value = Array.prototype.join.call(this, " ");
if ((newclass != null) && !dont_do_it) {
value += " " + newclass;
}
return that.attributes.val('class', value);
};
_proxy.call(this.xml.classList, 'add', renewClassList);
_proxy.call(this.xml.classList, 'remove', renewClassList);
this.xml.__defineSetter__('className', function(val) {
return this.setAttribute('class', val);
});
this.xml.__defineGetter__('className', function() {
return that.attributes.val('class');
});
this.xml.__defineSetter__('textContent', function(val) {
var elem, remove, text_node;
elem = that.xml.firstChild;
while (elem != null) {
remove = elem;
elem = elem.nextSibling;
that.xml.removeChild(remove);
}
if (val !== "") {
text_node = document.createTextNode(val);
return that.xml.appendChild(text_node);
}
});
removeChild = function(node) {
var d, elem;
elem = findNode(node);
if (!elem) {
throw new Error("You are only allowed to delete existing (direct) child elements!");
}
d = new types.Delete(void 0, elem);
HB.addOperation(d).execute();
return node._yatta = null;
};
this.xml._proxy('removeChild', removeChild);
return this.xml._proxy('replaceChild', function(insertedNode, replacedNode) {
insertBefore.call(this, insertedNode, replacedNode);
return removeChild.call(this, replacedNode);
});
};
XmlType.prototype.val = function(enforce) {
var a, attr, attr_name, e, n, text_node, value;
if (enforce == null) {
enforce = false;
}
if (typeof document !== "undefined" && document !== null) {
if ((this.xml == null) || enforce) {
this.xml = document.createElement(this.tagname);
attr = this.attributes.val();
for (attr_name in attr) {
value = attr[attr_name];
if (value != null) {
a = document.createAttribute(attr_name);
a.value = value;
this.xml.setAttributeNode(a);
}
}
e = this.elements.beginning.next_cl;
while (e.type !== "Delimiter") {
n = e.content;
if (!e.isDeleted() && (e.content != null)) {
if (n.type === "XmlType") {
this.xml.appendChild(n.val(enforce));
} else if (n.type === "TextNodeType") {
text_node = n.val();
this.xml.appendChild(text_node);
} else {
throw new Error("Internal structure cannot be transformed to dom");
}
}
e = e.next_cl;
}
}
this.setXmlProxy();
return this.xml;
}
};
XmlType.prototype.execute = function() {
return XmlType.__super__.execute.call(this);
};
/*
if not @validateSavedOperations()
return false
else
return true
*/
XmlType.prototype.getParent = function() {
return this.parent;
};
XmlType.prototype._encode = function() {
var json;
json = {
'type': this.type,
'attributes': this.attributes.getUid(),
'elements': this.elements.getUid(),
'tagname': this.tagname,
'uid': this.getUid()
};
return json;
};
return XmlType;
})(types.Insert);
parser['XmlType'] = function(json) {
var attributes, elements, tagname, uid;
uid = json['uid'], attributes = json['attributes'], elements = json['elements'], tagname = json['tagname'];
return new XmlType(uid, tagname, attributes, elements, void 0);
};
TextNodeType = (function(_super) {
__extends(TextNodeType, _super);
function TextNodeType(uid, content) {
var d;
if (content._yatta != null) {
d = new types.Delete(void 0, content._yatta);
HB.addOperation(d).execute();
content._yatta = null;
}
content._yatta = this;
TextNodeType.__super__.constructor.call(this, uid, content);
}
TextNodeType.prototype.applyDelete = function(op) {
if ((this.insert_parent != null) && !this.insert_parent.isDeleted()) {
return this.insert_parent.applyDelete(op);
} else {
return TextNodeType.__super__.applyDelete.apply(this, arguments);
}
};
TextNodeType.prototype.type = "TextNodeType";
TextNodeType.prototype._encode = function() {
var json;
json = {
'type': this.type,
'uid': this.getUid(),
'content': this.content.textContent
};
return json;
};
return TextNodeType;
})(types.ImmutableObject);
parser['TextNodeType'] = function(json) {
var content, textnode, uid;
uid = json['uid'], content = json['content'];
textnode = document.createTextNode(content);
return new TextNodeType(uid, textnode);
};
types['XmlType'] = XmlType;
return json_types;
};
}).call(this);

File diff suppressed because one or more lines are too long

View File

@@ -7,6 +7,8 @@
exports['TextFramework'] = require('./Frameworks/TextFramework');
exports['XmlFramework'] = require('./Frameworks/XmlFramework');
}).call(this);
//# sourceMappingURL=index.js.map

View File

@@ -1 +1 @@
{"version":3,"sources":["index.coffee"],"names":[],"mappings":"AAEA;AAAA,EAAA,OAAQ,CAAA,cAAA,CAAR,GACE,OAAA,CAAQ,2BAAR,CADF,CAAA;;AAAA,EAEA,OAAQ,CAAA,eAAA,CAAR,GACE,OAAA,CAAQ,4BAAR,CAHF,CAAA;;AAAA,EAIA,OAAQ,CAAA,eAAA,CAAR,GACE,OAAA,CAAQ,4BAAR,CALF,CAAA;;AAAA,EAMA,OAAQ,CAAA,eAAA,CAAR,GACE,OAAA,CAAQ,4BAAR,CAPF,CAAA;AAAA","file":"index.js","sourceRoot":"/source/","sourcesContent":["\n\nexports['IwcConnector'] =\n require './Connectors/IwcConnector'\nexports['TestConnector'] =\n require './Connectors/TestConnector'\nexports['JsonFramework'] =\n require './Frameworks/JsonFramework'\nexports['TextFramework'] =\n require './Frameworks/TextFramework'\n\n"]}
{"version":3,"sources":["index.coffee"],"names":[],"mappings":"AAEA;AAAA,EAAA,OAAQ,CAAA,cAAA,CAAR,GACE,OAAA,CAAQ,2BAAR,CADF,CAAA;;AAAA,EAEA,OAAQ,CAAA,eAAA,CAAR,GACE,OAAA,CAAQ,4BAAR,CAHF,CAAA;;AAAA,EAIA,OAAQ,CAAA,eAAA,CAAR,GACE,OAAA,CAAQ,4BAAR,CALF,CAAA;;AAAA,EAMA,OAAQ,CAAA,eAAA,CAAR,GACE,OAAA,CAAQ,4BAAR,CAPF,CAAA;;AAAA,EAQA,OAAQ,CAAA,cAAA,CAAR,GACE,OAAA,CAAQ,2BAAR,CATF,CAAA;AAAA","file":"index.js","sourceRoot":"/source/","sourcesContent":["\n\nexports['IwcConnector'] =\n require './Connectors/IwcConnector'\nexports['TestConnector'] =\n require './Connectors/TestConnector'\nexports['JsonFramework'] =\n require './Frameworks/JsonFramework'\nexports['TextFramework'] =\n require './Frameworks/TextFramework'\nexports['XmlFramework'] =\n require './Frameworks/XmlFramework'\n\n"]}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -6,13 +6,21 @@
</head>
<body>
<div id="mocha"></div>
<script src="../../node_modules/mocha/mocha.js"></script>
<script>mocha.setup('bdd')</script>
<script src="TextYatta_test.js"></script>
<script src="JsonYatta_test.js"></script>
<div id="test_dom" test_attribute="the test" class="stuffy" style="color: blue"><p id="replaceme">replace me</p><p id="removeme">remove me</p><p>This is a test object for <b>XmlFramework</b></p><span class="span_element"><p>span</p></span></div>
<script src="../../node_modules/mocha/mocha.js" class="awesome"></script>
<script>
mocha.checkLeaks();
mocha.run();
mocha.setup('bdd');
mocha.ui('bdd');
mocha.reporter('html');
</script>
<!--script src="TextYatta_test.js"></script>
<script src="JsonYatta_test.js"></script-->
<script src="XmlYatta_test_browser.js"></script>
<script>
//mocha.checkLeaks();
//mocha.run();
if (window.mochaPhantomJS) { mochaPhantomJS.run(); }
else { mocha.run(); }
</script>
</body>
</html>

View File

@@ -92,6 +92,16 @@
</li>
</ul>
</ul>
<ul>
<li class='letter'>x</li>
<ul>
<li>
<a href='class/XmlFramework.html'>
XmlFramework
</a>
</li>
</ul>
</ul>
</div>
<h2>File Listing A-Z</h2>
<div class='index'>
@@ -235,11 +245,11 @@
<li class='letter'>x</li>
<ul>
<li>
<a href='file/lib/Types/XmlTypes.coffee.html'>
XmlTypes.coffee
<a href='file/lib/Frameworks/XmlFramework.coffee.html'>
XmlFramework.coffee
</a>
<small>
(lib&#47;Types)
(lib&#47;Frameworks)
</small>
</li>
</ul>
@@ -248,7 +258,7 @@
</div>
</div>
<div id='footer'>
August 24, 14 01:51:08 by
October 17, 14 12:58:33 by
<a href='https://github.com/coffeedoc/codo' title='CoffeeScript API documentation generator'>
Codo
</a>

View File

@@ -302,7 +302,7 @@ data from the received intent.</p>
</div>
</div>
<div id='footer'>
August 24, 14 01:51:08 by
October 17, 14 12:58:33 by
<a href='https://github.com/coffeedoc/codo' title='CoffeeScript API documentation generator'>
Codo
</a>

View File

@@ -120,7 +120,7 @@ Known values that are supported:</p><ul>
<a href='#val-dynamic'>
#
(void)
<b>val</b><span>(name, content, mutable)</span>
<b>val</b><span>()</span>
</a>
</span>
<span class='desc'>
@@ -288,7 +288,7 @@ JsonFramework was initialized (Depending on the HistoryBuffer implementation).</
<p class='signature' id='val-dynamic'>
#
(void)
<b>val</b><span>(name, content, mutable)</span>
<b>val</b><span>()</span>
<br>
</p>
<div class='tags'>
@@ -335,7 +335,7 @@ JsonFramework was initialized (Depending on the HistoryBuffer implementation).</
</div>
</div>
<div id='footer'>
August 24, 14 01:51:08 by
October 17, 14 12:58:33 by
<a href='https://github.com/coffeedoc/codo' title='CoffeeScript API documentation generator'>
Codo
</a>

View File

@@ -85,6 +85,28 @@ if (x.type === &quot;JsonType&quot;) {
</dl>
<h2>Instance Method Summary</h2>
<ul class='summary'>
<li>
<span class='signature'>
<a href='#applyDelete-dynamic'>
#
(void)
<b>applyDelete</b><span>()</span>
</a>
</span>
<span class='desc'>
</span>
</li>
<li>
<span class='signature'>
<a href='#cleanup-dynamic'>
#
(void)
<b>cleanup</b><span>()</span>
</a>
</span>
<span class='desc'>
</span>
</li>
<li>
<span class='signature'>
<a href='#toJson-dynamic'>
@@ -102,7 +124,7 @@ if (x.type === &quot;JsonType&quot;) {
<a href='#setReplaceManager-dynamic'>
#
(void)
<b>setReplaceManager</b><span>(rm)</span>
<b>setReplaceManager</b><span>(replace_manager)</span>
</a>
</span>
<span class='desc'>
@@ -205,6 +227,24 @@ if (x.type === &quot;JsonType&quot;) {
</div>
<h2>Instance Method Details</h2>
<div class='methods'>
<div class='method_details'>
<p class='signature' id='applyDelete-dynamic'>
#
(void)
<b>applyDelete</b><span>()</span>
<br>
</p>
</div>
<div class='method_details'>
<p class='signature' id='cleanup-dynamic'>
#
(void)
<b>cleanup</b><span>()</span>
<br>
</p>
</div>
<div class='method_details'>
<p class='signature' id='toJson-dynamic'>
#
@@ -229,7 +269,7 @@ if (x.type === &quot;JsonType&quot;) {
<p class='signature' id='setReplaceManager-dynamic'>
#
(void)
<b>setReplaceManager</b><span>(rm)</span>
<b>setReplaceManager</b><span>(replace_manager)</span>
<br>
</p>
<div class='docstring'>
@@ -426,7 +466,7 @@ if (x.type === &quot;JsonType&quot;) {
</div>
</div>
<div id='footer'>
August 24, 14 01:51:08 by
October 17, 14 12:58:33 by
<a href='https://github.com/coffeedoc/codo' title='CoffeeScript API documentation generator'>
Codo
</a>

View File

@@ -139,7 +139,7 @@ console.log(w.newProperty == &quot;Awesome&quot;) # true!</code></pre>
</div>
</div>
<div id='footer'>
August 24, 14 01:51:08 by
October 17, 14 12:58:33 by
<a href='https://github.com/coffeedoc/codo' title='CoffeeScript API documentation generator'>
Codo
</a>

View File

@@ -72,6 +72,17 @@ user-id of the peer (browser/client). And then you can connect to it.</p>
Receive the id of every connected peer.
</span>
</li>
<li>
<span class='signature'>
<a href='#onNewConnection-dynamic'>
#
(void)
<b>onNewConnection</b><span>(f)</span>
</a>
</span>
<span class='desc'>
</span>
</li>
<li>
<span class='signature'>
<a href='#addConnection-dynamic'>
@@ -194,6 +205,15 @@ on how to do that with urls.</p>
</li>
</ul>
</div>
</div>
<div class='method_details'>
<p class='signature' id='onNewConnection-dynamic'>
#
(void)
<b>onNewConnection</b><span>(f)</span>
<br>
</p>
</div>
<div class='method_details'>
<p class='signature' id='addConnection-dynamic'>
@@ -222,7 +242,7 @@ on how to do that with urls.</p>
</div>
</div>
<div id='footer'>
August 24, 14 01:51:08 by
October 17, 14 12:58:33 by
<a href='https://github.com/coffeedoc/codo' title='CoffeeScript API documentation generator'>
Codo
</a>

View File

@@ -356,7 +356,7 @@ JsonFramework was initialized (Depending on the HistoryBuffer implementation).</
</div>
</div>
<div id='footer'>
August 24, 14 01:51:08 by
October 17, 14 12:58:33 by
<a href='https://github.com/coffeedoc/codo' title='CoffeeScript API documentation generator'>
Codo
</a>

View File

@@ -77,6 +77,50 @@ if (x.type === &quot;WordType&quot;) {
</dl>
<h2>Instance Method Summary</h2>
<ul class='summary'>
<li>
<span class='signature'>
<a href='#applyDelete-dynamic'>
#
(void)
<b>applyDelete</b><span>()</span>
</a>
</span>
<span class='desc'>
</span>
</li>
<li>
<span class='signature'>
<a href='#cleanup-dynamic'>
#
(void)
<b>cleanup</b><span>()</span>
</a>
</span>
<span class='desc'>
</span>
</li>
<li>
<span class='signature'>
<a href='#push-dynamic'>
#
(void)
<b>push</b><span>(content)</span>
</a>
</span>
<span class='desc'>
</span>
</li>
<li>
<span class='signature'>
<a href='#insertAfter-dynamic'>
#
(void)
<b>insertAfter</b><span>(left, content)</span>
</a>
</span>
<span class='desc'>
</span>
</li>
<li>
<span class='signature'>
<a href='#insertText-dynamic'>
@@ -206,6 +250,42 @@ if (x.type === &quot;WordType&quot;) {
</div>
<h2>Instance Method Details</h2>
<div class='methods'>
<div class='method_details'>
<p class='signature' id='applyDelete-dynamic'>
#
(void)
<b>applyDelete</b><span>()</span>
<br>
</p>
</div>
<div class='method_details'>
<p class='signature' id='cleanup-dynamic'>
#
(void)
<b>cleanup</b><span>()</span>
<br>
</p>
</div>
<div class='method_details'>
<p class='signature' id='push-dynamic'>
#
(void)
<b>push</b><span>(content)</span>
<br>
</p>
</div>
<div class='method_details'>
<p class='signature' id='insertAfter-dynamic'>
#
(void)
<b>insertAfter</b><span>(left, content)</span>
<br>
</p>
</div>
<div class='method_details'>
<p class='signature' id='insertText-dynamic'>
#
@@ -375,7 +455,7 @@ yatta.bind(textbox);</code></pre>
</div>
</div>
<div id='footer'>
August 24, 14 01:51:08 by
October 17, 14 12:58:33 by
<a href='https://github.com/coffeedoc/codo' title='CoffeeScript API documentation generator'>
Codo
</a>

377
doc/class/XmlFramework.html Normal file
View File

@@ -0,0 +1,377 @@
<!DOCTYPE html>
<html>
<head>
<meta charset='UTF-8'>
<title>Yatta! API</title>
<script src='../javascript/application.js'></script>
<script src='../javascript/search.js'></script>
<link rel='stylesheet' href='../stylesheets/application.css' type='text/css'>
</head>
<body>
<div id='base' data-path='../'></div>
<div id='header'>
<div id='menu'>
<a href='../extra/README.md.html' title='Yatta!'>
Yatta!
</a>
&raquo;
<a href='../alphabetical_index.html' title='Index'>
Index
</a>
&raquo;
<span class='title'>XmlFramework</span>
</div>
</div>
<div id='content'>
<h1>
Class:
XmlFramework
</h1>
<table class='box'>
<tr>
<td>Defined in:</td>
<td>lib&#47;Frameworks&#47;XmlFramework.coffee</td>
</tr>
</table>
<h2>Overview</h2>
<div class='docstring'>
<p>Framework for Xml-like data-structures.
Known values that are supported:</p>
</div>
<div class='tags'>
</div>
<h2>Instance Method Summary</h2>
<ul class='summary'>
<li>
<span class='signature'>
<a href='#getSharedObject-dynamic'>
#
(?)
<b>getSharedObject</b><span>()</span>
</a>
</span>
<span class='desc'>
</span>
</li>
<li>
<span class='signature'>
<a href='#getConnector-dynamic'>
#
(void)
<b>getConnector</b><span>()</span>
</a>
</span>
<span class='desc'>
Get the initialized connector.
</span>
</li>
<li>
<span class='signature'>
<a href='#getHistoryBuffer-dynamic'>
#
(void)
<b>getHistoryBuffer</b><span>()</span>
</a>
</span>
<span class='desc'>
</span>
</li>
<li>
<span class='signature'>
<a href='#setMutableDefault-dynamic'>
#
(void)
<b>setMutableDefault</b><span>(mutable)</span>
</a>
</span>
<span class='desc'>
</span>
</li>
<li>
<span class='signature'>
<a href='#getUserId-dynamic'>
#
(void)
<b>getUserId</b><span>()</span>
</a>
</span>
<span class='desc'>
Get the UserId from the HistoryBuffer object.
</span>
</li>
<li>
<span class='signature'>
<a href='#toJson-dynamic'>
#
(void)
<b>toJson</b><span>()</span>
</a>
</span>
<span class='desc'>
</span>
</li>
<li>
<span class='signature'>
<a href='#val-dynamic'>
#
(void)
<b>val</b><span>()</span>
</a>
</span>
<span class='desc'>
</span>
</li>
<li>
<span class='signature'>
<a href='#on-dynamic'>
#
(void)
<b>on</b><span>()</span>
</a>
</span>
<span class='desc'>
</span>
</li>
</ul>
<h2>Constructor Details</h2>
<div class='methods'>
<div class='method_details'>
<p class='signature' id='constructor-dynamic'>
#
(void)
<b>constructor</b><span>(user_id, Connector)</span>
<br>
</p>
<div class='tags'>
<h3>Parameters:</h3>
<ul class='param'>
<li>
<span class='name'>user_id</span>
<span class='type'>
(
<tt>String</tt>
)
</span>
&mdash;
<span class='desc'>Unique id of the peer. </span>
</li>
<li>
<span class='name'>Connector</span>
<span class='type'>
(
<tt>Connector</tt>
)
</span>
&mdash;
<span class='desc'>the connector class. </span>
</li>
</ul>
</div>
</div>
</div>
<h2>Instance Method Details</h2>
<div class='methods'>
<div class='method_details'>
<p class='signature' id='getSharedObject-dynamic'>
#
(?)
<b>getSharedObject</b><span>()</span>
<br>
</p>
<div class='tags'>
<h3>Returns:</h3>
<ul class='return'>
<li>
<span class='type'></span>
(
<tt>?</tt>
)
&mdash;
<span class='desc'>JsonType </span>
</li>
</ul>
</div>
</div>
<div class='method_details'>
<p class='signature' id='getConnector-dynamic'>
#
(void)
<b>getConnector</b><span>()</span>
<br>
</p>
<div class='docstring'>
<p>Get the initialized connector.</p>
</div>
<div class='tags'>
</div>
</div>
<div class='method_details'>
<p class='signature' id='getHistoryBuffer-dynamic'>
#
(void)
<b>getHistoryBuffer</b><span>()</span>
<br>
</p>
<div class='tags'>
<h3>See also:</h3>
<ul class='see'>
<li>
<a href='HistoryBuffer'>HistoryBuffer</a>
</li>
</ul>
</div>
</div>
<div class='method_details'>
<p class='signature' id='setMutableDefault-dynamic'>
#
(void)
<b>setMutableDefault</b><span>(mutable)</span>
<br>
</p>
<div class='tags'>
<h3>See also:</h3>
<ul class='see'>
<li>
<a href='JsonType.setMutableDefault'>JsonType.setMutableDefault</a>
</li>
</ul>
</div>
</div>
<div class='method_details'>
<p class='signature' id='getUserId-dynamic'>
#
(void)
<b>getUserId</b><span>()</span>
<br>
</p>
<div class='docstring'>
<p>Get the UserId from the HistoryBuffer object.
In most cases this will be the same as the user_id value with which
JsonFramework was initialized (Depending on the HistoryBuffer implementation).</p>
</div>
<div class='tags'>
</div>
</div>
<div class='method_details'>
<p class='signature' id='toJson-dynamic'>
#
(void)
<b>toJson</b><span>()</span>
<br>
</p>
<div class='tags'>
<h3>See also:</h3>
<ul class='see'>
<li>
<a href='JsonType.toJson'>JsonType.toJson</a>
</li>
</ul>
</div>
</div>
<div class='method_details'>
<p class='signature' id='val-dynamic'>
#
(void)
<b>val</b><span>()</span>
<br>
</p>
<div class='tags'>
<h3>See also:</h3>
<ul class='see'>
<li>
<a href='JsonType.val'>JsonType.val</a>
</li>
</ul>
</div>
</div>
<div class='method_details'>
<p class='signature' id='on-dynamic'>
#
(void)
<b>on</b><span>()</span>
<br>
</p>
<div class='tags'>
<h3>See also:</h3>
<ul class='see'>
<li>
<a href='Operation.on'>Operation.on</a>
</li>
</ul>
</div>
</div>
</div>
</div>
<div id='footer'>
October 17, 14 12:58:33 by
<a href='https://github.com/coffeedoc/codo' title='CoffeeScript API documentation generator'>
Codo
</a>
2.0.9
&#10034;
Press H to see the keyboard shortcuts
&#10034;
<a href='http://twitter.com/netzpirat' target='_parent'>@netzpirat</a>
&#10034;
<a href='http://twitter.com/_inossidabile' target='_parent'>@_inossidabile</a>
</div>
<iframe id='search_frame'></iframe>
<div id='fuzzySearch'>
<input type='text'>
<ol></ol>
</div>
<div id='help'>
<p>
Quickly fuzzy find classes, mixins, methods, file:
</p>
<ul>
<li>
<span>T</span>
Open fuzzy finder dialog
</li>
</ul>
<p>
Control the navigation frame:
</p>
<ul>
<li>
<span>L</span>
Toggle list view
</li>
<li>
<span>C</span>
Show class list
</li>
<li>
<span>I</span>
Show mixin list
</li>
<li>
<span>F</span>
Show file list
</li>
<li>
<span>M</span>
Show method list
</li>
<li>
<span>E</span>
Show extras list
</li>
</ul>
<p>
You can focus and blur the search input:
</p>
<ul>
<li>
<span>S</span>
Focus search input
</li>
<li>
<span>Esc</span>
Blur search input
</li>
</ul>
</div>
</body>
</html>

326
doc/class/XmlType.html Normal file
View File

@@ -0,0 +1,326 @@
<!DOCTYPE html>
<html>
<head>
<meta charset='UTF-8'>
<title>Yatta! API</title>
<script src='../javascript/application.js'></script>
<script src='../javascript/search.js'></script>
<link rel='stylesheet' href='../stylesheets/application.css' type='text/css'>
</head>
<body>
<div id='base' data-path='../'></div>
<div id='header'>
<div id='menu'>
<a href='../extra/README.md.html' title='Yatta!'>
Yatta!
</a>
&raquo;
<a href='../alphabetical_index.html' title='Index'>
Index
</a>
&raquo;
<span class='title'>XmlType</span>
</div>
</div>
<div id='content'>
<h1>
Class:
XmlType
</h1>
<table class='box'>
<tr>
<td>Defined in:</td>
<td>lib&#47;Types&#47;XmlTypes.coffee</td>
</tr>
<tr>
<td>Inherits:</td>
<td>
types.Insert
</td>
</tr>
</table>
<h2>Overview</h2>
<div class='docstring'>
<p>Manages XML types
Not supported:</p><ul>
<li>Attribute nodes</li>
<li>Real replace of child elements (to much overhead). Currently, the new element is inserted after the &#39;replaced&#39; element, and then it is deleted.</li>
<li>Namespaces (*NS)</li>
<li>Browser specific methods (webkit-* operations)</li>
</ul>
</div>
<div class='tags'>
</div>
<h2>Variables Summary</h2>
<dl class='constants'>
<dt id='type-variable'>
type
=
</dt>
<dd>
<pre><code class='coffeescript'>&quot;XmlType&quot;</code></pre>
<div class='docstring'>
<p>Identifies this class.
Use it in order to check whether this is an xml-type or something else.</p>
</div>
<div class='tags'>
</div>
</dd>
</dl>
<h2>Instance Method Summary</h2>
<ul class='summary'>
<li>
<span class='signature'>
<a href='#applyDelete-dynamic'>
#
(void)
<b>applyDelete</b><span>()</span>
</a>
</span>
<span class='desc'>
</span>
</li>
<li>
<span class='signature'>
<a href='#cleanup-dynamic'>
#
(void)
<b>cleanup</b><span>()</span>
</a>
</span>
<span class='desc'>
</span>
</li>
<li>
<span class='signature'>
<a href='#setXmlProxy-dynamic'>
#
(void)
<b>setXmlProxy</b><span>()</span>
</a>
</span>
<span class='desc'>
</span>
</li>
<li>
<span class='signature'>
<a href='#val-dynamic'>
#
(void)
<b>val</b><span>(enforce = false)</span>
</a>
</span>
<span class='desc'>
</span>
</li>
<li>
<span class='signature'>
<a href='#execute-dynamic'>
#
(void)
<b>execute</b><span>()</span>
</a>
</span>
<span class='desc'>
</span>
</li>
<li>
<span class='signature'>
<a href='#getParent-dynamic'>
#
(XmlType)
<b>getParent</b><span>()</span>
</a>
</span>
<span class='desc'>
Get the parent of this JsonType.
</span>
</li>
<li>
<span class='signature'>
<a href='#_encode-dynamic'>
#
(void)
<b>_encode</b><span>()</span>
</a>
</span>
<span class='note private title'>Private</span>
<span class='desc'>
Convert all relevant information of this operation to the json-format.
</span>
</li>
</ul>
<h2>Constructor Details</h2>
<div class='methods'>
<div class='method_details'>
<p class='signature' id='constructor-dynamic'>
#
(void)
<b>constructor</b><span>(uid, tagname, attributes, elements, xml)</span>
<br>
</p>
</div>
</div>
<h2>Instance Method Details</h2>
<div class='methods'>
<div class='method_details'>
<p class='signature' id='applyDelete-dynamic'>
#
(void)
<b>applyDelete</b><span>()</span>
<br>
</p>
</div>
<div class='method_details'>
<p class='signature' id='cleanup-dynamic'>
#
(void)
<b>cleanup</b><span>()</span>
<br>
</p>
</div>
<div class='method_details'>
<p class='signature' id='setXmlProxy-dynamic'>
#
(void)
<b>setXmlProxy</b><span>()</span>
<br>
</p>
</div>
<div class='method_details'>
<p class='signature' id='val-dynamic'>
#
(void)
<b>val</b><span>(enforce = false)</span>
<br>
</p>
</div>
<div class='method_details'>
<p class='signature' id='execute-dynamic'>
#
(void)
<b>execute</b><span>()</span>
<br>
</p>
</div>
<div class='method_details'>
<p class='signature' id='getParent-dynamic'>
#
(XmlType)
<b>getParent</b><span>()</span>
<br>
</p>
<div class='docstring'>
<p>Get the parent of this JsonType.</p>
</div>
<div class='tags'>
<h3>Returns:</h3>
<ul class='return'>
<li>
<span class='type'></span>
<tt><a href='../class/XmlType.html'>XmlType</a></tt>
</li>
</ul>
</div>
</div>
<div class='method_details'>
<p class='signature' id='_encode-dynamic'>
#
(void)
<b>_encode</b><span>()</span>
<span class='note private'>Private</span>
<br>
</p>
<div class='docstring'>
<p>Convert all relevant information of this operation to the json-format.
This result can be send to other clients.</p>
</div>
<div class='tags'>
</div>
</div>
</div>
</div>
<div id='footer'>
<<<<<<< HEAD
October 06, 14 10:34:33 by
=======
October 02, 14 15:16:56 by
>>>>>>> XML-Support
<a href='https://github.com/coffeedoc/codo' title='CoffeeScript API documentation generator'>
Codo
</a>
2.0.9
&#10034;
Press H to see the keyboard shortcuts
&#10034;
<a href='http://twitter.com/netzpirat' target='_parent'>@netzpirat</a>
&#10034;
<a href='http://twitter.com/_inossidabile' target='_parent'>@_inossidabile</a>
</div>
<iframe id='search_frame'></iframe>
<div id='fuzzySearch'>
<input type='text'>
<ol></ol>
</div>
<div id='help'>
<p>
Quickly fuzzy find classes, mixins, methods, file:
</p>
<ul>
<li>
<span>T</span>
Open fuzzy finder dialog
</li>
</ul>
<p>
Control the navigation frame:
</p>
<ul>
<li>
<span>L</span>
Toggle list view
</li>
<li>
<span>C</span>
Show class list
</li>
<li>
<span>I</span>
Show mixin list
</li>
<li>
<span>F</span>
Show file list
</li>
<li>
<span>M</span>
Show method list
</li>
<li>
<span>E</span>
Show extras list
</li>
</ul>
<p>
You can focus and blur the search input:
</p>
<ul>
<li>
<span>S</span>
Focus search input
</li>
<li>
<span>Esc</span>
Blur search input
</li>
</ul>
</div>
</body>
</html>

View File

@@ -61,6 +61,14 @@
</small>
</li>
<li>
<a href='class/XmlFramework.html' target='main'>
XmlFramework
</a>
<small class='namespace'>
</small>
</li>
<li>
<a href='class/JsonTypeWrapper.html' target='main'>
JsonTypeWrapper

View File

@@ -38,7 +38,7 @@
</div>
</div>
<div id='footer'>
August 24, 14 01:51:08 by
October 17, 14 12:58:33 by
<a href='https://github.com/coffeedoc/codo' title='CoffeeScript API documentation generator'>
Codo
</a>

View File

@@ -39,9 +39,10 @@ Yatta! provides similar functionality as <a href="https://github.com/share/Share
but does not require you to understand how the internals work. The predefined data structures provide a simple API to access your shared data structures.</p><p>Predefined data structures:</p><ul>
<li>Text - <a href="http://dadamonad.github.io/Yatta/examples/TextEditing/">Collaborative Text Editing Example</a> and <a href="./examples/TextEditing/">Source</a></li>
<li>Json - <a href="./examples/PeerJs-Json/">Tutorial</a></li>
<li>XML (coming soon)</li>
<li>XML - <a href="./XmlExample">XML Example</a> Collaboratively manipulate the dom with native dom-features and jQuery.</li>
</ul><p>Unlike other frameworks, Yatta! supports P2P message propagation and is not bound to a specific communication protocol.</p><p>It is possible to add any communication protocol to Yatta. Currently it supports:</p><ul>
<li><a href="http://peerjs.com/">PeerJs</a> - A WebRTC Framework</li>
<li><a href="http://simplewebrtc.com/">SimpleWebRTC</a> - Another WebRTC Framework (coming soon)</li>
<li><a href="http://dbis.rwth-aachen.de/cms/projects/the-xmpp-experience#interwidget-communication">IWC</a> - Inter-widget Communication</li>
</ul>
<h2 id="use-it-">Use it!</h2><p>The <a href="./examples/">examples</a> provide an excellent starting point for beginners. Also the <a href="http://dadamonad.github.io/Yatta/doc/">API Documentation</a> could prove to be very helpful.</p><p>Either clone this git repository, install it with <a href="http://bower.io/">bower</a>, or install it with <a href="https://www.npmjs.org/package/yatta">npm</a>.</p><h3 id="bower">Bower</h3>
@@ -66,11 +67,16 @@ on which the operation was created. This is not necessary in Yata.</p><p>The dow
In contrast, an OT algorithm can have an empty History Buffer while the document size is very big.</p><p>Eventually (after my thesis), I will publish more information about Yata.</p><p>So, how did I come up with the name for the implementation (Yatta! is not Yata)?
Yatta! means &quot;I did it!&quot; in Japanese. You scream it when you accomplish something (for proper application I refer to the Yatta-man in <a href="http://heroeswiki.com/Yatta!">Heroes</a>).
There is also this awesome video on the Internet that will change your life <a href="https://www.youtube.com/watch?v=kL5DDSglM_s">Yatta</a>.</p><h2 id="status">Status</h2><p>Yatta! is still in an early development phase. Don&#39;t expect that everything is working fine.
But I would become really motivated if you gave me some feedback :) (<a href="https://github.com/DadaMonad/Yatta/issues">github</a>).</p><h3 id="current-issues">Current Issues</h3><p>Currently, I don&#39;t perform Garbage Collection. Therefore, the space requirement will never decrease.</p><ul>
<li>Garbage Collection</li>
<li>XML support</li>
But I would become really motivated if you gave me some feedback :) (<a href="https://github.com/DadaMonad/Yatta/issues">github</a>).</p><h3 id="current-issues">Current Issues</h3>
<ul>
<li>HTML editable tag</li>
<li>More efficient representation of text.</li>
<li>Use a better data structure for the History Buffer - it should be possible to use Arrays.</li>
<li>SimpleRTC support</li>
</ul>
<h2 id="support">Support</h2><p>Please report any issues to the <a href="https://github.com/DadaMonad/Yatta/issues">Github issue page</a>!</p><h2 id="license">License</h2><p>Yatta! is licensed under the <a href="./LICENSE.txt">MIT License</a>.</p><a href="&#109;&#x61;&#105;&#108;&#116;&#111;&#x3a;&#x6b;&#101;&#118;&#x69;&#110;&#x2e;&#106;&#97;&#104;&#110;&#115;&#64;&#x72;&#x77;&#116;&#104;&#x2d;&#x61;&#97;&#99;&#x68;&#x65;&#x6e;&#46;&#100;&#101;">&#x6b;&#101;&#118;&#x69;&#110;&#x2e;&#106;&#97;&#104;&#110;&#115;&#64;&#x72;&#x77;&#116;&#104;&#x2d;&#x61;&#97;&#99;&#x68;&#x65;&#x6e;&#46;&#100;&#101;</a>
<h2 id="support">Support</h2><p>Please report <em>any</em> issues to the <a href="https://github.com/DadaMonad/Yatta/issues">Github issue page</a>!
I would appreciate if developers gave me feedback on how <em>convenient</em> the framework is, and if it is easy to use. Particularly the XML-support may not support every DOM-methods - if you encounter a method that does not cause any change on other peers,
please state function name, and sample parameters. However, there are browser-specific features, that Yatta won&#39;t support.</p><h2 id="license">License</h2><p>Yatta! is licensed under the <a href="./LICENSE.txt">MIT License</a>.</p><a href="&#x6d;&#97;&#105;&#108;&#x74;&#x6f;&#x3a;&#107;&#101;&#118;&#105;&#110;&#x2e;&#106;&#x61;&#104;&#110;&#115;&#x40;&#114;&#119;&#x74;&#104;&#45;&#x61;&#x61;&#99;&#x68;&#101;&#x6e;&#46;&#100;&#101;">&#107;&#101;&#118;&#105;&#110;&#x2e;&#106;&#x61;&#104;&#110;&#115;&#x40;&#114;&#119;&#x74;&#104;&#45;&#x61;&#x61;&#99;&#x68;&#101;&#x6e;&#46;&#100;&#101;</a>
@@ -79,7 +85,7 @@ But I would become really motivated if you gave me some feedback :) (<a href="ht
</div>
</div>
<div id='footer'>
August 24, 14 01:51:08 by
October 17, 14 12:58:33 by
<a href='https://github.com/coffeedoc/codo' title='CoffeeScript API documentation generator'>
Codo
</a>

View File

@@ -100,7 +100,7 @@
</div>
</div>
<div id='footer'>
August 24, 14 01:51:08 by
October 17, 14 12:58:33 by
<a href='https://github.com/coffeedoc/codo' title='CoffeeScript API documentation generator'>
Codo
</a>

View File

@@ -158,7 +158,7 @@
</div>
</div>
<div id='footer'>
August 24, 14 01:51:08 by
October 17, 14 12:58:33 by
<a href='https://github.com/coffeedoc/codo' title='CoffeeScript API documentation generator'>
Codo
</a>

View File

@@ -39,7 +39,7 @@
</table>
</div>
<div id='footer'>
August 24, 14 01:51:08 by
October 17, 14 12:58:33 by
<a href='https://github.com/coffeedoc/codo' title='CoffeeScript API documentation generator'>
Codo
</a>

View File

@@ -48,7 +48,7 @@
</dl>
</div>
<div id='footer'>
August 24, 14 01:51:08 by
October 17, 14 12:58:33 by
<a href='https://github.com/coffeedoc/codo' title='CoffeeScript API documentation generator'>
Codo
</a>

View File

@@ -60,7 +60,7 @@
</dl>
</div>
<div id='footer'>
August 24, 14 01:51:08 by
October 17, 14 12:58:33 by
<a href='https://github.com/coffeedoc/codo' title='CoffeeScript API documentation generator'>
Codo
</a>

View File

@@ -60,7 +60,7 @@
</dl>
</div>
<div id='footer'>
August 24, 14 01:51:08 by
October 17, 14 12:58:33 by
<a href='https://github.com/coffeedoc/codo' title='CoffeeScript API documentation generator'>
Codo
</a>

View File

@@ -0,0 +1,134 @@
<!DOCTYPE html>
<html>
<head>
<meta charset='UTF-8'>
<title>Yatta! API</title>
<script src='../../../javascript/application.js'></script>
<script src='../../../javascript/search.js'></script>
<link rel='stylesheet' href='../../../stylesheets/application.css' type='text/css'>
</head>
<body>
<div id='base' data-path='../../../'></div>
<div id='header'>
<div id='menu'>
<a href='../../../extra/README.md.html' title='Yatta!'>
Yatta!
</a>
&raquo;
<a href='../../../alphabetical_index.html' title='Index'>
Index
</a>
&raquo;
<span class='title'>lib</span>
&raquo;
<span class='title'>Frameworks</span>
&raquo;
<span class='title'>XmlFramework.coffee</span>
</div>
</div>
<div id='content'>
<h1>
File:
XmlFramework.coffee
</h1>
<table class='box'>
<tr>
<td>Defined in:</td>
<td>lib&#47;Frameworks</td>
</tr>
<tr>
<td>
Classes:
</td>
<td>
<a href='../../../class/XmlFramework.html'>
XmlFramework
</a>
</td>
</tr>
</table>
<h2>Variables Summary</h2>
<dl class='constants'>
<dt id='module.exports-variable'>
module.exports
=
</dt>
<dd>
<pre><code class='coffeescript'>XmlFramework</code></pre>
</dd>
</dl>
</div>
<div id='footer'>
October 17, 14 12:58:33 by
<a href='https://github.com/coffeedoc/codo' title='CoffeeScript API documentation generator'>
Codo
</a>
2.0.9
&#10034;
Press H to see the keyboard shortcuts
&#10034;
<a href='http://twitter.com/netzpirat' target='_parent'>@netzpirat</a>
&#10034;
<a href='http://twitter.com/_inossidabile' target='_parent'>@_inossidabile</a>
</div>
<iframe id='search_frame'></iframe>
<div id='fuzzySearch'>
<input type='text'>
<ol></ol>
</div>
<div id='help'>
<p>
Quickly fuzzy find classes, mixins, methods, file:
</p>
<ul>
<li>
<span>T</span>
Open fuzzy finder dialog
</li>
</ul>
<p>
Control the navigation frame:
</p>
<ul>
<li>
<span>L</span>
Toggle list view
</li>
<li>
<span>C</span>
Show class list
</li>
<li>
<span>I</span>
Show mixin list
</li>
<li>
<span>F</span>
Show file list
</li>
<li>
<span>M</span>
Show method list
</li>
<li>
<span>E</span>
Show extras list
</li>
</ul>
<p>
You can focus and blur the search input:
</p>
<ul>
<li>
<span>S</span>
Focus search input
</li>
<li>
<span>Esc</span>
Blur search input
</li>
</ul>
</div>
</body>
</html>

View File

@@ -48,7 +48,7 @@
</dl>
</div>
<div id='footer'>
August 24, 14 01:51:08 by
October 17, 14 12:58:33 by
<a href='https://github.com/coffeedoc/codo' title='CoffeeScript API documentation generator'>
Codo
</a>

View File

@@ -39,7 +39,7 @@
</table>
</div>
<div id='footer'>
August 24, 14 01:51:08 by
October 17, 14 12:58:33 by
<a href='https://github.com/coffeedoc/codo' title='CoffeeScript API documentation generator'>
Codo
</a>

View File

@@ -39,7 +39,7 @@
</table>
</div>
<div id='footer'>
August 24, 14 01:51:08 by
October 17, 14 12:58:33 by
<a href='https://github.com/coffeedoc/codo' title='CoffeeScript API documentation generator'>
Codo
</a>

View File

@@ -39,7 +39,7 @@
</table>
</div>
<div id='footer'>
August 24, 14 01:51:08 by
October 17, 14 12:58:33 by
<a href='https://github.com/coffeedoc/codo' title='CoffeeScript API documentation generator'>
Codo
</a>

View File

@@ -39,7 +39,7 @@
</table>
</div>
<div id='footer'>
August 24, 14 01:51:08 by
October 17, 14 12:58:33 by
<a href='https://github.com/coffeedoc/codo' title='CoffeeScript API documentation generator'>
Codo
</a>

View File

@@ -37,9 +37,84 @@
<td>lib&#47;Types</td>
</tr>
</table>
<h2>Variables Summary</h2>
<dl class='constants'>
<dt id='proxy_token-variable'>
proxy_token
=
</dt>
<dd>
<pre><code class='coffeescript'>false</code></pre>
<div class='docstring'>
<p>some dom implementations may call another dom.method that simulates the behavior of another.
For example xml.insertChild(dom) , wich inserts an element at the end, and xml.insertAfter(dom,null) wich does the same
But yatta&#39;s proxy may be called only once!</p>
</div>
<div class='tags'>
</div>
</dd>
<dt id='Element.prototype._proxy-variable'>
Element.prototype._proxy
=
</dt>
<dd>
<pre><code class='coffeescript'>_proxy</code></pre>
</dd>
</dl>
<h2>Method Summary</h2>
<ul class='summary'>
<li>
<span class='signature'>
<a href='#dont_proxy-'>
~
(void)
<b>dont_proxy</b><span>(f)</span>
</a>
</span>
<span class='desc'>
</span>
</li>
<li>
<span class='signature'>
<a href='#_proxy-'>
~
(void)
<b>_proxy</b><span>(f_name, f)</span>
</a>
</span>
<span class='desc'>
</span>
</li>
</ul>
<h2>Method Details</h2>
<div class='methods'>
<div class='method_details'>
<p class='signature' id='dont_proxy-'>
~
(void)
<b>dont_proxy</b><span>(f)</span>
<br>
</p>
</div>
<div class='method_details'>
<p class='signature' id='_proxy-'>
~
(void)
<b>_proxy</b><span>(f_name, f)</span>
<br>
</p>
</div>
</div>
</div>
<div id='footer'>
August 24, 14 01:51:08 by
<<<<<<< HEAD
October 06, 14 10:34:33 by
=======
October 02, 14 15:16:56 by
>>>>>>> XML-Support
<a href='https://github.com/coffeedoc/codo' title='CoffeeScript API documentation generator'>
Codo
</a>

View File

@@ -37,7 +37,7 @@
</table>
</div>
<div id='footer'>
August 24, 14 01:51:08 by
October 17, 14 12:58:33 by
<a href='https://github.com/coffeedoc/codo' title='CoffeeScript API documentation generator'>
Codo
</a>

View File

@@ -97,6 +97,14 @@
lib&#47;Frameworks
</small>
</li>
<li>
<a href='file/lib/Frameworks/XmlFramework.coffee.html' target='main'>
XmlFramework.coffee
</a>
<small class='namespace'>
lib&#47;Frameworks
</small>
</li>
</ul>
<li>
@@ -145,14 +153,6 @@
lib&#47;Types
</small>
</li>
<li>
<a href='file/lib/Types/XmlTypes.coffee.html' target='main'>
XmlTypes.coffee
</a>
<small class='namespace'>
lib&#47;Types
</small>
</li>
</ul>
<li>

File diff suppressed because one or more lines are too long

View File

@@ -53,6 +53,22 @@
(PeerJsConnector)
</small>
</li>
<li>
<a href='class/WordType.html#applyDelete-dynamic' target='main' title='applyDelete'>
#applyDelete
</a>
<small>
(WordType)
</small>
</li>
<li>
<a href='class/JsonType.html#applyDelete-dynamic' target='main' title='applyDelete'>
#applyDelete
</a>
<small>
(JsonType)
</small>
</li>
<li>
<a href='class/WordType.html#bind-dynamic' target='main' title='bind'>
#bind
@@ -69,6 +85,22 @@
(TextFramework)
</small>
</li>
<li>
<a href='class/WordType.html#cleanup-dynamic' target='main' title='cleanup'>
#cleanup
</a>
<small>
(WordType)
</small>
</li>
<li>
<a href='class/JsonType.html#cleanup-dynamic' target='main' title='cleanup'>
#cleanup
</a>
<small>
(JsonType)
</small>
</li>
<li>
<a href='class/PeerJsConnector.html#connectToPeer-dynamic' target='main' title='connectToPeer'>
#connectToPeer
@@ -85,30 +117,6 @@
(IwcConnector)
</small>
</li>
<li>
<a href='class/WordType.html#constructor-dynamic' target='main' title='constructor'>
#constructor
</a>
<small>
(WordType)
</small>
</li>
<li>
<a href='class/JsonTypeWrapper.html#constructor-dynamic' target='main' title='constructor'>
#constructor
</a>
<small>
(JsonTypeWrapper)
</small>
</li>
<li>
<a href='class/TextFramework.html#constructor-dynamic' target='main' title='constructor'>
#constructor
</a>
<small>
(TextFramework)
</small>
</li>
<li>
<a href='class/JsonFramework.html#constructor-dynamic' target='main' title='constructor'>
#constructor
@@ -117,6 +125,14 @@
(JsonFramework)
</small>
</li>
<li>
<a href='class/PeerJsConnector.html#constructor-dynamic' target='main' title='constructor'>
#constructor
</a>
<small>
(PeerJsConnector)
</small>
</li>
<li>
<a href='class/JsonType.html#constructor-dynamic' target='main' title='constructor'>
#constructor
@@ -126,11 +142,35 @@
</small>
</li>
<li>
<a href='class/PeerJsConnector.html#constructor-dynamic' target='main' title='constructor'>
<a href='class/JsonTypeWrapper.html#constructor-dynamic' target='main' title='constructor'>
#constructor
</a>
<small>
(PeerJsConnector)
(JsonTypeWrapper)
</small>
</li>
<li>
<a href='class/XmlFramework.html#constructor-dynamic' target='main' title='constructor'>
#constructor
</a>
<small>
(XmlFramework)
</small>
</li>
<li>
<a href='class/WordType.html#constructor-dynamic' target='main' title='constructor'>
#constructor
</a>
<small>
(WordType)
</small>
</li>
<li>
<a href='class/TextFramework.html#constructor-dynamic' target='main' title='constructor'>
#constructor
</a>
<small>
(TextFramework)
</small>
</li>
<li>
@@ -198,11 +238,11 @@
</small>
</li>
<li>
<a href='class/JsonFramework.html#getHistoryBuffer-dynamic' target='main' title='getHistoryBuffer'>
#getHistoryBuffer
<a href='class/XmlFramework.html#getConnector-dynamic' target='main' title='getConnector'>
#getConnector
</a>
<small>
(JsonFramework)
(XmlFramework)
</small>
</li>
<li>
@@ -213,6 +253,22 @@
(TextFramework)
</small>
</li>
<li>
<a href='class/XmlFramework.html#getHistoryBuffer-dynamic' target='main' title='getHistoryBuffer'>
#getHistoryBuffer
</a>
<small>
(XmlFramework)
</small>
</li>
<li>
<a href='class/JsonFramework.html#getHistoryBuffer-dynamic' target='main' title='getHistoryBuffer'>
#getHistoryBuffer
</a>
<small>
(JsonFramework)
</small>
</li>
<li>
<a href='class/JsonType.html#getParent-dynamic' target='main' title='getParent'>
#getParent
@@ -221,6 +277,14 @@
(JsonType)
</small>
</li>
<li>
<a href='class/XmlFramework.html#getSharedObject-dynamic' target='main' title='getSharedObject'>
#getSharedObject
</a>
<small>
(XmlFramework)
</small>
</li>
<li>
<a href='class/TextFramework.html#getSharedObject-dynamic' target='main' title='getSharedObject'>
#getSharedObject
@@ -237,6 +301,22 @@
(JsonFramework)
</small>
</li>
<li>
<a href='class/TextFramework.html#getUserId-dynamic' target='main' title='getUserId'>
#getUserId
</a>
<small>
(TextFramework)
</small>
</li>
<li>
<a href='class/XmlFramework.html#getUserId-dynamic' target='main' title='getUserId'>
#getUserId
</a>
<small>
(XmlFramework)
</small>
</li>
<li>
<a href='class/JsonFramework.html#getUserId-dynamic' target='main' title='getUserId'>
#getUserId
@@ -246,11 +326,11 @@
</small>
</li>
<li>
<a href='class/TextFramework.html#getUserId-dynamic' target='main' title='getUserId'>
#getUserId
<a href='class/WordType.html#insertAfter-dynamic' target='main' title='insertAfter'>
#insertAfter
</a>
<small>
(TextFramework)
(WordType)
</small>
</li>
<li>
@@ -269,6 +349,22 @@
(WordType)
</small>
</li>
<li>
<a href='class/TextFramework.html#on-dynamic' target='main' title='on'>
#on
</a>
<small>
(TextFramework)
</small>
</li>
<li>
<a href='class/XmlFramework.html#on-dynamic' target='main' title='on'>
#on
</a>
<small>
(XmlFramework)
</small>
</li>
<li>
<a href='class/JsonFramework.html#on-dynamic' target='main' title='on'>
#on
@@ -278,11 +374,19 @@
</small>
</li>
<li>
<a href='class/TextFramework.html#on-dynamic' target='main' title='on'>
#on
<a href='class/PeerJsConnector.html#onNewConnection-dynamic' target='main' title='onNewConnection'>
#onNewConnection
</a>
<small>
(TextFramework)
(PeerJsConnector)
</small>
</li>
<li>
<a href='class/WordType.html#push-dynamic' target='main' title='push'>
#push
</a>
<small>
(WordType)
</small>
</li>
<li>
@@ -333,6 +437,14 @@
(IwcConnector)
</small>
</li>
<li>
<a href='class/JsonFramework.html#setMutableDefault-dynamic' target='main' title='setMutableDefault'>
#setMutableDefault
</a>
<small>
(JsonFramework)
</small>
</li>
<li>
<a href='class/JsonType.html#setMutableDefault-dynamic' target='main' title='setMutableDefault'>
#setMutableDefault
@@ -342,11 +454,11 @@
</small>
</li>
<li>
<a href='class/JsonFramework.html#setMutableDefault-dynamic' target='main' title='setMutableDefault'>
<a href='class/XmlFramework.html#setMutableDefault-dynamic' target='main' title='setMutableDefault'>
#setMutableDefault
</a>
<small>
(JsonFramework)
(XmlFramework)
</small>
</li>
<li>
@@ -366,11 +478,11 @@
</small>
</li>
<li>
<a href='class/JsonType.html#toJson-dynamic' target='main' title='toJson'>
<a href='class/XmlFramework.html#toJson-dynamic' target='main' title='toJson'>
#toJson
</a>
<small>
(JsonType)
(XmlFramework)
</small>
</li>
<li>
@@ -381,6 +493,14 @@
(JsonFramework)
</small>
</li>
<li>
<a href='class/JsonType.html#toJson-dynamic' target='main' title='toJson'>
#toJson
</a>
<small>
(JsonType)
</small>
</li>
<li>
<a href='class/WordType.html#toString-dynamic' target='main' title='toString'>
#toString
@@ -389,14 +509,6 @@
(WordType)
</small>
</li>
<li>
<a href='class/JsonType.html#val-dynamic' target='main' title='val'>
#val
</a>
<small>
(JsonType)
</small>
</li>
<li>
<a href='class/WordType.html#val-dynamic' target='main' title='val'>
#val
@@ -405,14 +517,6 @@
(WordType)
</small>
</li>
<li>
<a href='class/JsonFramework.html#val-dynamic' target='main' title='val'>
#val
</a>
<small>
(JsonFramework)
</small>
</li>
<li>
<a href='class/TextFramework.html#val-dynamic' target='main' title='val'>
#val
@@ -421,6 +525,30 @@
(TextFramework)
</small>
</li>
<li>
<a href='class/XmlFramework.html#val-dynamic' target='main' title='val'>
#val
</a>
<small>
(XmlFramework)
</small>
</li>
<li>
<a href='class/JsonType.html#val-dynamic' target='main' title='val'>
#val
</a>
<small>
(JsonType)
</small>
</li>
<li>
<a href='class/JsonFramework.html#val-dynamic' target='main' title='val'>
#val
</a>
<small>
(JsonFramework)
</small>
</li>
</ul>
</div>
</body>

View File

@@ -1,6 +1,6 @@
## PeerJs + JSON Tutorial
## PeerJs + JSON Example
Here, I will give a short overview on how to enable collaborative json with the
[PeerJs](http://peerjs.com/) Connector and the JsonYatta Framework. Open
[PeerJs](http://peerjs.com/) Connector and the Json Framework. Open
[index.html](http://dadamonad.github.io/Yatta/examples/PeerJs-Json/index.html) in your Browser and
use the console to explore Yatta!
@@ -24,16 +24,38 @@ Therefore, you may also specify the server/port here, if you have set up your ow
```js
var yatta, yattaHandler;
Y.createPeerJsConnector({key: 'h7nlefbgavh1tt9'}, function(Connector, user_id){
```
This will connect to the server owned by the peerjs team.
For now, you can use my API key.
```js
// var conn = {key: 'h7nlefbgavh1tt9'};
```
This will connect to one of my peerjs instances.
I can't guaranty that this will be always up. This is why you should use the previous method with the api key,
or set up your own server.
```js
var conn = {
host: "terrific-peerjs.herokuapp.com",
port: "", // this works because heroku can forward to the right port.
// debug: true,
};
Y.createPeerJsConnector(conn, function(Connector, user_id){
```
You can also specify your own user_id with peerjs.
But you have to make sure that no other client associated to your API-key has the same user_id.
```js
// Y.createPeerJsConnector("unique_id", {key: 'h7nlefbgavh1tt9'}, function(Connector, user_id){
But then you have to make sure that no other client associated to your API-key has the same user_id.
```
Y.createPeerJsConnector("unique_id", conn, function(Connector, user_id){
```
@@ -57,7 +79,7 @@ on how to do that with urls.
```js
console.log("This is your user-id: "+user_id);
console.log("Copy your user-id: " + user_id);
// yatta.connector.connectToPeer(peer_user_id);
```
@@ -93,13 +115,13 @@ A string property can be either mutable or immutable.
```
Did you recognize that we have to use anoter `.val()` for mutable strings?
Thats because yatta.val('mutable_string') is of type WordType.
Since we implemented `toString` in this for WordType's, you can use it like a string:
Did you recognize that we use anoter `.val()` for mutable strings?
Thats because `yatta.val('mutable_string')` is of type *WordType*.
Since WordType implements `toString`, you can use it like a string:
```js
console.log(""+yatta.val("mutable_string") === "eXXXxt") // true, concatenating it with a string will implicitly invoke toString()
console.log("" + yatta.val("mutable_string") === "eXXXxt") // true, concatenating it with a string will implicitly invoke toString()
```
@@ -117,7 +139,7 @@ Initially the default is 'mutable'. You can set it like this:
```
yatta is chainable:
Yatta is [chainable](http://schier.co/post/method-chaining-in-javascript):
```js
@@ -140,13 +162,14 @@ Lists are always immutable.
```js
yatta.val('list', [1,2,3]);
console.log(yatta.val('list')[2] === 3); // true
yatta.val('list', [0,1,2]);
console.log(yatta.val('list')[2] === 2); // true
```
### Check Types
Certainly you want to check types!
You get the type of an YattaType with the `.type` property.
Here, we create a function that parses a Yatta type to a string.
@@ -171,8 +194,9 @@ Apply a 'addProperty' - listener to a JsonType.
```js
function addProperty(event_name, property_name){
console.log("Property '" + property_name + "' was created!");
function addProperty(event_name, property_name, op){
// op is the operation that changed the objects value. In addProperty it is most likely to be a 'Replaceable' (see doc).
console.log("Property '" + property_name + "' was created by '"+op.creator+"'!");
console.log("Value: " + show(this.val(property_name))); // 'this' is the object on which the property was created.
};
yatta.on('addProperty', addProperty);
@@ -184,12 +208,17 @@ Apply a 'change' - listener to a JsonType.
```js
function change(event_name, property_name){
console.log("Value of property '" + property_name + "' changed!");
function change(event_name, property_name, op){
// Check who made this property change!
if(op.creator == yatta.getUserId()){
console.log("You changed the value of property '" + property_name + "'!");
}else{
console.log("User '"+op.creator+"' changed the value of property '" + property_name + "'!");
}
console.log("New value: " + show(this.val(property_name)) + ""); // 'this' is the object on which the property changed.
};
yatta.on('change', change);
yatta.val('mutable_string', "text", 'mutable'); // Property 'mutable_string' was replaced or changed!
yatta.val('mutable_string', "text", 'mutable'); // You changed the value of property 'mutable_string'!
```
@@ -224,11 +253,11 @@ Apply 'insert' and 'delete' - listeners to Words.
### Experimental method
But there is a much more convenient way!
Nah.. this is only for the cool kids.
```js
console.log(yatta.value.list[2] === 3) // true
console.log(yatta.value.list[2] === 2) // true
yatta.value.list = [3,4,5]
console.log(yatta.val('list')[2] === 5) // true
yatta.value.object = {c : 4}
@@ -236,6 +265,10 @@ But there is a much more convenient way!
```
How did I do that? ^^
In Javascript it is possible set setter- and getter- for properties. This is
why this method feels much more natural.
The downside is that you are only allowed to overwrite existing properties.
@@ -254,7 +287,7 @@ So, how do we create new properties?
```
This is stupid! I need to create new properties!
This is stupid! I don't want to overwrite all my existing properties!
Very well.. The solution is that we merge yatta.value with the new assignment.
For example: assuming we want to overwrite yatta.value with some object o.
Then these two rules apply:
@@ -292,7 +325,8 @@ Yatta can't observe if you overwrite object references `yatta = "Awesome"`.
```
Please also read [JsonWrapper](https://rawgit.com/DadaMonad/Yatta/master/doc/class/JsonWrapper.html)
Please also read [JsonWrapper](https://rawgit.com/DadaMonad/Yatta/master/doc/class/JsonWrapper.html).
I really want to hear what you think about this method :)
```js

View File

@@ -24,13 +24,31 @@ The first parameter of `createPeerJsConnector` is forwarded as the options objec
Therefore, you may also specify the server/port here, if you have set up your own server.
*/
var yatta, yattaHandler;
Y.createPeerJsConnector({key: 'h7nlefbgavh1tt9'}, function(Connector, user_id){
/**
This will connect to the server owned by the peerjs team.
For now, you can use my API key.
*/
// var conn = {key: 'h7nlefbgavh1tt9'};
/**
This will connect to one of my peerjs instances.
I can't guaranty that this will be always up. This is why you should use the previous method with the api key,
or set up your own server.
*/
var conn = {
host: "terrific-peerjs.herokuapp.com",
port: "", // this works because heroku can forward to the right port.
// debug: true,
};
Y.createPeerJsConnector(conn, function(Connector, user_id){
/**
You can also specify your own user_id with peerjs.
But then you have to make sure that no other client associated to your API-key has the same user_id.
```
Y.createPeerJsConnector("unique_id", {key: 'h7nlefbgavh1tt9'}, function(Connector, user_id){
Y.createPeerJsConnector("unique_id", conn, function(Connector, user_id){
```
*/
@@ -135,8 +153,9 @@ Y.createPeerJsConnector("unique_id", {key: 'h7nlefbgavh1tt9'}, function(Connecto
### Add listeners
Apply a 'addProperty' - listener to a JsonType.
*/
function addProperty(event_name, property_name){
console.log("Property '" + property_name + "' was created!");
function addProperty(event_name, property_name, op){
// op is the operation that changed the objects value. In addProperty it is most likely to be a 'Replaceable' (see doc).
console.log("Property '" + property_name + "' was created by '"+op.creator+"'!");
console.log("Value: " + show(this.val(property_name))); // 'this' is the object on which the property was created.
};
yatta.on('addProperty', addProperty);
@@ -145,12 +164,17 @@ Y.createPeerJsConnector("unique_id", {key: 'h7nlefbgavh1tt9'}, function(Connecto
/**
Apply a 'change' - listener to a JsonType.
*/
function change(event_name, property_name){
console.log("Value of property '" + property_name + "' changed!");
function change(event_name, property_name, op){
// Check who made this property change!
if(op.creator == yatta.getUserId()){
console.log("You changed the value of property '" + property_name + "'!");
}else{
console.log("User '"+op.creator+"' changed the value of property '" + property_name + "'!");
}
console.log("New value: " + show(this.val(property_name)) + ""); // 'this' is the object on which the property changed.
};
yatta.on('change', change);
yatta.val('mutable_string', "text", 'mutable'); // Property 'mutable_string' was replaced or changed!
yatta.val('mutable_string', "text", 'mutable'); // You changed the value of property 'mutable_string'!
/**
'change' and 'addProperty' do also fire for nested properties.
@@ -181,7 +205,7 @@ Y.createPeerJsConnector("unique_id", {key: 'h7nlefbgavh1tt9'}, function(Connecto
### Experimental method
Nah.. this is only for the cool kids.
*/
console.log(yatta.value.list[2] === 3) // true
console.log(yatta.value.list[2] === 2) // true
yatta.value.list = [3,4,5]
console.log(yatta.val('list')[2] === 5) // true
yatta.value.object = {c : 4}

View File

@@ -7,6 +7,7 @@ Here you find some (hopefully) usefull examples on how to use Yatta!
* [IWC Tutorial](./Iwc/) Tutorial on how to use IWC Connector.
## Demos
* [Text Editing](./TextEditing/) Simple collaborative text editing demo with PeerJs and Text Framework.
* [IWC Demo](./IwcDemo/) More IWC example widgets..
* [Text Editing](http://dadamonad.github.io/Yatta/TextEditing/) Simple collaborative text editing demo with PeerJs and Text Framework.
* [XML Example](http://dadamonad.github.io/Yatta/XmlExample) Collaboratively manipulate the dom with native dom-features and jQuery.
* [IWC Demo](./IwcDemo/) More IWC example widgets.

View File

@@ -28,8 +28,28 @@ First create the connector - the underlaying communication protocol.
Here, we use the PeerJs connector. Its first parameter is the API key that you need to specify (see [website](http://peerjs.com/))
This will connect to the server owned by the peerjs team.
For now, you can use my API key.
```js
Y.createPeerJsConnector({key: 'h7nlefbgavh1tt9'}, function(Connector, user_id){
// var conn = {key: 'h7nlefbgavh1tt9'};
```
This will connect to one of my peerjs instances.
I can't guaranty that this will be always up. This is why you should use the previous method with the api key,
or set up your own server.
```js
var conn = {
host: "terrific-peerjs.herokuapp.com",
port: "", // this works because heroku can forward to the right port.
// debug: true,
};
Y.createPeerJsConnector(conn, function(Connector, user_id){
```

View File

@@ -25,7 +25,25 @@ function init(){
First create the connector - the underlaying communication protocol.
Here, we use the PeerJs connector. Its first parameter is the API key that you need to specify (see [website](http://peerjs.com/))
*/
Y.createPeerJsConnector({key: 'h7nlefbgavh1tt9'}, function(Connector, user_id){
/**
This will connect to the server owned by the peerjs team.
For now, you can use my API key.
*/
// var conn = {key: 'h7nlefbgavh1tt9'};
/**
This will connect to one of my peerjs instances.
I can't guaranty that this will be always up. This is why you should use the previous method with the api key,
or set up your own server.
*/
var conn = {
host: "terrific-peerjs.herokuapp.com",
port: "", // this works because heroku can forward to the right port.
// debug: true,
};
Y.createPeerJsConnector(conn, function(Connector, user_id){
/**
TextFramework is a shared text object. If you change something on this object,
it will be instantaneously shared with all the other collaborators.

View File

@@ -0,0 +1,92 @@
## PeerJs + JSON Example
Here, I will give a short overview on how to enable collaborative json with the
[PeerJs](http://peerjs.com/) Connector and the Json Framework. Open
[index.html](http://dadamonad.github.io/Yatta/examples/PeerJs-Json/index.html) in your Browser and
use the console to explore Yatta!
[PeerJs](http://peerjs.com) is a Framework that enables you to connect to other peers. You just need the
user-id of the peer (browser/client). And then you can connect to it.
First you have to include the following libraries in your html file:
```
<script src="http://cdn.peerjs.com/0.3/peer.js"></script>
<script src="../../build/browser/Frameworks/XmlFramework.js"></script>
<script src="../../build/browser/Connectors/PeerJsConnector.js"></script>
<script src="./index.js"></script>
```
### Create Connector
The PeerJs Framework requires an API key, or you need to set up your own PeerJs server.
Get an API key from the [Website](http://peerjs.com/peerserver).
The first parameter of `createPeerJsConnector` is forwarded as the options object in PeerJs.
Therefore, you may also specify the server/port here, if you have set up your own server.
```js
var yatta, yattaHandler;
```
This will connect to the server owned by the peerjs team.
For now, you can use my API key.
```js
// var conn = {key: 'h7nlefbgavh1tt9'};
```
This will connect to one of my peerjs instances.
I can't guaranty that this will be always up. This is why you should use the previous method with the api key,
or set up your own server.
```js
var conn = {
host: "terrific-peerjs.herokuapp.com",
port: "", // this works because heroku can forward to the right port.
// debug: true,
};
Y.createPeerJsConnector(conn, function(Connector, user_id){
yatta = new Y.XmlFramework(user_id, Connector);
```
Get the url of this frame. If it has a url-encoded parameter
we will connect to the foreign peer.
```js
var url = window.location.href;
var peer_id = location.search
var url = url.substring(0,-peer_id.length);
peer_id = peer_id.substring(1);
```
Set the shareable link.
```js
document.getElementById("peer_link").setAttribute("href",url+"?"+user_id);
```
Connect to other peer.
```js
if (peer_id.length > 0){
yatta.connector.connectToPeer(peer_id);
}
yatta.connector.onNewConnection(function(){
$("#collaborative").replaceWith(yatta.val())
});
yatta.val($("#collaborative")[0])
console.log(yatta.getUserId());
});
```

View File

@@ -0,0 +1,44 @@
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8 />
<title>PeerJs Xml Example</title>
<script src="http://cdn.peerjs.com/0.3/peer.js"></script>
<script src="../../build/browser/Frameworks/XmlFramework.js"></script>
<script src="../../build/browser/Connectors/PeerJsConnector.js"></script>
<script src="./index.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<style>
/* Browser specific (not valid) styles to make preformatted text wrap */
code {
white-space: nowrap; /* css-3 */
}
</style>
</head>
<body id="collaborative">
<h1> PeerJs + XML Example</h1>
<p> Collaborative XML editing with <a href="https://github.com/DadaMonad/Yatta/">Yatta</a>
and <a href="http://peerjs.com/">PeerJs</a> (WebRTC). </p>
<p> <a href="https://github.com/DadaMonad/Yatta/">Yatta</a> is a Framework for Real-Time collaboration on arbitrary data structures.
You can find the code for this example <a href="https://github.com/DadaMonad/Yatta/tree/master/examples/XmlExample">here</a>.
<p>Open this link on other clients: <a id="peer_link" target="_blank">Drop me </a></p> Share this link, in order to build a peer-to-peer based network via WebRTC. Changes won't be recorded by any server.
<p>Open the console and use DOM and jQuery in order to manipulate the body-element of this website. You can see live changes on other clients.</p>
<h4>Sample code for the console: </h4>
<nowrap>
<code>$("body").append('&lt;div id="div_tag"&gt;some new div tag &lt;/div&gt;');</code><br />
<code>$("#div_tag").empty();</code><br />
<code>$("&lt;p&gt;&lt;i&gt;This is inserted after every p tag&lt;/i&gt;&lt;/p&gt;").insertAfter("p");</code><br />
<code>$("p i").remove();</code><br />
</nowrap>
</p>
</body>
</html>

View File

@@ -0,0 +1,77 @@
/**
## PeerJs + JSON Example
Here, I will give a short overview on how to enable collaborative json with the
[PeerJs](http://peerjs.com/) Connector and the Json Framework. Open
[index.html](http://dadamonad.github.io/Yatta/examples/PeerJs-Json/index.html) in your Browser and
use the console to explore Yatta!
[PeerJs](http://peerjs.com) is a Framework that enables you to connect to other peers. You just need the
user-id of the peer (browser/client). And then you can connect to it.
First you have to include the following libraries in your html file:
```
<script src="http://cdn.peerjs.com/0.3/peer.js"></script>
<script src="../../build/browser/Frameworks/XmlFramework.js"></script>
<script src="../../build/browser/Connectors/PeerJsConnector.js"></script>
<script src="./index.js"></script>
```
### Create Connector
The PeerJs Framework requires an API key, or you need to set up your own PeerJs server.
Get an API key from the [Website](http://peerjs.com/peerserver).
The first parameter of `createPeerJsConnector` is forwarded as the options object in PeerJs.
Therefore, you may also specify the server/port here, if you have set up your own server.
*/
var yatta, yattaHandler;
/**
This will connect to the server owned by the peerjs team.
For now, you can use my API key.
*/
// var conn = {key: 'h7nlefbgavh1tt9'};
/**
This will connect to one of my peerjs instances.
I can't guaranty that this will be always up. This is why you should use the previous method with the api key,
or set up your own server.
*/
var conn = {
host: "terrific-peerjs.herokuapp.com",
port: "", // this works because heroku can forward to the right port.
// debug: true,
};
Y.createPeerJsConnector(conn, function(Connector, user_id){
yatta = new Y.XmlFramework(user_id, Connector);
/**
Get the url of this frame. If it has a url-encoded parameter
we will connect to the foreign peer.
*/
var url = window.location.href;
var peer_id = location.search
var url = url.substring(0,-peer_id.length);
peer_id = peer_id.substring(1);
/**
Set the shareable link.
*/
document.getElementById("peer_link").setAttribute("href",url+"?"+user_id);
/**
Connect to other peer.
*/
if (peer_id.length > 0){
yatta.connector.connectToPeer(peer_id);
}
yatta.connector.onNewConnection(function(){
$("#collaborative").replaceWith(yatta.val())
});
yatta.val($("#collaborative")[0])
console.log(yatta.getUserId());
});

View File

@@ -0,0 +1,92 @@
## PeerJs + JSON Example
Here, I will give a short overview on how to enable collaborative json with the
[PeerJs](http://peerjs.com/) Connector and the Json Framework. Open
[index.html](http://dadamonad.github.io/Yatta/examples/PeerJs-Json/index.html) in your Browser and
use the console to explore Yatta!
[PeerJs](http://peerjs.com) is a Framework that enables you to connect to other peers. You just need the
user-id of the peer (browser/client). And then you can connect to it.
First you have to include the following libraries in your html file:
```
<script src="http://cdn.peerjs.com/0.3/peer.js"></script>
<script src="../../build/browser/Frameworks/JsonFramework.js"></script>
<script src="../../build/browser/Connectors/PeerJsConnector.js"></script>
<script src="./index.js"></script>
```
### Create Connector
The PeerJs Framework requires an API key, or you need to set up your own PeerJs server.
Get an API key from the [Website](http://peerjs.com/peerserver).
The first parameter of `createPeerJsConnector` is forwarded as the options object in PeerJs.
Therefore, you may also specify the server/port here, if you have set up your own server.
```js
var yatta, yattaHandler;
```
This will connect to the server owned by the peerjs team.
For now, you can use my API key.
```js
// var conn = {key: 'h7nlefbgavh1tt9'};
```
This will connect to one of my peerjs instances.
I can't guaranty that this will be always up. This is why you should use the previous method with the api key,
or set up your own server.
```js
var conn = {
host: "terrific-peerjs.herokuapp.com",
port: "", // this works because heroku can forward to the right port.
// debug: true
};
Y.createPeerJsConnector(conn, function(Connector, user_id){
```
You can also specify your own user_id with peerjs.
But then you have to make sure that no other client associated to your API-key has the same user_id.
```
Y.createPeerJsConnector("unique_id", conn, function(Connector, user_id){
```
### Yatta
yatta is the shared json object. If you change something on this object,
it will be instantly shared with all the other collaborators.
```js
yatta = new Y.JsonFramework(user_id, Connector);
console.log(yatta.getUserId());
function show(o){
if (o.type === "JsonType"){
return JSON.stringify(o.toJson());
} else if (o.type === "WordType") {
return o.val();
} else if (o.constructor === {}.constructor) { // It's an Object
return JSON.stringify(o);
} else { // It's a primitive data type (E.g. string, int)
return o;
}
}
function addProperty(event_name, property_name, op){
// op is the operation that changed the objects value. In addProperty it is most likely to be a 'Replaceable' (see doc).
console.log("Property '" + property_name + "' was created by '"+op.creator+"'!");
console.log("Value: " + show(this.val(property_name))); // 'this' is the object on which the property was created.
};
yatta.on('addProperty', addProperty);
});
```

View File

@@ -0,0 +1,21 @@
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8 />
<title>PeerJs Json Example</title>
<script src="http://cdn.peerjs.com/0.3/peer.js"></script>
<script src="../../build/browser/Frameworks/JsonFramework.js"></script>
<script src="../../build/browser/Connectors/PeerJsConnector.js"></script>
<script src="./index.js"></script>
</head>
<body>
<h1> PeerJs + Json Tutorial</h1>
<p> Collaborative Json editing with <a href="https://github.com/DadaMonad/Yatta/">Yatta</a>
and <a href="http://peerjs.com/">PeerJs</a> (WebRTC). </p>
<p> <a href="https://github.com/DadaMonad/Yatta/">Yatta</a> is a Framework for Real-Time collaboration on arbitrary data structures.
You can find the code for this example <a href="https://github.com/DadaMonad/Yatta/tree/master/examples/PeerJs-Json">here</a>.
</p>
</body>
</html>

83
examples/temp_ex/index.js Normal file
View File

@@ -0,0 +1,83 @@
/**
## PeerJs + JSON Example
Here, I will give a short overview on how to enable collaborative json with the
[PeerJs](http://peerjs.com/) Connector and the Json Framework. Open
[index.html](http://dadamonad.github.io/Yatta/examples/PeerJs-Json/index.html) in your Browser and
use the console to explore Yatta!
[PeerJs](http://peerjs.com) is a Framework that enables you to connect to other peers. You just need the
user-id of the peer (browser/client). And then you can connect to it.
First you have to include the following libraries in your html file:
```
<script src="http://cdn.peerjs.com/0.3/peer.js"></script>
<script src="../../build/browser/Frameworks/JsonFramework.js"></script>
<script src="../../build/browser/Connectors/PeerJsConnector.js"></script>
<script src="./index.js"></script>
```
### Create Connector
The PeerJs Framework requires an API key, or you need to set up your own PeerJs server.
Get an API key from the [Website](http://peerjs.com/peerserver).
The first parameter of `createPeerJsConnector` is forwarded as the options object in PeerJs.
Therefore, you may also specify the server/port here, if you have set up your own server.
*/
var yatta, yattaHandler;
/**
This will connect to the server owned by the peerjs team.
For now, you can use my API key.
*/
// var conn = {key: 'h7nlefbgavh1tt9'};
/**
This will connect to one of my peerjs instances.
I can't guaranty that this will be always up. This is why you should use the previous method with the api key,
or set up your own server.
*/
var conn = {
host: "terrific-peerjs.herokuapp.com",
port: "", // this works because heroku can forward to the right port.
// debug: true
};
Y.createPeerJsConnector(conn, function(Connector, user_id){
/**
You can also specify your own user_id with peerjs.
But then you have to make sure that no other client associated to your API-key has the same user_id.
```
Y.createPeerJsConnector("unique_id", conn, function(Connector, user_id){
```
*/
/**
### Yatta
yatta is the shared json object. If you change something on this object,
it will be instantly shared with all the other collaborators.
*/
yatta = new Y.JsonFramework(user_id, Connector);
console.log(yatta.getUserId());
function show(o){
if (o.type === "JsonType"){
return JSON.stringify(o.toJson());
} else if (o.type === "WordType") {
return o.val();
} else if (o.constructor === {}.constructor) { // It's an Object
return JSON.stringify(o);
} else { // It's a primitive data type (E.g. string, int)
return o;
}
}
function addProperty(event_name, property_name, op){
// op is the operation that changed the objects value. In addProperty it is most likely to be a 'Replaceable' (see doc).
console.log("Property '" + property_name + "' was created by '"+op.creator+"'!");
console.log("Value: " + show(this.val(property_name))); // 'this' is the object on which the property was created.
};
yatta.on('addProperty', addProperty);
});

View File

@@ -14,9 +14,11 @@ coffeelint = require 'gulp-coffeelint'
mocha = require 'gulp-mocha'
run = require 'gulp-run'
ljs = require 'gulp-ljs'
mochaPhantomJS = require 'gulp-mocha-phantomjs'
gulp.task 'default', ['build', 'test', 'literate', 'lib', 'codo']
gulp.task 'default', ['lint', 'browser', 'test', 'literate', 'lib', 'codo']
gulp.task 'build', ['lint', 'browser']
files =
@@ -24,6 +26,7 @@ files =
build : ['./build/**']
browser : ['./lib/{Connectors,Frameworks}/**/*']
test : ['./test/**/*_test.coffee']
browser_test : ['./test/**/*_test_browser.coffee']
gulp : ['./gulpfile.coffee']
examples : ['./examples/**/*.js']
@@ -55,7 +58,7 @@ gulp.task 'browser', ->
.pipe gulp.dest 'build/browser'
.pipe gulpif '!**/', git.add({args : "-A"})
gulp.src files.test, {read: false}
gulp.src (files.test.concat files.browser_test), {read: false}
.pipe browserify
transform: ['coffeeify']
extensions: ['.coffee']
@@ -91,6 +94,9 @@ gulp.task 'watch', ['default'], ->
gulp.watch files.test, ['test']
gulp.watch files.examples, ['literate']
gulp.task 'phantom_watch', ['phantom_test'], ->
gulp.watch files.all, ['phantom_test']
gulp.task 'literate', ->
gulp.src files.examples
.pipe ljs { code : true }
@@ -104,9 +110,13 @@ gulp.task 'upload', [], ()->
run('scp -r ./build ./examples jahns@manet.informatik.rwth-aachen.de:/home/jahns/public_html/collaborative_preview/').exec()
gulp.task 'codo', [], ()->
command = 'codo -o "./doc" --name "Yatta!" --readme "README.md" --undocumented false --private true --title "Yatta! API" ./lib - LICENSE.txt ./extras/*'
command = 'codo -o "./doc" --name "Yatta!" --readme "README.md" --undocumented false --private true --title "Yatta! API" ./lib - LICENSE.txt '
run(command).exec()
gulp.task 'phantom_test', ['browser'], ()->
gulp.src 'build/test/index.html'
.pipe mochaPhantomJS()
gulp.task 'clean', ->
gulp.src './build/{browser,test,node}/**/*.{js,map}', { read: false }
.pipe ignore '*.html'

View File

@@ -15,6 +15,10 @@ createPeerJsConnector = ()->
callback = arguments[1]
else
peer = new Peer arguments[0], arguments[1]
peer.on 'error', (err)->
throw new Error "Peerjs connector: #{err}"
peer.on 'disconnected', ()->
throw new Error "Peerjs connector disconnected from signalling server. Cannot accept new connections. Not fatal, but not so good either.."
callback = arguments[2]
@@ -35,10 +39,17 @@ createPeerJsConnector = ()->
@peer = peer
@connections = {}
@new_connection_listeners = []
@peer.on 'connection', (conn)=>
@addConnection conn
sync_every_collaborator = ()=>
for conn_id, conn of @connections
conn.send
sync_state_vector: @HB.getOperationCounter()
setInterval sync_every_collaborator, 4000
send_ = (o)=>
if o.uid.creator is @HB.getUserId() and (typeof o.uid.op_number isnt "string")
for conn_id,conn of @connections
@@ -46,6 +57,9 @@ createPeerJsConnector = ()->
op: o
@execution_listener.push send_
#
# Connect the Framework to another peer. Therefore you have to receive his
# user_id. If the other peer is connected to other peers, the PeerJsConnector
@@ -69,6 +83,9 @@ createPeerJsConnector = ()->
for conn_id of @connections
conn_id
onNewConnection: (f)->
@new_connection_listeners.push f
#
# Adds an existing connection to this connector.
# @param conn {PeerJsConnection}
@@ -89,21 +106,29 @@ createPeerJsConnector = ()->
else if data.HB?
initialized_me = true
@engine.applyOpsCheckDouble data.HB
conn.send
conns: @getAllConnectionIds()
if not data.initialized
conn.send
conns: @getAllConnectionIds()
@new_connection_listeners.map (f)->
f(conn)
else if data.op?
@engine.applyOp data.op
else if data.conns?
for conn_id in data.conns
@connectToPeer conn_id
else if data.sync_state_vector?
conn.send
HB: @yatta.getHistoryBuffer()._encode(data.sync_state_vector)
initialized: true
else if data.state_vector?
if not initialized_him
# make sure, that it is sent only once
conn.send
HB: @yatta.getHistoryBuffer()._encode(data.state_vector)
initialized: false
initialized_him = true
else
throw new Error "Can't parse this operation"
throw new Error "Can't parse this operation: #{data}"
sendStateVector = ()=>
conn.send

View File

@@ -27,6 +27,7 @@ module.exports = (user_list)->
if not (user_list?.length is 0)
@engine.applyOps user_list[0].getHistoryBuffer()._encode()
@HB.setManualGarbageCollect()
@unexecuted = {}
#

View File

@@ -61,7 +61,8 @@ class Engine
o = @parseOperation op_json
@HB.addToCounter o
# @HB.addOperation o
if not o.execute()
if @HB.getOperation(o)?
else if not o.execute()
@unprocessed_ops.push o
else
@HB.addOperation o
@@ -76,7 +77,8 @@ class Engine
old_length = @unprocessed_ops.length
unprocessed = []
for op in @unprocessed_ops
if not op.execute()
if @HB.getOperation(op)?
else if not op.execute()
unprocessed.push op
else
@HB.addOperation op

View File

@@ -21,16 +21,25 @@ class JsonFramework
type_manager = json_types_uninitialized @HB
@types = type_manager.types
@engine = new Engine @HB, type_manager.parser
@HB.engine = @engine # TODO: !! only for debugging
@connector = new Connector @engine, @HB, type_manager.execution_listener, @
first_word = new @types.JsonType @HB.getReservedUniqueIdentifier()
first_word = new @types.JsonType(@HB.getReservedUniqueIdentifier())
@HB.addOperation(first_word).execute()
@root_element = first_word
uid_beg = @HB.getReservedUniqueIdentifier()
uid_end = @HB.getReservedUniqueIdentifier()
beg = @HB.addOperation(new @types.Delimiter uid_beg, undefined, uid_end).execute()
end = @HB.addOperation(new @types.Delimiter uid_end, beg, undefined).execute()
@root_element = new @types.ReplaceManager undefined, @HB.getReservedUniqueIdentifier(), beg, end
@HB.addOperation(@root_element).execute()
@root_element.replace first_word, @HB.getReservedUniqueIdentifier()
#
# @return JsonType
#
getSharedObject: ()->
@root_element
@root_element.val()
#
# Get the initialized connector.
@@ -48,7 +57,7 @@ class JsonFramework
# @see JsonType.setMutableDefault
#
setMutableDefault: (mutable)->
@root_element.setMutableDefault(mutable)
@getSharedObject().setMutableDefault(mutable)
#
# Get the UserId from the HistoryBuffer object.
@@ -62,31 +71,31 @@ class JsonFramework
# @see JsonType.toJson
#
toJson : ()->
@root_element.toJson()
@getSharedObject().toJson()
#
# @see JsonType.val
#
val : (name, content, mutable)->
@root_element.val(name, content, mutable)
val : ()->
@getSharedObject().val arguments...
#
# @see Operation.on
#
on: ()->
@root_element.on arguments...
@getSharedObject().on arguments...
#
# @see Operation.deleteListener
#
deleteListener: ()->
@root_element.deleteListener arguments...
@getSharedObject().deleteListener arguments...
#
# @see JsonType.value
#
Object.defineProperty JsonFramework.prototype, 'value',
get : -> @root_element.value
get : -> @getSharedObject().value
set : (o)->
if o.constructor is {}.constructor
for o_name,o_obj of o

View File

@@ -0,0 +1,100 @@
json_types_uninitialized = require "../Types/XmlTypes"
HistoryBuffer = require "../HistoryBuffer"
Engine = require "../Engine"
#
# Framework for Xml-like data-structures.
# Known values that are supported:
#
class XmlFramework
#
# @param {String} user_id Unique id of the peer.
# @param {Connector} Connector the connector class.
#
constructor: (user_id, Connector)->
@HB = new HistoryBuffer user_id
type_manager = json_types_uninitialized @HB
@types = type_manager.types
@engine = new Engine @HB, type_manager.parser
@HB.engine = @engine # TODO: !! only for debugging
@connector = new Connector @engine, @HB, type_manager.execution_listener, @
#first_word = new @types.XmlType(undefined, undefined, undefined, undefined, document.createElement("shared"))
#@HB.addOperation(first_word).execute()
uid_beg = @HB.getReservedUniqueIdentifier()
uid_end = @HB.getReservedUniqueIdentifier()
beg = @HB.addOperation(new @types.Delimiter uid_beg, undefined, uid_end).execute()
end = @HB.addOperation(new @types.Delimiter uid_end, beg, undefined).execute()
@root_element = new @types.ReplaceManager undefined, @HB.getReservedUniqueIdentifier(), beg, end
@HB.addOperation(@root_element).execute()
#@root_element.replace first_word
#
# @return JsonType
#
getSharedObject: ()->
@root_element.val()
#
# Get the initialized connector.
#
getConnector: ()->
@connector
#
# @see HistoryBuffer
#
getHistoryBuffer: ()->
@HB
#
# @see JsonType.setMutableDefault
#
setMutableDefault: (mutable)->
@getSharedObject().setMutableDefault(mutable)
#
# Get the UserId from the HistoryBuffer object.
# In most cases this will be the same as the user_id value with which
# JsonFramework was initialized (Depending on the HistoryBuffer implementation).
#
getUserId: ()->
@HB.getUserId()
#
# @see JsonType.toJson
#
toJson : ()->
@getSharedObject().toJson()
#
# @see JsonType.val
#
val : ()->
if (arguments.length is 0) or (typeof arguments[0] is "boolean")
@getSharedObject().val(arguments[0])
else if arguments.length is 1
newXml = new @types.XmlType(undefined, undefined, undefined, undefined, arguments[0])
@HB.addOperation(newXml).execute()
@root_element.replace newXml
newXml
else
throw new Error "can only parse 0, or 1 parameter!"
#
# @see Operation.on
#
on: ()->
@getSharedObject().on arguments...
module.exports = XmlFramework
if window?
if not window.Y?
window.Y = {}
window.Y.XmlFramework = XmlFramework

View File

@@ -7,6 +7,8 @@
#
class HistoryBuffer
#
# Creates an empty HB.
# @param {Object} user_id Creator of the HB.
@@ -15,6 +17,23 @@ class HistoryBuffer
@operation_counter = {}
@buffer = {}
@change_listeners = []
@garbage = [] # Will be cleaned on next call of garbageCollector
@trash = [] # Is deleted. Wait until it is not used anymore.
@performGarbageCollection = true
@garbageCollectTimeout = 1000
@reserved_identifier_counter = 0
setTimeout @emptyGarbage, @garbageCollectTimeout
emptyGarbage: ()=>
for o in @garbage
#if @getOperationCounter(o.creator) > o.op_number
o.cleanup?()
@garbage = @trash
@trash = []
if @garbageCollectTimeout isnt -1
@garbageCollectTimeoutId = setTimeout @emptyGarbage, @garbageCollectTimeout
undefined
#
# Get the user id with wich the History Buffer was initialized.
@@ -22,8 +41,26 @@ class HistoryBuffer
getUserId: ()->
@user_id
addToGarbageCollector: ()->
if @performGarbageCollection
for o in arguments
if o?
@garbage.push o
stopGarbageCollection: ()->
@performGarbageCollection = false
@setManualGarbageCollect()
@garbage = []
@trash = []
setManualGarbageCollect: ()->
@garbageCollectTimeout = -1
clearTimeout @garbageCollectTimeoutId
@garbageCollectTimeoutId = undefined
setGarbageCollectTimeout: (@garbageCollectTimeout)->
#
# There is only one reserved unique identifier (uid), so use it wisely.
# I propose to use it in your Framework, to create something like a root element.
# An operation with this identifier is not propagated to other clients.
# This is why everybode must create the same operation with this uid.
@@ -31,17 +68,22 @@ class HistoryBuffer
getReservedUniqueIdentifier: ()->
{
creator : '_'
op_number : '_'
op_number : "_#{@reserved_identifier_counter++}"
doSync: false
}
#
# Get the operation counter that describes the current state of the document.
#
getOperationCounter: ()->
res = {}
for user,ctn of @operation_counter
res[user] = ctn
res
getOperationCounter: (user_id)->
if not user_id?
res = {}
for user,ctn of @operation_counter
res[user] = ctn
res
else
@operation_counter[user_id]
#
# Encode this operation in such a way that it can be parsed by remote peers.
@@ -54,17 +96,18 @@ class HistoryBuffer
not state_vector[user]? or state_vector[user] <= o_number
for u_name,user of @buffer
# TODO next, if @state_vector[user] <= state_vector[user]
for o_number,o of user
if (not isNaN(parseInt(o_number))) and unknown(u_name, o_number)
if o.doSync and unknown(u_name, o_number)
# its necessary to send it, and not known in state_vector
o_json = o._encode()
if o.next_cl?
if o.next_cl? # applies for all ops but the most right delimiter!
# search for the next _known_ operation. (When state_vector is {} then this is the Delimiter)
o_next = o.next_cl
while o_next.next_cl? and unknown(o_next.creator, o_next.op_number)
o_next = o_next.next_cl
o_json.next = o_next.getUid()
else if o.prev_cl?
else if o.prev_cl? # most right delimiter only!
# same as the above with prev.
o_prev = o.prev_cl
while o_prev.prev_cl? and unknown(o_prev.creator, o_prev.op_number)
@@ -109,8 +152,13 @@ class HistoryBuffer
if @buffer[o.creator][o.op_number]?
throw new Error "You must not overwrite operations!"
@buffer[o.creator][o.op_number] = o
@number_of_operations_added_to_HB ?= 0 # TODO: Debug, remove this
@number_of_operations_added_to_HB++
o
removeOperation: (o)->
delete @buffer[o.creator]?[o.op_number]
#
# Increment the operation_counter that defines the current state of the Engine.
#

View File

@@ -23,13 +23,21 @@ module.exports = (HB)->
# @see HistoryBuffer.getNextOperationIdentifier
#
constructor: (uid)->
@is_deleted = false
@doSync = true
@garbage_collected = false
if not uid?
uid = HB.getNextOperationIdentifier()
if not uid.doSync?
uid.doSync = not isNaN(parseInt(uid.op_number))
{
'creator': @creator
'op_number' : @op_number
'doSync' : @doSync
} = uid
type: "Insert"
#
# Add an event listener. It depends on the operation which events are supported.
# @param {String} event Name of the event.
@@ -76,6 +84,21 @@ module.exports = (HB)->
for f in @event_listeners[event]
f.call op, event, args...
isDeleted: ()->
@is_deleted
applyDelete: (garbagecollect = true)->
if not @garbage_collected
#console.log "applyDelete: #{@type}"
@is_deleted = true
if garbagecollect
@garbage_collected = true
HB.addToGarbageCollector @
cleanup: ()->
#console.log "cleanup: #{@type}"
HB.removeOperation @
#
# Set the parent of this operation.
#
@@ -91,7 +114,10 @@ module.exports = (HB)->
# Computes a unique identifier (uid) that identifies this operation.
#
getUid: ()->
{ 'creator': @creator, 'op_number': @op_number }
{ 'creator': @creator, 'op_number': @op_number , 'sync': @doSync}
dontSync: ()->
@doSync = false
#
# @private
@@ -162,7 +188,7 @@ module.exports = (HB)->
#
# @nodoc
# A simple Delete-type operation that deletes an Insert-type operation.
# A simple Delete-type operation that deletes an operation.
#
class Delete extends Operation
@@ -174,6 +200,8 @@ module.exports = (HB)->
@saveOperation 'deletes', deletes
super uid
type: "Delete"
#
# @private
# Convert all relevant information of this operation to the json-format.
@@ -235,21 +263,49 @@ module.exports = (HB)->
@saveOperation 'origin', prev_cl
super uid
type: "Insert"
#
# set content to null and other stuff
# @private
#
applyDelete: (o)->
@deleted_by ?= []
@deleted_by.push o
if @parent? and @deleted_by.length is 1
callLater = false
if @parent? and not @isDeleted()
# call iff wasn't deleted earlyer
@parent.callEvent "delete", @
callLater = true
if o?
@deleted_by.push o
garbagecollect = false
if not (@prev_cl? and @next_cl?) or @prev_cl.isDeleted()
garbagecollect = true
super garbagecollect
if callLater
@parent.callEvent "delete", @, o
if @next_cl?.isDeleted()
# garbage collect next_cl
@next_cl.applyDelete()
cleanup: ()->
# TODO: Debugging
if @prev_cl?.isDeleted()
# delete all ops that delete this insertion
for d in @deleted_by
d.cleanup()
# throw new Error "left is not deleted. inconsistency!, wrararar"
# delete origin references to the right
o = @next_cl
while o.type isnt "Delimiter"
if o.origin is @
o.origin = @prev_cl
o = o.next_cl
# reconnect left/right
@prev_cl.next_cl = @next_cl
@next_cl.prev_cl = @prev_cl
super
#
# If isDeleted() is true this operation won't be maintained in the sl
#
isDeleted: ()->
@deleted_by?.length > 0
#
# @private
@@ -262,45 +318,22 @@ module.exports = (HB)->
if @origin is o
break
d++
#TODO: delete this
if @ is @prev_cl
throw new Error "this should not happen ;) "
o = o.prev_cl
d
#
# @private
# Update the short list
# TODO (Unused)
update_sl: ()->
o = @prev_cl
update: (dest_cl,dest_sl)->
while true
if o.isDeleted()
o = o[dest_cl]
else
@[dest_sl] = o
break
update "prev_cl", "prev_sl"
update "next_cl", "prev_sl"
#
# @private
# Include this operation in the associative lists.
#
execute: ()->
if @is_executed?
return @
# @param fire_event {boolean} Whether to fire the insert-event.
execute: (fire_event = true)->
if not @validateSavedOperations()
return false
else
if @prev_cl?.validateSavedOperations() and @next_cl?.validateSavedOperations() and @prev_cl.next_cl isnt @
distance_to_origin = 0
if @prev_cl?
distance_to_origin = @getDistanceToOrigin() # most cases: 0
o = @prev_cl.next_cl
i = 0
i = distance_to_origin # loop counter
# $this has to find a unique position between origin and the next known character
# case 1: $origin equals $o.origin: the $creator parameter decides if left or right
# let $OL= [o1,o2,o3,o4], whereby $this is to be inserted between o1 and o4
@@ -314,10 +347,6 @@ module.exports = (HB)->
# case 3: $origin > $o.origin
# $this insert_position is to the left of $o (forever!)
while true
if not o?
# TODO: Debugging
console.log JSON.stringify @prev_cl.getUid()
console.log JSON.stringify @next_cl.getUid()
if o isnt @next_cl
# $o happened concurrently
if o.getDistanceToOrigin() is i
@@ -346,8 +375,9 @@ module.exports = (HB)->
@next_cl = @prev_cl.next_cl
@prev_cl.next_cl = @
@next_cl.prev_cl = @
parent = @prev_cl?.getParent()
if parent?
if parent? and fire_event
@setParent parent
@parent.callEvent "insert", @
super # notify the execution_listeners
@@ -361,7 +391,7 @@ module.exports = (HB)->
while true
if prev instanceof Delimiter
break
if prev.isDeleted? and not prev.isDeleted()
if not prev.isDeleted()
position++
prev = prev.prev_cl
position
@@ -370,7 +400,7 @@ module.exports = (HB)->
# @nodoc
# Defines an object that is cannot be changed. You can use this to set an immutable string, or a number.
#
class ImmutableObject extends Insert
class ImmutableObject extends Operation
#
# @param {Object} uid A unique identifier. If uid is undefined, a new uid will be created.
@@ -379,6 +409,8 @@ module.exports = (HB)->
constructor: (uid, @content, prev, next, origin)->
super uid, prev, next, origin
type: "ImmutableObject"
#
# @return [String] The content of this operation.
#
@@ -398,8 +430,8 @@ module.exports = (HB)->
json['prev'] = @prev_cl.getUid()
if @next_cl?
json['next'] = @next_cl.getUid()
if @origin? and @origin isnt @prev_cl
json["origin"] = @origin.getUid()
if @origin? # and @origin isnt @prev_cl
json["origin"] = @origin().getUid()
json
parser['ImmutableObject'] = (json)->
@@ -432,11 +464,18 @@ module.exports = (HB)->
@saveOperation 'origin', prev_cl
super uid
#
# If isDeleted() is true this operation won't be maintained in the sl
#
isDeleted: ()->
false
type: "Delimiter"
applyDelete: ()->
super()
o = @next_cl
while o?
o.applyDelete()
o = o.next_cl
undefined
cleanup: ()->
super()
#
# @private

View File

@@ -123,6 +123,11 @@ module.exports = (HB)->
#
type: "JsonType"
applyDelete: ()->
super()
cleanup: ()->
super()
#
# Transform this to a Json and loose all the sharing-abilities (the new object will be a deep clone)!
# @return {Json}
@@ -131,7 +136,9 @@ module.exports = (HB)->
val = @val()
json = {}
for name, o of val
if o.constructor is {}.constructor
if o is null
json[name] = o
else if o.constructor is {}.constructor
json[name] = @val(name).toJson()
else if o instanceof types.Operation
while o instanceof types.Operation
@@ -145,16 +152,18 @@ module.exports = (HB)->
# @see WordType.setReplaceManager
# Sets the parent of this JsonType object.
#
setReplaceManager: (rm)->
@parent = rm.parent
setReplaceManager: (replace_manager)->
@replace_manager = replace_manager
@on ['change','addProperty'], ()->
rm.parent.forwardEvent this, arguments...
if replace_manager.parent?
replace_manager.parent.forwardEvent this, arguments...
#
# Get the parent of this JsonType.
# @return {JsonType}
#
getParent: ()->
@parent
@replace_manager.parent
#
# Whether the default is 'mutable' (true) or 'immutable' (false)
@@ -194,10 +203,11 @@ module.exports = (HB)->
if typeof name is 'object'
# Special case. First argument is an object. Then the second arg is mutable.
# Keep that in mind when reading the following..
for o_name,o of name
@val(o_name,o,content)
json = new JsonType undefined, name, content
HB.addOperation(json).execute()
@replace_manager.replace json
@
else if name? and content?
else if name? and arguments.length > 1
if mutable?
if mutable is true or mutable is 'mutable'
mutable = true
@@ -207,7 +217,7 @@ module.exports = (HB)->
mutable = @mutable_default
if typeof content is 'function'
@ # Just do nothing
else if ((not mutable) or typeof content is 'number') and content.constructor isnt Object
else if (not content?) or (((not mutable) or typeof content is 'number') and content.constructor isnt Object)
obj = HB.addOperation(new types.ImmutableObject undefined, content).execute()
super name, obj
else

View File

@@ -18,6 +18,16 @@ module.exports = (HB)->
@map = {}
super uid
type: "MapManager"
applyDelete: ()->
for name,p of @map
p.applyDelete()
super()
cleanup: ()->
super()
#
# @see JsonTypes.val
#
@@ -60,6 +70,14 @@ module.exports = (HB)->
@saveOperation 'map_manager', map_manager
super uid
type: "AddName"
applyDelete: ()->
super()
cleanup: ()->
super()
#
# If map_manager doesn't have the property name, then add it.
# The ReplaceManager that is being written on the property is unique
@@ -81,6 +99,7 @@ module.exports = (HB)->
end = HB.addOperation(new types.Delimiter uid_end, beg, undefined).execute()
@map_manager.map[@name] = HB.addOperation(new ReplaceManager undefined, uid_r, beg, end)
@map_manager.map[@name].setParent @map_manager, @name
(@map_manager.map[@name].add_name_ops ?= []).push @
@map_manager.map[@name].execute()
super
@@ -107,7 +126,7 @@ module.exports = (HB)->
# @nodoc
# Manages a list of Insert-type operations.
#
class ListManager extends types.Insert
class ListManager extends types.Operation
#
# A ListManager maintains a non-empty list that has a beginning and an end (both Delimiters!)
@@ -126,6 +145,8 @@ module.exports = (HB)->
@end.execute()
super uid, prev, next, origin
type: "ListManager"
#
# @private
# @see Operation.execute
@@ -195,6 +216,22 @@ module.exports = (HB)->
if initial_content?
@replace initial_content
type: "ReplaceManager"
applyDelete: ()->
o = @beginning
while o?
o.applyDelete()
o = o.next_cl
# if this was created by an AddName operation, delete it too
if @add_name_ops?
for o in @add_name_ops
o.applyDelete()
super()
cleanup: ()->
super()
#
# Replace the existing word with a new word.
#
@@ -205,24 +242,26 @@ module.exports = (HB)->
o = @getLastOperation()
op = new Replaceable content, @, replaceable_uid, o, o.next_cl
HB.addOperation(op).execute()
undefined
#
# Add change listeners for parent.
#
setParent: (parent, property_name)->
@on 'insert', (event, op)=>
repl_manager = this
@on 'insert', (event, op)->
if op.next_cl instanceof types.Delimiter
@parent.callEvent 'change', property_name
@on 'change', (event)=>
@parent.callEvent 'change', property_name
repl_manager.parent.callEvent 'change', property_name, op
@on 'change', (event, op)->
if repl_manager isnt this
repl_manager.parent.callEvent 'change', property_name, op
# Call this, when the first element is inserted. Then delete the listener.
addPropertyListener = (event, op)=>
if op.next_cl instanceof types.Delimiter and op.prev_cl instanceof types.Delimiter
@parent.callEvent 'addProperty', property_name
@deleteListener 'addProperty', addPropertyListener
addPropertyListener = (event, op)->
repl_manager.deleteListener 'addProperty', addPropertyListener
repl_manager.parent.callEvent 'addProperty', property_name, op
@on 'insert', addPropertyListener
super parent
#
# Get the value of this WordType
# @return {String}
@@ -247,8 +286,8 @@ module.exports = (HB)->
if @prev_cl? and @next_cl?
json['prev'] = @prev_cl.getUid()
json['next'] = @next_cl.getUid()
if @origin? and @origin isnt @prev_cl
json["origin"] = @origin.getUid()
if @origin? # and @origin isnt @prev_cl
json["origin"] = @origin().getUid()
json
parser["ReplaceManager"] = (json)->
@@ -279,10 +318,12 @@ module.exports = (HB)->
constructor: (content, parent, uid, prev, next, origin)->
@saveOperation 'content', content
@saveOperation 'parent', parent
if not (prev? and next? and content?)
throw new Error "You must define content, prev, and next for Replaceable-types!"
if not (prev? and next?)
throw new Error "You must define prev, and next for Replaceable-types!"
super uid, prev, next, origin
type: "Replaceable"
#
# Return the content that this operation holds.
#
@@ -295,6 +336,16 @@ module.exports = (HB)->
replace: (content)->
@parent.replace content
applyDelete: ()->
if @content?
@content.applyDelete()
@content.dontSync()
@content = null
super
cleanup: ()->
super
#
# If possible set the replace manager in the content.
# @see WordType.setReplaceManager
@@ -303,8 +354,18 @@ module.exports = (HB)->
if not @validateSavedOperations()
return false
else
@content.setReplaceManager?(@parent)
super
@content?.setReplaceManager?(@parent)
# only fire 'insert-event' (which will result in addProperty and change events),
# when content is added. In case of Json, empty content means that this is not the last update,
# since content is deleted when 'applyDelete' was exectuted.
ins_result = super(@content?) # @content? whether to fire or not
if ins_result
if @next_cl.type is "Delimiter" and @prev_cl.type isnt "Delimiter"
@prev_cl.applyDelete()
else if @next_cl.type isnt "Delimiter"
@applyDelete()
return ins_result
#
# Encode this operation in such a way that it can be parsed by remote peers.
@@ -313,7 +374,7 @@ module.exports = (HB)->
json =
{
'type': "Replaceable"
'content': @content.getUid()
'content': @content?.getUid()
'ReplaceManager' : @parent.getUid()
'prev': @prev_cl.getUid()
'next': @next_cl.getUid()
@@ -334,8 +395,6 @@ module.exports = (HB)->
} = json
new Replaceable content, parent, uid, prev, next, origin
types['ListManager'] = ListManager
types['MapManager'] = MapManager
types['ReplaceManager'] = ReplaceManager

View File

@@ -22,10 +22,17 @@ module.exports = (HB)->
# @param {String} content The content of this Insert-type Operation. Usually you restrict the length of content to size 1
# @param {Object} uid A unique identifier. If uid is undefined, a new uid will be created.
#
constructor: (@content, uid, prev, next, origin)->
constructor: (content, uid, prev, next, origin)->
if content?.creator?
@saveOperation 'content', content
else
@content = content
if not (prev? and next?)
throw new Error "You must define prev, and next for TextInsert-types!"
super uid, prev, next, origin
type: "TextInsert"
#
# Retrieve the effective length of the $content of this operation.
#
@@ -35,13 +42,27 @@ module.exports = (HB)->
else
@content.length
applyDelete: ()->
super # no braces indeed!
if @content instanceof types.Operation
@content.applyDelete()
@content = null
execute: ()->
if not @validateSavedOperations()
return false
else
if @content instanceof types.Operation
@content.insert_parent = @
super()
#
# The result will be concatenated with the results from the other insert operations
# in order to retrieve the content of the engine.
# @see HistoryBuffer.toExecutedArray
#
val: (current_position)->
if @isDeleted()
if @isDeleted() or not @content?
""
else
@content
@@ -54,12 +75,15 @@ module.exports = (HB)->
json =
{
'type': "TextInsert"
'content': @content
'uid' : @getUid()
'prev': @prev_cl.getUid()
'next': @next_cl.getUid()
}
if @origin? and @origin isnt @prev_cl
if @content?.getUid?
json['content'] = @content.getUid()
else
json['content'] = @content
if @origin isnt @prev_cl
json["origin"] = @origin.getUid()
json
@@ -98,17 +122,42 @@ module.exports = (HB)->
#
type: "WordType"
applyDelete: ()->
o = @beginning
while o?
o.applyDelete()
o = o.next_cl
super()
cleanup: ()->
super()
push: (content)->
@insertAfter @end.prev_cl, content
insertAfter: (left, content)->
while left.isDeleted()
left = left.prev_cl # find the first character to the left, that is not deleted. Case position is 0, its the Delimiter.
right = left.next_cl
if content.type?
op = new TextInsert content, undefined, left, right
HB.addOperation(op).execute()
else
for c in content
op = new TextInsert c, undefined, left, right
HB.addOperation(op).execute()
left = op
@
#
# Inserts a string into the word.
#
# @return {WordType} This WordType object.
#
insertText: (position, content)->
o = @getOperationByPosition position
for c in content
op = new TextInsert c, undefined, o.prev_cl, o
HB.addOperation(op).execute()
@
# TODO: getOperationByPosition should return "(i-2)th" character
ith = @getOperationByPosition position # the (i-1)th character. e.g. "abc" a is the 0th character
left = ith.prev_cl # left is the non-deleted charather to the left of ith
@insertAfter left, content
#
# Deletes a part of the word.
@@ -173,9 +222,10 @@ module.exports = (HB)->
setReplaceManager: (op)->
@saveOperation 'replace_manager', op
@validateSavedOperations()
@on ['insert', 'delete'], ()=>
@replace_manager?.callEvent 'change'
@on 'insert', (event, ins)=>
@replace_manager?.forwardEvent @, 'change', ins
@on 'delete', (event, ins, del)=>
@replace_manager?.forwardEvent @, 'change', del
#
# Bind this WordType to a textfield or input field.
#
@@ -300,8 +350,8 @@ module.exports = (HB)->
json['prev'] = @prev_cl.getUid()
if @next_cl?
json['next'] = @next_cl.getUid()
if @origin? and @origin isnt @prev_cl
json["origin"] = @origin.getUid()
if @origin? # and @origin isnt @prev_cl
json["origin"] = @origin().getUid()
json
parser['WordType'] = (json)->

View File

@@ -0,0 +1,366 @@
json_types_uninitialized = require "./JsonTypes"
# some dom implementations may call another dom.method that simulates the behavior of another.
# For example xml.insertChild(dom) , wich inserts an element at the end, and xml.insertAfter(dom,null) wich does the same
# But yatta's proxy may be called only once!
proxy_token = false
dont_proxy = (f)->
proxy_token = true
try
f()
catch e
proxy_token = false
throw new Error e
proxy_token = false
_proxy = (f_name, f)->
old_f = @[f_name]
if old_f?
@[f_name] = ()->
if not proxy_token and not @_yatta?.isDeleted()
that = this
args = arguments
dont_proxy ()->
f.apply that, args
old_f.apply that, args
else
old_f.apply this, arguments
#else
# @[f_name] = f
Element?.prototype._proxy = _proxy
module.exports = (HB)->
json_types = json_types_uninitialized HB
types = json_types.types
parser = json_types.parser
#
# Manages XML types
# Not supported:
# * Attribute nodes
# * Real replace of child elements (to much overhead). Currently, the new element is inserted after the 'replaced' element, and then it is deleted.
# * Namespaces (*NS)
# * Browser specific methods (webkit-* operations)
class XmlType extends types.Insert
constructor: (uid, @tagname, attributes, elements, @xml)->
### In case you make this instanceof Insert again
if prev? and (not next?) and prev.type?
# adjust what you actually mean. you want to insert after prev, then
# next is not defined. but we only insert after non-deleted elements.
# This is also handled in TextInsert.
while prev.isDeleted()
prev = prev.prev_cl
next = prev.next_cl
###
super(uid)
if @xml?._yatta?
d = new types.Delete undefined, @xml._yatta
HB.addOperation(d).execute()
@xml._yatta = null
if attributes? and elements?
@saveOperation 'attributes', attributes
@saveOperation 'elements', elements
else if (not attributes?) and (not elements?)
@attributes = new types.JsonType()
@attributes.setMutableDefault 'immutable'
HB.addOperation(@attributes).execute()
@elements = new types.WordType()
@elements.parent = @
HB.addOperation(@elements).execute()
else
throw new Error "Either define attribute and elements both, or none of them"
if @xml?
@tagname = @xml.tagName
for i in [0...@xml.attributes.length]
attr = xml.attributes[i]
@attributes.val(attr.name, attr.value)
for n in @xml.childNodes
if n.nodeType is n.TEXT_NODE
word = new TextNodeType(undefined, n)
HB.addOperation(word).execute()
@elements.push word
else if n.nodeType is n.ELEMENT_NODE
element = new XmlType undefined, undefined, undefined, undefined, n
HB.addOperation(element).execute()
@elements.push element
else
throw new Error "I don't know Node-type #{n.nodeType}!!"
@setXmlProxy()
undefined
#
# Identifies this class.
# Use it in order to check whether this is an xml-type or something else.
#
type: "XmlType"
applyDelete: (op)->
if @insert_parent? and not @insert_parent.isDeleted()
@insert_parent.applyDelete op
else
@attributes.applyDelete()
@elements.applyDelete()
super
cleanup: ()->
super()
setXmlProxy: ()->
@xml._yatta = @
that = @
@elements.on 'insert', (event, op)->
if op.creator isnt HB.getUserId() and this is that.elements
newNode = op.content.val()
right = op.next_cl
while right? and right.isDeleted()
right = right.next_cl
rightNode = null
if right.type isnt 'Delimiter'
rightNode = right.val().val()
dont_proxy ()->
that.xml.insertBefore newNode, rightNode
@elements.on 'delete', (event, op)->
del_op = op.deleted_by[0]
if del_op? and del_op.creator isnt HB.getUserId() and this is that.elements
deleted = op.content.val()
dont_proxy ()->
that.xml.removeChild deleted
@attributes.on ['addProperty', 'change'], (event, property_name, op)->
if op.creator isnt HB.getUserId() and this is that.attributes
dont_proxy ()->
newval = op.val().val()
if newval?
that.xml.setAttribute(property_name, op.val().val())
else
that.xml.removeAttribute(property_name)
## Here are all methods that proxy the behavior of the xml
# you want to find a specific child element. Since they are carried by an Insert-Type, you want to find that Insert-Operation.
# @param child {DomElement} Dom element.
# @return {InsertType} This carries the XmlType that represents the DomElement (child). false if i couldn't find it.
#
findNode = (child)->
if not child?
throw new Error "you must specify a parameter!"
child = child._yatta
elem = that.elements.beginning.next_cl
while elem.type isnt 'Delimiter' and elem.content isnt child
elem = elem.next_cl
if elem.type is 'Delimiter'
false
else
elem
insertBefore = (insertedNode_s, adjacentNode)->
next = null
if adjacentNode?
next = findNode adjacentNode
prev = null
if next
prev = next.prev_cl
else
prev = @_yatta.elements.end.prev_cl
while prev.isDeleted()
prev = prev.prev_cl
inserted_nodes = null
if insertedNode_s.nodeType is insertedNode_s.DOCUMENT_FRAGMENT_NODE
child = insertedNode_s.lastChild
while child?
element = new XmlType undefined, undefined, undefined, undefined, child
HB.addOperation(element).execute()
that.elements.insertAfter prev, element
child = child.previousSibling
else
element = new XmlType undefined, undefined, undefined, undefined, insertedNode_s
HB.addOperation(element).execute()
that.elements.insertAfter prev, element
@xml._proxy 'insertBefore', insertBefore
@xml._proxy 'appendChild', insertBefore
@xml._proxy 'removeAttribute', (name)->
that.attributes.val(name, undefined)
@xml._proxy 'setAttribute', (name, value)->
that.attributes.val name, value
renewClassList = (newclass)->
dont_do_it = false
if newclass?
for elem in this
if newclass is elem
dont_do_it = true
value = Array.prototype.join.call this, " "
if newclass? and not dont_do_it
value += " "+newclass
that.attributes.val('class', value )
_proxy.call @xml.classList, 'add', renewClassList
_proxy.call @xml.classList, 'remove', renewClassList
@xml.__defineSetter__ 'className', (val)->
@setAttribute('class', val)
@xml.__defineGetter__ 'className', ()->
that.attributes.val('class')
@xml.__defineSetter__ 'textContent', (val)->
# remove all nodes
elem = that.xml.firstChild
while elem?
remove = elem
elem = elem.nextSibling
that.xml.removeChild remove
# insert word content
if val isnt ""
text_node = document.createTextNode val
that.xml.appendChild text_node
removeChild = (node)->
elem = findNode node
if not elem
throw new Error "You are only allowed to delete existing (direct) child elements!"
d = new types.Delete undefined, elem
HB.addOperation(d).execute()
node._yatta = null
@xml._proxy 'removeChild', removeChild
@xml._proxy 'replaceChild', (insertedNode, replacedNode)->
insertBefore.call this, insertedNode, replacedNode
removeChild.call this, replacedNode
val: (enforce = false)->
if document?
if (not @xml?) or enforce
@xml = document.createElement @tagname
attr = @attributes.val()
for attr_name, value of attr
if value?
a = document.createAttribute attr_name
a.value = value
@xml.setAttributeNode a
e = @elements.beginning.next_cl
while e.type isnt "Delimiter"
n = e.content
if not e.isDeleted() and e.content? # TODO: how can this happen? Probably because listeners
if n.type is "XmlType"
@xml.appendChild n.val(enforce)
else if n.type is "TextNodeType"
text_node = n.val()
@xml.appendChild text_node
else
throw new Error "Internal structure cannot be transformed to dom"
e = e.next_cl
@setXmlProxy()
@xml
execute: ()->
super()
###
if not @validateSavedOperations()
return false
else
return true
###
#
# Get the parent of this JsonType.
# @return {XmlType}
#
getParent: ()->
@parent
#
# @private
#
# Convert all relevant information of this operation to the json-format.
# This result can be send to other clients.
#
_encode: ()->
json =
{
'type' : @type
'attributes' : @attributes.getUid()
'elements' : @elements.getUid()
'tagname' : @tagname
'uid' : @getUid()
}
json
parser['XmlType'] = (json)->
{
'uid' : uid
'attributes' : attributes
'elements' : elements
'tagname' : tagname
} = json
new XmlType uid, tagname, attributes, elements, undefined
#
# @nodoc
# Defines an object that is cannot be changed. You can use this to set an immutable string, or a number.
#
class TextNodeType extends types.ImmutableObject
#
# @param {Object} uid A unique identifier. If uid is undefined, a new uid will be created.
# @param {Object} content
#
constructor: (uid, content)->
if content._yatta?
d = new types.Delete undefined, content._yatta
HB.addOperation(d).execute()
content._yatta = null
content._yatta = @
super uid, content
applyDelete: (op)->
if @insert_parent? and not @insert_parent.isDeleted()
@insert_parent.applyDelete op
else
super
type: "TextNodeType"
#
# Encode this operation in such a way that it can be parsed by remote peers.
#
_encode: ()->
json = {
'type': @type
'uid' : @getUid()
'content' : @content.textContent
}
json
parser['TextNodeType'] = (json)->
{
'uid' : uid
'content' : content
} = json
textnode = document.createTextNode content
new TextNodeType uid, textnode
types['XmlType'] = XmlType
json_types

View File

@@ -8,4 +8,6 @@ exports['JsonFramework'] =
require './Frameworks/JsonFramework'
exports['TextFramework'] =
require './Frameworks/TextFramework'
exports['XmlFramework'] =
require './Frameworks/XmlFramework'

View File

@@ -1,6 +1,6 @@
{
"name": "yatta",
"version": "0.0.5",
"version": "0.1.0",
"description": "A Framework that enables Real-Time Collaboration on arbitrary data structures.",
"main": "./build/node/index",
"directories": {
@@ -34,6 +34,7 @@
},
"devDependencies": {
"chai": "^1.9.1",
"codo": "^2.0.9",
"coffee-script": "^1.7.1",
"coffeeify": "^0.6.0",
"gulp": "^3.8.7",
@@ -47,11 +48,13 @@
"gulp-ignore": "^1.2.0",
"gulp-ljs": "^0.1.1",
"gulp-mocha": "^0.5.2",
"gulp-mocha-phantomjs": "^0.5.0",
"gulp-rename": "^1.2.0",
"gulp-rimraf": "^0.1.0",
"gulp-run": "^1.6.3",
"gulp-sourcemaps": "^1.1.1",
"gulp-uglify": "^0.3.1",
"jquery": "^2.1.1",
"mocha": "^1.21.4",
"sinon": "^1.10.2",
"sinon-chai": "^2.5.0"

View File

@@ -14,7 +14,9 @@ Test = require "./TestSuite"
class JsonTest extends Test
makeNewUser: (user, conn)->
new Y.JsonFramework user, conn
super new Y.JsonFramework user, conn
type: "JsonTest"
getRandomRoot: (user_num, root)->
root ?= @users[user_num].getSharedObject()
@@ -69,6 +71,7 @@ describe "JsonFramework", ->
done()
it "can handle many engines, many operations, concurrently (random)", ->
console.log "" # TODO
@yTest.run()
it "has a change listener", ()->
@@ -95,7 +98,7 @@ describe "JsonFramework", ->
it "has a JsonTypeWrapper", ->
y = this.yTest.getSomeUser().root_element
y = this.yTest.getSomeUser().getSharedObject()
y.val('x',"dtrn", 'immutable')
y.val('set',{x:"x"}, 'immutable')
w = y.value
@@ -109,6 +112,7 @@ describe "JsonFramework", ->
y.value.x = {q:4}
expect(y.value.x.q).to.equal(4)
### TODO: Handle this test
it "handles double-late-join", ->
test = new JsonTest("double")
test.run()
@@ -120,7 +124,10 @@ describe "JsonFramework", ->
u1.engine.applyOps ops2
u2.engine.applyOps ops1
expect(u2.value.name.val()).to.equal(u2.value.name.val())
###
it "has a working test suite", ->
@yTest.compareAll()
it "can handle creaton of complex json", ->
@@ -130,32 +137,21 @@ describe "JsonFramework", ->
@yTest.getSomeUser().val('c', {'a':'c'})
@yTest.getSomeUser().val('c', {'a':'b'})
@yTest.compareAll()
@yTest.getSomeUser().value.a.a.q.insertText(0,'AAA')
q = @yTest.getSomeUser().value.a.a.q
q.insertText(0,'A')
@yTest.compareAll()
expect(@yTest.getSomeUser().value.a.a.q.val()).to.equal("AAAdtrndtrtdrntdrnrtdnrtdnrtdnrtdnrdnrdt")
expect(@yTest.getSomeUser().value.a.a.q.val()).to.equal("Adtrndtrtdrntdrnrtdnrtdnrtdnrtdnrdnrdt")
it "handles some immutable tests", ->
it "handles immutables and primitive data types", ->
@yTest.getSomeUser().val('string', "text", "immutable")
@yTest.getSomeUser().val('number', 4, "immutable")
@yTest.getSomeUser().val('object', {q:"rr"}, "immutable")
@yTest.getSomeUser().val('null', null)
@yTest.compareAll()
expect(@yTest.getSomeUser().val('string')).to.equal "text"
expect(@yTest.getSomeUser().val('number')).to.equal 4
expect(@yTest.getSomeUser().val('object').val('q')).to.equal "rr"
expect(@yTest.getSomeUser().val('null') is null).to.be.ok
it "converges t1", ->
op0 = {"type":"Delimiter","uid":{"creator":0,"op_number":0},"next":{"creator":0,"op_number":1}}
op1 = {"type":"Delimiter","uid":{"creator":0,"op_number":1},"prev":{"creator":0,"op_number":0}}
op2 = {"type":"WordType","uid":{"creator":0,"op_number":2},"beginning":{"creator":0,"op_number":0},"end":{"creator":0,"op_number":1}}
op3 = {"type":"AddName","uid":{"creator":0,"op_number":3},"map_manager":{"creator":"_","op_number":"_"},"name":"name"}
op4 = {"type":"Replaceable","content":{"creator":0,"op_number":2},"ReplaceManager":{"creator":"_","op_number":"___RM_name"},"prev":{"creator":"_","op_number":"___RM_name_beginning"},"next":{"creator":"_","op_number":"___RM_name_end"},"uid":{"creator":0,"op_number":4}}
op5 = {"type":"TextInsert","content":"u","uid":{"creator":1,"op_number":2},"prev":{"creator":1,"op_number":0},"next":{"creator":1,"op_number":1}}
op6 = {"type":"TextInsert","content":"w","uid":{"creator":2,"op_number":0},"prev":{"creator":0,"op_number":0},"next":{"creator":0,"op_number":1}}
op7 = {"type":"TextInsert","content":"d","uid":{"creator":1,"op_number":0},"prev":{"creator":0,"op_number":0},"next":{"creator":2,"op_number":0}}
op8 = {"type":"TextInsert","content":"a","uid":{"creator":1,"op_number":1},"prev":{"creator":1,"op_number":0},"next":{"creator":2,"op_number":0}}
ops = [op0, op1, op2, op3, op4, op5, op6, op7, op8]
@test_user.engine.applyOps ops
expect(@test_user.val('name').val()).to.equal("duaw")

View File

@@ -14,8 +14,8 @@ module.exports = class Test
constructor: (@name_suffix = "")->
@number_of_test_cases_multiplier = 1
@repeat_this = 1 * @number_of_test_cases_multiplier
@doSomething_amount = 200 * @number_of_test_cases_multiplier
@number_of_engines = 7 + @number_of_test_cases_multiplier - 1
@doSomething_amount = 20 + @number_of_test_cases_multiplier
@number_of_engines = 5 + @number_of_test_cases_multiplier - 1
@time = 0
@ops = 0
@@ -29,12 +29,14 @@ module.exports = class Test
@users = []
@Connector = Connector_uninitialized @users
for i in [0...@number_of_engines]
@users.push @makeNewUser (i+@name_suffix), @Connector
@users[0].val('name',"i")
u = @makeNewUser (i+@name_suffix), @Connector
@users.push u
#@users[0].val('name',"i")
@flushAll()
makeNewUser: (user_id, Connector)->
throw new Error "overwrite me!"
makeNewUser: (user)->
user.HB.setManualGarbageCollect()
user
getSomeUser: ()->
i = _.random 0, (@users.length-1)
@@ -43,6 +45,7 @@ module.exports = class Test
getRandomText: (chars, min_length = 0)->
chars ?= "abcdefghijklmnopqrstuvwxyz"
length = _.random min_length, 10
#length = 1
nextchar = chars[(_.random 0, (chars.length-1))]
text = ""
_(length).times ()-> text += nextchar
@@ -74,11 +77,6 @@ module.exports = class Test
y.insertText pos, @getRandomText()
null
types: [types.WordType]
,
f : (y)=> # REPLACE TEXT
y.replaceText @getRandomText()
null
types: [types.WordType]
,
f : (y)-> # DELETE TEXT
if y.val().length > 0
@@ -87,8 +85,12 @@ module.exports = class Test
ops1 = y.deleteText pos, length
undefined
types : [types.WordType]
,
f : (y)=> # REPLACE TEXT
y.replaceText @getRandomText()
null
types: [types.WordType]
]
getRandomRoot: (user_num)->
throw new Error "overwrite me!"
@@ -116,8 +118,10 @@ module.exports = class Test
choice = _.random (choices.length-1)
choices[choice](user_num)
flushAll: ()->
if @users.length <= 1
flushAll: (final)->
# TODO:!!
final = false
if @users.length <= 1 or not final
for user,user_number in @users
user.getConnector().flushAll()
else
@@ -129,7 +133,7 @@ module.exports = class Test
compareAll: (test_number)->
@flushAll()
@flushAll(true)
@time += (new Date()).getTime() - @time_now
@@ -139,8 +143,8 @@ module.exports = class Test
@ops += number_of_created_operations*@users.length
ops_per_msek = Math.floor(@ops/@time)
if test_number? and @debug
console.log "#{test_number}/#{@repeat_this}: Every collaborator (#{@users.length}) applied #{number_of_created_operations} ops in a different order." + " Over all we consumed #{@ops} operations in #{@time/1000} seconds (#{ops_per_msek} ops/msek)."
if test_number? # and @debug
console.log "#{test_number}/#{@repeat_this}: #{number_of_created_operations} were created and applied on (#{@users.length}) users ops in a different order." + " Over all we consumed #{@ops} operations in #{@time/1000} seconds (#{ops_per_msek} ops/msek)."
for i in [0...(@users.length-1)]
if @debug
@@ -179,7 +183,12 @@ module.exports = class Test
console.log ''
for times in [1..@repeat_this]
@time_now = (new Date).getTime()
for i in [1..@doSomething_amount]
for i in [1..Math.floor(@doSomething_amount/2)]
@doSomething()
@flushAll(false)
for u in @users
u.HB.emptyGarbage()
for i in [1..Math.floor(@doSomething_amount/2)]
@doSomething()
@compareAll(times)
@@ -188,8 +197,13 @@ module.exports = class Test
@reinitialize()
testHBencoding: ()->
@users[@users.length] = @makeNewUser 'testuser', (Connector_uninitialized [])
# in case of JsonFramework, every user will create its JSON first! therefore, the testusers id must be small than all the others (see InsertType)
@users[@users.length] = @makeNewUser (-1), (Connector_uninitialized [])
@users[@users.length-1].engine.applyOps @users[0].HB._encode()
#if @getContent(@users.length-1) isnt @getContent(0)
# console.log "testHBencoding:"
# console.log "Unprocessed ops first: #{@users[0].engine.unprocessed_ops.length}"
# console.log "Unprocessed ops last: #{@users[@users.length-1].engine.unprocessed_ops.length}"
expect(@getContent(@users.length-1)).to.deep.equal(@getContent(0))

Some files were not shown because too many files have changed in this diff Show More