From 634f1f3981ba3ac8d9e3f39137d352809040a31a Mon Sep 17 00:00:00 2001 From: Brian Chin Date: Mon, 9 Feb 2015 15:50:38 -0800 Subject: [PATCH 1/2] Adding the ArrayContentsObserver class. --- README.md | 29 +++++++++++++ src/observe.js | 116 ++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 144 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f56cb5a..da1454a 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,35 @@ ArrayObserver also exposes a utility function: `applySplices`. The purpose of `a AraryObserver.applySplices = function(previous, current, splices) { } ``` +### ArrayContentsObserver + +ArrayContentsObserver observes the state of the given array, as well as the elements of the array itself. To observe the elements, the constructor takes a function which, when passed an element of the array, returns a new observer object for observing that element. + +```JavaScript +var arr = [{foo: 1, bar: 2}]; +var observerFactory = function(elem) { + return new ObjectObserver(elem); +}; +var observer = ArrayContentsObserver(arr, observerFactory); +observer.open(function(type, data) { + switch (type) { + case 'splices': { + // "data" is an array of splice arguments, as with ArrayObserver + break; + } + + case 'elemChange': { + data.index; // The index of the array that the change occured at. + data.changeArgs; // The array of arguments that were passed to the + // element's observation function + } + } +}); + +arr[0].foo = 2; // Function called with ['elemChanged', [{}, {}, {foo: 2}]] +arr.push({foo: 3, bar: 4}); // Function called with ['splices', [{index: 1, removed: [], addedCount: 1}]] +``` + ### ObjectObserver ObjectObserver observes the set of own-properties of an object and their values. diff --git a/src/observe.js b/src/observe.js index f96ba57..a9dcb1d 100644 --- a/src/observe.js +++ b/src/observe.js @@ -973,6 +973,106 @@ }); }; + function ArrayContentsObserver(array, observerConstructor) { + if (!Array.isArray(array)) { + throw new Error('Input object not an array: ' + input); + } + Observer.call(this, array); + + this.elemObserverConstructor_ = observerConstructor; + this.elemObservers_ = null; + this.arrayObserver_ = null; + this.isSkippingChanges_ = false; + } + + ArrayContentsObserver.prototype = { + __proto__: Observer.prototype, + connect_: function() { + this.elemObservers_ = []; + + var self = this; + + this.value_.forEach(function(elem) { + self.elemObservers_.push(self.startElemObserver_(elem)); + }); + + this.arrayObserver_ = new ArrayObserver(this.value_); + this.arrayObserver_.open(function(splices) { + // Check to ensure that indexes are in increasing order + var currIndex = -1; + slices.forEach(function(splice) { + if (currIndex >= splice.index) { + throw new Error('Got bad ordering of splices'); + } + + currIndex = splice.index; + + self.handleSplice_(splice); + }); + + if (!this.isSkippingChanges_) { + this.report_(['splice', slices]); + } + }); + }, + + disconnect_: function() { + this.elemObservers_.forEach(function(obs) { + obs.close(); + }); + + this.elemObservers_ = null; + + this.arrayObserver_.close(); + this.arrayObserver_ = null; + }, + + check_: function(changeRecords, skipChanges) { + // changeRecords should always be null/undefined, since we only depend + // on the observer API. + if (skipChanges) { + for (var i = 0; i < this.elemObservers_; i++) { + this.elemObservers_[i].discardChanges(); + } + // Apply changes from the array, but don't report any messages. + this.isSkippingChanges_ = true; + this.arrayObserver_.dispatch(); + this.isSkippingChanges_ = false; + } + + return false; + }, + + startElemObserver_: function(elem) { + var observer = this.elemObserverConstructor_(elem); + var self = this; + observer.open(function() { + self.report_(['indexChange', { + index: self.elemObservers_.indexOf(observer), + changeArgs: arguments, + }]); + }); + return observer; + }, + + handleSplice_: function(splice) { + // Close the entries that have been removed. + for (var i = splice.index; i < splice.index + splice.removed.length; i++) { + this.tokenObservers[i].close(); + } + + var addedObservers = []; + + for (var i = splice.index; i < splice.index + splice.addedCount; i++) { + addedObservers.push(this.startElemObserver_(this.value_[i])); + } + + Array.prototype.splice.apply( + this.tokenObservers, + [splice.index, splice.removed.length].concat(addedObservers)); + }, + } + function PathObserver(object, path) { Observer.call(this); @@ -1158,6 +1258,19 @@ } }); + function CustomObjectObservable(object, types) { + Observable.call(this); + + this.object = object; + this.types = types; + } + + CustomObjectObservable.prototype = createObject({ + connect_: function() { + + } + }); + function identFn(value) { return value; } function ObserverTransform(observable, getValueFn, setValueFn, @@ -1700,6 +1813,7 @@ expose.ArrayObserver.calculateSplices = function(current, previous) { return arraySplice.calculateSplices(current, previous); }; + expose.ArrayContentsObserver = ArrayContentsObserver; expose.ArraySplice = ArraySplice; expose.ObjectObserver = ObjectObserver; @@ -1707,5 +1821,5 @@ expose.CompoundObserver = CompoundObserver; expose.Path = Path; expose.ObserverTransform = ObserverTransform; - + })(typeof global !== 'undefined' && global && typeof module !== 'undefined' && module ? global : this || window); From 8259a19ca52b23f90d59a8f5650052e315028308 Mon Sep 17 00:00:00 2001 From: Brian Chin Date: Mon, 9 Feb 2015 16:16:55 -0800 Subject: [PATCH 2/2] Remove unused dead code. --- src/observe.js | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/observe.js b/src/observe.js index a9dcb1d..fc8d24a 100644 --- a/src/observe.js +++ b/src/observe.js @@ -1258,19 +1258,6 @@ } }); - function CustomObjectObservable(object, types) { - Observable.call(this); - - this.object = object; - this.types = types; - } - - CustomObjectObservable.prototype = createObject({ - connect_: function() { - - } - }); - function identFn(value) { return value; } function ObserverTransform(observable, getValueFn, setValueFn,