diff --git a/Examples/TextBind/index.js b/Examples/TextBind/index.js index 32d25c4c..90d5657f 100644 --- a/Examples/TextBind/index.js +++ b/Examples/TextBind/index.js @@ -14,16 +14,11 @@ Y({ window.yconfig = yconfig var textarea = document.getElementById('textfield') var contenteditable = document.getElementById('contenteditable') - yconfig.root.observe(function (events) { - for (var e in events) { - var event = events[e] - if (event.name === 'text' && (event.type === 'add' || event.type === 'update')) { - event.object.get(event.name).then(function (text) { // eslint-disable-line - text.bind(textarea) - text.bind(contenteditable) - window.ytext = text - }) - } + yconfig.root.observePath(['text'], function (text) { + if (text != null) { + text.bind(textarea) + text.bind(contenteditable) + window.ytext = text } }) yconfig.root.set('text', Y.TextBind) diff --git a/src/OperationStore.js b/src/OperationStore.js index 3464bd60..b1eb02cf 100644 --- a/src/OperationStore.js +++ b/src/OperationStore.js @@ -81,6 +81,7 @@ class AbstractTransaction { /* Get a type based on the id of its model. If it does not exist yes, create it. + TODO: delete type from store.initializedTypes[id] when corresponding id was deleted! */ * getType (id) { var sid = JSON.stringify(id) diff --git a/src/Types/Array.spec.js b/src/Types/Array.spec.js index 90e10862..a65d45ac 100644 --- a/src/Types/Array.spec.js +++ b/src/Types/Array.spec.js @@ -1,8 +1,8 @@ /* global createUsers, wait, Y, compareAllUsers, getRandomNumber, applyRandomTransactions, async, garbageCollectAllUsers, describeManyTimes */ /* eslint-env browser,jasmine */ -var numberOfYArrayTests = 1000 -var repeatArrayTests = 5 +var numberOfYArrayTests = 200 +var repeatArrayTests = 1 describe('Array Type', function () { var y1, y2, y3, yconfig1, yconfig2, yconfig3, flushAll diff --git a/src/Types/Map.js b/src/Types/Map.js index cb769b11..fbafbc6f 100644 --- a/src/Types/Map.js +++ b/src/Types/Map.js @@ -3,12 +3,12 @@ ;(function () { class YMap { - constructor (os, model) { + constructor (os, model, contents, opContents) { this._model = model.id this.os = os this.map = Y.utils.copyObject(model.map) - this.contents = {} - this.opContents = {} + this.contents = contents + this.opContents = opContents this.eventHandler = new Y.utils.EventHandler(ops => { var userEvents = [] for (var i in ops) { @@ -84,12 +84,11 @@ // return property. // if property does not exist, return null // if property is a type, return a promise + if (key == null) { + throw new Error('You must specify key!') + } if (this.opContents[key] == null) { - if (key == null) { - return Y.utils.copyObject(this.contents) - } else { - return this.contents[key] - } + return this.contents[key] } else { return new Promise((resolve) => { var oid = this.opContents[key] @@ -99,6 +98,19 @@ }) } } + /* + If there is a primitive (not a custom type), then return it. + Returns all primitive values, if propertyName is specified! + Note: modifying the return value could result in inconsistencies! + -- so make sure to copy it first! + */ + getPrimitive (key) { + if (key == null) { + return Y.utils.copyObject(this.contents) + } else { + return this.contents[key] + } + } delete (key) { var right = this.map[key] if (right != null) { @@ -161,10 +173,48 @@ unobserve (f) { this.eventHandler.removeEventListener(f) } + /* + Observe a path. + + E.g. + ``` + o.set('textarea', Y.TextBind) + o.observePath(['textarea'], function(t){ + // is called whenever textarea is replaced + t.bind(textarea) + }) + + returns a Promise that contains a function that removes the observer from the path. + */ observePath (path, f) { var self = this - if (path.length === 0) { - this.observe(f) + function observeProperty (events) { + // call f whenever path changes + for (var i = 0; i < events.length; i++) { + var event = events[i] + if (event.name === propertyName) { + // call this also for delete events! + var property = self.get(propertyName) + if (property instanceof Promise) { + property.then(f) + } else { + f(property) + } + } + } + } + + if (path.length < 1) { + throw new Error('Path must contain at least one element!') + } else if (path.length === 1) { + var propertyName = path[0] + var property = self.get(propertyName) + if (property instanceof Promise) { + property.then(f) + } else { + f(property) + } + this.observe(observeProperty) return Promise.resolve(function () { self.unobserve(f) }) @@ -173,14 +223,15 @@ var resetObserverPath = function () { var promise = self.get(path[0]) if (!promise instanceof Promise) { - // its either not defined or a premitive value + // its either not defined or a primitive value promise = self.set(path[0], Y.Map) } return promise.then(function (map) { return map.observePath(path.slice(1), f) }).then(function (_deleteChildObservers) { + // update deleteChildObservers deleteChildObservers = _deleteChildObservers - return Promise.resolve() + return Promise.resolve() // Promise does not return anything }) } var observer = function (events) { @@ -191,11 +242,14 @@ if (event.type === 'add' || event.type === 'update') { resetObserverPath() } + // TODO: what about the delete events? } } } self.observe(observer) return resetObserverPath().then( + // this promise contains a function that deletes all the child observers + // and how to unobserve the observe from this object Promise.resolve(function () { deleteChildObservers() self.unobserve(observer) @@ -223,8 +277,19 @@ yield* this.applyCreatedOperations([model]) return modelid }, - initType: function * YMapInitializer (os, model) { // eslint-disable-line - return new YMap(os, model) + initType: function * YMapInitializer (os, model) { + var contents = {} + var opContents = {} + var map = model.map + for (var name in map) { + var op = yield* this.getOperation(map[name]) + if (op.opContent != null) { + opContents[name] = op.opContent + } else { + contents[name] = op.content + } + } + return new YMap(os, model, contents, opContents) } }) })() diff --git a/src/Types/Map.spec.js b/src/Types/Map.spec.js index a6496e8f..ff075432 100644 --- a/src/Types/Map.spec.js +++ b/src/Types/Map.spec.js @@ -2,7 +2,7 @@ /* eslint-env browser,jasmine */ var numberOfYMapTests = 100 -var repeatMapTeasts = 10 +var repeatMapTeasts = 1 describe('Map Type', function () { var y1, y2, y3, y4, flushAll @@ -117,6 +117,18 @@ describe('Map Type', function () { } done() })) + it('observePath properties', async(function * (done) { + y1.observePath(['map'], function (map) { + if (map != null) { + map.set('yay', 4) + } + }) + yield y2.set('map', Y.Map) + yield flushAll() + var map = yield y3.get('map') + expect(map.get('yay')).toEqual(4) + done() + })) it('throws add & update & delete events (with type and primitive content)', async(function * (done) { var event yield flushAll() @@ -170,7 +182,7 @@ describe('Map Type', function () { function compareMapValues (maps) { var firstMap for (var map of maps) { - var val = map.get() + var val = map.getPrimitive() if (firstMap == null) { firstMap = val } else {