There are now "Pseudo operations" that are not sent, and get be queried by a special parameter with the HB.getOperation. This will reduce the number of operations that are sent and is necessary for the Array implementation, that I plan to implement in the near future

This commit is contained in:
DadaMonad 2015-01-16 13:36:15 +00:00
parent 6b46500325
commit b647b2af58
20 changed files with 543 additions and 1254 deletions

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

@ -2,12 +2,4 @@
Here you find some (hopefully) usefull examples on how to use Yatta! Here you find some (hopefully) usefull examples on how to use Yatta!
## Tutorials Please note, that the XMPP Connector is the best supported Connector at the moment.
* [PeerJs-Json Tutorial](./PeerJs-Json/) Tutorial on how to use Yatta! with Json and PeerJs Connector.
* [IWC Tutorial](./Iwc/) Tutorial on how to use IWC Connector.
## Demos
* [Text Editing](http://dadamonad.github.io/Yatta/examples/TextEditing/) Simple collaborative text editing demo with PeerJs and Text Framework.
* [XML Example](http://dadamonad.github.io/Yatta/examples/XmlExample) Collaboratively manipulate the dom with native dom-features and jQuery.
* [IWC Demo](./IwcDemo/) More IWC example widgets.

View File

@ -1,23 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8 />
<title>PeerJs Json Example</title>
<script src="../../../Connector/xmpp-connector/strophe.js"></script>
<script src="../../../Connector/bower_components/strophejs-plugins/muc/strophe.muc.js"></script>
<script src="../../../Connector/xmpp-connector/xmpp-connector.js"></script>
<script src="../../build/browser/yatta.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>
<textarea style="width:80%;" rows=40 id="textfield"></textarea>
<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>

View File

@ -1,53 +0,0 @@
/**
## 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.
*/
connector = new XMPPConnector();
/**
### 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 Yatta(connector);
window.onload = function(){
var textbox = document.getElementById("textfield");
yatta.observe(function(events){
for(var i=0; i<events.length; i++){
var event = events[i];
if(event.name === "textfield" && event.type !== "delete"){
yatta.val("textfield").bind(textbox);
}
}
});
yatta.val("textfield","");
};

View File

@ -1,106 +0,0 @@
## Text Editing Example
Here, I will give a short overview on how to enable collaborative text editing with the
[PeerJs](http://peerjs.com/) Connector and the TextFramework Framework.
PeerJs 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. In this example we will encode
the client-id to which this client shall connect, in the url.
It should look like this: http://../index.html?user_id
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/TextFramework.js"></script>
<script src="../../build/browser/Connectors/PeerJsConnector.js"></script>
<script src="./index.js"></script>
```
Open [index.html](./index.html) in order to start collaboration.
```js
var yatta;
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/))
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){
```
TextFramework is a shared text object. If you change something on this object,
it will be instantaneously shared with all the other collaborators.
```js
yatta = new Y.TextFramework(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);
}
```
Bind yatta to the textfield.
The .bind property is a method of the Word class. You can also use it with all the other Frameworks in Yatta (e.g. Json).
```js
var textbox = document.getElementById("textfield");
yatta.bind(textbox);
});
}
window.onload = init
```

View File

@ -1,22 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8 />
<title>PeerJs Json Example</title>
<script src="../../bower_components/peerjs/peer.js"></script>
<script src="../../bower_components/connector/peerjs-connector/peerjs-connector.js"></script>
<script src="../../build/browser/yatta.js"></script>
<script src="./index.js"></script>
</head>
<body>
<h1> Text Editing Demo</h1>
<p> Collaborative text editing with <a href="https://github.com/DadaMonad/Yatta/">Yatta</a>
and <a href="http://peerjs.com/">PeerJs</a> (WebRTC). Open this link in other browsers: <a id="peer_link" target="_blank">Drop me </a> </p>
<textarea style="width:80%;" rows=40 id="textfield"></textarea>
<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/TextEditing">here</a>.
</p>
</body>
</html>

View File

@ -1,92 +0,0 @@
/**
## Text Editing Example
Here, I will give a short overview on how to enable collaborative text editing with the
[PeerJs](http://peerjs.com/) Connector and the TextFramework Framework.
PeerJs 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. In this example we will encode
the client-id to which this client shall connect, in the url.
It should look like this: http://../index.html?user_id
First you have to include the following libraries in your html file:
```
<script src="../../bower_components/peerjs/peer.js"></script>
<script src="../../bower_components/connector/peerjs-connector/peerjs-connector.js"></script>
<script src="../../yatta.js"></script>
<script src="./index.js"></script>
```
Open [index.html](./index.html) in order to start collaboration.
*/
var yatta;
var connector;
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/))
*/
/**
This will connect to the server owned by the peerjs team.
For now, you can use my API key.
*/
var options = {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 options = {
host: "terrific-peerjs.herokuapp.com",
port: "", // this works because heroku can forward to the right port.
// debug: true,
};*/
var user_id = Math.ceil(Math.random()*1000);
connector = new PeerJsConnector(user_id, options);
/**
TextFramework is a shared text object. If you change something on this object,
it will be instantaneously shared with all the other collaborators.
*/
yatta = new Yatta(connector);
yatta.val()
/**
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.join(peer_id);
}
/**
Bind yatta to the textfield.
The .bind property is a method of the Word class. You can also use it with all the other Frameworks in Yatta (e.g. Json).
*/
var textbox = document.getElementById("textfield");
function textbind(){
yatta.val("textbox").bind(textbox);
}
if(peer_id.length > 0){
connector.whenSynced([textbind]);
} else {
yatta.val("textbox",textbox.value)
textbind()
}
}
window.onload = init

