1.0.4 • Published 3 years ago

listening-proxy v1.0.4

Weekly downloads
-
License
Apache-2.0
Repository
github
Last release
3 years ago

LOGO

npm GitHub

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 to addEventListener()
  • 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() and event.stopPropogation() within listeners
  • beforeChange and afterChange events
  • getProperty 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;
});

Supported On