Backbone Proxy
Model proxies for Backbone. Notably useful in applications where sharing a single model instance among many components (e.g. views) with different concerns is a common pattern. In such cases, the need for multiple components to reference the same model-encapsulated state prohibits the specialization of model-behaviour. Additionaly it leads to a design where 'all code is created equal', i.e. all components have the exact same priviledge-level in respect to data-access. BackboneProxy faciliates the creation of model-proxies that may be specialized in terms of behaviour while referencing the same, shared state.
For example, you can create:
Proxies that log attribute changes
// Create a UserProxy class. Instances will proxy the given user model// Note that user is a model _instance_ - not a classvar UserProxy = BackboneProxy; // Instantiate a logging proxy - a proxy that logs all invocations of .setvar userWithLog = ;userWithLog { console; // Delegate to UserProxy implementation UserProxyprototypeset;}; // Will not loguser; // Will log 'setting name to Mairy on user'userWithLog; // Will log 'user name is Mairy'console;
Readonly proxies
// Create a UserProxy classvar UserProxy = BackboneProxy; // Instantiate a readonly proxy - a proxy that throws on any invocation of .setvar userReadonly = ;userReadonly { throw 'cannot set attributes on readonly user model';}; // Attributes cannot be set on userReadonly// However attribute changes can be listened foruserReadonly; // Will throwuserReadonly; // Will alert 'user name was set to Mairy'user;
Vew-specific proxies
// Create a UserProxy classvar UserProxy = BackboneProxy; // Instantiate a proxy to pass to view1var userProxy1 = ;userProxy1 { // Handle both key/value and {key: value} - style arguments var attrs; if typeof key === 'object' attrs = key; opts = val; else attrs = {}key = val; // Automatically update lastUpdatedBy to 'view1' on every invocation of set attrslastUpdatedBy = 'view1'; // Delegate to UserProxy implementation UserProxyprototypeset;}; // Instantiate a proxy to pass to view2. Similar to userProxy1 -// will automatically set the lastUpdatedBy attr to 'view2'var userProxy2 = ;userProxy2 { var attrs; if typeof key === 'object' attrs = key; opts = val; else attrs = {}key = val; attrslastUpdatedBy = 'view2'; UserProxyprototypeset;}; var view1 = model: userProxy1 ;var view2 = model: userProxy2 ; // Will log modifications of user object '..by view1' / '..by view2'user;
Set up
- install with bower,
bower install backbone-proxy
, - install with npm,
npm install backbone-proxy_
or - just include the
latest stable
backbone-proxy.js
.
Backbone proxy may be used as an exported global, a CommonJS module or an AMD module depending on the current environment:
-
In projects targetting browsers, without an AMD module loader, include backbone-proxy.js after backbone.js:
......This will export the
BackboneProxy
global. -
require
when working with CommonJS (e.g. Node). Assuming BackboneProxy isnpm install
ed:var BackboneProxy = ; -
Or list as a dependency when working with an AMD loader (e.g. RequireJS):
// Your module;Note that the AMD definition of BackboneProxy depends on
backbone
andunderscore
so some loader setup will be required. For non-AMD compliant versions of Backbone (< 1.1.1) or Undescore (< 1.6.0), James Burke's amdjs forks may be used instead, along with the necessary paths configurationrequire;or you may prefer to just shim them.
At the time of this writing, BackboneProxy has only been tested against Backbone 1.2.1
Usage
BackboneProxy exposes a single extend
method as the means of creating a Proxy 'class' for any
given model
(or model proxy):
// Create Proxy class for given modelvar Proxy = BackboneProxy; // Instantiate any number of proxiesvar someProxy = ;var someOtherProxy = ; // Yes, you can proxy a proxyvar ProxyProxy = BackboneProxy;var someProxyProxy = ;
For any given proxied
/proxy
models that have a proxied-to-proxy relationship, the following
apply:
Any attribute set
on proxied
will be set on proxy
and vice versa (the same applies for
unset
and clear
):
proxied;console; // Will log 'Betty' proxy;console; // Will log 'Charles'
Built-in model events (add, remove, reset, change, destroy, request, sync, error, invalid)
triggered in response to actions performed on proxied
will also be triggered on proxy
. And vice
versa:
proxy;proxied; // Will log 'name set to Betty' proxied;proxy; // Will log 'model synced'
User-defined events triggered on proxied
will also be triggered on proxy
. The opposite is not
true:
proxied;proxy; proxied; // Will log 'a scare on proxied' & 'a scare on proxy'proxy; // Will only log 'a scare on proxy'
Additions and removals of event listeners are, generally speaking, 'scoped' to each model. That is to say, event listeners may be safely added on, and - primarily - removed from the proxied (/proxy) without affecting event listeners on the proxy (/proxied). This holds when removing listeners by callback or context. For example, when removing listeners by callback:
var { console;}; proxied;proxy; proxied; // Will log 'model changed' twice // Will only remove the listener previously added on proxiedproxied; proxied; // Will log 'model changed' once
Removing listeners by event name works similarly:
proxied;proxy; proxied; // Will log 'caught on proxied' & 'caught on proxy' // Will only remove the listener previously added on proxiedproxied; proxied; // Will only log 'caught on proxy'
However, removing listeners registered for the 'all' event presents a special case as it will
interfere with BackboneProxy's internal event-forwarding. Essentially, you should avoid
.off('all')
for models which are proxied - it will disable event notifications on the proxy:
proxy;proxy; // Will log 'changed name' & 'changed age'proxied; // Bad moveproxied; // Will log nothingproxied;
The model
argument passed to a listener will always be set to the model to which the listener was
added. The same is true for the context (as long as it's not explicitly set):
var { model; // Or alternatively, this.dump(); }; proxied { console;};proxy { console;}; proxied;proxy; proxied; // Will log 'proxied changed: ..' & 'proxy changed: ..' proxy;proxy; // Will log 'proxied changed: ..'
Backbone's 'overridables', i.e. properties and methods that affect model behaviour when set
(namely collection
, idAttribute
, sync
, parse
, validate
, url
, urlRoot
and toJSON
)
should be set on the proxied model. That is to say, the root proxied Backbone.Model
. Setting
them on a proxied model is not meant to (and will generally not) produce the intended result. As
an example:
// Setting a validate method on a proxy ..proxy { if attrsname === 'Betty' return 'Betty is not a valid name'; }; // .. will not work: This will log 'validation error: none'proxy;console; // Setting a validate method on the proxied ..proxied { if attrsname === 'Charles' return 'Charles is not a valid name'; }; // .. will produce the intended result:// This will log 'validation error: Charles is not a valid name'proxy;console;
Aside from the prior examples, the annotated version of the source is available as a reference.
Contributing ( / Testing )
Contributions are obviously appreciated. In lieu of a formal styleguide, take care to maintain the
existing coding style. Please make sure your changes test out green prior to pull requests. The
QUnit test suite may be run in a browser (test/index.html) or on the command line, by running
make test
or npm test
. The command line version runs on Node and depends on
node-qunit (npm install
to fetch it before testing). A
coverage report
is also available.
License
Licensed and freely distributed under the MIT License (LICENSE.txt).
Copyright (c) 2014 Alex Lambiris