From 60b75d186290735f2013c917ca2f3a5ca80c4e2e Mon Sep 17 00:00:00 2001 From: Kevin Jahns Date: Wed, 15 Jul 2015 21:24:05 +0200 Subject: [PATCH] array & type are observeable --- .travis.yml | 5 ++- src/Types/Array.js | 17 ++++++++ src/Types/Array.spec.js | 23 +++++++++++ src/Types/Map.js | 91 +++++++++++++++++++++++++++++++++-------- src/Types/Map.spec.js | 40 ++++++++++++++++++ 5 files changed, 157 insertions(+), 19 deletions(-) diff --git a/.travis.yml b/.travis.yml index e830c82b..45b66d72 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: node_js before_install: - - "npm install -g bower coffee-script" + - "npm install -g bower" - "bower install" node_js: - "0.12" @@ -8,4 +8,5 @@ node_js: - "0.10" branches: only: - - master \ No newline at end of file + - master + - 0.6 diff --git a/src/Types/Array.js b/src/Types/Array.js index d591b361..4e35dcce 100644 --- a/src/Types/Array.js +++ b/src/Types/Array.js @@ -11,6 +11,7 @@ // Array of all the values this.valArray = valArray; this.eventHandler = new EventHandler( ops =>{ + var userEvents = []; for (var i in ops) { var op = ops[i]; if (op.struct === "Insert") { @@ -28,14 +29,27 @@ } this.idArray.splice(pos, 0, JSON.stringify(op.id)); this.valArray.splice(pos, 0, op.content); + userEvents.push({ + 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); + userEvents.push({ + type: "delete", + object: this, + index: pos, + length: 1 + }); } else { throw new Error("Unexpected struct!"); } } + this.eventHandler.callUserEventListeners(userEvents); }); } get (pos) { @@ -121,6 +135,9 @@ eventHandler.awaitedLastDeletes(dels.length, newLeft); }); } + observe (f) { + this.eventHandler.addUserEventListener(f); + } *_changed (transaction, op) { if (op.struct === "Insert") { var l = op.left; diff --git a/src/Types/Array.spec.js b/src/Types/Array.spec.js index 31f62dbb..5c0d845e 100644 --- a/src/Types/Array.spec.js +++ b/src/Types/Array.spec.js @@ -89,6 +89,29 @@ describe("Array Type", function(){ done(); }); }); + it("throw insert & delete events", function(done){ + this.users[0].root.set("array", Y.Array).then(function(array){ + var event; + array.observe(function(e){ + event = e; + }); + array.insert(0, [0]); + expect(event).toEqual([{ + type: "insert", + object: array, + index: 0, + length: 1 + }]); + array.delete(0); + expect(event).toEqual([{ + type: "delete", + object: array, + index: 0, + length: 1 + }]); + done(); + }); + }); }); describe(`${numberOfYArrayTests} Random tests`, function(){ var randomArrayTransactions = [ diff --git a/src/Types/Map.js b/src/Types/Map.js index 7c5f977d..fc3b553f 100644 --- a/src/Types/Map.js +++ b/src/Types/Map.js @@ -6,6 +6,7 @@ class EventHandler { this.waiting = []; this.awaiting = 0; this.onevent = onevent; + this.userEventListeners = []; } receivedOp (op) { if (this.awaiting <= 0) { @@ -18,6 +19,26 @@ class EventHandler { this.awaiting++; this.onevent(ops); } + addUserEventListener (f) { + this.userEventListeners.push(f); + } + removeUserEventListener (f) { + this.userEventListeners = this.userEventListeners.filter(function(g){ + return f !== g; + }); + } + removeAllUserEventListeners () { + this.userEventListeners = []; + } + callUserEventListeners (event) { + for (var i in this.userEventListeners) { + try { + this.userEventListeners[i](event); + } catch (e) { + console.log("User events must not throw Errors!");//eslint-disable-line + } + } + } awaitedLastInserts (n) { var ops = this.waiting.splice(this.waiting.length - n); for (var oid = 0; oid < ops.length; oid++) { @@ -56,7 +77,7 @@ class EventHandler { } tryCallEvents () { this.awaiting--; - if (this.awaiting <= 0) { + if (this.awaiting <= 0 && this.waiting.length > 0) { var events = this.waiting; this.waiting = []; this.onevent(events); @@ -73,30 +94,69 @@ class EventHandler { this.contents = {}; this.opContents = {}; this.eventHandler = new EventHandler( ops =>{ + var userEvents = []; for (var i in ops) { 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; + + // 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; + }; + } else { + oldValue = this.contents[key]; + } + // compute op event if (op.struct === "Insert"){ if (op.left === null) { if (op.opContent != null) { - this.opContents[op.parentSub] = op.opContent; - } else { - this.contents[op.parentSub] = op.content; - } - this.map[op.parentSub] = op.id; - } - } else if (op.struct === "Delete") { - var key = op.key; - if (compareIds(this.map[key], op.target)) { - if (this.contents[key] != null) { delete this.contents[key]; + this.opContents[key] = op.opContent; } else { delete this.opContents[key]; + this.contents[key] = op.content; } + 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); + } + } else if (op.struct === "Delete") { + if (compareIds(this.map[key], op.target)) { + if (this.opContents[key] != null) { + delete this.opContents[key]; + } else { + delete this.contents[key]; + } + var deleteEvent = { + name: key, + object: this, + oldValue: oldValue, + type: "delete" + }; + userEvents.push(deleteEvent); } } else { throw new Error("Unexpected Operation!"); } } + this.eventHandler.callUserEventListeners(userEvents); }); } get (key) { @@ -173,12 +233,9 @@ class EventHandler { } return def.promise; } - /* - *delete (key) { - var t = yield "transaction"; - var model = yield* t.getOperation(this._model); - yield* Y.Struct.Map.delete.call(t, model, key); - }*/ + observe (f) { + this.eventHandler.addUserEventListener(f); + } *_changed (transaction, op) { if (op.struct === "Delete") { op.key = (yield* transaction.getOperation(op.target)).parentSub; diff --git a/src/Types/Map.spec.js b/src/Types/Map.spec.js index 585e5d58..ae35b5b5 100644 --- a/src/Types/Map.spec.js +++ b/src/Types/Map.spec.js @@ -136,6 +136,46 @@ describe("Map Type", function(){ done(); }, 50); }); + it("throws add & update & delete events (with type and primitive content)", function(done){ + var y = this.users[0].root; + var event; + y.observe(function(e){ + event = e; // just put it on event, should be thrown synchronously anyway + }); + y.set("stuff", 4); + expect(event).toEqual([{ + type: "add", + object: y, + name: "stuff" + }]); + // update, oldValue is in contents + y.set("stuff", Y.Array); + expect(event).toEqual([{ + type: "update", + object: y, + name: "stuff", + oldValue: 4 + }]); + y.get("stuff").then(function(replacedArray){ + // update, oldValue is in opContents + y.set("stuff", 5); + var getYArray = event[0].oldValue; + expect(typeof getYArray.constructor === "function").toBeTruthy(); + getYArray().then(function(array){ + expect(array).toEqual(replacedArray); + + // delete + y.delete("stuff"); + expect(event).toEqual([{ + type: "delete", + name: "stuff", + object: y, + oldValue: 5 + }]); + done(); + }); + }); + }); }); describe(`${numberOfYMapTests} Random tests`, function(){ var randomMapTransactions = [