View File

@ -49,5 +49,5 @@ window.onload = function(){
} }
} }
}); });
yatta.val("textfield",""); yatta.val("textfield","", "mutable");
}; };

View File

@ -152,10 +152,17 @@ class HistoryBuffer
# #
# Retrieve an operation from a unique id. # Retrieve an operation from a unique id.
# #
# when uid has a "sub" property, the value of it will be applied
# on the operations retrieveSub method (which must! be defined)
#
getOperation: (uid)-> getOperation: (uid)->
if uid.uid? if uid.uid?
uid = uid.uid uid = uid.uid
@buffer[uid.creator]?[uid.op_number] o = @buffer[uid.creator]?[uid.op_number]
if uid.sub? and o?
o.retrieveSub uid.sub
else
o
# #
# Add an operation to the HB. Note that this will not link it against # Add an operation to the HB. Note that this will not link it against

View File

@ -31,6 +31,9 @@ module.exports = (HB)->
type: "Operation" type: "Operation"
retrieveSub: ()->
throw new Error "sub properties are not enable on this operation type!"
# #
# Add an event listener. It depends on the operation which events are supported. # Add an event listener. It depends on the operation which events are supported.
# @param {Function} f f is executed in case the event fires. # @param {Function} f f is executed in case the event fires.
@ -101,7 +104,16 @@ module.exports = (HB)->
# Computes a unique identifier (uid) that identifies this operation. # Computes a unique identifier (uid) that identifies this operation.
# #
getUid: ()-> getUid: ()->
@uid if not @uid.noOperation?
@uid
else
@uid.alt # could be (safely) undefined
cloneUid: ()->
uid = {}
for n,v of @getUid()
uid[n] = v
uid
dontSync: ()-> dontSync: ()->
@uid.doSync = false @uid.doSync = false
@ -119,9 +131,10 @@ module.exports = (HB)->
# There is only one other place, where this can be done - before an Insertion # There is only one other place, where this can be done - before an Insertion
# is executed (because we need the creator_id) # is executed (because we need the creator_id)
@uid = HB.getNextOperationIdentifier() @uid = HB.getNextOperationIdentifier()
HB.addOperation @ if not @uid.noOperation?
for l in execution_listener HB.addOperation @
l @_encode() for l in execution_listener
l @_encode()
@ @
# #
@ -180,7 +193,6 @@ module.exports = (HB)->
success success
# #
# @nodoc # @nodoc
# A simple Delete-type operation that deletes an operation. # A simple Delete-type operation that deletes an operation.
@ -249,7 +261,8 @@ module.exports = (HB)->
# @param {Operation} prev_cl The predecessor of this operation in the complete-list (cl) # @param {Operation} prev_cl The predecessor of this operation in the complete-list (cl)
# @param {Operation} next_cl The successor of this operation in the complete-list (cl) # @param {Operation} next_cl The successor of this operation in the complete-list (cl)
# #
constructor: (uid, prev_cl, next_cl, origin)-> constructor: (uid, prev_cl, next_cl, origin, parent)->
@saveOperation 'parent', parent
@saveOperation 'prev_cl', prev_cl @saveOperation 'prev_cl', prev_cl
@saveOperation 'next_cl', next_cl @saveOperation 'next_cl', next_cl
if origin? if origin?
@ -283,7 +296,6 @@ module.exports = (HB)->
@prev_cl.applyDelete() @prev_cl.applyDelete()
cleanup: ()-> cleanup: ()->
# TODO: Debugging
if @next_cl.isDeleted() if @next_cl.isDeleted()
# delete all ops that delete this insertion # delete all ops that delete this insertion
for d in @deleted_by for d in @deleted_by
@ -300,8 +312,9 @@ module.exports = (HB)->
@prev_cl.next_cl = @next_cl @prev_cl.next_cl = @next_cl
@next_cl.prev_cl = @prev_cl @next_cl.prev_cl = @prev_cl
super super
else if @next_cl? and @prev_cl? # else
throw new Error "This insertion was not supposed to be deleted!" # Someone inserted something in the meantime.
# Remember: this can only be garbage collected when next_cl is deleted
# #
# @private # @private
@ -324,6 +337,13 @@ module.exports = (HB)->
if not @validateSavedOperations() if not @validateSavedOperations()
return false return false
else else
if @parent?
if not @prev_cl?
@prev_cl = @parent.beginning
if not @origin?
@origin = @parent.beginning
if not @next_cl?
@next_cl = @parent.end
if @prev_cl? if @prev_cl?
distance_to_origin = @getDistanceToOrigin() # most cases: 0 distance_to_origin = @getDistanceToOrigin() # most cases: 0
o = @prev_cl.next_cl o = @prev_cl.next_cl
@ -419,8 +439,8 @@ 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 {Object} content # @param {Object} content
# #
constructor: (uid, @content, prev, next, origin)-> constructor: (uid, @content)->
super uid, prev, next, origin super uid
type: "ImmutableObject" type: "ImmutableObject"
@ -439,23 +459,14 @@ module.exports = (HB)->
'uid' : @getUid() 'uid' : @getUid()
'content' : @content 'content' : @content
} }
if @prev_cl?
json['prev'] = @prev_cl.getUid()
if @next_cl?
json['next'] = @next_cl.getUid()
if @origin? # and @origin isnt @prev_cl
json["origin"] = @origin().getUid()
json json
parser['ImmutableObject'] = (json)-> parser['ImmutableObject'] = (json)->
{ {
'uid' : uid 'uid' : uid
'content' : content 'content' : content
'prev': prev
'next': next
'origin' : origin
} = json } = json
new ImmutableObject uid, content, prev, next, origin new ImmutableObject uid, content
# #
# @nodoc # @nodoc
@ -469,11 +480,11 @@ module.exports = (HB)->
# @param {Operation} prev_cl The predecessor of this operation in the complete-list (cl) # @param {Operation} prev_cl The predecessor of this operation in the complete-list (cl)
# @param {Operation} next_cl The successor of this operation in the complete-list (cl) # @param {Operation} next_cl The successor of this operation in the complete-list (cl)
# #
constructor: (uid, prev_cl, next_cl, origin)-> constructor: (prev_cl, next_cl, origin)->
@saveOperation 'prev_cl', prev_cl @saveOperation 'prev_cl', prev_cl
@saveOperation 'next_cl', next_cl @saveOperation 'next_cl', next_cl
@saveOperation 'origin', prev_cl @saveOperation 'origin', prev_cl
super uid super {noOperation: true}
type: "Delimiter" type: "Delimiter"

