API reference

There is an automatically generated API reference available.

Tests

To validate that jinfinote is working correctly, we have a test suite which is provided by the developers of libinfinity.

Quick introduction

Please note: You should have at least a basic understanding of how the adOPTed algorithm works. The Infinote site has an overview of how it is used. Yes, we're working on an better introduction.

jinfinote is split into four JavaScript files:

You need to include all of these source files on a page that uses jinfinote. Alternatively, you can concatenate them into a single JavaScript file for easier handling.

Example script

This is a commented version of Test 01 in the Infinote test suite. You can also go to the HTML file of this example.

We create an initial buffer that consists of one segment by user 0. This is the starting point.

var initial_segment = new Segment(0, "abcdefghi");
var initial_buffer = new Buffer([initial_segment]);

document.write(initial_buffer + "\n"); // this outputs "abcdefghi"

Now, we create a State object from this so we can manipulate this buffer.

var state = new State(initial_buffer);

At this moment, the state object is initialized with "abcdefghi" and an empty state vector (because no requests have been executed yet).

Now let's suppose user 2 wants to insert some text at position 2. We first create a buffer for the inserted data.

var r1_segment = new Segment(2, "ac"); // 2 is the segment author, "ac" is the data
var r1_buffer = new Buffer([r1_segment]);

Now we create an operation for this request. Since the user wants to insert text, the Operations.Insert class is what we need.

var r1_operation = new Operations.Insert(2, r1_buffer); // 2 is the insert offset

The operation was issued when the document was still at its initial state (i.e. when no other users had issued requests). Therefore, we create an empty state vector.

var r1_vector = new Vector();

Finally, we wrap this into a DoRequest object.

var r1 = new DoRequest(2, r1_vector, r1_operation);

We now ask the state object to execute this request for us.

state.execute(r1);

At this point, the request has been executed and the changes have been applied to the document.

document.write(state.buffer + "\n"); // this outputs "abaccdefghi"

If we look at the state vector, we can see that it has increased:

document.write(state.vector + "\n"); // this outputs 2:1

A state vector of 2:1 means that user 2 has issued 1 request.

Now we get a request from user 3, who wants to insert text at position 3.

var r2_segment = new Segment(3, "bc");
var r2_buffer = new Buffer([r2_segment]);
var r2_operation = new Operations.Insert(3, r2_buffer);

There's a problem, however: user 3 too made his request at the initial state of the document - he didn't know yet that user 2 had already inserted text at position 2. This state vector is therefore empty too.

var r2_vector = new Vector();
var r2 = new DoRequest(3, r2_vector, r2_operation);

Despite these requests conflict with each other, we pass it to the state object to let it handle that conflict.

var executed_r2 = state.execute(r2);

The state object takes care that the request is changed so that it fits with the current state of the document. In this case, it is automatically transformed to the state vector 2:1 and then applied. Since the state.execute method returns the request that was executed, we can see which one that was:

document.write(executed_r2 + "\n");
// this outputs DoRequest(3, 2:1, Insert(5, bc))

As we can see, the state object automatically translated the request to the required state - in this case 2:1. We can see that the insert offset has changed from 3 to 5, because user 2 has inserted 2 characters before the location into which user 3 wanted to insert his text.

document.write(state.buffer + "\n"); // outputs "abaccbcdefghi"

Now there's another user who, at the same time as users 2 and 3 inserted text, removed 5 characters from the document.

var r3_operation = new Operations.Delete(0, 5);
var r3_vector = new Vector(); // again this request was issued in the beginning
var r3 = new DoRequest(4, r3_vector, r3_operation);

Obviously, if we just removed 5 characters from the beginning of our current text "abaccbcdefghi", it would lead to a bogus result - user 4 didn't even know that users 2 and 3 had inserted text at the same time as he did, so he can't remove that text.

Again, the state object resolves that conflict for us:

var executed_r3 = state.execute(r3);
document.write(executed_r3 + "\n");
// this outputs DoRequest(4, 2:1;3:1, Split(Split(Delete(0, 2), Delete(4, 1)), Delete(7, 2)))

Now this deserves a bit more of an explanation. As we can see, this request was transformed to 2:1;3:1 - i.e. it was changed so that it correctly accounts for the changes made by users 2 and 3. The state object translated our request into a group of smaller delete operations which remove the parts that were present in the original document.

document.write(state.buffer + "\n");
// this outputs "acbcfghi"

Let's go through this again.

// In the beginning, we had:
//     abcdefghi
// Then user 2 inserted some text (capitalized for clarity:)
//     abACcdefghi
// Then user 3 inserted some text:
//     abACcBCdefghi
// Then user 4 wanted to delete "abcdef" from the beginning.
//     abACcBCdefghi
//     ^^  ^  ^^
// The delete operation is changed so it removes these parts instead.
// The result is:
//     ACBCfghi

We can also try something different: What if we executed the delete request by user 4 before the insert operations of users 2 and 3?

First, we start with a new state object:

var s_new = new State(initial_buffer);

Then we execute request 3:

s_new.execute(r3);
document.write(s_new.buffer + "\n"); // this outputs "fghi"

Request 2:

s_new.execute(r2);
document.write(s_new.buffer + "\n"); // this outputs "bcfghi"

Request 1:

s_new.execute(r1);
document.write(s_new.buffer + "\n"); // this outputs "acbcfghi"

Here, you can see an important characteristic of the adOPTed algorithm: As long as requests do not depend on each other, you can execute them in an arbitrary order while still getting the same result.