listening-proxy
v1.0.4
Published
JavaScript Deep Proxy that can have listeners added
Downloads
6
Maintainers
Readme
Overview
A Javascript deep proxy that can have listeners added (an event listener based alternative to both Proxy and the defunct Object.observe()).
Example
import { CreateListeningProxy, EVENT_TYPE_BEFORE_CHANGE, EVENT_TYPE_AFTER_CHANGE } from 'listening-proxy.js';
const myOriginalObject = {
foo: 'bar',
buzz: false
};
const myProxyObject = CreateListeningProxy(myOriginalObject);
// add some listeners...
myProxyObject.addListener(EVENT_TYPE_BEFORE_CHANGE, evt => {
console.log('First listener', evt);
if (evt.action === 'set' && evt.property === 'buzz' && evt.value === true) {
// stop other 'beforeChange' listeners firing...
evt.stopPropagation();
}
});
myProxyObject.addListener(EVENT_TYPE_BEFORE_CHANGE, evt => {
console.log('Second listener', evt);
if (evt.action === 'set' && evt.property === 'foo') {
// stop the property actually being set...
// (will also stop any 'afterChange' listeners firing)
evt.preventDefault();
}
});
myProxyObject.addListener(EVENT_TYPE_AFTER_CHANGE, evt => {
console.log('Third listener', evt);
});
// now make some changes to our object...
myProxyObject.foo = 'blah';
console.log('Foo should still be bar', myProxyObject.foo);
myProxyObject.buzz = true;
Advantages over normal Proxy
- Uses a single handler - that many listeners can hook into
addListener()
style similar toaddEventListener()
- Deep listening (i.e. deep proxy)
- Add listeners at any level in the object tree
- Objects in tree shared in other trees fire all listeners
- Familiar
event.preventDefault()
andevent.stopPropogation()
within listeners beforeChange
andafterChange
eventsgetProperty
events - allow 'simulating' properties/functions that aren't really there (without messing with prototype)- Multiple event listeners - with propagation prevention
- Proxy listen on special objects (with event notification of all setter/change methods)
- Typed Arrays
- Date
- Set
- Map
- and class instances
Reference
Exports
Creating the listening proxy
import { CreateListeningProxy } from 'listening-proxy.js';
let obj = {
'foo': 'bar'
};
let objProxy = CreateListeningProxy(obj);
Determining if an object is a listening proxy
import { CreateListeningProxy, SYMBOL_IS_PROXY } from 'listening-proxy.js';
let obj = {
'foo': 'bar'
};
let objProxy = CreateListeningProxy(obj);
// see if each is a proxy...
console.log( obj[SYMBOL_IS_PROXY] ); // expect output: undefined
console.log( myProxy[SYMBOL_IS_PROXY] ); // expect output: true
Obtaining the underlying target object of a listening proxy
import { CreateListeningProxy, SYMBOL_PROXY_TARGET } from 'listening-proxy.js';
let obj = {
'foo': 'bar'
};
let objProxy = CreateListeningProxy(obj);
// get the target...
let target = myProxy[SYMBOL_PROXY_TARGET];
Creating a listening proxy on an object that is already a listening proxy?
Don't Panic! You do not need to check if the object is already a listening proxy - creating a listening proxy on an object that is already a listening proxy will just return the original listening proxy.
import { CreateListeningProxy } from 'listening-proxy.js';
let obj = {
'foo': 'bar'
};
let objProxy = CreateListeningProxy(obj);
let anotherProxy = CreateListeningProxy(objProxy);
console.log(objProxy === anotherProxy); // output: true
Adding listeners when creating a listening proxy
There are two ways to achieve this...
import { CreateListeningProxy, EVENT_TYPE_BEFORE_CHANGE, EVENT_TYPE_BEFORE_CHANGE } from 'listening-proxy.js';
let obj = {
'foo': 'bar'
};
let objProxy = CreateListeningProxy(obj)
.addListener(EVENT_TYPE_BEFORE_CHANGE, evt => {
console.log('Before change', evt.snapshot);
})
.addListener(EVENT_TYPE_AFTER_CHANGE, evt => {
console.log('After change', evt.snapshot);
});
objProxy.foo = 'baz';
or...
import { CreateListeningProxy, EVENT_TYPE_BEFORE_CHANGE, EVENT_TYPE_BEFORE_CHANGE } from 'listening-proxy.js';
let obj = {
'foo': 'bar'
};
let objProxy = CreateListeningProxy(obj,
{
'eventType': EVENT_TYPE_BEFORE_CHANGE,
'listener': evt => {
console.log('Before change', evt.snapshot);
}
},
{
'eventType': EVENT_TYPE_AFTER_CHANGE,
'listener': evt => {
console.log('After change', evt.snapshot);
}
}
);
objProxy.foo = 'baz';
Can I add listeners to different parts of an object tree?
Yes!...
import { CreateListeningProxy, EVENT_TYPE_BEFORE_CHANGE, EVENT_TYPE_BEFORE_CHANGE } from 'listening-proxy.js';
let obj = {
foo: {
bar: {
baz: {
qux: true
}
}
}
};
let objProxy = CreateListeningProxy(obj)
.addListener(EVENT_TYPE_BEFORE_CHANGE, evt => {
console.log('Before change', evt.snapshot);
})
.addListener(EVENT_TYPE_AFTER_CHANGE, evt => {
console.log('After change', evt.snapshot);
});
let sub = objProxy.foo.bar;
sub.addListener(EVENT_TYPE_BEFORE_CHANGE, evt => {
console.log('Sub before change', evt.snapshot);
}).addListener(EVENT_TYPE_AFTER_CHANGE, evt => {
console.log('Sub after change', evt.snapshot);
});
objProxy.foo.bar.baz.qux = false; // will fire all 4 event listeners!
sub.baz.qux = true; // will also fire all 4 event listeners!
// note that listeners added at different parts of the tree - the event .path property is relative!
Listeners & Event Reference
EVENT_TYPE_BEFORE_CHANGE
Listen for changes prior to them being enacted on the underlying target
Example:
import { CreateListeningProxy, EVENT_TYPE_BEFORE_CHANGE } from 'listening-proxy.js';
const obj = { foo: 'bar' };
const proxy = CreateListeningProxy(obj);
proxy.addListener(EVENT_TYPE_BEFORE_CHANGE, event => {
// handle the 'event' as instance of BeforeChangeEvent
});
EVENT_TYPE_AFTER_CHANGE
Listen for changes after they have been enacted on the underlying target
Example:
import { CreateListeningProxy, EVENT_TYPE_AFTER_CHANGE } from 'listening-proxy.js';
const obj = { foo: 'bar' };
const proxy = CreateListeningProxy(obj);
proxy.addListener(EVENT_TYPE_AFTER_CHANGE, event => {
// handle the 'event' as instance of AfterChangeEvent
});
EVENT_TYPE_GET_PROPERTY
Listen for all get property actions on an object (this includes gets for functions/methods)
Example:
import { CreateListeningProxy, EVENT_TYPE_GET_PROPERTY } from 'listening-proxy.js';
const obj = { foo: 'bar' };
const proxy = CreateListeningProxy(obj);
proxy.addListener(EVENT_TYPE_GET_PROPERTY, event => {
// handle the 'event' as instance of GetPropertyEvent
});
EVENT_TYPE_EXCEPTION_HANDLER
Listen for exceptions in other listeners.
By default, listening proxy 'swallows' any exceptions throwm/encountered within listeners (although they are still output as console errors). By adding an EVENT_TYPE_EXCEPTION_HANDLER listener such exceptions can be handled and, if required, surfaced.
Example:
import { CreateListeningProxy, EVENT_TYPE_EXCEPTION_HANDLER } from 'listening-proxy.js';
const obj = { foo: 'bar' };
const proxy = CreateListeningProxy(obj);
proxy.addListener(EVENT_TYPE_EXCEPTION_HANDLER, event => {
// handle the 'event' as instance of ExceptionHandlerEvent
// example to surface exception...
throw event.exception;
});