added tests for observer types

This commit is contained in:
DadaMonad 2014-12-24 11:35:06 +00:00
parent 2f47ad9e3a
commit fd1128d456
33 changed files with 607 additions and 240 deletions

@ -1,11 +1,11 @@
# ![Yatta!](https://dadamonad.github.io/files/layout/Yatta_logo.png) # ![Yatta!](https://dadamonad.github.io/files/layout/Yatta_logo.png)
A Real-Time web framework that manages concurrency control for arbitrary data structures. A Real-Time web framework that manages concurrency control for arbitrary data types.
Yatta! provides similar functionality as [ShareJs](https://github.com/share/ShareJS) and [OpenCoweb](https://github.com/opencoweb/coweb), Yatta! provides similar functionality as [ShareJs](https://github.com/share/ShareJS) and [OpenCoweb](https://github.com/opencoweb/coweb),
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. but does not require you to understand how the internals work. The predefined data types provide a simple API to access your shared data types.
Predefined data structures: Predefined data types:
* Text - [Collaborative Text Editing Example](http://dadamonad.github.io/Yatta/examples/TextEditing/) * Text - [Collaborative Text Editing Example](http://dadamonad.github.io/Yatta/examples/TextEditing/)
* Json - [Tutorial](http://dadamonad.github.io/Yatta/examples/PeerJs-Json/) * 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. * XML - [XML Example](http://dadamonad.github.io/Yatta/examples/XmlExample/) Collaboratively manipulate the dom with native dom-features and jQuery.
@ -27,6 +27,9 @@ Either clone this git repository, install it with [bower](http://bower.io/), or
bower install Yatta bower install Yatta
``` ```
Then you include the libraries directly from the installation folder. Then you include the libraries directly from the installation folder.
```
<script src="./bower_components/yatta.js"></script>
```
### Npm ### Npm
``` ```
@ -35,9 +38,7 @@ npm install yatta --save
And use it like this with *npm*: And use it like this with *npm*:
``` ```
Y = require("yatta"); Yatta = require("yatta");
Y.createPeerJsConnector({key: 'xxx'}, function(Connector, user_id){
yatta = new Y.JsonFramework(user_id, Connector);
``` ```

File diff suppressed because one or more lines are too long

@ -24,7 +24,7 @@
}; };
Operation.prototype.unobserve = function(f) { Operation.prototype.unobserve = function(f) {
return this.event_listeners.filter(function(g) { return this.event_listeners = this.event_listeners.filter(function(g) {
return f !== g; return f !== g;
}); });
}; };
@ -156,9 +156,13 @@
}; };
Delete.prototype.execute = function() { Delete.prototype.execute = function() {
var res;
if (this.validateSavedOperations()) { if (this.validateSavedOperations()) {
this.deletes.applyDelete(this); res = Delete.__super__.execute.apply(this, arguments);
return Delete.__super__.execute.apply(this, arguments); if (res) {
this.deletes.applyDelete(this);
}
return res;
} else { } else {
return false; return false;
} }
@ -307,7 +311,8 @@
type: "insert", type: "insert",
position: this.getPosition(), position: this.getPosition(),
object: this.parent, object: this.parent,
changed_by: this.uid.creator changed_by: this.uid.creator,
value: this.content
} }
]) : void 0; ]) : void 0;
}; };

File diff suppressed because one or more lines are too long