View File

@ -33,9 +33,7 @@ module.exports = (HB)->
# #
val: (name, content)-> val: (name, content)->
if content? if content?
if not @map[name]? @retrieveSub(name).replace content
(new AddName undefined, @, name).execute()
@map[name].replace content
@ @
else if name? else if name?
prop = @map[name] prop = @map[name]
@ -60,85 +58,22 @@ module.exports = (HB)->
delete: (name)-> delete: (name)->
@map[name]?.deleteContent() @map[name]?.deleteContent()
@ @
#
# @nodoc
# When a new property in a map manager is created, then the uids of the inserted Operations
# must be unique (think about concurrent operations). Therefore only an AddName operation is allowed to
# add a property in a MapManager. If two AddName operations on the same MapManager name happen concurrently
# only one will AddName operation will be executed.
#
class AddName extends types.Operation
# retrieveSub: (property_name)->
# @param {Object} uid A unique identifier. If uid is undefined, a new uid will be created. if not @map[property_name]?
# @param {Object} map_manager Uid or reference to the MapManager. event_properties =
# @param {String} name Name of the property that will be added. name: property_name
# event_this = @
constructor: (uid, map_manager, @name)-> map_uid = @cloneUid()
@saveOperation 'map_manager', map_manager map_uid.sub = property_name
super uid rm_uid =
noOperation: true
type: "AddName" alt: map_uid
rm = new ReplaceManager event_properties, event_this, rm_uid # this operation shall not be saved in the HB
applyDelete: ()-> @map[property_name] = rm
super() rm.setParent @, property_name
rm.execute()
cleanup: ()-> @map[property_name]
super()
#
# If map_manager doesn't have the property name, then add it.
# The ReplaceManager that is being written on the property is unique
# in such a way that if AddName is executed (from another peer) it will
# always have the same result (ReplaceManager, and its beginning and end are the same)
#
execute: ()->
if not @validateSavedOperations()
return false
else
# helper for cloning an object
clone = (o)->
p = {}
for name,value of o
p[name] = value
p
uid_r = clone(@map_manager.getUid())
uid_r.doSync = false
uid_r.op_number = "_#{uid_r.op_number}_RM_#{@name}"
if not HB.getOperation(uid_r)?
uid_beg = clone(uid_r)
uid_beg.op_number = "#{uid_r.op_number}_beginning"
uid_end = clone(uid_r)
uid_end.op_number = "#{uid_r.op_number}_end"
beg = (new types.Delimiter uid_beg, undefined, uid_end).execute()
end = (new types.Delimiter uid_end, beg, undefined).execute()
event_properties =
name: @name
event_this = @map_manager
@map_manager.map[@name] = new ReplaceManager event_properties, event_this, 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
#
# Encode this operation in such a way that it can be parsed by remote peers.
#
_encode: ()->
{
'type' : "AddName"
'uid' : @getUid()
'map_manager' : @map_manager.getUid()
'name' : @name
}
parser['AddName'] = (json)->
{
'map_manager' : map_manager
'uid' : uid
'name' : name
} = json
new AddName uid, map_manager, name
# #
# @nodoc # @nodoc
@ -151,17 +86,13 @@ 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: (uid, beginning, end, prev, next, origin)-> constructor: (uid)->
if beginning? and end? @beginning = new types.Delimiter undefined, undefined
@saveOperation 'beginning', beginning @end = new types.Delimiter @beginning, undefined
@saveOperation 'end', end @beginning.next_cl = @end
else @beginning.execute()
@beginning = new types.Delimiter undefined, undefined, undefined @end.execute()
@end = new types.Delimiter undefined, @beginning, undefined super uid
@beginning.next_cl = @end
@beginning.execute()
@end.execute()
super uid, prev, next, origin
type: "ListManager" type: "ListManager"
@ -236,10 +167,10 @@ 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_properties, @event_this, uid, beginning, end, prev, next, origin)-> constructor: (@event_properties, @event_this, uid, beginning, end)->
if not @event_properties['object']? if not @event_properties['object']?
@event_properties['object'] = @event_this @event_properties['object'] = @event_this
super uid, beginning, end, prev, next, origin super uid, beginning, end
type: "ReplaceManager" type: "ReplaceManager"
@ -248,10 +179,6 @@ module.exports = (HB)->
while o? while o?
o.applyDelete() o.applyDelete()
o = o.next_cl 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() super()
cleanup: ()-> cleanup: ()->
@ -312,25 +239,8 @@ module.exports = (HB)->
'beginning' : @beginning.getUid() 'beginning' : @beginning.getUid()
'end' : @end.getUid() 'end' : @end.getUid()
} }
if @prev_cl? and @next_cl?
json['prev'] = @prev_cl.getUid()
json['next'] = @next_cl.getUid()
if @origin? # TODO: do this everywhere: and @origin isnt @prev_cl
json["origin"] = @origin().getUid()
json json
parser["ReplaceManager"] = (json)->
{
'uid' : uid
'prev': prev
'next': next
'origin' : origin
'beginning' : beginning
'end' : end
} = json
new ReplaceManager uid, beginning, end, prev, next, origin
# #
# @nodoc # @nodoc
# The ReplaceManager manages Replaceables. # The ReplaceManager manages Replaceables.
@ -346,9 +256,7 @@ module.exports = (HB)->
constructor: (content, parent, uid, prev, next, origin, is_deleted)-> constructor: (content, parent, uid, prev, next, origin, is_deleted)->
@saveOperation 'content', content @saveOperation 'content', content
@saveOperation 'parent', parent @saveOperation 'parent', parent
if not (prev? and next?) super uid, prev, next, origin # Parent is already saved by Replaceable
throw new Error "You must define prev, and next for Replaceable-types!"
super uid, prev, next, origin
@is_deleted = is_deleted @is_deleted = is_deleted
type: "Replaceable" type: "Replaceable"
@ -415,20 +323,19 @@ module.exports = (HB)->
{ {
'type': "Replaceable" 'type': "Replaceable"
'content': @content?.getUid() 'content': @content?.getUid()
'replace_manager' : @parent.getUid() 'parent' : @parent.getUid()
'prev': @prev_cl.getUid() 'prev': @prev_cl.getUid()
'next': @next_cl.getUid() 'next': @next_cl.getUid()
'origin' : @origin.getUid()
'uid' : @getUid() 'uid' : @getUid()
'is_deleted': @is_deleted 'is_deleted': @is_deleted
} }
if @origin? and @origin isnt @prev_cl
json["origin"] = @origin.getUid()
json json
parser["Replaceable"] = (json)-> parser["Replaceable"] = (json)->
{ {
'content' : content 'content' : content
'replace_manager' : parent 'parent' : parent
'uid' : uid 'uid' : uid
'prev': prev 'prev': prev
'next': next 'next': next

View File

@ -22,14 +22,12 @@ module.exports = (HB)->
# @param {String} content The content of this Insert-type Operation. Usually you restrict the length of content to size 1 # @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. # @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, parent)->
if content?.uid?.creator if content?.uid?.creator
@saveOperation 'content', content @saveOperation 'content', content
else else
@content = content @content = content
if not (prev? and next?) super uid, prev, next, origin, parent
throw new Error "You must define prev, and next for TextInsert-types!"
super uid, prev, next, origin
type: "TextInsert" type: "TextInsert"
@ -78,13 +76,14 @@ module.exports = (HB)->
'uid' : @getUid() 'uid' : @getUid()
'prev': @prev_cl.getUid() 'prev': @prev_cl.getUid()
'next': @next_cl.getUid() 'next': @next_cl.getUid()
'origin': @origin.getUid()
'parent': @parent.getUid()
} }
if @content?.getUid? if @content?.getUid?
json['content'] = @content.getUid() json['content'] = @content.getUid()
else else
json['content'] = @content json['content'] = @content
if @origin isnt @prev_cl
json["origin"] = @origin.getUid()
json json
parser["TextInsert"] = (json)-> parser["TextInsert"] = (json)->
@ -94,8 +93,9 @@ module.exports = (HB)->
'prev': prev 'prev': prev
'next': next 'next': next
'origin' : origin 'origin' : origin
'parent' : parent
} = json } = json
new TextInsert content, uid, prev, next, origin new TextInsert content, uid, prev, next, origin, parent
# #
# Handles a WordType-like data structures with support for insertText/deleteText at a word-position. # Handles a WordType-like data structures with support for insertText/deleteText at a word-position.
@ -107,9 +107,9 @@ module.exports = (HB)->
# @private # @private
# @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.
# #
constructor: (uid, beginning, end, prev, next, origin)-> constructor: (uid)->
@textfields = [] @textfields = []
super uid, beginning, end, prev, next, origin super uid
# #
# Identifies this class. # Identifies this class.
@ -331,27 +331,14 @@ module.exports = (HB)->
json = { json = {
'type': "WordType" 'type': "WordType"
'uid' : @getUid() 'uid' : @getUid()
'beginning' : @beginning.getUid()
'end' : @end.getUid()
} }
if @prev_cl?
json['prev'] = @prev_cl.getUid()
if @next_cl?
json['next'] = @next_cl.getUid()
if @origin? # and @origin isnt @prev_cl
json["origin"] = @origin().getUid()
json json
parser['WordType'] = (json)-> parser['WordType'] = (json)->
{ {
'uid' : uid 'uid' : uid
'beginning' : beginning
'end' : end
'prev': prev
'next': next
'origin' : origin
} = json } = json
new WordType uid, beginning, end, prev, next, origin new WordType uid
types['TextInsert'] = TextInsert types['TextInsert'] = TextInsert
types['TextDelete'] = TextDelete types['TextDelete'] = TextDelete

