switched to *standard* coding style
This commit is contained in:
@@ -1,86 +1,85 @@
|
||||
/* global EventHandler, Y, CustomType, Struct */
|
||||
|
||||
|
||||
(function(){
|
||||
|
||||
;(function () {
|
||||
class YArray {
|
||||
constructor (os, _model, idArray, valArray) {
|
||||
this.os = os;
|
||||
this._model = _model;
|
||||
this.os = os
|
||||
this._model = _model
|
||||
// Array of all the operation id's
|
||||
this.idArray = idArray;
|
||||
this.idArray = idArray
|
||||
// Array of all the values
|
||||
this.valArray = valArray;
|
||||
this.eventHandler = new EventHandler( ops =>{
|
||||
var userEvents = [];
|
||||
this.valArray = valArray
|
||||
this.eventHandler = new EventHandler(ops => {
|
||||
var userEvents = []
|
||||
for (var i in ops) {
|
||||
var op = ops[i];
|
||||
if (op.struct === "Insert") {
|
||||
let pos;
|
||||
var op = ops[i]
|
||||
if (op.struct === 'Insert') {
|
||||
let pos
|
||||
// we check op.left only!,
|
||||
// because op.right might not be defined when this is called
|
||||
if (op.left === null) {
|
||||
pos = 0;
|
||||
pos = 0
|
||||
} else {
|
||||
var sid = JSON.stringify(op.left);
|
||||
pos = this.idArray.indexOf(sid) + 1;
|
||||
var sid = JSON.stringify(op.left)
|
||||
pos = this.idArray.indexOf(sid) + 1
|
||||
if (pos <= 0) {
|
||||
throw new Error("Unexpected operation!");
|
||||
throw new Error('Unexpected operation!')
|
||||
}
|
||||
}
|
||||
this.idArray.splice(pos, 0, JSON.stringify(op.id));
|
||||
this.valArray.splice(pos, 0, op.content);
|
||||
this.idArray.splice(pos, 0, JSON.stringify(op.id))
|
||||
this.valArray.splice(pos, 0, op.content)
|
||||
userEvents.push({
|
||||
type: "insert",
|
||||
type: 'insert',
|
||||
object: this,
|
||||
index: pos,
|
||||
length: 1
|
||||
});
|
||||
} else if (op.struct === "Delete") {
|
||||
let pos = this.idArray.indexOf(JSON.stringify(op.target));
|
||||
this.idArray.splice(pos, 1);
|
||||
this.valArray.splice(pos, 1);
|
||||
})
|
||||
} else if (op.struct === 'Delete') {
|
||||
let pos = this.idArray.indexOf(JSON.stringify(op.target))
|
||||
this.idArray.splice(pos, 1)
|
||||
this.valArray.splice(pos, 1)
|
||||
userEvents.push({
|
||||
type: "delete",
|
||||
type: 'delete',
|
||||
object: this,
|
||||
index: pos,
|
||||
length: 1
|
||||
});
|
||||
})
|
||||
} else {
|
||||
throw new Error("Unexpected struct!");
|
||||
throw new Error('Unexpected struct!')
|
||||
}
|
||||
}
|
||||
this.eventHandler.callUserEventListeners(userEvents);
|
||||
});
|
||||
this.eventHandler.callUserEventListeners(userEvents)
|
||||
})
|
||||
}
|
||||
get length () {
|
||||
return this.idArray.length;
|
||||
return this.idArray.length
|
||||
}
|
||||
get (pos) {
|
||||
if (pos == null || typeof pos !== "number") {
|
||||
throw new Error("pos must be a number!");
|
||||
if (pos == null || typeof pos !== 'number') {
|
||||
throw new Error('pos must be a number!')
|
||||
}
|
||||
return this.valArray[pos];
|
||||
return this.valArray[pos]
|
||||
}
|
||||
toArray() {
|
||||
return this.valArray.slice();
|
||||
toArray () {
|
||||
return this.valArray.slice()
|
||||
}
|
||||
insert (pos, contents) {
|
||||
if (typeof pos !== "number") {
|
||||
throw new Error("pos must be a number!");
|
||||
if (typeof pos !== 'number') {
|
||||
throw new Error('pos must be a number!')
|
||||
}
|
||||
if (!(contents instanceof Array)) {
|
||||
throw new Error("contents must be an Array of objects!");
|
||||
throw new Error('contents must be an Array of objects!')
|
||||
}
|
||||
if (contents.length === 0) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
if (pos > this.idArray.length || pos < 0) {
|
||||
throw new Error("This position exceeds the range of the array!");
|
||||
throw new Error('This position exceeds the range of the array!')
|
||||
}
|
||||
var mostLeft = pos === 0 ? null : JSON.parse(this.idArray[pos - 1]);
|
||||
var mostLeft = pos === 0 ? null : JSON.parse(this.idArray[pos - 1])
|
||||
|
||||
var ops = [];
|
||||
var prevId = mostLeft;
|
||||
var ops = []
|
||||
var prevId = mostLeft
|
||||
for (var i = 0; i < contents.length; i++) {
|
||||
var op = {
|
||||
left: prevId,
|
||||
@@ -90,97 +89,97 @@
|
||||
// at the time of creating this operation, and is therefore not defined in idArray
|
||||
parent: this._model,
|
||||
content: contents[i],
|
||||
struct: "Insert",
|
||||
struct: 'Insert',
|
||||
id: this.os.getNextOpId()
|
||||
};
|
||||
ops.push(op);
|
||||
prevId = op.id;
|
||||
}
|
||||
ops.push(op)
|
||||
prevId = op.id
|
||||
}
|
||||
var eventHandler = this.eventHandler;
|
||||
eventHandler.awaitAndPrematurelyCall(ops);
|
||||
this.os.requestTransaction(function*(){
|
||||
var eventHandler = this.eventHandler
|
||||
eventHandler.awaitAndPrematurelyCall(ops)
|
||||
this.os.requestTransaction(function *() {
|
||||
// now we can set the right reference.
|
||||
var mostRight;
|
||||
var mostRight
|
||||
if (mostLeft != null) {
|
||||
mostRight = (yield* this.getOperation(mostLeft)).right;
|
||||
mostRight = (yield* this.getOperation(mostLeft)).right
|
||||
} else {
|
||||
mostRight = (yield* this.getOperation(ops[0].parent)).start;
|
||||
mostRight = (yield* this.getOperation(ops[0].parent)).start
|
||||
}
|
||||
for (var j in ops) {
|
||||
ops[j].right = mostRight;
|
||||
ops[j].right = mostRight
|
||||
}
|
||||
yield* this.applyCreatedOperations(ops);
|
||||
eventHandler.awaitedLastInserts(ops.length);
|
||||
});
|
||||
yield* this.applyCreatedOperations(ops)
|
||||
eventHandler.awaitedLastInserts(ops.length)
|
||||
})
|
||||
}
|
||||
delete (pos, length = 1) {
|
||||
if (typeof length !== "number") {
|
||||
throw new Error("pos must be a number!");
|
||||
if (typeof length !== 'number') {
|
||||
throw new Error('pos must be a number!')
|
||||
}
|
||||
if (typeof pos !== "number") {
|
||||
throw new Error("pos must be a number!");
|
||||
if (typeof pos !== 'number') {
|
||||
throw new Error('pos must be a number!')
|
||||
}
|
||||
if (pos + length > this.idArray.length || pos < 0 || length < 0) {
|
||||
throw new Error("The deletion range exceeds the range of the array!");
|
||||
throw new Error('The deletion range exceeds the range of the array!')
|
||||
}
|
||||
if (length === 0) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
var eventHandler = this.eventHandler;
|
||||
var newLeft = pos > 0 ? JSON.parse(this.idArray[pos - 1]) : null;
|
||||
var dels = [];
|
||||
var eventHandler = this.eventHandler
|
||||
var newLeft = pos > 0 ? JSON.parse(this.idArray[pos - 1]) : null
|
||||
var dels = []
|
||||
for (var i = 0; i < length; i++) {
|
||||
dels.push({
|
||||
target: JSON.parse(this.idArray[pos + i]),
|
||||
struct: "Delete"
|
||||
});
|
||||
struct: 'Delete'
|
||||
})
|
||||
}
|
||||
eventHandler.awaitAndPrematurelyCall(dels);
|
||||
this.os.requestTransaction(function*(){
|
||||
yield* this.applyCreatedOperations(dels);
|
||||
eventHandler.awaitedLastDeletes(dels.length, newLeft);
|
||||
});
|
||||
eventHandler.awaitAndPrematurelyCall(dels)
|
||||
this.os.requestTransaction(function *() {
|
||||
yield* this.applyCreatedOperations(dels)
|
||||
eventHandler.awaitedLastDeletes(dels.length, newLeft)
|
||||
})
|
||||
}
|
||||
observe (f) {
|
||||
this.eventHandler.addUserEventListener(f);
|
||||
this.eventHandler.addUserEventListener(f)
|
||||
}
|
||||
*_changed (transaction, op) {
|
||||
if (op.struct === "Insert") {
|
||||
var l = op.left;
|
||||
var left;
|
||||
* _changed (transaction, op) {
|
||||
if (op.struct === 'Insert') {
|
||||
var l = op.left
|
||||
var left
|
||||
while (l != null) {
|
||||
left = yield* transaction.getOperation(l);
|
||||
left = yield* transaction.getOperation(l)
|
||||
if (!left.deleted) {
|
||||
break;
|
||||
break
|
||||
}
|
||||
l = left.left;
|
||||
l = left.left
|
||||
}
|
||||
op.left = l;
|
||||
op.left = l
|
||||
}
|
||||
this.eventHandler.receivedOp(op);
|
||||
this.eventHandler.receivedOp(op)
|
||||
}
|
||||
}
|
||||
|
||||
Y.Array = new CustomType({
|
||||
class: YArray,
|
||||
createType: function* YArrayCreator () {
|
||||
createType: function * YArrayCreator () {
|
||||
var model = {
|
||||
start: null,
|
||||
end: null,
|
||||
struct: "List",
|
||||
type: "Array",
|
||||
struct: 'List',
|
||||
type: 'Array',
|
||||
id: this.store.getNextOpId()
|
||||
};
|
||||
yield* this.applyCreatedOperations([model]);
|
||||
return yield* this.createType(model);
|
||||
}
|
||||
yield* this.applyCreatedOperations([model])
|
||||
return yield* this.createType(model)
|
||||
},
|
||||
initType: function* YArrayInitializer(os, model){
|
||||
var valArray = [];
|
||||
var idArray = yield* Y.Struct.List.map.call(this, model, function(c){
|
||||
valArray.push(c.content);
|
||||
return JSON.stringify(c.id);
|
||||
});
|
||||
return new YArray(os, model.id, idArray, valArray);
|
||||
initType: function * YArrayInitializer (os, model) {
|
||||
var valArray = []
|
||||
var idArray = yield* Struct.List.map.call(this, model, function (c) {
|
||||
valArray.push(c.content)
|
||||
return JSON.stringify(c.id)
|
||||
})
|
||||
return new YArray(os, model.id, idArray, valArray)
|
||||
}
|
||||
});
|
||||
})();
|
||||
})
|
||||
})()
|
||||
|
||||
@@ -1,143 +1,145 @@
|
||||
/* @flow */
|
||||
/*eslint-env browser,jasmine */
|
||||
/* global createUsers, wait, Y, compareAllUsers, getRandomNumber, applyRandomTransactions */
|
||||
/* eslint-env browser,jasmine */
|
||||
|
||||
var numberOfYArrayTests = 80;
|
||||
var numberOfYArrayTests = 80
|
||||
|
||||
describe("Array Type", function(){
|
||||
var y1, y2, y3, flushAll;
|
||||
describe('Array Type', function () {
|
||||
var y1, y2, y3, flushAll
|
||||
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 50000;
|
||||
beforeEach(async function(done){
|
||||
await createUsers(this, 5);
|
||||
y1 = this.users[0].root;
|
||||
y2 = this.users[1].root;
|
||||
y3 = this.users[2].root;
|
||||
flushAll = this.users[0].connector.flushAll;
|
||||
done();
|
||||
});
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 50000
|
||||
beforeEach(async function (done) {
|
||||
await createUsers(this, 5)
|
||||
y1 = this.users[0].root
|
||||
y2 = this.users[1].root
|
||||
y3 = this.users[2].root
|
||||
flushAll = this.users[0].connector.flushAll
|
||||
done()
|
||||
})
|
||||
afterEach(async function(done) {
|
||||
await compareAllUsers(this.users);
|
||||
done();
|
||||
});
|
||||
await compareAllUsers(this.users)
|
||||
done()
|
||||
})
|
||||
|
||||
describe("Basic tests", function(){
|
||||
it("insert three elements, try re-get property", async function(done){
|
||||
var array = await y1.set("Array", Y.Array);
|
||||
array.insert(0, [1, 2, 3]);
|
||||
array = await y1.get("Array"); // re-get property
|
||||
expect(array.toArray()).toEqual([1, 2, 3]);
|
||||
done();
|
||||
});
|
||||
it("Basic insert in array (handle three conflicts)", async function(done){
|
||||
var l1, l2, l3;
|
||||
await y1.set("Array", Y.Array);
|
||||
await flushAll();
|
||||
(l1 = await y1.get("Array")).insert(0, [0]);
|
||||
(l2 = await y2.get("Array")).insert(0, [1]);
|
||||
(l3 = await y3.get("Array")).insert(0, [2]);
|
||||
await flushAll();
|
||||
expect(l1.toArray()).toEqual(l2.toArray());
|
||||
expect(l2.toArray()).toEqual(l3.toArray());
|
||||
done();
|
||||
});
|
||||
it("Basic insert&delete in array (handle three conflicts)", async function(done){
|
||||
var l1, l2, l3;
|
||||
l1 = await y1.set("Array", Y.Array);
|
||||
l1.insert(0, ["x", "y", "z"]);
|
||||
await flushAll();
|
||||
l1.insert(1, [0]);
|
||||
l2 = await y2.get("Array");
|
||||
l2.delete(0);
|
||||
l2.delete(1);
|
||||
l3 = await y3.get("Array");
|
||||
l3.insert(1, [2]);
|
||||
await flushAll();
|
||||
expect(l1.toArray()).toEqual(l2.toArray());
|
||||
expect(l2.toArray()).toEqual(l3.toArray());
|
||||
expect(l2.toArray()).toEqual([0, 2, "y"]);
|
||||
done();
|
||||
});
|
||||
it("Basic insert. Then delete the whole array", async function(done){
|
||||
var l1, l2, l3;
|
||||
l1 = await y1.set("Array", Y.Array);
|
||||
l1.insert(0, ["x", "y", "z"]);
|
||||
await flushAll();
|
||||
l1.delete(0, 3);
|
||||
l2 = await y2.get("Array");
|
||||
l3 = await y3.get("Array");
|
||||
await flushAll();
|
||||
expect(l1.toArray()).toEqual(l2.toArray());
|
||||
expect(l2.toArray()).toEqual(l3.toArray());
|
||||
expect(l2.toArray()).toEqual([]);
|
||||
done();
|
||||
});
|
||||
it("throw insert & delete events", async function(done){
|
||||
var array = await this.users[0].root.set("array", Y.Array);
|
||||
var event;
|
||||
array.observe(function(e){
|
||||
event = e;
|
||||
});
|
||||
array.insert(0, [0]);
|
||||
describe('Basic tests', function () {
|
||||
it('insert three elements, try re-get property', async function (done) {
|
||||
var array = await y1.set('Array', Y.Array)
|
||||
array.insert(0, [1, 2, 3])
|
||||
array = await y1.get('Array') // re-get property
|
||||
expect(array.toArray()).toEqual([1, 2, 3])
|
||||
done()
|
||||
})
|
||||
it('Basic insert in array (handle three conflicts)', async function (done) {
|
||||
await y1.set('Array', Y.Array)
|
||||
await flushAll()
|
||||
var l1 = await y1.get('Array')
|
||||
l1.insert(0, [0])
|
||||
var l2 = await y2.get('Array')
|
||||
l2.insert(0, [1])
|
||||
var l3 = await y3.get('Array')
|
||||
l3.insert(0, [2])
|
||||
await flushAll()
|
||||
expect(l1.toArray()).toEqual(l2.toArray())
|
||||
expect(l2.toArray()).toEqual(l3.toArray())
|
||||
done()
|
||||
})
|
||||
it('Basic insert&delete in array (handle three conflicts)', async function (done) {
|
||||
var l1, l2, l3
|
||||
l1 = await y1.set('Array', Y.Array)
|
||||
l1.insert(0, ['x', 'y', 'z'])
|
||||
await flushAll()
|
||||
l1.insert(1, [0])
|
||||
l2 = await y2.get('Array')
|
||||
l2.delete(0)
|
||||
l2.delete(1)
|
||||
l3 = await y3.get('Array')
|
||||
l3.insert(1, [2])
|
||||
await flushAll()
|
||||
expect(l1.toArray()).toEqual(l2.toArray())
|
||||
expect(l2.toArray()).toEqual(l3.toArray())
|
||||
expect(l2.toArray()).toEqual([0, 2, 'y'])
|
||||
done()
|
||||
})
|
||||
it('Basic insert. Then delete the whole array', async function (done) {
|
||||
var l1, l2, l3
|
||||
l1 = await y1.set('Array', Y.Array)
|
||||
l1.insert(0, ['x', 'y', 'z'])
|
||||
await flushAll()
|
||||
l1.delete(0, 3)
|
||||
l2 = await y2.get('Array')
|
||||
l3 = await y3.get('Array')
|
||||
await flushAll()
|
||||
expect(l1.toArray()).toEqual(l2.toArray())
|
||||
expect(l2.toArray()).toEqual(l3.toArray())
|
||||
expect(l2.toArray()).toEqual([])
|
||||
done()
|
||||
})
|
||||
it('throw insert & delete events', async function (done) {
|
||||
var array = await this.users[0].root.set('array', Y.Array)
|
||||
var event
|
||||
array.observe(function (e) {
|
||||
event = e
|
||||
})
|
||||
array.insert(0, [0])
|
||||
expect(event).toEqual([{
|
||||
type: "insert",
|
||||
type: 'insert',
|
||||
object: array,
|
||||
index: 0,
|
||||
length: 1
|
||||
}]);
|
||||
array.delete(0);
|
||||
}])
|
||||
array.delete(0)
|
||||
expect(event).toEqual([{
|
||||
type: "delete",
|
||||
type: 'delete',
|
||||
object: array,
|
||||
index: 0,
|
||||
length: 1
|
||||
}]);
|
||||
await wait(50);
|
||||
done();
|
||||
});
|
||||
});
|
||||
describe(`${numberOfYArrayTests} Random tests`, function(){
|
||||
}])
|
||||
await wait(50)
|
||||
done()
|
||||
})
|
||||
})
|
||||
describe(`${numberOfYArrayTests} Random tests`, function () {
|
||||
var randomArrayTransactions = [
|
||||
function insert (array) {
|
||||
array.insert(getRandomNumber(array.toArray().length), [getRandomNumber()]);
|
||||
array.insert(getRandomNumber(array.toArray().length), [getRandomNumber()])
|
||||
},
|
||||
function _delete (array) {
|
||||
var length = array.toArray().length;
|
||||
var length = array.toArray().length
|
||||
if (length > 0) {
|
||||
array.delete(getRandomNumber(length - 1));
|
||||
array.delete(getRandomNumber(length - 1))
|
||||
}
|
||||
}
|
||||
];
|
||||
function compareArrayValues(arrays){
|
||||
var firstArray;
|
||||
]
|
||||
function compareArrayValues (arrays) {
|
||||
var firstArray
|
||||
for (var l of arrays) {
|
||||
var val = l.toArray();
|
||||
var val = l.toArray()
|
||||
if (firstArray == null) {
|
||||
firstArray = val;
|
||||
firstArray = val
|
||||
} else {
|
||||
expect(val).toEqual(firstArray);
|
||||
expect(val).toEqual(firstArray)
|
||||
}
|
||||
}
|
||||
}
|
||||
beforeEach(async function(done){
|
||||
await this.users[0].root.set("Array", Y.Array);
|
||||
await flushAll();
|
||||
beforeEach(async function (done) {
|
||||
await this.users[0].root.set('Array', Y.Array)
|
||||
await flushAll()
|
||||
|
||||
var promises = [];
|
||||
var promises = []
|
||||
for (var u = 0; u < this.users.length; u++) {
|
||||
promises.push(this.users[u].root.get("Array"));
|
||||
promises.push(this.users[u].root.get('Array'))
|
||||
}
|
||||
this.arrays = await Promise.all(promises);
|
||||
done();
|
||||
});
|
||||
it("arrays.length equals users.length", async function(done){
|
||||
expect(this.arrays.length).toEqual(this.users.length);
|
||||
done();
|
||||
});
|
||||
it(`succeed after ${numberOfYArrayTests} actions`, async function(done){
|
||||
await applyRandomTransactions(this.users, this.arrays, randomArrayTransactions, numberOfYArrayTests);
|
||||
await flushAll();
|
||||
await compareArrayValues(this.arrays);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
this.arrays = await Promise.all(promises)
|
||||
done()
|
||||
})
|
||||
it('arrays.length equals users.length', async function (done) { // eslint-disable-line
|
||||
expect(this.arrays.length).toEqual(this.users.length)
|
||||
done()
|
||||
})
|
||||
it(`succeed after ${numberOfYArrayTests} actions`, async function (done) {
|
||||
await applyRandomTransactions(this.users, this.arrays, randomArrayTransactions, numberOfYArrayTests)
|
||||
await flushAll()
|
||||
await compareArrayValues(this.arrays)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
260
src/Types/Map.js
260
src/Types/Map.js
@@ -1,76 +1,78 @@
|
||||
(function(){
|
||||
/* global EventHandler, Y, CustomType, copyObject, compareIds */
|
||||
|
||||
;(function () {
|
||||
class YMap {
|
||||
constructor (os, model) {
|
||||
this._model = model.id;
|
||||
this.os = os;
|
||||
this.map = copyObject(model.map);
|
||||
this.contents = {};
|
||||
this.opContents = {};
|
||||
this.eventHandler = new EventHandler( ops =>{
|
||||
var userEvents = [];
|
||||
this._model = model.id
|
||||
this.os = os
|
||||
this.map = copyObject(model.map)
|
||||
this.contents = {}
|
||||
this.opContents = {}
|
||||
this.eventHandler = new EventHandler(ops => {
|
||||
var userEvents = []
|
||||
for (var i in ops) {
|
||||
var op = ops[i];
|
||||
var oldValue;
|
||||
var op = ops[i]
|
||||
var oldValue
|
||||
// key is the name to use to access (op)content
|
||||
var key = op.struct === "Delete" ? op.key : op.parentSub;
|
||||
var key = op.struct === 'Delete' ? op.key : op.parentSub
|
||||
|
||||
// compute oldValue
|
||||
if (this.opContents[key] != null) {
|
||||
let prevType = this.opContents[key];
|
||||
oldValue = () => { //eslint-disable-line
|
||||
let def = Promise.defer();
|
||||
this.os.requestTransaction(function*(){//eslint-disable-line
|
||||
def.resolve(yield* this.getType(prevType));
|
||||
});
|
||||
return def.promise;
|
||||
};
|
||||
let prevType = this.opContents[key]
|
||||
oldValue = () => {// eslint-disable-line
|
||||
let def = Promise.defer()
|
||||
this.os.requestTransaction(function *() {// eslint-disable-line
|
||||
def.resolve(yield* this.getType(prevType))
|
||||
})
|
||||
return def.promise
|
||||
}
|
||||
} else {
|
||||
oldValue = this.contents[key];
|
||||
oldValue = this.contents[key]
|
||||
}
|
||||
// compute op event
|
||||
if (op.struct === "Insert"){
|
||||
if (op.struct === 'Insert') {
|
||||
if (op.left === null) {
|
||||
if (op.opContent != null) {
|
||||
delete this.contents[key];
|
||||
this.opContents[key] = op.opContent;
|
||||
delete this.contents[key]
|
||||
this.opContents[key] = op.opContent
|
||||
} else {
|
||||
delete this.opContents[key];
|
||||
this.contents[key] = op.content;
|
||||
delete this.opContents[key]
|
||||
this.contents[key] = op.content
|
||||
}
|
||||
this.map[key] = op.id;
|
||||
this.map[key] = op.id
|
||||
var insertEvent = {
|
||||
name: key,
|
||||
object: this
|
||||
};
|
||||
if (oldValue === undefined) {
|
||||
insertEvent.type = "add";
|
||||
} else {
|
||||
insertEvent.type = "update";
|
||||
insertEvent.oldValue = oldValue;
|
||||
}
|
||||
userEvents.push(insertEvent);
|
||||
if (oldValue === undefined) {
|
||||
insertEvent.type = 'add'
|
||||
} else {
|
||||
insertEvent.type = 'update'
|
||||
insertEvent.oldValue = oldValue
|
||||
}
|
||||
userEvents.push(insertEvent)
|
||||
}
|
||||
} else if (op.struct === "Delete") {
|
||||
} else if (op.struct === 'Delete') {
|
||||
if (compareIds(this.map[key], op.target)) {
|
||||
if (this.opContents[key] != null) {
|
||||
delete this.opContents[key];
|
||||
delete this.opContents[key]
|
||||
} else {
|
||||
delete this.contents[key];
|
||||
delete this.contents[key]
|
||||
}
|
||||
var deleteEvent = {
|
||||
name: key,
|
||||
object: this,
|
||||
oldValue: oldValue,
|
||||
type: "delete"
|
||||
};
|
||||
userEvents.push(deleteEvent);
|
||||
type: 'delete'
|
||||
}
|
||||
userEvents.push(deleteEvent)
|
||||
}
|
||||
} else {
|
||||
throw new Error("Unexpected Operation!");
|
||||
throw new Error('Unexpected Operation!')
|
||||
}
|
||||
}
|
||||
this.eventHandler.callUserEventListeners(userEvents);
|
||||
});
|
||||
this.eventHandler.callUserEventListeners(userEvents)
|
||||
})
|
||||
}
|
||||
get (key) {
|
||||
// return property.
|
||||
@@ -78,34 +80,34 @@
|
||||
// if property is a type, return a promise
|
||||
if (this.opContents[key] == null) {
|
||||
if (key == null) {
|
||||
return copyObject(this.contents);
|
||||
return copyObject(this.contents)
|
||||
} else {
|
||||
return this.contents[key];
|
||||
return this.contents[key]
|
||||
}
|
||||
} else {
|
||||
let def = Promise.defer();
|
||||
var oid = this.opContents[key];
|
||||
this.os.requestTransaction(function*(){
|
||||
def.resolve(yield* this.getType(oid));
|
||||
});
|
||||
return def.promise;
|
||||
let def = Promise.defer()
|
||||
var oid = this.opContents[key]
|
||||
this.os.requestTransaction(function *() {
|
||||
def.resolve(yield* this.getType(oid))
|
||||
})
|
||||
return def.promise
|
||||
}
|
||||
}
|
||||
delete (key) {
|
||||
var right = this.map[key];
|
||||
var right = this.map[key]
|
||||
if (right != null) {
|
||||
var del = {
|
||||
target: right,
|
||||
struct: "Delete"
|
||||
};
|
||||
var eventHandler = this.eventHandler;
|
||||
var modDel = copyObject(del);
|
||||
modDel.key = key;
|
||||
eventHandler.awaitAndPrematurelyCall([modDel]);
|
||||
this.os.requestTransaction(function*(){
|
||||
yield* this.applyCreatedOperations([del]);
|
||||
eventHandler.awaitedLastDeletes(1);
|
||||
});
|
||||
struct: 'Delete'
|
||||
}
|
||||
var eventHandler = this.eventHandler
|
||||
var modDel = copyObject(del)
|
||||
modDel.key = key
|
||||
eventHandler.awaitAndPrematurelyCall([modDel])
|
||||
this.os.requestTransaction(function *() {
|
||||
yield* this.applyCreatedOperations([del])
|
||||
eventHandler.awaitedLastDeletes(1)
|
||||
})
|
||||
}
|
||||
}
|
||||
set (key, value) {
|
||||
@@ -113,108 +115,108 @@
|
||||
// if property is a type, return a promise
|
||||
// if not, apply immediately on this type an call event
|
||||
|
||||
var right = this.map[key] || null;
|
||||
var right = this.map[key] || null
|
||||
var insert = {
|
||||
left: null,
|
||||
right: right,
|
||||
origin: null,
|
||||
parent: this._model,
|
||||
parentSub: key,
|
||||
struct: "Insert"
|
||||
};
|
||||
var def = Promise.defer();
|
||||
if ( value instanceof CustomType) {
|
||||
// construct a new type
|
||||
this.os.requestTransaction(function*(){
|
||||
var type = yield* value.createType.call(this);
|
||||
insert.opContent = type._model;
|
||||
insert.id = this.store.getNextOpId();
|
||||
yield* this.applyCreatedOperations([insert]);
|
||||
def.resolve(type);
|
||||
});
|
||||
} else {
|
||||
insert.content = value;
|
||||
insert.id = this.os.getNextOpId();
|
||||
var eventHandler = this.eventHandler;
|
||||
eventHandler.awaitAndPrematurelyCall([insert]);
|
||||
|
||||
this.os.requestTransaction(function*(){
|
||||
yield* this.applyCreatedOperations([insert]);
|
||||
eventHandler.awaitedLastInserts(1);
|
||||
});
|
||||
def.resolve(value);
|
||||
struct: 'Insert'
|
||||
}
|
||||
return def.promise;
|
||||
var def = Promise.defer()
|
||||
if (value instanceof CustomType) {
|
||||
// construct a new type
|
||||
this.os.requestTransaction(function *() {
|
||||
var type = yield* value.createType.call(this)
|
||||
insert.opContent = type._model
|
||||
insert.id = this.store.getNextOpId()
|
||||
yield* this.applyCreatedOperations([insert])
|
||||
def.resolve(type)
|
||||
})
|
||||
} else {
|
||||
insert.content = value
|
||||
insert.id = this.os.getNextOpId()
|
||||
var eventHandler = this.eventHandler
|
||||
eventHandler.awaitAndPrematurelyCall([insert])
|
||||
|
||||
this.os.requestTransaction(function *() {
|
||||
yield* this.applyCreatedOperations([insert])
|
||||
eventHandler.awaitedLastInserts(1)
|
||||
})
|
||||
def.resolve(value)
|
||||
}
|
||||
return def.promise
|
||||
}
|
||||
observe (f) {
|
||||
this.eventHandler.addUserEventListener(f);
|
||||
this.eventHandler.addUserEventListener(f)
|
||||
}
|
||||
unobserve (f) {
|
||||
this.eventHandler.removeUserEventListener(f);
|
||||
this.eventHandler.removeUserEventListener(f)
|
||||
}
|
||||
observePath (path, f) {
|
||||
var self = this;
|
||||
var self = this
|
||||
if (path.length === 0) {
|
||||
this.observe(f);
|
||||
return Promise.resolve(function(){
|
||||
self.unobserve(f);
|
||||
});
|
||||
this.observe(f)
|
||||
return Promise.resolve(function () {
|
||||
self.unobserve(f)
|
||||
})
|
||||
} else {
|
||||
var deleteChildObservers;
|
||||
var resetObserverPath = function(){
|
||||
var promise = self.get(path[0]);
|
||||
var deleteChildObservers
|
||||
var resetObserverPath = function () {
|
||||
var promise = self.get(path[0])
|
||||
if (!promise instanceof Promise) {
|
||||
// its either not defined or a premitive value
|
||||
promise = self.set(path[0], Y.Map);
|
||||
promise = self.set(path[0], Y.Map)
|
||||
}
|
||||
return promise.then(function(map){
|
||||
return map.observePath(path.slice(1), f);
|
||||
}).then(function(_deleteChildObservers){
|
||||
deleteChildObservers = _deleteChildObservers;
|
||||
return Promise.resolve();
|
||||
});
|
||||
};
|
||||
var observer = function(events){
|
||||
return promise.then(function (map) {
|
||||
return map.observePath(path.slice(1), f)
|
||||
}).then(function (_deleteChildObservers) {
|
||||
deleteChildObservers = _deleteChildObservers
|
||||
return Promise.resolve()
|
||||
})
|
||||
}
|
||||
var observer = function (events) {
|
||||
for (var e in events) {
|
||||
var event = events[e];
|
||||
var event = events[e]
|
||||
if (event.name === path[0]) {
|
||||
deleteChildObservers();
|
||||
if (event.type === "add" || event.type === "update") {
|
||||
resetObserverPath();
|
||||
deleteChildObservers()
|
||||
if (event.type === 'add' || event.type === 'update') {
|
||||
resetObserverPath()
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
self.observe(observer);
|
||||
}
|
||||
self.observe(observer)
|
||||
return resetObserverPath().then(
|
||||
Promise.resolve(function(){
|
||||
deleteChildObservers();
|
||||
self.unobserve(observer);
|
||||
Promise.resolve(function () {
|
||||
deleteChildObservers()
|
||||
self.unobserve(observer)
|
||||
})
|
||||
);
|
||||
)
|
||||
}
|
||||
}
|
||||
*_changed (transaction, op) {
|
||||
if (op.struct === "Delete") {
|
||||
op.key = (yield* transaction.getOperation(op.target)).parentSub;
|
||||
* _changed (transaction, op) {
|
||||
if (op.struct === 'Delete') {
|
||||
op.key = (yield* transaction.getOperation(op.target)).parentSub
|
||||
}
|
||||
this.eventHandler.receivedOp(op);
|
||||
this.eventHandler.receivedOp(op)
|
||||
}
|
||||
}
|
||||
Y.Map = new CustomType({
|
||||
class: YMap,
|
||||
createType: function* YMapCreator(){
|
||||
createType: function * YMapCreator () {
|
||||
var model = {
|
||||
map: {},
|
||||
struct: "Map",
|
||||
type: "Map",
|
||||
struct: 'Map',
|
||||
type: 'Map',
|
||||
id: this.store.getNextOpId()
|
||||
};
|
||||
yield* this.applyCreatedOperations([model]);
|
||||
return yield* this.createType(model);
|
||||
}
|
||||
yield* this.applyCreatedOperations([model])
|
||||
return yield* this.createType(model)
|
||||
},
|
||||
initType: function* YMapInitializer(os, model){
|
||||
return new YMap(os, model);
|
||||
initType: function * YMapInitializer (os, model) { // eslint-disable-line
|
||||
return new YMap(os, model)
|
||||
}
|
||||
});
|
||||
})();
|
||||
})
|
||||
})()
|
||||
|
||||
@@ -1,207 +1,207 @@
|
||||
/* @flow */
|
||||
/*eslint-env browser,jasmine */
|
||||
/* global createUsers, Y, compareAllUsers, getRandomNumber, applyRandomTransactions */
|
||||
/* eslint-env browser,jasmine */
|
||||
|
||||
var numberOfYMapTests = 100;
|
||||
var numberOfYMapTests = 100
|
||||
|
||||
describe("Map Type", function(){
|
||||
var y1, y2, y3, y4, flushAll;
|
||||
describe('Map Type', function () {
|
||||
var y1, y2, y3, y4, flushAll
|
||||
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 50000;
|
||||
beforeEach(async function(done){
|
||||
await createUsers(this, 5);
|
||||
y1 = this.users[0].root;
|
||||
y2 = this.users[1].root;
|
||||
y3 = this.users[2].root;
|
||||
y4 = this.users[3].root;
|
||||
flushAll = this.users[0].connector.flushAll;
|
||||
done();
|
||||
});
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 50000
|
||||
beforeEach(async function (done) {
|
||||
await createUsers(this, 5)
|
||||
y1 = this.users[0].root
|
||||
y2 = this.users[1].root
|
||||
y3 = this.users[2].root
|
||||
y4 = this.users[3].root
|
||||
flushAll = this.users[0].connector.flushAll
|
||||
done()
|
||||
})
|
||||
afterEach(async function(done) {
|
||||
await compareAllUsers(this.users);
|
||||
done();
|
||||
}, 5000);
|
||||
await compareAllUsers(this.users)
|
||||
done()
|
||||
}, 5000)
|
||||
|
||||
describe("Basic tests", function(){
|
||||
it("Basic get&set of Map property (converge via sync)", async function(done){
|
||||
y1.set("stuff", "stuffy");
|
||||
expect(y1.get("stuff")).toEqual("stuffy");
|
||||
await flushAll();
|
||||
describe('Basic tests', function () {
|
||||
it('Basic get&set of Map property (converge via sync)', async function (done) {
|
||||
y1.set('stuff', 'stuffy')
|
||||
expect(y1.get('stuff')).toEqual('stuffy')
|
||||
await flushAll()
|
||||
for (var key in this.users) {
|
||||
var u = this.users[key].root;
|
||||
expect(u.get("stuff")).toEqual("stuffy");
|
||||
var u = this.users[key].root
|
||||
expect(u.get('stuff')).toEqual('stuffy')
|
||||
}
|
||||
await compareAllUsers(this.users);
|
||||
done();
|
||||
});
|
||||
it("Map can set custom types (Map)", async function(done){
|
||||
var map = await y1.set("Map", Y.Map);
|
||||
map.set("one", 1);
|
||||
map = await y1.get("Map");
|
||||
expect(map.get("one")).toEqual(1);
|
||||
await compareAllUsers(this.users);
|
||||
done();
|
||||
});
|
||||
it("Map can set custom types (Array)", async function(done){
|
||||
var array = await y1.set("Array", Y.Array);
|
||||
array.insert(0, [1, 2, 3]);
|
||||
array = await y1.get("Array");
|
||||
expect(array.toArray()).toEqual([1, 2, 3]);
|
||||
await compareAllUsers(this.users);
|
||||
done();
|
||||
});
|
||||
it("Basic get&set of Map property (converge via update)", async function(done){
|
||||
await flushAll();
|
||||
y1.set("stuff", "stuffy");
|
||||
expect(y1.get("stuff")).toEqual("stuffy");
|
||||
await compareAllUsers(this.users)
|
||||
done()
|
||||
})
|
||||
it('Map can set custom types (Map)', async function (done) {
|
||||
var map = await y1.set('Map', Y.Map)
|
||||
map.set('one', 1)
|
||||
map = await y1.get('Map')
|
||||
expect(map.get('one')).toEqual(1)
|
||||
await compareAllUsers(this.users)
|
||||
done()
|
||||
})
|
||||
it('Map can set custom types (Array)', async function (done) {
|
||||
var array = await y1.set('Array', Y.Array)
|
||||
array.insert(0, [1, 2, 3])
|
||||
array = await y1.get('Array')
|
||||
expect(array.toArray()).toEqual([1, 2, 3])
|
||||
await compareAllUsers(this.users)
|
||||
done()
|
||||
})
|
||||
it('Basic get&set of Map property (converge via update)', async function (done) {
|
||||
await flushAll()
|
||||
y1.set('stuff', 'stuffy')
|
||||
expect(y1.get('stuff')).toEqual('stuffy')
|
||||
|
||||
await flushAll();
|
||||
await flushAll()
|
||||
for (var key in this.users) {
|
||||
var r = this.users[key].root;
|
||||
expect(r.get("stuff")).toEqual("stuffy");
|
||||
var r = this.users[key].root
|
||||
expect(r.get('stuff')).toEqual('stuffy')
|
||||
}
|
||||
done();
|
||||
});
|
||||
it("Basic get&set of Map property (handle conflict)", async function(done){
|
||||
await flushAll();
|
||||
y1.set("stuff", "c0");
|
||||
done()
|
||||
})
|
||||
it('Basic get&set of Map property (handle conflict)', async function (done) {
|
||||
await flushAll()
|
||||
y1.set('stuff', 'c0')
|
||||
|
||||
y2.set("stuff", "c1");
|
||||
y2.set('stuff', 'c1')
|
||||
|
||||
await flushAll();
|
||||
await flushAll()
|
||||
for (var key in this.users) {
|
||||
var u = this.users[key];
|
||||
expect(u.root.get("stuff")).toEqual("c0");
|
||||
var u = this.users[key]
|
||||
expect(u.root.get('stuff')).toEqual('c0')
|
||||
}
|
||||
await compareAllUsers(this.users);
|
||||
done();
|
||||
});
|
||||
it("Basic get&set&delete of Map property (handle conflict)", async function(done){
|
||||
await flushAll();
|
||||
y1.set("stuff", "c0");
|
||||
y1.delete("stuff");
|
||||
y2.set("stuff", "c1");
|
||||
await flushAll();
|
||||
await compareAllUsers(this.users)
|
||||
done()
|
||||
})
|
||||
it('Basic get&set&delete of Map property (handle conflict)', async function (done) {
|
||||
await flushAll()
|
||||
y1.set('stuff', 'c0')
|
||||
y1.delete('stuff')
|
||||
y2.set('stuff', 'c1')
|
||||
await flushAll()
|
||||
|
||||
for (var key in this.users) {
|
||||
var u = this.users[key];
|
||||
expect(u.root.get("stuff")).toBeUndefined();
|
||||
var u = this.users[key]
|
||||
expect(u.root.get('stuff')).toBeUndefined()
|
||||
}
|
||||
await compareAllUsers(this.users);
|
||||
done();
|
||||
});
|
||||
it("Basic get&set of Map property (handle three conflicts)", async function(done){
|
||||
await flushAll();
|
||||
y1.set("stuff", "c0");
|
||||
y2.set("stuff", "c1");
|
||||
y2.set("stuff", "c2");
|
||||
y3.set("stuff", "c3");
|
||||
await flushAll();
|
||||
await compareAllUsers(this.users)
|
||||
done()
|
||||
})
|
||||
it('Basic get&set of Map property (handle three conflicts)', async function (done) {
|
||||
await flushAll()
|
||||
y1.set('stuff', 'c0')
|
||||
y2.set('stuff', 'c1')
|
||||
y2.set('stuff', 'c2')
|
||||
y3.set('stuff', 'c3')
|
||||
await flushAll()
|
||||
|
||||
for (var key in this.users) {
|
||||
var u = this.users[key];
|
||||
expect(u.root.get("stuff")).toEqual("c0");
|
||||
var u = this.users[key]
|
||||
expect(u.root.get('stuff')).toEqual('c0')
|
||||
}
|
||||
await compareAllUsers(this.users);
|
||||
done();
|
||||
});
|
||||
it("Basic get&set&delete of Map property (handle three conflicts)", async function(done){
|
||||
await flushAll();
|
||||
y1.set("stuff", "c0");
|
||||
y2.set("stuff", "c1");
|
||||
y2.set("stuff", "c2");
|
||||
y3.set("stuff", "c3");
|
||||
await flushAll();
|
||||
y1.set("stuff", "deleteme");
|
||||
y1.delete("stuff");
|
||||
y2.set("stuff", "c1");
|
||||
y3.set("stuff", "c2");
|
||||
y4.set("stuff", "c3");
|
||||
await flushAll();
|
||||
await compareAllUsers(this.users)
|
||||
done()
|
||||
})
|
||||
it('Basic get&set&delete of Map property (handle three conflicts)', async function (done) {
|
||||
await flushAll()
|
||||
y1.set('stuff', 'c0')
|
||||
y2.set('stuff', 'c1')
|
||||
y2.set('stuff', 'c2')
|
||||
y3.set('stuff', 'c3')
|
||||
await flushAll()
|
||||
y1.set('stuff', 'deleteme')
|
||||
y1.delete('stuff')
|
||||
y2.set('stuff', 'c1')
|
||||
y3.set('stuff', 'c2')
|
||||
y4.set('stuff', 'c3')
|
||||
await flushAll()
|
||||
|
||||
for (var key in this.users) {
|
||||
var u = this.users[key];
|
||||
expect(u.root.get("stuff")).toBeUndefined();
|
||||
var u = this.users[key]
|
||||
expect(u.root.get('stuff')).toBeUndefined()
|
||||
}
|
||||
await compareAllUsers(this.users);
|
||||
done();
|
||||
});
|
||||
it("throws add & update & delete events (with type and primitive content)", async function(done){
|
||||
var event;
|
||||
await flushAll();
|
||||
y1.observe(function(e){
|
||||
event = e; // just put it on event, should be thrown synchronously anyway
|
||||
});
|
||||
y1.set("stuff", 4);
|
||||
await compareAllUsers(this.users)
|
||||
done()
|
||||
})
|
||||
it('throws add & update & delete events (with type and primitive content)', async function (done) {
|
||||
var event
|
||||
await flushAll()
|
||||
y1.observe(function (e) {
|
||||
event = e // just put it on event, should be thrown synchronously anyway
|
||||
})
|
||||
y1.set('stuff', 4)
|
||||
expect(event).toEqual([{
|
||||
type: "add",
|
||||
type: 'add',
|
||||
object: y1,
|
||||
name: "stuff"
|
||||
}]);
|
||||
name: 'stuff'
|
||||
}])
|
||||
// update, oldValue is in contents
|
||||
await y1.set("stuff", Y.Array);
|
||||
await y1.set('stuff', Y.Array)
|
||||
expect(event).toEqual([{
|
||||
type: "update",
|
||||
type: 'update',
|
||||
object: y1,
|
||||
name: "stuff",
|
||||
name: 'stuff',
|
||||
oldValue: 4
|
||||
}]);
|
||||
y1.get("stuff").then(function(replacedArray){
|
||||
}])
|
||||
y1.get('stuff').then(function (replacedArray) {
|
||||
// update, oldValue is in opContents
|
||||
y1.set("stuff", 5);
|
||||
var getYArray = event[0].oldValue;
|
||||
expect(typeof getYArray.constructor === "function").toBeTruthy();
|
||||
getYArray().then(function(array){
|
||||
expect(array).toEqual(replacedArray);
|
||||
y1.set('stuff', 5)
|
||||
var getYArray = event[0].oldValue
|
||||
expect(typeof getYArray.constructor === 'function').toBeTruthy()
|
||||
getYArray().then(function (array) {
|
||||
expect(array).toEqual(replacedArray)
|
||||
|
||||
// delete
|
||||
y1.delete("stuff");
|
||||
y1.delete('stuff')
|
||||
expect(event).toEqual([{
|
||||
type: "delete",
|
||||
name: "stuff",
|
||||
type: 'delete',
|
||||
name: 'stuff',
|
||||
object: y1,
|
||||
oldValue: 5
|
||||
}]);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
describe(`${numberOfYMapTests} Random tests`, function(){
|
||||
}])
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
describe(`${numberOfYMapTests} Random tests`, function () {
|
||||
var randomMapTransactions = [
|
||||
function set (map) {
|
||||
map.set("somekey", getRandomNumber());
|
||||
map.set('somekey', getRandomNumber())
|
||||
},
|
||||
function delete_ (map) {
|
||||
map.delete("somekey");
|
||||
map.delete('somekey')
|
||||
}
|
||||
];
|
||||
function compareMapValues(maps){
|
||||
var firstMap;
|
||||
]
|
||||
function compareMapValues (maps) {
|
||||
var firstMap
|
||||
for (var map of maps) {
|
||||
var val = map.get();
|
||||
var val = map.get()
|
||||
if (firstMap == null) {
|
||||
firstMap = val;
|
||||
firstMap = val
|
||||
} else {
|
||||
expect(val).toEqual(firstMap);
|
||||
expect(val).toEqual(firstMap)
|
||||
}
|
||||
}
|
||||
}
|
||||
beforeEach(async function(done){
|
||||
await y1.set("Map", Y.Map);
|
||||
await flushAll();
|
||||
beforeEach(async function (done) {
|
||||
await y1.set('Map', Y.Map)
|
||||
await flushAll()
|
||||
|
||||
var promises = [];
|
||||
var promises = []
|
||||
for (var u = 0; u < this.users.length; u++) {
|
||||
promises.push(this.users[u].root.get("Map"));
|
||||
promises.push(this.users[u].root.get('Map'))
|
||||
}
|
||||
this.maps = await Promise.all(promises);
|
||||
done();
|
||||
});
|
||||
it(`succeed after ${numberOfYMapTests} actions`, async function(done){
|
||||
await applyRandomTransactions(this.users, this.maps, randomMapTransactions, numberOfYMapTests);
|
||||
await flushAll();
|
||||
await compareMapValues(this.maps);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
this.maps = await Promise.all(promises)
|
||||
done()
|
||||
})
|
||||
it(`succeed after ${numberOfYMapTests} actions`, async function (done) {
|
||||
await applyRandomTransactions(this.users, this.maps, randomMapTransactions, numberOfYMapTests)
|
||||
await flushAll()
|
||||
await compareMapValues(this.maps)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,287 +1,288 @@
|
||||
/* global Y, CustomType */
|
||||
|
||||
(function(){
|
||||
class YTextBind extends Y.Array.class {
|
||||
;(function () {
|
||||
class YTextBind extends Y . Array . class {
|
||||
constructor (os, _model, idArray, valArray) {
|
||||
super(os, _model, idArray, valArray);
|
||||
this.textfields = [];
|
||||
super(os, _model, idArray, valArray)
|
||||
this.textfields = []
|
||||
}
|
||||
toString () {
|
||||
return this.valArray.join("");
|
||||
return this.valArray.join('')
|
||||
}
|
||||
insert (pos, content) {
|
||||
super(pos, content.split(""));
|
||||
super(pos, content.split(''))
|
||||
}
|
||||
bind (textfield, domRoot) {
|
||||
domRoot = domRoot || window; //eslint-disable-line
|
||||
if (domRoot.getSelection == null) {
|
||||
domRoot = window;//eslint-disable-line
|
||||
}
|
||||
domRoot = domRoot || window; // eslint-disable-line
|
||||
if (domRoot.getSelection == null) {
|
||||
domRoot = window;// eslint-disable-line
|
||||
}
|
||||
|
||||
// don't duplicate!
|
||||
for (var t in this.textfields) {
|
||||
if (this.textfields[t] === textfield) {
|
||||
return;
|
||||
// don't duplicate!
|
||||
for (var t in this.textfields) {
|
||||
if (this.textfields[t] === textfield) {
|
||||
return
|
||||
}
|
||||
}
|
||||
var creatorToken = false
|
||||
|
||||
var word = this
|
||||
textfield.value = this.toString()
|
||||
this.textfields.push(textfield)
|
||||
var createRange, writeRange, writeContent
|
||||
if (textfield.selectionStart != null && textfield.setSelectionRange != null) {
|
||||
createRange = function (fix) {
|
||||
var left = textfield.selectionStart
|
||||
var right = textfield.selectionEnd
|
||||
if (fix != null) {
|
||||
left = fix(left)
|
||||
right = fix(right)
|
||||
}
|
||||
return {
|
||||
left: left,
|
||||
right: right
|
||||
}
|
||||
}
|
||||
var creatorToken = false;
|
||||
writeRange = function (range) {
|
||||
writeContent(word.toString())
|
||||
textfield.setSelectionRange(range.left, range.right)
|
||||
}
|
||||
writeContent = function (content) {
|
||||
textfield.value = content
|
||||
}
|
||||
} else {
|
||||
createRange = function (fix) {
|
||||
var range = {}
|
||||
var s = domRoot.getSelection()
|
||||
var clength = textfield.textContent.length
|
||||
range.left = Math.min(s.anchorOffset, clength)
|
||||
range.right = Math.min(s.focusOffset, clength)
|
||||
if (fix != null) {
|
||||
range.left = fix(range.left)
|
||||
range.right = fix(range.right)
|
||||
}
|
||||
var editedElement = s.focusNode
|
||||
if (editedElement === textfield || editedElement === textfield.childNodes[0]) {
|
||||
range.isReal = true
|
||||
} else {
|
||||
range.isReal = false
|
||||
}
|
||||
return range
|
||||
}
|
||||
|
||||
var word = this;
|
||||
textfield.value = this.toString();
|
||||
this.textfields.push(textfield);
|
||||
var createRange, writeRange, writeContent;
|
||||
if(textfield.selectionStart != null && textfield.setSelectionRange != null) {
|
||||
createRange = function (fix) {
|
||||
var left = textfield.selectionStart;
|
||||
var right = textfield.selectionEnd;
|
||||
if (fix != null) {
|
||||
left = fix(left);
|
||||
right = fix(right);
|
||||
writeRange = function (range) {
|
||||
writeContent(word.toString())
|
||||
var textnode = textfield.childNodes[0]
|
||||
if (range.isReal && textnode != null) {
|
||||
if (range.left < 0) {
|
||||
range.left = 0
|
||||
}
|
||||
return {
|
||||
left: left,
|
||||
right: right
|
||||
};
|
||||
};
|
||||
writeRange = function (range) {
|
||||
writeContent(word.toString());
|
||||
textfield.setSelectionRange(range.left, range.right);
|
||||
};
|
||||
writeContent = function (content){
|
||||
textfield.value = content;
|
||||
};
|
||||
range.right = Math.max(range.left, range.right)
|
||||
if (range.right > textnode.length) {
|
||||
range.right = textnode.length
|
||||
}
|
||||
range.left = Math.min(range.left, range.right)
|
||||
var r = document.createRange(); // eslint-disable-line
|
||||
r.setStart(textnode, range.left)
|
||||
r.setEnd(textnode, range.right)
|
||||
var s = window.getSelection(); // eslint-disable-line
|
||||
s.removeAllRanges()
|
||||
s.addRange(r)
|
||||
}
|
||||
}
|
||||
writeContent = function (content) {
|
||||
var contentArray = content.replace(new RegExp('\n', 'g'), ' ').split(' ');// eslint-disable-line
|
||||
textfield.innerText = ''
|
||||
for (var i in contentArray) {
|
||||
var c = contentArray[i]
|
||||
textfield.innerText += c
|
||||
if (i !== contentArray.length - 1) {
|
||||
textfield.innerHTML += ' '
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
writeContent(this.toString())
|
||||
|
||||
this.observe(function (events) {
|
||||
for (var e in events) {
|
||||
var event = events[e]
|
||||
if (!creatorToken) {
|
||||
var oPos, fix
|
||||
if (event.type === 'insert') {
|
||||
oPos = event.index
|
||||
fix = function (cursor) {// eslint-disable-line
|
||||
if (cursor <= oPos) {
|
||||
return cursor
|
||||
} else {
|
||||
cursor += 1
|
||||
return cursor
|
||||
}
|
||||
}
|
||||
var r = createRange(fix)
|
||||
writeRange(r)
|
||||
} else if (event.type === 'delete') {
|
||||
oPos = event.index
|
||||
fix = function (cursor) {// eslint-disable-line
|
||||
if (cursor < oPos) {
|
||||
return cursor
|
||||
} else {
|
||||
cursor -= 1
|
||||
return cursor
|
||||
}
|
||||
}
|
||||
r = createRange(fix)
|
||||
writeRange(r)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
// consume all text-insert changes.
|
||||
textfield.onkeypress = function (event) {
|
||||
if (word.is_deleted) {
|
||||
// if word is deleted, do not do anything ever again
|
||||
textfield.onkeypress = null
|
||||
return true
|
||||
}
|
||||
creatorToken = true
|
||||
var char
|
||||
if (event.keyCode === 13) {
|
||||
char = '\n'
|
||||
} else if (event.key != null) {
|
||||
if (event.charCode === 32) {
|
||||
char = ' '
|
||||
} else {
|
||||
char = event.key
|
||||
}
|
||||
} else {
|
||||
createRange = function (fix) {
|
||||
var range = {};
|
||||
var s = domRoot.getSelection();
|
||||
var clength = textfield.textContent.length;
|
||||
range.left = Math.min(s.anchorOffset, clength);
|
||||
range.right = Math.min(s.focusOffset, clength);
|
||||
if(fix != null){
|
||||
range.left = fix(range.left);
|
||||
range.right = fix(range.right);
|
||||
}
|
||||
var editedElement = s.focusNode;
|
||||
if(editedElement === textfield || editedElement === textfield.childNodes[0]){
|
||||
range.isReal = true;
|
||||
} else {
|
||||
range.isReal = false;
|
||||
}
|
||||
return range;
|
||||
};
|
||||
|
||||
writeRange = function (range) {
|
||||
writeContent(word.toString());
|
||||
var textnode = textfield.childNodes[0];
|
||||
if(range.isReal && textnode != null) {
|
||||
if(range.left < 0){
|
||||
range.left = 0;
|
||||
}
|
||||
range.right = Math.max(range.left, range.right);
|
||||
if (range.right > textnode.length) {
|
||||
range.right = textnode.length;
|
||||
}
|
||||
range.left = Math.min(range.left, range.right);
|
||||
var r = document.createRange(); //eslint-disable-line
|
||||
r.setStart(textnode, range.left);
|
||||
r.setEnd(textnode, range.right);
|
||||
var s = window.getSelection(); //eslint-disable-line
|
||||
s.removeAllRanges();
|
||||
s.addRange(r);
|
||||
}
|
||||
};
|
||||
writeContent = function (content) {
|
||||
var contentArray = content.replace(new RegExp("\n", 'g')," ").split(" ");//eslint-disable-line
|
||||
textfield.innerText = "";
|
||||
for(var i in contentArray){
|
||||
var c = contentArray[i];
|
||||
textfield.innerText += c;
|
||||
if(i !== contentArray.length - 1){
|
||||
textfield.innerHTML += " ";
|
||||
}
|
||||
}
|
||||
};
|
||||
char = window.String.fromCharCode(event.keyCode); // eslint-disable-line
|
||||
}
|
||||
writeContent(this.toString());
|
||||
|
||||
this.observe(function (events) {
|
||||
for(var e in events) {
|
||||
var event = events[e];
|
||||
if (!creatorToken) {
|
||||
var oPos, fix;
|
||||
if( event.type === "insert") {
|
||||
oPos = event.index;
|
||||
fix = function (cursor) {//eslint-disable-line
|
||||
if (cursor <= oPos) {
|
||||
return cursor;
|
||||
} else {
|
||||
cursor += 1;
|
||||
return cursor;
|
||||
}
|
||||
};
|
||||
var r = createRange(fix);
|
||||
writeRange(r);
|
||||
} else if (event.type === "delete") {
|
||||
oPos = event.index;
|
||||
fix = function (cursor){//eslint-disable-line
|
||||
if (cursor < oPos) {
|
||||
return cursor;
|
||||
} else {
|
||||
cursor -= 1;
|
||||
return cursor;
|
||||
}
|
||||
};
|
||||
r = createRange(fix);
|
||||
writeRange(r);
|
||||
if (char.length > 1) {
|
||||
return true
|
||||
} else if (char.length > 0) {
|
||||
var r = createRange()
|
||||
var pos = Math.min(r.left, r.right, word.length)
|
||||
var diff = Math.abs(r.right - r.left)
|
||||
word.delete(pos, diff)
|
||||
word.insert(pos, char)
|
||||
r.left = pos + char.length
|
||||
r.right = r.left
|
||||
writeRange(r)
|
||||
}
|
||||
event.preventDefault()
|
||||
creatorToken = false
|
||||
return false
|
||||
}
|
||||
textfield.onpaste = function (event) {
|
||||
if (word.is_deleted) {
|
||||
// if word is deleted, do not do anything ever again
|
||||
textfield.onpaste = null
|
||||
return true
|
||||
}
|
||||
event.preventDefault()
|
||||
}
|
||||
textfield.oncut = function (event) {
|
||||
if (word.is_deleted) {
|
||||
// if word is deleted, do not do anything ever again
|
||||
textfield.oncut = null
|
||||
return true
|
||||
}
|
||||
event.preventDefault()
|
||||
}
|
||||
//
|
||||
// consume deletes. Note that
|
||||
// chrome: won't consume deletions on keypress event.
|
||||
// keyCode is deprecated. BUT: I don't see another way.
|
||||
// since event.key is not implemented in the current version of chrome.
|
||||
// Every browser supports keyCode. Let's stick with it for now..
|
||||
//
|
||||
textfield.onkeydown = function (event) {
|
||||
creatorToken = true
|
||||
if (word.is_deleted) {
|
||||
// if word is deleted, do not do anything ever again
|
||||
textfield.onkeydown = null
|
||||
return true
|
||||
}
|
||||
var r = createRange()
|
||||
var pos = Math.min(r.left, r.right, word.toString().length)
|
||||
var diff = Math.abs(r.left - r.right)
|
||||
if (event.keyCode != null && event.keyCode === 8) { // Backspace
|
||||
if (diff > 0) {
|
||||
word.delete(pos, diff)
|
||||
r.left = pos
|
||||
r.right = pos
|
||||
writeRange(r)
|
||||
} else {
|
||||
if (event.ctrlKey != null && event.ctrlKey) {
|
||||
var val = word.toString()
|
||||
var newPos = pos
|
||||
var delLength = 0
|
||||
if (pos > 0) {
|
||||
newPos--
|
||||
delLength++
|
||||
}
|
||||
while (newPos > 0 && val[newPos] !== ' ' && val[newPos] !== '\n') {
|
||||
newPos--
|
||||
delLength++
|
||||
}
|
||||
word.delete(newPos, pos - newPos)
|
||||
r.left = newPos
|
||||
r.right = newPos
|
||||
writeRange(r)
|
||||
} else {
|
||||
if (pos > 0) {
|
||||
word.delete(pos - 1, 1)
|
||||
r.left = pos - 1
|
||||
r.right = pos - 1
|
||||
writeRange(r)
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
// consume all text-insert changes.
|
||||
textfield.onkeypress = function (event) {
|
||||
if (word.is_deleted) {
|
||||
// if word is deleted, do not do anything ever again
|
||||
textfield.onkeypress = null;
|
||||
return true;
|
||||
}
|
||||
creatorToken = true;
|
||||
var char;
|
||||
if (event.keyCode === 13) {
|
||||
char = "\n";
|
||||
} else if (event.key != null) {
|
||||
if (event.charCode === 32) {
|
||||
char = " ";
|
||||
} else {
|
||||
char = event.key;
|
||||
}
|
||||
event.preventDefault()
|
||||
creatorToken = false
|
||||
return false
|
||||
} else if (event.keyCode != null && event.keyCode === 46) { // Delete
|
||||
if (diff > 0) {
|
||||
word.delete(pos, diff)
|
||||
r.left = pos
|
||||
r.right = pos
|
||||
writeRange(r)
|
||||
} else {
|
||||
char = window.String.fromCharCode(event.keyCode); //eslint-disable-line
|
||||
word.delete(pos, 1)
|
||||
r.left = pos
|
||||
r.right = pos
|
||||
writeRange(r)
|
||||
}
|
||||
if (char.length > 1) {
|
||||
return true;
|
||||
} else if (char.length > 0) {
|
||||
var r = createRange();
|
||||
var pos = Math.min(r.left, r.right, word.length);
|
||||
var diff = Math.abs(r.right - r.left);
|
||||
word.delete(pos, diff);
|
||||
word.insert(pos, char);
|
||||
r.left = pos + char.length;
|
||||
r.right = r.left;
|
||||
writeRange(r);
|
||||
}
|
||||
event.preventDefault();
|
||||
creatorToken = false;
|
||||
return false;
|
||||
};
|
||||
textfield.onpaste = function (event) {
|
||||
if (word.is_deleted) {
|
||||
// if word is deleted, do not do anything ever again
|
||||
textfield.onpaste = null;
|
||||
return true;
|
||||
}
|
||||
event.preventDefault();
|
||||
};
|
||||
textfield.oncut = function (event) {
|
||||
if (word.is_deleted) {
|
||||
// if word is deleted, do not do anything ever again
|
||||
textfield.oncut = null;
|
||||
return true;
|
||||
}
|
||||
event.preventDefault();
|
||||
};
|
||||
//
|
||||
// consume deletes. Note that
|
||||
// chrome: won't consume deletions on keypress event.
|
||||
// keyCode is deprecated. BUT: I don't see another way.
|
||||
// since event.key is not implemented in the current version of chrome.
|
||||
// Every browser supports keyCode. Let's stick with it for now..
|
||||
//
|
||||
textfield.onkeydown = function (event) {
|
||||
creatorToken = true;
|
||||
if (word.is_deleted) {
|
||||
// if word is deleted, do not do anything ever again
|
||||
textfield.onkeydown = null;
|
||||
return true;
|
||||
}
|
||||
var r = createRange();
|
||||
var pos = Math.min(r.left, r.right, word.toString().length);
|
||||
var diff = Math.abs(r.left - r.right);
|
||||
if (event.keyCode != null && event.keyCode === 8) { // Backspace
|
||||
if (diff > 0) {
|
||||
word.delete(pos, diff);
|
||||
r.left = pos;
|
||||
r.right = pos;
|
||||
writeRange(r);
|
||||
} else {
|
||||
if (event.ctrlKey != null && event.ctrlKey) {
|
||||
var val = word.toString();
|
||||
var newPos = pos;
|
||||
var delLength = 0;
|
||||
if (pos > 0) {
|
||||
newPos--;
|
||||
delLength++;
|
||||
}
|
||||
while (newPos > 0 && val[newPos] !== " " && val[newPos] !== "\n") {
|
||||
newPos--;
|
||||
delLength++;
|
||||
}
|
||||
word.delete(newPos, pos - newPos);
|
||||
r.left = newPos;
|
||||
r.right = newPos;
|
||||
writeRange(r);
|
||||
} else {
|
||||
if (pos > 0) {
|
||||
word.delete(pos - 1, 1);
|
||||
r.left = pos - 1;
|
||||
r.right = pos - 1;
|
||||
writeRange(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
event.preventDefault();
|
||||
creatorToken = false;
|
||||
return false;
|
||||
} else if (event.keyCode != null && event.keyCode === 46) { // Delete
|
||||
if (diff > 0) {
|
||||
word.delete(pos, diff);
|
||||
r.left = pos;
|
||||
r.right = pos;
|
||||
writeRange(r);
|
||||
} else {
|
||||
word.delete(pos, 1);
|
||||
r.left = pos;
|
||||
r.right = pos;
|
||||
writeRange(r);
|
||||
}
|
||||
event.preventDefault();
|
||||
creatorToken = false;
|
||||
return false;
|
||||
} else {
|
||||
creatorToken = false;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
event.preventDefault()
|
||||
creatorToken = false
|
||||
return false
|
||||
} else {
|
||||
creatorToken = false
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Y.TextBind = new CustomType({
|
||||
class: YTextBind,
|
||||
createType: function* YTextBindCreator () {
|
||||
createType: function * YTextBindCreator () {
|
||||
var model = {
|
||||
start: null,
|
||||
end: null,
|
||||
struct: "List",
|
||||
type: "TextBind",
|
||||
struct: 'List',
|
||||
type: 'TextBind',
|
||||
id: this.store.getNextOpId()
|
||||
};
|
||||
yield* this.applyCreatedOperations([model]);
|
||||
return yield* this.createType(model);
|
||||
}
|
||||
yield* this.applyCreatedOperations([model])
|
||||
return yield* this.createType(model)
|
||||
},
|
||||
initType: function* YTextBindInitializer(os, model){
|
||||
var valArray = [];
|
||||
var idArray = yield* Y.Struct.List.map.call(this, model, function(c){
|
||||
valArray.push(c.content);
|
||||
return JSON.stringify(c.id);
|
||||
});
|
||||
return new YTextBind(os, model.id, idArray, valArray);
|
||||
initType: function * YTextBindInitializer (os, model) {
|
||||
var valArray = []
|
||||
var idArray = yield* Y.Struct.List.map.call(this, model, function (c) {
|
||||
valArray.push(c.content)
|
||||
return JSON.stringify(c.id)
|
||||
})
|
||||
return new YTextBind(os, model.id, idArray, valArray)
|
||||
}
|
||||
});
|
||||
})();
|
||||
})
|
||||
})()
|
||||
|
||||
Reference in New Issue
Block a user