Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/biril/backbone-proxy
Model proxies for Backbone
https://github.com/biril/backbone-proxy
backbone backbonejs model
Last synced: 1 day ago
JSON representation
Model proxies for Backbone
- Host: GitHub
- URL: https://github.com/biril/backbone-proxy
- Owner: biril
- License: mit
- Created: 2014-08-16T21:50:21.000Z (over 10 years ago)
- Default Branch: master
- Last Pushed: 2015-03-01T13:09:42.000Z (almost 10 years ago)
- Last Synced: 2025-01-23T07:03:28.260Z (16 days ago)
- Topics: backbone, backbonejs, model
- Language: JavaScript
- Size: 845 KB
- Stars: 4
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE.txt
Awesome Lists containing this project
README
Backbone Proxy
==============[![Build Status](https://travis-ci.org/biril/backbone-proxy.png)](https://travis-ci.org/biril/backbone-proxy)
[![NPM version](https://badge.fury.io/js/backbone-proxy_.png)](http://badge.fury.io/js/backbone-proxy_)
[![Bower version](https://badge.fury.io/bo/backbone-proxy.png)](http://badge.fury.io/bo/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
```javascript
// Create a UserProxy class. Instances will proxy the given user model
// Note that user is a model _instance_ - not a class
var UserProxy = BackboneProxy.extend(user);// Instantiate a logging proxy - a proxy that logs all invocations of .set
var userWithLog = new UserProxy();
userWithLog.set = function (key, val) {
console.log('setting ' + key + ' to ' + val + ' on user');// Delegate to UserProxy implementation
UserProxy.prototype.set.apply(this, arguments);
};// Will not log
user.set('name', 'Norman');// Will log 'setting name to Mairy on user'
userWithLog.set('name', 'Mairy');// Will log 'user name is Mairy'
console.log('user name is ' + user.get('name'));
```#### Readonly proxies
```javascript
// Create a UserProxy class
var UserProxy = BackboneProxy.extend(user);// Instantiate a readonly proxy - a proxy that throws on any invocation of .set
var userReadonly = new UserProxy();
userReadonly.set = function () {
throw 'cannot set attributes on readonly user model';
};// Attributes cannot be set on userReadonly
// However attribute changes can be listened for
userReadonly.on('change:name', function () {
alert('user name was set to ' + userReadonly.get('name'));
});// Will throw
userReadonly.set('name', 'Mairy');// Will alert 'user name was set to Mairy'
user.set('name', 'Mairy');
```#### View-specific proxies
```javascript
// Create a UserProxy class
var UserProxy = BackboneProxy.extend(user);// Instantiate a proxy to pass to view1
var userProxy1 = new UserProxy();
userProxy1.set = function (key, val, opts) {// 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
attrs.lastUpdatedBy = 'view1';// Delegate to UserProxy implementation
UserProxy.prototype.set.call(this, attrs, opts);
};// Instantiate a proxy to pass to view2. Similar to userProxy1 -
// will automatically set the lastUpdatedBy attr to 'view2'
var userProxy2 = new UserProxy();
userProxy2.set = function (key, val, opts) {
var attrs;
if (typeof key === 'object') { attrs = key; opts = val; }
else { (attrs = {})[key] = val; }attrs.lastUpdatedBy = 'view2';
UserProxy.prototype.set.call(this, attrs, opts);
};var view1 = new SomeView({ model: userProxy1 });
var view2 = new SomeOtherView({ model: userProxy2 });// Will log modifications of user object '..by view1' / '..by view2'
user.on('change', function () {
console.log('user modified by ' + user.get('lastUpdatedBy'));
});```
Set up
------* install with bower, `bower install backbone-proxy`,
* install with npm, `npm install backbone-proxy_` (please note the intentional underscore) or
* just include the
[latest stable `backbone-proxy.js`](https://github.com/biril/backbone-proxy/releases).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:```html
...
...
```This will export the `BackboneProxy` global.
* `require` when working _with CommonJS_ (e.g. Node). Assuming BackboneProxy is `npm install`ed:
```javascript
var BackboneProxy = require('backbone-proxy');
```* Or list as a dependency when working _with an AMD loader_ (e.g. RequireJS):
```javascript
// Your module
define(['backbone-proxy'], function (BackboneProxy) {
// ...
});
```Note that the AMD definition of BackboneProxy depends on `backbone` and `underscore` 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](https://github.com/amdjs) may be used instead,
along with the necessary paths configuration```javascript
require.config({
baseUrl: 'myapp/',
paths: {
'underscore': 'mylibs/underscore',
'backbone': 'mylibs/backbone'
}
});
```or you may prefer to just [shim them](http://requirejs.org/docs/api.html#config-shim).
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):```javascript
// Create Proxy class for given model
var Proxy = BackboneProxy.extend(model);// Instantiate any number of proxies
var someProxy = new Proxy();
var someOtherProxy = new Proxy();// Yes, you can proxy a proxy
var ProxyProxy = BackboneProxy.extend(someProxy);
var someProxyProxy = new ProxyProxy();
```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`):```javascript
proxied.set({ name: 'Betty' });
console.log(proxy.get('name')); // Will log 'Betty'proxy.set({ name: 'Charles' });
console.log(proxied.get('name')); // 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:```javascript
proxy.on('change:name', function (model) {
console.log('name set to ' + model.get('name'))
});
proxied.set({ name: 'Betty' }); // Will log 'name set to Betty'proxied.on('sync', function () {
console.log('model synced');
});
proxy.fetch(); // Will log 'model synced'```
User-defined events triggered on `proxied` will also be triggered on `proxy`. The opposite is _not_
true:```javascript
proxied.on('boo', function () {
console.log('a scare on proxied');
});
proxy.on('boo', function () {
console.log('a scare on proxy');
});proxied.trigger('boo'); // Will log 'a scare on proxied' & 'a scare on proxy'
proxy.trigger('boo'); // 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:```javascript
var onModelChange = function (model) {
console.log('model changed');
};proxied.on('change', onModelChange);
proxy.on('change', onModelChange);proxied.set({ name: 'Betty' }); // Will log 'model changed' twice
// Will only remove the listener previously added on proxied
proxied.off(null, onModelChange);proxied.set({ name: 'Charles' }); // Will log 'model changed' once
```Removing listeners by event name works similarly:
```javascript
proxied.on('change:name', function () {
console.log('caught on proxied');
});
proxy.on('change:name', function () {
console.log('caught on proxy');
});proxied.set({ name: 'Betty' }); // Will log 'caught on proxied' & 'caught on proxy'
// Will only remove the listener previously added on proxied
proxied.off('change:name');proxied.set({ name: 'Charles' }); // 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:```javascript
proxy.on('change:name', function () {
console.log('changed name');
});
proxy.on('change:age', function () {
console.log('changed age');
});// Will log 'changed name' & 'changed age'
proxied.set({
name: 'Betty',
age: 29
});// Bad move
proxied.off('all');// Will log nothing
proxied.set({
name: 'Charles',
age: 31
});
```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):```javascript
var onModelChanged = function (model) {
model.dump(); // Or alternatively, this.dump();
};proxied.dump = function () {
console.log('proxied changed: ' + JSON.stringify(this.changedAttributes()));
};
proxy.dump = function () {
console.log('proxy changed: ' + JSON.stringify(this.changedAttributes()));
};proxied.on('change', onModelChanged);
proxy.on('change', onModelChanged);proxied.set({ name: 'Betty' }); // Will log 'proxied changed: ..' & 'proxy changed: ..'
proxy.off(null, onModelChanged);
proxy.set({ name: 'Charles' }); // 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:```javascript
// Setting a validate method on a proxy ..
proxy.validate = function (attrs) {
if (attrs.name === 'Betty') {
return 'Betty is not a valid name';
}
};// .. will not work: This will log 'validation error: none'
proxy.set({ name: 'Betty' }, { validate: true });
console.log('validation error: ' + (proxy.validationError || 'none'));// Setting a validate method on the proxied ..
proxied.validate = function (attrs) {
if (attrs.name === '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.set({ name: 'Charles' }, { validate: true });
console.log('validation error: ' + (proxy.validationError || 'none'));```
Aside from the prior examples, the
[annotated version of the source](http://biril.github.io/backbone-proxy/) 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](https://github.com/kof/node-qunit) (`npm install` to fetch it before testing). A
[coverage report](http://biril.github.io/backbone-proxy/lcov-report/backbone-proxy/backbone-proxy.js.html)
is also available.License
-------Licensed and freely distributed under the MIT License (LICENSE.txt).
Copyright (c) 2014-2015 Alex Lambiris