View File

@ -97,22 +97,6 @@ describe "JsonFramework", ->
expect(change2).to.equal 8 expect(change2).to.equal 8
### ###
it "has a JsonTypeWrapper", ->
y = this.yTest.getSomeUser()
y.val('x',"dtrn", 'immutable')
y.val('set',{x:"x"}, 'immutable')
w = y.value
w.x
w.set = {y:""}
w.x
w.set
w.set.x
expect(w.x).to.equal("dtrn")
expect(w.set.x).to.equal("x")
y.value.x = {q:4}
expect(y.value.x.q).to.equal(4)
it "has a working test suite", -> it "has a working test suite", ->
@yTest.compareAll() @yTest.compareAll()
@ -131,17 +115,17 @@ describe "JsonFramework", ->
expect(test.getContent(0)).to.deep.equal(@yTest.getContent(1)) expect(test.getContent(0)).to.deep.equal(@yTest.getContent(1))
it "can handle creaton of complex json (1)", -> it "can handle creaton of complex json (1)", ->
@yTest.users[0].val('a', 'q') @yTest.users[0].val('a', 'q', "mutable")
@yTest.users[2].val('a', 't') @yTest.users[1].val('a', 't', "mutable")
@yTest.compareAll() @yTest.compareAll()
q = @yTest.users[1].val('a') q = @yTest.users[2].val('a')
q.insertText(0,'A') q.insertText(0,'A')
@yTest.compareAll() @yTest.compareAll()
expect(@yTest.getSomeUser().value.a.val()).to.equal("At") expect(@yTest.getSomeUser().value.a.val()).to.equal("At")
it "can handle creaton of complex json (2)", -> it "can handle creaton of complex json (2)", ->
@yTest.getSomeUser().val('x', {'a':'b'}) @yTest.getSomeUser().val('x', {'a':'b'})
@yTest.getSomeUser().val('a', {'a':{q:"dtrndtrtdrntdrnrtdnrtdnrtdnrtdnrdnrdt"}}) @yTest.getSomeUser().val('a', {'a':{q:"dtrndtrtdrntdrnrtdnrtdnrtdnrtdnrdnrdt"}}, "mutable")
@yTest.getSomeUser().val('b', {'a':{}}) @yTest.getSomeUser().val('b', {'a':{}})
@yTest.getSomeUser().val('c', {'a':'c'}) @yTest.getSomeUser().val('c', {'a':'c'})
@yTest.getSomeUser().val('c', {'a':'b'}) @yTest.getSomeUser().val('c', {'a':'b'})
@ -176,7 +160,7 @@ describe "JsonFramework", ->
expect(change.name).to.equal("newStuff") expect(change.name).to.equal("newStuff")
last_task = "observer1" last_task = "observer1"
u.observe observer1 u.observe observer1
u.val("newStuff","someStuff") u.val("newStuff","someStuff","mutable")
expect(last_task).to.equal("observer1") expect(last_task).to.equal("observer1")
u.unobserve observer1 u.unobserve observer1
@ -196,7 +180,7 @@ describe "JsonFramework", ->
u.unobserve observer2 u.unobserve observer2
it "Observers work on JSON Types (update type observers, local and foreign)", -> it "Observers work on JSON Types (update type observers, local and foreign)", ->
u = @yTest.users[0].val("newStuff","oldStuff").val("moreStuff","moreOldStuff") u = @yTest.users[0].val("newStuff","oldStuff","mutable").val("moreStuff","moreOldStuff","mutable")
@yTest.flushAll() @yTest.flushAll()
last_task = null last_task = null
observer1 = (changes)-> observer1 = (changes)->
@ -231,7 +215,7 @@ describe "JsonFramework", ->
it "Observers work on JSON Types (delete type observers, local and foreign)", -> it "Observers work on JSON Types (delete type observers, local and foreign)", ->
u = @yTest.users[0].val("newStuff","oldStuff").val("moreStuff","moreOldStuff") u = @yTest.users[0].val("newStuff","oldStuff","mutable").val("moreStuff","moreOldStuff","mutable")
@yTest.flushAll() @yTest.flushAll()
last_task = null last_task = null
observer1 = (changes)-> observer1 = (changes)->

