Skip to content

Commit

Permalink
Observable.remove() and tests.
Browse files Browse the repository at this point in the history
- Add Observable.prototype.remove()
- panoptic() now optionally takes root Observable
- Add tests for remove() and replace()
- Add remove(), updated replace() in README and extern
  • Loading branch information
rekow committed Dec 12, 2014
1 parent 7b1036b commit acd82b3
Show file tree
Hide file tree
Showing 6 changed files with 286 additions and 54 deletions.
43 changes: 41 additions & 2 deletions README.md
Expand Up @@ -9,7 +9,7 @@ Add to your `dependencies` in `package.json`:
```javascript
...
"dependencies": {
"panoptic": "~0.0.5",
"panoptic": "~0.0.6",
...
},
...
Expand Down Expand Up @@ -75,12 +75,51 @@ observable.set({ // as a fully-structured object - will be set as diff
```
Setting via object property syntax only works if the key has already been seen -
if you're adding a new key, use `set()` to ensure the observation chain is set up.
###removing
```javascript
observable.remove('a.b');
observable.set('a.b', null);
observable.a.b = null;
```
To remove a key from an observable object call `remove()`, or simply set it to `null`.
If the key currently points to a nested object, watchers for any existing nested
properties will be invoked before removing the key:
```javascript
observable = panoptic({
a: {
b: {
c: 1
}
}
});

observable.watch('a.b.c', function (value) {
console.log('"a.b.c" value: ' + value);
});
observable.watch('a.b.d', function (value) {
console.log('"a.b.d" value: ' + value);
});
observable.watch('a.b', function (value) {
console.log('"a.b" value: ' + value);
});

observable.remove('a.b');
```
outputs
```
"a.b.c" value: null
"a.b" value: null
```
Because the key `a.b.d` had not yet been set, its watcher did not fire.
###replacing
```javascript
observable.replace({key: 'newValue'});
```
Calling `replace()` replaces the current observed data entirely with the passed data,
triggering watchers for removed, modified and added keys.
triggering watchers for removed, modified and added keys. This method uses `remove()`
behind the scenes, so only watchers for existing properties will fire upon removal
or modification. Any already-registered watchers for properties being added will be
invoked.
###watching
```javascript
observable.watch('a.b.c', function (newValue) {
Expand Down
7 changes: 4 additions & 3 deletions dist/panoptic.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
@@ -1,7 +1,7 @@
{
"name": "panoptic",
"description": "see all, do all. simple object keypath observers.",
"version": "0.0.5",
"version": "0.0.6",
"license": "MIT",
"author": {
"name": "David Rekow",
Expand Down
7 changes: 6 additions & 1 deletion src/extern.js
Expand Up @@ -22,6 +22,11 @@ Observable.prototype.get = function (key) {};
*/
Observable.prototype.set = function (key, value) {};

/**
* @param {string} key
*/
Observable.prototype.remove = function (key) {};

/**
* @param {Object.<string, ?>} data
*/
Expand All @@ -45,6 +50,6 @@ Observable.prototype.unwatch = function (key, observer) {};
Observable.prototype.toJSON = function () {};

/**
* @typedef {function(?): Observable}
* @typedef {function(?, Observable=): Observable}
*/
var panoptic;
109 changes: 76 additions & 33 deletions src/index.js
Expand Up @@ -66,26 +66,62 @@ Observable.prototype = {

return;
}

Observable.resolve(this, /** @type {string} */(key), value);
},

/**
* Removes a key from the observed object, triggering all nested watchers.
* @expose
* @param {string} key
*/
remove: function (key) {
var k;

if (this._root)
return this._root.remove(this._path + '.' + key);

for (k in this._cb) {
if (this._cb.hasOwnProperty(k) &&
k.indexOf(key) === 0 &&
k !== key &&
this.get(k))
this.emit(k, null, this);
}

this[key] = undefined;
},

/**
* Replaces the current observed object with a new object. Triggers change events for
* all removed, modified and added keys.
* @param {Object.<string, ?>} data
* @expose
* @param {!Object.<string, ?>} data
* @throws {TypeError} If data is not an object.
*/
replace: function (data) {
var current = this._prop,
key;
/*jshint eqnull:true */
var added = {},
key, value;

for (key in current) {
if (current.hasOwnProperty(key) && data[key] == null)
this.set(key, null);
if (typeof data !== 'object')
throw new TypeError('Fragment.replace() expects an object as the only parameter.');

for (key in this._prop) {
if (this._prop.hasOwnProperty(key) && data[key] === undefined)
this[key] = null;
}

this._prop = {};
for (key in data) {
if (data.hasOwnProperty(key)) {
value = this.get(key);

if (value instanceof Observable)
value.replace(data[key] || {});

this.set(data);
this.set(key, data[key]);
}
}
},

/**
Expand Down Expand Up @@ -159,29 +195,6 @@ Observable.prototype = {
*/
constructor: Observable,

/**
* Emits a change event for a particular key.
* @private
* @param {string} key
* @param {?} value
* @param {Observable} observed
*/
emit: function (key, value, observed) {
var observers;

if (this._root)
return this._root.emit(this._path + '.' + key, value, observed);

observers = this._cb[key];

if (!observers)
return;

observers.forEach(function (observer) {
observer.call(observed, value);
});
},

/**
* Binds observation to a particular key path and value.
* @private
Expand Down Expand Up @@ -211,6 +224,9 @@ Observable.prototype = {
* @param {?} value
*/
set: function (value) {
if (value === null)
return this.remove(key);

this._prop[key] = value;
this.emit(key, value, this);
}
Expand All @@ -219,6 +235,29 @@ Observable.prototype = {
this.set(key, value);
},

/**
* Emits a change event for a particular key.
* @private
* @param {string} key
* @param {?} value
* @param {Observable} observed
*/
emit: function (key, value, observed) {
var observers;

if (this._root)
return this._root.emit(this._path + '.' + key, value, observed);

observers = this._cb[key];

if (!observers)
return;

observers.forEach(function (observer) {
observer.call(observed, value);
});
},

/**
* Sets another Observable object as the root for the current object.
* @private
Expand Down Expand Up @@ -298,10 +337,14 @@ Observable.resolve = function (observed, key, value) {
* Return a new Observable object.
* @expose
* @param {?} data
* @param {Observable=} root
* @return {Observable}
*/
var panoptic = function (data) {
return new Observable(data);
var panoptic = function (data, root) {
if (data instanceof Observable)
return root ? (data._root = root, data) : data;

return new Observable(data, root);
};

if (typeof window !== 'undefined' && window.self === window) {
Expand Down

0 comments on commit acd82b3

Please sign in to comment.