@ -195,22 +195,21 @@
ListManager.prototype.getOperationByPosition = function(position) { ListManager.prototype.getOperationByPosition = function(position) {
var o; var o;
o = this.beginning.next_cl; o = this.beginning;
if ((position > 0 || o.isDeleted()) && !(o instanceof types.Delimiter)) { while (true) {
while (o.isDeleted() && !(o instanceof types.Delimiter)) { if (o instanceof types.Delimiter && (o.prev_cl != null)) {
o = o.next_cl; o = o.prev_cl;
while (o.isDeleted() || !(o instanceof types.Delimiter)) {
o = o.prev_cl;
}
break;
} }
while (true) { if (position <= 0 && !o.isDeleted()) {
if (o instanceof types.Delimiter) { break;
break; }
} o = o.next_cl;
if (position <= 0 && !o.isDeleted()) { if (!o.isDeleted()) {
break; position -= 1;
}
o = o.next_cl;
if (!o.isDeleted()) {
position -= 1;
}
} }
} }
return o; return o;

File diff suppressed because one or more lines are too long

@ -147,15 +147,14 @@
}; };
WordType.prototype.insertText = function(position, content) { WordType.prototype.insertText = function(position, content) {
var ith, left; var ith;
ith = this.getOperationByPosition(position); ith = this.getOperationByPosition(position);
left = ith.prev_cl; return this.insertAfter(ith, content);
return this.insertAfter(left, content);
}; };
WordType.prototype.deleteText = function(position, length) { WordType.prototype.deleteText = function(position, length) {
var d, delete_ops, i, o, _i; var d, delete_ops, i, o, _i;
o = this.getOperationByPosition(position); o = this.getOperationByPosition(position + 1);
delete_ops = []; delete_ops = [];
for (i = _i = 0; 0 <= length ? _i < length : _i > length; i = 0 <= length ? ++_i : --_i) { for (i = _i = 0; 0 <= length ? _i < length : _i > length; i = 0 <= length ? ++_i : --_i) {
if (o instanceof types.Delimiter) { if (o instanceof types.Delimiter) {

File diff suppressed because one or more lines are too long

@ -42,7 +42,7 @@
module.exports = createYatta; module.exports = createYatta;
if ((typeof window !== "undefined" && window !== null) && (window.Yatta == null)) { if ((typeof window !== "undefined" && window !== null) && (window.Yatta == null)) {
window.Yatta = Yatta; window.Yatta = createYatta;
} }
}).call(this); }).call(this);

@ -1 +1 @@
{"version":3,"sources":["Yatta.coffee"],"names":[],"mappings":"AACA;AAAA,MAAA,4EAAA;IAAA;mSAAA;;AAAA,EAAA,wBAAA,GAA2B,OAAA,CAAQ,mBAAR,CAA3B,CAAA;;AAAA,EACA,aAAA,GAAgB,OAAA,CAAQ,iBAAR,CADhB,CAAA;;AAAA,EAEA,MAAA,GAAS,OAAA,CAAQ,UAAR,CAFT,CAAA;;AAAA,EAGA,cAAA,GAAiB,OAAA,CAAQ,oBAAR,CAHjB,CAAA;;AAAA,EAKA,WAAA,GAAc,SAAC,SAAD,GAAA;AACZ,QAAA,uCAAA;AAAA,IAAA,OAAA,GAAU,SAAS,CAAC,EAApB,CAAA;AAAA,IACA,EAAA,GAAS,IAAA,aAAA,CAAc,OAAd,CADT,CAAA;AAAA,IAEA,YAAA,GAAe,wBAAA,CAAyB,EAAzB,CAFf,CAAA;AAAA,IAGA,KAAA,GAAQ,YAAY,CAAC,KAHrB,CAAA;AAAA,IAYM;AAMJ,8BAAA,CAAA;;AAAa,MAAA,eAAA,GAAA;AACX,QAAA,IAAC,CAAA,SAAD,GAAa,SAAb,CAAA;AAAA,QACA,IAAC,CAAA,EAAD,GAAM,EADN,CAAA;AAAA,QAEA,IAAC,CAAA,KAAD,GAAS,KAFT,CAAA;AAAA,QAGA,IAAC,CAAA,MAAD,GAAc,IAAA,MAAA,CAAO,IAAC,CAAA,EAAR,EAAY,YAAY,CAAC,MAAzB,CAHd,CAAA;AAAA,QAIA,cAAA,CAAe,IAAC,CAAA,SAAhB,EAA2B,IAAC,CAAA,MAA5B,EAAoC,IAAC,CAAA,EAArC,EAAyC,YAAY,CAAC,kBAAtD,CAJA,CAAA;AAAA,QAKA,wCAAA,SAAA,CALA,CADW;MAAA,CAAb;;AAAA,sBAQA,YAAA,GAAc,SAAA,GAAA;eACZ,IAAC,CAAA,UADW;MAAA,CARd,CAAA;;mBAAA;;OANkB,KAAK,CAAC,SAZ1B,CAAA;AA6BA,WAAW,IAAA,KAAA,CAAM,EAAE,CAAC,2BAAH,CAAA,CAAN,CAAuC,CAAC,OAAxC,CAAA,CAAX,CA9BY;EAAA,CALd,CAAA;;AAAA,EAqCA,MAAM,CAAC,OAAP,GAAiB,WArCjB,CAAA;;AAsCA,EAAA,IAAG,kDAAA,IAAgB,sBAAnB;AACE,IAAA,MAAM,CAAC,KAAP,GAAe,KAAf,CADF;GAtCA;AAAA","file":"Yatta.js","sourceRoot":"/source/","sourcesContent":["\njson_types_uninitialized = require \"./Types/JsonTypes\"\nHistoryBuffer = require \"./HistoryBuffer\"\nEngine = require \"./Engine\"\nadaptConnector = require \"./ConnectorAdapter\"\n\ncreateYatta = (connector)->\n user_id = connector.id # TODO: change to getUniqueId()\n HB = new HistoryBuffer user_id\n type_manager = json_types_uninitialized HB\n types = type_manager.types\n\n #\n # Framework for Json data-structures.\n # Known values that are supported:\n # * String\n # * Integer\n # * Array\n #\n class Yatta extends types.JsonType\n\n #\n # @param {String} user_id Unique id of the peer.\n # @param {Connector} Connector the connector class.\n #\n constructor: ()->\n @connector = connector\n @HB = HB\n @types = types\n @engine = new Engine @HB, type_manager.parser\n adaptConnector @connector, @engine, @HB, type_manager.execution_listener\n super\n\n getConnector: ()->\n @connector\n\n return new Yatta(HB.getReservedUniqueIdentifier()).execute()\n\nmodule.exports = createYatta\nif window? and not window.Yatta?\n window.Yatta = Yatta\n"]} {"version":3,"sources":["Yatta.coffee"],"names":[],"mappings":"AACA;AAAA,MAAA,4EAAA;IAAA;mSAAA;;AAAA,EAAA,wBAAA,GAA2B,OAAA,CAAQ,mBAAR,CAA3B,CAAA;;AAAA,EACA,aAAA,GAAgB,OAAA,CAAQ,iBAAR,CADhB,CAAA;;AAAA,EAEA,MAAA,GAAS,OAAA,CAAQ,UAAR,CAFT,CAAA;;AAAA,EAGA,cAAA,GAAiB,OAAA,CAAQ,oBAAR,CAHjB,CAAA;;AAAA,EAKA,WAAA,GAAc,SAAC,SAAD,GAAA;AACZ,QAAA,uCAAA;AAAA,IAAA,OAAA,GAAU,SAAS,CAAC,EAApB,CAAA;AAAA,IACA,EAAA,GAAS,IAAA,aAAA,CAAc,OAAd,CADT,CAAA;AAAA,IAEA,YAAA,GAAe,wBAAA,CAAyB,EAAzB,CAFf,CAAA;AAAA,IAGA,KAAA,GAAQ,YAAY,CAAC,KAHrB,CAAA;AAAA,IAYM;AAMJ,8BAAA,CAAA;;AAAa,MAAA,eAAA,GAAA;AACX,QAAA,IAAC,CAAA,SAAD,GAAa,SAAb,CAAA;AAAA,QACA,IAAC,CAAA,EAAD,GAAM,EADN,CAAA;AAAA,QAEA,IAAC,CAAA,KAAD,GAAS,KAFT,CAAA;AAAA,QAGA,IAAC,CAAA,MAAD,GAAc,IAAA,MAAA,CAAO,IAAC,CAAA,EAAR,EAAY,YAAY,CAAC,MAAzB,CAHd,CAAA;AAAA,QAIA,cAAA,CAAe,IAAC,CAAA,SAAhB,EAA2B,IAAC,CAAA,MAA5B,EAAoC,IAAC,CAAA,EAArC,EAAyC,YAAY,CAAC,kBAAtD,CAJA,CAAA;AAAA,QAKA,wCAAA,SAAA,CALA,CADW;MAAA,CAAb;;AAAA,sBAQA,YAAA,GAAc,SAAA,GAAA;eACZ,IAAC,CAAA,UADW;MAAA,CARd,CAAA;;mBAAA;;OANkB,KAAK,CAAC,SAZ1B,CAAA;AA6BA,WAAW,IAAA,KAAA,CAAM,EAAE,CAAC,2BAAH,CAAA,CAAN,CAAuC,CAAC,OAAxC,CAAA,CAAX,CA9BY;EAAA,CALd,CAAA;;AAAA,EAqCA,MAAM,CAAC,OAAP,GAAiB,WArCjB,CAAA;;AAsCA,EAAA,IAAG,kDAAA,IAAgB,sBAAnB;AACE,IAAA,MAAM,CAAC,KAAP,GAAe,WAAf,CADF;GAtCA;AAAA","file":"Yatta.js","sourceRoot":"/source/","sourcesContent":["\njson_types_uninitialized = require \"./Types/JsonTypes\"\nHistoryBuffer = require \"./HistoryBuffer\"\nEngine = require \"./Engine\"\nadaptConnector = require \"./ConnectorAdapter\"\n\ncreateYatta = (connector)->\n user_id = connector.id # TODO: change to getUniqueId()\n HB = new HistoryBuffer user_id\n type_manager = json_types_uninitialized HB\n types = type_manager.types\n\n #\n # Framework for Json data-structures.\n # Known values that are supported:\n # * String\n # * Integer\n # * Array\n #\n class Yatta extends types.JsonType\n\n #\n # @param {String} user_id Unique id of the peer.\n # @param {Connector} Connector the connector class.\n #\n constructor: ()->\n @connector = connector\n @HB = HB\n @types = types\n @engine = new Engine @HB, type_manager.parser\n adaptConnector @connector, @engine, @HB, type_manager.execution_listener\n super\n\n getConnector: ()->\n @connector\n\n return new Yatta(HB.getReservedUniqueIdentifier()).execute()\n\nmodule.exports = createYatta\nif window? and not window.Yatta?\n window.Yatta = createYatta\n"]}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -178,7 +178,7 @@
</div> </div>
</div> </div>
<div id='footer'> <div id='footer'>
December 22, 14 14:36:45 by December 23, 14 11:09:09 by
<a href='https://github.com/coffeedoc/codo' title='CoffeeScript API documentation generator'> <a href='https://github.com/coffeedoc/codo' title='CoffeeScript API documentation generator'>
Codo Codo
</a> </a>

@ -359,7 +359,7 @@ E.g.: let x = {a:[]}. Then x.a.push 1 wouldn&#39;t change anything</p>
</div> </div>
</div> </div>
<div id='footer'> <div id='footer'>
December 22, 14 14:36:45 by December 23, 14 11:09:09 by
<a href='https://github.com/coffeedoc/codo' title='CoffeeScript API documentation generator'> <a href='https://github.com/coffeedoc/codo' title='CoffeeScript API documentation generator'>
Codo Codo
</a> </a>

@ -139,7 +139,7 @@ console.log(w.newProperty == &quot;Awesome&quot;) # true!</code></pre>
</div> </div>
</div> </div>
<div id='footer'> <div id='footer'>
December 22, 14 14:36:45 by December 23, 14 11:09:09 by
<a href='https://github.com/coffeedoc/codo' title='CoffeeScript API documentation generator'> <a href='https://github.com/coffeedoc/codo' title='CoffeeScript API documentation generator'>
Codo Codo
</a> </a>

@ -389,7 +389,7 @@ yatta.bind(textbox);</code></pre>
</div> </div>
</div> </div>
<div id='footer'> <div id='footer'>
December 22, 14 14:36:45 by December 23, 14 11:09:09 by
<a href='https://github.com/coffeedoc/codo' title='CoffeeScript API documentation generator'> <a href='https://github.com/coffeedoc/codo' title='CoffeeScript API documentation generator'>
Codo Codo
</a> </a>

@ -115,7 +115,7 @@ Known values that are supported:</p><ul>
</div> </div>
</div> </div>
<div id='footer'> <div id='footer'>
December 22, 14 14:36:45 by December 23, 14 11:09:09 by
<a href='https://github.com/coffeedoc/codo' title='CoffeeScript API documentation generator'> <a href='https://github.com/coffeedoc/codo' title='CoffeeScript API documentation generator'>
Codo Codo
</a> </a>

@ -38,7 +38,7 @@
</div> </div>
</div> </div>
<div id='footer'> <div id='footer'>
December 22, 14 14:36:45 by December 23, 14 11:09:09 by
<a href='https://github.com/coffeedoc/codo' title='CoffeeScript API documentation generator'> <a href='https://github.com/coffeedoc/codo' title='CoffeeScript API documentation generator'>
Codo Codo
</a> </a>

@ -76,7 +76,7 @@ But I would become really motivated if you gave me some feedback :) (<a href="ht
</ul> </ul>
<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>! <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, 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;&#x69;&#108;&#116;&#111;&#58;&#107;&#101;&#118;&#105;&#110;&#46;&#x6a;&#x61;&#x68;&#x6e;&#115;&#x40;&#x72;&#119;&#116;&#x68;&#45;&#x61;&#97;&#x63;&#104;&#x65;&#110;&#x2e;&#x64;&#x65;">&#107;&#101;&#118;&#105;&#110;&#46;&#x6a;&#x61;&#x68;&#x6e;&#115;&#x40;&#x72;&#119;&#116;&#x68;&#45;&#x61;&#97;&#x63;&#104;&#x65;&#110;&#x2e;&#x64;&#x65;</a> 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="&#109;&#97;&#x69;&#108;&#116;&#111;&#x3a;&#107;&#101;&#118;&#105;&#x6e;&#46;&#x6a;&#x61;&#104;&#110;&#115;&#64;&#114;&#119;&#x74;&#104;&#45;&#97;&#97;&#99;&#104;&#x65;&#x6e;&#x2e;&#x64;&#x65;">&#107;&#101;&#118;&#105;&#x6e;&#46;&#x6a;&#x61;&#104;&#110;&#115;&#64;&#114;&#119;&#x74;&#104;&#45;&#97;&#97;&#99;&#104;&#x65;&#x6e;&#x2e;&#x64;&#x65;</a>
@ -85,7 +85,7 @@ please state function name, and sample parameters. However, there are browser-sp
</div> </div>
</div> </div>
<div id='footer'> <div id='footer'>
December 22, 14 14:36:45 by December 23, 14 11:09:09 by
<a href='https://github.com/coffeedoc/codo' title='CoffeeScript API documentation generator'> <a href='https://github.com/coffeedoc/codo' title='CoffeeScript API documentation generator'>
Codo Codo
</a> </a>

@ -106,7 +106,7 @@
</div> </div>
</div> </div>
<div id='footer'> <div id='footer'>
December 22, 14 14:36:45 by December 23, 14 11:09:09 by
<a href='https://github.com/coffeedoc/codo' title='CoffeeScript API documentation generator'> <a href='https://github.com/coffeedoc/codo' title='CoffeeScript API documentation generator'>
Codo Codo
</a> </a>

@ -72,7 +72,7 @@
</dl> </dl>
</div> </div>
<div id='footer'> <div id='footer'>
December 22, 14 14:36:45 by December 23, 14 11:09:09 by
<a href='https://github.com/coffeedoc/codo' title='CoffeeScript API documentation generator'> <a href='https://github.com/coffeedoc/codo' title='CoffeeScript API documentation generator'>
Codo Codo
</a> </a>

@ -48,7 +48,7 @@
</dl> </dl>
</div> </div>
<div id='footer'> <div id='footer'>
December 22, 14 14:36:45 by December 23, 14 11:09:09 by
<a href='https://github.com/coffeedoc/codo' title='CoffeeScript API documentation generator'> <a href='https://github.com/coffeedoc/codo' title='CoffeeScript API documentation generator'>
Codo Codo
</a> </a>

@ -39,7 +39,7 @@
</table> </table>
</div> </div>
<div id='footer'> <div id='footer'>
December 22, 14 14:36:45 by December 23, 14 11:09:09 by
<a href='https://github.com/coffeedoc/codo' title='CoffeeScript API documentation generator'> <a href='https://github.com/coffeedoc/codo' title='CoffeeScript API documentation generator'>
Codo Codo
</a> </a>

@ -39,7 +39,7 @@
</table> </table>
</div> </div>
<div id='footer'> <div id='footer'>
December 22, 14 14:36:45 by December 23, 14 11:09:09 by
<a href='https://github.com/coffeedoc/codo' title='CoffeeScript API documentation generator'> <a href='https://github.com/coffeedoc/codo' title='CoffeeScript API documentation generator'>
Codo Codo
</a> </a>

@ -39,7 +39,7 @@
</table> </table>
</div> </div>
<div id='footer'> <div id='footer'>
December 22, 14 14:36:45 by December 23, 14 11:09:09 by
<a href='https://github.com/coffeedoc/codo' title='CoffeeScript API documentation generator'> <a href='https://github.com/coffeedoc/codo' title='CoffeeScript API documentation generator'>
Codo Codo
</a> </a>

@ -39,7 +39,7 @@
</table> </table>
</div> </div>
<div id='footer'> <div id='footer'>
December 22, 14 14:36:45 by December 23, 14 11:09:09 by
<a href='https://github.com/coffeedoc/codo' title='CoffeeScript API documentation generator'> <a href='https://github.com/coffeedoc/codo' title='CoffeeScript API documentation generator'>
Codo Codo
</a> </a>

@ -74,7 +74,7 @@
</div> </div>
</div> </div>
<div id='footer'> <div id='footer'>
December 22, 14 14:36:45 by December 23, 14 11:09:09 by
<a href='https://github.com/coffeedoc/codo' title='CoffeeScript API documentation generator'> <a href='https://github.com/coffeedoc/codo' title='CoffeeScript API documentation generator'>
Codo Codo
</a> </a>

@ -277,12 +277,7 @@ module.exports = (HB)->
garbagecollect = true garbagecollect = true
super garbagecollect super garbagecollect
if callLater if callLater
@parent.callEvent [ @callOperationSpecificDeleteEvents(o)
type: "insert"
position: @getPosition()
object: @parent # TODO: You can combine getPosition + getParent in a more efficient manner! (only left Delimiter will hold @parent)
changed_by: o.uid.creator
]
if @next_cl?.isDeleted() if @next_cl?.isDeleted()
# garbage collect next_cl # garbage collect next_cl
@next_cl.applyDelete() @next_cl.applyDelete()
@ -378,18 +373,27 @@ module.exports = (HB)->
@setParent @prev_cl.getParent() # do Insertions always have a parent? @setParent @prev_cl.getParent() # do Insertions always have a parent?
super # notify the execution_listeners super # notify the execution_listeners
@callOperationSpecificEvents() @callOperationSpecificInsertEvents()
@ @
callOperationSpecificEvents: ()-> callOperationSpecificInsertEvents: ()->
@parent?.callEvent [ @parent?.callEvent [
type: "insert" type: "insert"
position: @getPosition() position: @getPosition()
object: @parent object: @parent
changed_by: @uid.creator changedBy: @uid.creator
value: @content value: @content
] ]
callOperationSpecificDeleteEvents: (o)->
@parent.callEvent [
type: "delete"
position: @getPosition()
object: @parent # TODO: You can combine getPosition + getParent in a more efficient manner! (only left Delimiter will hold @parent)
length: 1
changedBy: o.uid.creator
]
# #
# Compute the position of this operation. # Compute the position of this operation.
# #

@ -145,7 +145,7 @@ module.exports = (HB)->
that = @ that = @
Object.observe @bound_json, (events)-> Object.observe @bound_json, (events)->
for event in events for event in events
if not event.changed_by? and (event.type is "add" or event.type = "update") if not event.changedBy? and (event.type is "add" or event.type = "update")
# this event is not created by Yatta. # this event is not created by Yatta.
that.val(event.name, event.object[event.name]) that.val(event.name, event.object[event.name])
@observe (events)-> @observe (events)->
@ -162,7 +162,7 @@ module.exports = (HB)->
type: 'update' type: 'update'
name: event.name name: event.name
oldValue: oldVal oldValue: oldVal
changed_by: event.changed_by changedBy: event.changedBy
else else
notifier.performChange 'add', ()-> notifier.performChange 'add', ()->
that.bound_json[event.name] = that.val(event.name) that.bound_json[event.name] = that.val(event.name)
@ -172,7 +172,7 @@ module.exports = (HB)->
type: 'add' type: 'add'
name: event.name name: event.name
oldValue: oldVal oldValue: oldVal
changed_by:event.changed_by changedBy:event.changedBy
@bound_json @bound_json
# #

@ -35,29 +35,31 @@ module.exports = (HB)->
if content? if content?
if not @map[name]? if not @map[name]?
(new AddName undefined, @, name).execute() (new AddName undefined, @, name).execute()
## TODO: del this
if @map[name] == null
qqq = @
x = new AddName undefined, @, name
x.execute()
## endtodo
@map[name].replace content @map[name].replace content
@ @
else if name? else if name?
obj = @map[name]?.val() prop = @map[name]
if obj instanceof types.ImmutableObject if prop? and not prop.isContentDeleted()
obj.val() obj = prop.val()
if obj instanceof types.ImmutableObject
obj.val()
else
obj
else else
obj undefined
else else
result = {} result = {}
for name,o of @map for name,o of @map
obj = o.val() if not o.isContentDeleted()
if obj instanceof types.ImmutableObject or obj instanceof MapManager obj = o.val()
obj = obj.val() if obj instanceof types.ImmutableObject or obj instanceof MapManager
result[name] = obj obj = obj.val()
result[name] = obj
result result
delete: (name)->
@map[name]?.deleteContent()
@
# #
# @nodoc # @nodoc
# When a new property in a map manager is created, then the uids of the inserted Operations # When a new property in a map manager is created, then the uids of the inserted Operations
@ -234,7 +236,9 @@ module.exports = (HB)->
# @param {Object} uid A unique identifier. If uid is undefined, a new uid will be created. # @param {Object} uid A unique identifier. If uid is undefined, a new uid will be created.
# @param {Delimiter} beginning Reference or Object. # @param {Delimiter} beginning Reference or Object.
# @param {Delimiter} end Reference or Object. # @param {Delimiter} end Reference or Object.
constructor: (@event_porperties, @event_this, uid, beginning, end, prev, next, origin)-> constructor: (@event_properties, @event_this, uid, beginning, end, prev, next, origin)->
if not @event_properties['object']?
@event_properties['object'] = @event_this
super uid, beginning, end, prev, next, origin super uid, beginning, end, prev, next, origin
type: "ReplaceManager" type: "ReplaceManager"
@ -262,8 +266,8 @@ module.exports = (HB)->
# #
callEventDecorator: (events)-> callEventDecorator: (events)->
if not @isDeleted() if not @isDeleted()
for name,prop of @event_porperties for event in events
for event in events for name,prop of @event_properties
event[name] = prop event[name] = prop
@event_this.callEvent events @event_this.callEvent events
undefined undefined
@ -276,7 +280,15 @@ module.exports = (HB)->
# #
replace: (content, replaceable_uid)-> replace: (content, replaceable_uid)->
o = @getLastOperation() o = @getLastOperation()
(new Replaceable content, @, replaceable_uid, o, o.next_cl).execute() relp = (new Replaceable content, @, replaceable_uid, o, o.next_cl).execute()
# TODO: delete repl (for debugging)
undefined
isContentDeleted: ()->
@getLastOperation().isDeleted()
deleteContent: ()->
(new types.Delete undefined, @getLastOperation().uid).execute()
undefined undefined
# #
@ -347,13 +359,14 @@ module.exports = (HB)->
@content @content
applyDelete: ()-> applyDelete: ()->
res = super
if @content? if @content?
if @next_cl.type isnt "Delimiter" if @next_cl.type isnt "Delimiter"
@content.deleteAllObservers() @content.deleteAllObservers()
@content.applyDelete() @content.applyDelete()
@content.dontSync() @content.dontSync()
@content = null @content = null
super res
cleanup: ()-> cleanup: ()->
super super
@ -363,27 +376,35 @@ module.exports = (HB)->
# TODO: consider doing this in a more consistent manner. This could also be # TODO: consider doing this in a more consistent manner. This could also be
# done with execute. But currently, there are no specital Insert-types for ListManager. # done with execute. But currently, there are no specital Insert-types for ListManager.
# #
callOperationSpecificEvents: ()-> callOperationSpecificInsertEvents: ()->
if @next_cl.type is "Delimiter" and @prev_cl.type isnt "Delimiter" if @next_cl.type is "Delimiter" and @prev_cl.type isnt "Delimiter"
# this replaces another Replaceable # this replaces another Replaceable
old_value = @prev_cl.content old_value = @prev_cl.content
@prev_cl.applyDelete()
@parent.callEventDecorator [ @parent.callEventDecorator [
type: "update" type: "update"
changed_by: @uid.creator changedBy: @uid.creator
oldValue: old_value oldValue: old_value
] ]
@prev_cl.applyDelete()
else if @next_cl.type isnt "Delimiter" else if @next_cl.type isnt "Delimiter"
# This will never be recognized by the user, because another # This won't be recognized by the user, because another
# concurrent operation is set as the current value of the RM # concurrent operation is set as the current value of the RM
@applyDelete() @applyDelete()
else # prev _and_ next are Delimiters. This is the first created Replaceable in the RM else # prev _and_ next are Delimiters. This is the first created Replaceable in the RM
@parent.callEventDecorator [ @parent.callEventDecorator [
type: "add" type: "add"
changed_by: @uid.creator changedBy: @uid.creator
] ]
undefined undefined
callOperationSpecificDeleteEvents: (o)->
if @next_cl.type is "Delimiter"
@parent.callEventDecorator [
type: "delete"
changedBy: o.uid.creator
oldValue: @content
]
# #
# Encode this operation in such a way that it can be parsed by remote peers. # Encode this operation in such a way that it can be parsed by remote peers.
# #

@ -161,3 +161,106 @@ describe "JsonFramework", ->
expect(@yTest.getSomeUser().val('null') is null).to.be.ok expect(@yTest.getSomeUser().val('null') is null).to.be.ok
it "Observers work on JSON Types (add type observers, local and foreign)", ->
u = @yTest.users[0]
@yTest.flushAll()
last_task = null
observer1 = (changes)->
expect(changes.length).to.equal(1)
change = changes[0]
expect(change.type).to.equal("add")
expect(change.object).to.equal(u)
expect(change.changedBy).to.equal('0')
expect(change.name).to.equal("newStuff")
last_task = "observer1"
u.observe observer1
u.val("newStuff","someStuff")
expect(last_task).to.equal("observer1")
u.unobserve observer1
observer2 = (changes)->
expect(changes.length).to.equal(1)
change = changes[0]
expect(change.type).to.equal("add")
expect(change.object).to.equal(u)
expect(change.changedBy).to.equal('1')
expect(change.name).to.equal("moreStuff")
last_task = "observer2"
u.observe observer2
v = @yTest.users[1]
v.val("moreStuff","someMoreStuff")
@yTest.flushAll()
expect(last_task).to.equal("observer2")
u.unobserve observer2
it "Observers work on JSON Types (update type observers, local and foreign)", ->
u = @yTest.users[0].val("newStuff","oldStuff").val("moreStuff","moreOldStuff")
@yTest.flushAll()
last_task = null
observer1 = (changes)->
expect(changes.length).to.equal(1)
change = changes[0]
expect(change.type).to.equal("update")
expect(change.object).to.equal(u)
expect(change.changedBy).to.equal('0')
expect(change.name).to.equal("newStuff")
expect(change.oldValue.val()).to.equal("oldStuff")
last_task = "observer1"
u.observe observer1
u.val("newStuff","someStuff")
expect(last_task).to.equal("observer1")
u.unobserve observer1
observer2 = (changes)->
expect(changes.length).to.equal(1)
change = changes[0]
expect(change.type).to.equal("update")
expect(change.object).to.equal(u)
expect(change.changedBy).to.equal('1')
expect(change.name).to.equal("moreStuff")
expect(change.oldValue.val()).to.equal("moreOldStuff")
last_task = "observer2"
u.observe observer2
v = @yTest.users[1]
v.val("moreStuff","someMoreStuff")
@yTest.flushAll()
expect(last_task).to.equal("observer2")
u.unobserve observer2
it "Observers work on JSON Types (delete type observers, local and foreign)", ->
u = @yTest.users[0].val("newStuff","oldStuff").val("moreStuff","moreOldStuff")
@yTest.flushAll()
last_task = null
observer1 = (changes)->
expect(changes.length).to.equal(1)
change = changes[0]
expect(change.type).to.equal("delete")
expect(change.object).to.equal(u)
expect(change.changedBy).to.equal('0')
expect(change.name).to.equal("newStuff")
expect(change.oldValue.val()).to.equal("oldStuff")
last_task = "observer1"
u.observe observer1
u.delete("newStuff")
expect(last_task).to.equal("observer1")
u.unobserve observer1
observer2 = (changes)->
expect(changes.length).to.equal(1)
change = changes[0]
expect(change.type).to.equal("delete")
expect(change.object).to.equal(u)
expect(change.changedBy).to.equal('1')
expect(change.name).to.equal("moreStuff")
expect(change.oldValue.val()).to.equal("moreOldStuff")
last_task = "observer2"
u.observe observer2
v = @yTest.users[1]
v.delete("moreStuff")
@yTest.flushAll()
expect(last_task).to.equal("observer2")
u.unobserve observer2

@ -56,7 +56,7 @@ describe "TextFramework", ->
expect(change.object).to.equal(u) expect(change.object).to.equal(u)
expect(change.value).to.equal("a") expect(change.value).to.equal("a")
expect(change.position).to.equal(1) expect(change.position).to.equal(1)
expect(change.changed_by).to.equal('0') expect(change.changedBy).to.equal('0')
last_task = "observer1" last_task = "observer1"
u.observe observer1 u.observe observer1
u.insertText 1, "a" u.insertText 1, "a"
@ -70,7 +70,7 @@ describe "TextFramework", ->
expect(change.object).to.equal(u) expect(change.object).to.equal(u)
expect(change.value).to.equal("x") expect(change.value).to.equal("x")
expect(change.position).to.equal(0) expect(change.position).to.equal(0)
expect(change.changed_by).to.equal('1') expect(change.changedBy).to.equal('1')
last_task = "observer2" last_task = "observer2"
u.observe observer2 u.observe observer2
v = @yTest.users[1].val("TextTest") v = @yTest.users[1].val("TextTest")
@ -90,10 +90,10 @@ describe "TextFramework", ->
expect(change.object).to.equal(u) expect(change.object).to.equal(u)
expect(change.position).to.equal(1) expect(change.position).to.equal(1)
expect(change.length).to.equal(1) expect(change.length).to.equal(1)
expect(change.changed_by).to.equal('0') expect(change.changedBy).to.equal('0')
last_task = "observer1" last_task = "observer1"
u.observe observer1 u.observe observer1
u.deleteText 1 u.deleteText 1, 1
expect(last_task).to.equal("observer1") expect(last_task).to.equal("observer1")
u.unobserve observer1 u.unobserve observer1
@ -103,12 +103,12 @@ describe "TextFramework", ->
expect(change.type).to.equal("delete") expect(change.type).to.equal("delete")
expect(change.object).to.equal(u) expect(change.object).to.equal(u)
expect(change.position).to.equal(0) expect(change.position).to.equal(0)
expect(change.length).to.equal(0) expect(change.length).to.equal(1)
expect(change.changed_by).to.equal('1') expect(change.changedBy).to.equal('1')
last_task = "observer2" last_task = "observer2"
u.observe observer2 u.observe observer2
v = @yTest.users[1].val("TextTest") v = @yTest.users[1].val("TextTest")
v.deleteText 0 v.deleteText 0, 1
@yTest.flushAll() @yTest.flushAll()
expect(last_task).to.equal("observer2") expect(last_task).to.equal("observer2")
u.unobserve observer2 u.unobserve observer2

File diff suppressed because one or more lines are too long