Skip to content
This repository has been archived by the owner on Mar 13, 2018. It is now read-only.

Commit

Permalink
move property binding to the prototype
Browse files Browse the repository at this point in the history
  • Loading branch information
rafaelw committed Apr 11, 2014
1 parent 812e8a4 commit 762f600
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 90 deletions.
32 changes: 6 additions & 26 deletions README.md
Expand Up @@ -12,16 +12,16 @@ observe-js implements a set of observers (PathObserver, ArrayObserver, ObjectObs

```JavaScript
{
// Begins observation. Value changes will be reported by invoking |changeFn| with |opt_receiver| as
// Begins observation. Value changes will be reported by invoking |changeFn| with |opt_receiver| as
// the target, if provided. Returns the initial value of the observation.
open: function(changeFn, opt_receiver) {},

// Report any changes now (does nothing if there are no changes to report).
deliver: function() {}

// If there are changes to report, ignore them. Returns the current value of the observation.
discardChanges: function() {},

// Ends observation. Frees resources and drops references to observed objects.
close: function() {},
}
Expand Down Expand Up @@ -182,8 +182,8 @@ transform.deliver(); // 'new: 14, old: 6'
// Returns the current of the path from the provided object. If eval() is available, a compiled getter will be
// used for better performance.
getValueFrom: function(obj) { }


// Attempts to set the value of the path from the provided object. Returns true IFF the path was reachable and
// set.
setValueFrom: function(obj, newValue) { }
Expand All @@ -192,26 +192,6 @@ transform.deliver(); // 'new: 14, old: 6'

Path objects are unique (e.g. `assert(Path.get('foo.bar.baz') === Path.get('foo.bar.baz'));`) and are used internally to avoid excessive parsing of path strings. Observers which take path strings as arguments will also accept Path objects.

### Computed Properties
`Observer.defineComputedProperty` creates an ES5 accessor which acts as a computed property, given an observable:


```JavaScript
var obj = { a: { b: 1 } };
var target = { };

var observer = new PathObserver(obj, 'a.b');
var closer = Observer.defineComputedProperty(target, 'myProp', observer);

assert(obj.a.b === target.myProp);

obj.a.b = 2;
assert(obj.a.b === target.myProp);

target.myProp = 3;
assert(obj.a.b === target.myProp);
```

## About delivery of changes

observe-js is intended for use in environments which implement Object.observe, but it supports use in environments which do not.
Expand Down
89 changes: 58 additions & 31 deletions src/observe.js
Expand Up @@ -1039,51 +1039,78 @@
delete: true
};

function notifyFunction(object, name) {
if (typeof Object.observe !== 'function')
var updateRecord = {
object: undefined,
type: 'update',
name: undefined,
oldValue: undefined
};

function notify(object, name, value, oldValue) {
if (!hasObserve || areSameValue(value, oldValue))
return;

var notifier = Object.getNotifier(object);
return function(type, oldValue) {
var changeRecord = {
object: object,
type: type,
name: name
};
if (arguments.length === 2)
changeRecord.oldValue = oldValue;
notifier.notify(changeRecord);
}
var notifier = object.notifier_;
if (!notifier)
notifier = object.notifier_ = Object.getNotifier(object);

updateRecord.object = object;
updateRecord.name = name;
updateRecord.oldValue = oldValue;

notifier.notify(updateRecord);
}

Observer.defineComputedProperty = function(target, name, observable) {
var notify = notifyFunction(target, name);
var value = observable.open(function(newValue, oldValue) {
value = newValue;
if (notify)
notify('update', oldValue);
});
Observer.createBindablePrototypeAccessor = function(proto, name) {
var privateName = name + '_';
var privateObservable = name + 'Observable_';

if (proto.hasOwnProperty(name))
proto[privateName] = proto[name];

Object.defineProperty(target, name, {
Object.defineProperty(proto, name, {
get: function() {
observable.deliver();
return value;
var observable = this[privateObservable];
if (observable)
observable.deliver();

return this[privateName];
},
set: function(newValue) {
observable.setValue(newValue);
return newValue;
set: function(value) {
var observable = this[privateObservable];
if (observable) {
observable.setValue(value);
return;
}

var oldValue = this[privateName];
this[privateName] = value;
notify(this, name, value, oldValue);

return value;
},
configurable: true
});
}

Observer.bindToInstance = function(instance, name, observable) {
var privateName = name + '_';
var privateObservable = name + 'Observable_';

instance[privateObservable] = observable;
var oldValue = instance[privateName];
var value = observable.open(function(value, oldValue) {
instance[privateName] = value;
notify(instance, name, value, oldValue);
});

instance[privateName] = value;
notify(instance, name, value, oldValue);

return {
close: function() {
observable.close();
Object.defineProperty(target, name, {
value: value,
writable: true,
configurable: true
});
instance[privateObservable] = undefined;
}
};
}
Expand Down
70 changes: 40 additions & 30 deletions tests/test.js
Expand Up @@ -845,26 +845,32 @@ suite('PathObserver Tests', function() {
observer.close();
});

test('DefineProperty Cascade', function() {
test('Bound property cascade', function() {
function MyClass() {}

Observer.createBindablePrototypeAccessor(MyClass.prototype, 'value');

var a = new MyClass;
var b = new MyClass;
var c = new MyClass;

var root = {
value: 1,
a: {
b: {}
},
c: {}
a: new MyClass,
c: new MyClass
};

var a = {};
var b = {};
var c = {};
root.a.b = new MyClass;

root.a.observer = Observer.defineComputedProperty(root.a, 'value',
root.a.observer = Observer.bindToInstance(root.a, 'value',
new PathObserver(root, 'value'));

root.a.b.observer = Observer.defineComputedProperty(root.a.b, 'value',
root.a.value;

root.a.b.observer = Observer.bindToInstance(root.a.b, 'value',
new PathObserver(root.a, 'value'));

root.c.observer = Observer.defineComputedProperty(root.c, 'value',
root.c.observer = Observer.bindToInstance(root.c, 'value',
new PathObserver(root, 'value'));

root.c.value = 2;
Expand All @@ -875,9 +881,13 @@ suite('PathObserver Tests', function() {
root.c.observer.close();
});

test('DefineProperty', function() {
test('Bound Property', function() {
function MyClass() {}

Observer.createBindablePrototypeAccessor(MyClass.prototype, 'computed');

var source = { foo: { bar: 1 }};
var target = {};
var target = new MyClass;
var changeRecords;
var callback;
if (typeof Object.observe === 'function') {
Expand All @@ -889,10 +899,9 @@ suite('PathObserver Tests', function() {
Object.observe(target, callback);
}

var observer = Observer.defineComputedProperty(target, 'computed',
var observer = Observer.bindToInstance(target, 'computed',
new PathObserver(source, 'foo.bar'));

assert.isTrue(target.hasOwnProperty('computed'));
assert.strictEqual(1, target.computed);

target.computed = 2;
Expand All @@ -917,18 +926,22 @@ suite('PathObserver Tests', function() {
assert.strictEqual(9, target.computed);

observer.close();
assert.isTrue(target.hasOwnProperty('computed'));
assert.strictEqual(9, target.computed);

if (!changeRecords)
return;

Object.deliverChangeRecords(callback);
changeRecords = changeRecords.filter(function(rec) {
return rec.name == 'computed';
});

assert.deepEqual(changeRecords, [
{
object: target,
name: 'computed',
type: 'add'
type: 'update',
oldValue: undefined
},
{
object: target,
Expand Down Expand Up @@ -959,28 +972,25 @@ suite('PathObserver Tests', function() {
name: 'computed',
type: 'update',
oldValue: undefined
},
{
object: target,
name: 'computed',
type: 'reconfigure'
}
]);

Object.unobserve(target, callback);
});

test('DefineProperty - empty path', function() {
var target = {}
var observer = Observer.defineComputedProperty(target, 'foo',
new PathObserver(1));
assert.isTrue(target.hasOwnProperty('foo'));
test('Bound Property - empty path', function() {
function MyClass() {}

Observer.createBindablePrototypeAccessor(MyClass.prototype, 'foo');
Observer.createBindablePrototypeAccessor(MyClass.prototype, 'bar');

var target = new MyClass;
var observer = Observer.bindToInstance(target, 'foo', new PathObserver(1));
assert.strictEqual(1, target.foo);

var obj = {};
var observer2 = Observer.defineComputedProperty(target, 'bar',
new PathObserver(obj));
assert.isTrue(target.hasOwnProperty('bar'));
var observer2 = Observer.bindToInstance(target, 'bar',
new PathObserver(obj));
assert.strictEqual(obj, target.bar);
});

Expand Down
10 changes: 7 additions & 3 deletions tests/test_array_reduction.js
Expand Up @@ -4,7 +4,12 @@ suite('ArrayReduction Tests',function(){
var reductionObserver;

setup(function(){
obj = {};
function TestClass() {}

Observer.createBindablePrototypeAccessor(TestClass.prototype,
'reducedValue');

obj = new TestClass;
array = [
{id:0},
{id:1}
Expand All @@ -15,8 +20,7 @@ suite('ArrayReduction Tests',function(){
return value;
}, []);

reductionObserver = Observer.defineComputedProperty(obj,'reducedValue',
reduction);
reductionObserver = Observer.bindToInstance(obj,'reducedValue', reduction);
});

teardown(function(){
Expand Down

0 comments on commit 762f600

Please sign in to comment.