View File

@ -12,9 +12,9 @@ Connector = require "../bower_components/connector/lib/test-connector/test-conne
module.exports = class Test module.exports = class Test
constructor: (@name_suffix = "")-> constructor: (@name_suffix = "")->
@number_of_test_cases_multiplier = 1 @number_of_test_cases_multiplier = 1
@repeat_this = 1 * @number_of_test_cases_multiplier @repeat_this = 503 * @number_of_test_cases_multiplier
@doSomething_amount = 50 * @number_of_test_cases_multiplier @doSomething_amount = 5 * @number_of_test_cases_multiplier
@number_of_engines = 4 + @number_of_test_cases_multiplier - 1 @number_of_engines = 3 + @number_of_test_cases_multiplier - 1
@time = 0 # denotes to the time when run was started @time = 0 # denotes to the time when run was started
@ops = 0 # number of operations (used with @time) @ops = 0 # number of operations (used with @time)

View File

@ -17,9 +17,7 @@ class TextTest extends Test
makeNewUser: (userId)-> makeNewUser: (userId)->
conn = new Connector userId conn = new Connector userId
y = new Yatta conn new Yatta conn
y.val("TextTest","","mutable")
y
getRandomRoot: (user_num)-> getRandomRoot: (user_num)->
@users[user_num].val("TextTest") @users[user_num].val("TextTest")
@ -35,6 +33,8 @@ describe "TextFramework", ->
test_user_connector = new Connector 'test_user' test_user_connector = new Connector 'test_user'
@test_user = @yTest.makeNewUser 'test_user', test_user_connector @test_user = @yTest.makeNewUser 'test_user', test_user_connector
test_user_connector.join @users[0].connector test_user_connector.join @users[0].connector
@users[0].val("TextTest","","mutable")
@yTest.flushAll()
done() done()
it "simple multi-char insert", -> it "simple multi-char insert", ->
@ -46,7 +46,7 @@ describe "TextFramework", ->
expect(u.val()).to.equal("abcxyz") expect(u.val()).to.equal("abcxyz")
it "Observers work on shared Text (insert type observers, local and foreign)", -> it "Observers work on shared Text (insert type observers, local and foreign)", ->
u = @yTest.users[0].val("TextTest","my awesome Text").val("TextTest") u = @yTest.users[0].val("TextTest","my awesome Text","mutable").val("TextTest")
@yTest.flushAll() @yTest.flushAll()
last_task = null last_task = null
observer1 = (changes)-> observer1 = (changes)->
@ -80,7 +80,7 @@ describe "TextFramework", ->
u.unobserve observer2 u.unobserve observer2
it "Observers work on shared Text (delete type observers, local and foreign)", -> it "Observers work on shared Text (delete type observers, local and foreign)", ->
u = @yTest.users[0].val("TextTest","my awesome Text").val("TextTest") u = @yTest.users[0].val("TextTest","my awesome Text","mutable").val("TextTest")
@yTest.flushAll() @yTest.flushAll()
last_task = null last_task = null
observer1 = (changes)-> observer1 = (changes)->

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long