├── LICENSE
├── README.md
├── demo.js
├── lib
└── backscatter.js
└── package.json
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Tal Weinfeld
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # backscatter
2 | A reactive extension for Backbone Models
3 |
4 | Backscatter is a small Backbone extension that notifies you of events _anywhere_ in your Backbone model tree, no matter how deeply-nested they are. It's a great companion to React, since it enables you to carelessly trigger refreshes of your entire React tree whenever one or more base model(s) or their nested members change.
5 |
6 | ## Installation
7 |
8 | ### NPM
9 | ```sh
10 | npm install backscatter
11 | ```
12 |
13 | ## Types of intercepted events
14 | Backscatter is a "catchall" listener. Anything that triggers an "all" event on your Model/Collection will be relayed by it.
15 |
16 | ## createFactory(BackboneClass) -> BackboneClass
17 |
18 | If you already have custom Backbone models and collections defined in your projects, you can extend them using "createFactory" so they can be used by Backscatter.
19 |
20 | ## Backscatter.Model / Backscatter.Collection
21 |
22 | An extension of Backbone's native Model and Collection which contains "**backscatterOn**" and "**backscatterOff**"
23 |
24 | ### backscatterOn(handler)
25 |
26 | `handler` will be called whenever the Model/Collection `backscatterOn` is invoked on or any of its decendants (close or remote) trigger an 'all' event.
27 |
28 | The arguments passed to `handler` are Backbone's original 'all' event-handler arguments (target, event name etc.)
29 |
30 | _`handler` might be triggered several times sequently. For instance: Models that are members of a collection will trigger one event for the model, and another for the collection they're in, both will be intercepted by `handler`. Since you may be interested only in one of them, you can use underscore ".debounce()"_
31 |
32 | ### backscatterOff(handler)
33 |
34 | Removes the binding to `handler'. It's best to call this when the view hosting your react component dies.
35 |
36 | ## Examples
37 |
38 | # TodoMVC
39 |
40 | Check out [Backscatter's TodoMVC sample](https://github.com/tweinfeld/backscatter-todomvc).
41 |
42 | # Sample
43 |
44 | ```javascript
45 | import _ from 'underscore';
46 | import Backbone from 'backbone';
47 | import Backscatter from './lib/backscatter.js';
48 | import React from 'react';
49 |
50 | class MyCustomComponent extends React.Component {
51 | render(){
52 | return
{ this.props.title }, { this.props.name }
53 | }
54 | }
55 |
56 | // This model is an example of an existing model that's extended to enable backscatter updates (see "createFactory")
57 | let MyExistingModel = Backbone.Model.extend({ defaults: { id: "name", name: "John Doe" } });
58 |
59 | let A = new Backscatter.Model({ id: "title", "title": `Howdy` }),
60 | B = new (Backscatter.createFactory(MyExistingModel)),
61 | C = new Backscatter.Model({ "a": A, "b": B }),
62 | D = new Backscatter.Collection([C]);
63 |
64 | let renderComponent = function(){
65 | React.render(React.createElement(MyCustomComponent, { title: D.at(0).get('a').get('title'), name: D.at(0).get('b').get('name') }), document.querySelector('body'));
66 | };
67 |
68 | // Set backscatter to render your component whenever there are changes to your model
69 | D.backscatterOn(_.debounce(function(...[target, name]){
70 | console.log(`We've got a change on "${target.id}" with event name "${name}"`)
71 | renderComponent();
72 | }));
73 |
74 | // Perform a change somewhere in your model, and let backscatter react
75 | setTimeout(function(){
76 | // Let's touch our model somewhere in a deeply nested location
77 | A.set({ "title": `Hello` })
78 | }, 1000);
79 |
80 | setTimeout(function(){
81 | // Let's touch our model somewhere else in a deeply nested location
82 | B.set({ "name": `Mark Smith` })
83 | }, 2000);
84 |
85 | renderComponent();
86 | ```
87 |
--------------------------------------------------------------------------------
/demo.js:
--------------------------------------------------------------------------------
1 | import _ from 'underscore';
2 | import Backbone from 'backbone';
3 | import Backscatter from './lib/backscatter.js';
4 | import React from 'react';
5 |
6 | class MyCustomComponent extends React.Component {
7 | render(){
8 | return { this.props.title }, { this.props.name }
9 | }
10 | }
11 |
12 | // This model is an example of an existing model that's extended to enable backscatter updates (see "createFactory")
13 | let MyExistingModel = Backbone.Model.extend({ defaults: { id: "name", name: "John Doe" } });
14 |
15 | let A = new Backscatter.Model({ id: "title", "title": `Howdy` }),
16 | B = new (Backscatter.createFactory(MyExistingModel)),
17 | C = new Backscatter.Model({ "a": A, "b": B }),
18 | D = new Backscatter.Collection([C]);
19 |
20 | let renderComponent = function(){
21 | React.render(React.createElement(MyCustomComponent, { title: D.at(0).get('a').get('title'), name: D.at(0).get('b').get('name') }), document.querySelector('body'));
22 | };
23 |
24 | // Set backscatter to render your component whenever there are changes to your model
25 | D.backscatterOn(_.debounce(function(...[target, name]){
26 | console.log(`We've got a change on "${target.id}" with event name "${name}"`)
27 | renderComponent();
28 | }));
29 |
30 | // Perform a change somewhere in your model, and let backscatter react
31 | setTimeout(function(){
32 | // Let's touch our model somewhere in a deep nested location
33 | A.set({ "title": `Hello` })
34 | }, 1000);
35 |
36 | setTimeout(function(){
37 | // Let's touch our model somewhere else in a deep nested location
38 | B.set({ "name": `Mark Smith` })
39 | }, 2000);
40 |
41 | renderComponent();
--------------------------------------------------------------------------------
/lib/backscatter.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | (function(factory){
4 |
5 | if(typeof define === 'function' && define.amd){
6 | define(["underscore", "backbone"], factory);
7 | } else if (typeof exports !== 'undefined'){
8 | module.exports = factory(require('underscore'), require('backbone'));
9 | } else {
10 | window["Backscatter"] = factory(window["_"], window["Backbone"]);
11 | }
12 |
13 | })(function(_, Backbone){
14 |
15 | var exports = {},
16 | dispatchers = [],
17 | sink = function(){
18 | var args = _.toArray(arguments);
19 | dispatchers.forEach(function(dispatcher){
20 | // Each dispatcher is deferred
21 | _.defer(function(){
22 | dispatcher.apply(undefined, args);
23 | });
24 | });
25 | };
26 |
27 | var validateAncestry = function(root, target){
28 | return (function scan(current){
29 | return current === target ||
30 | (
31 | [Backbone.Model, Backbone.Collection].reduce(function(ac, cls){ return ac || (current instanceof cls); }, false) &&
32 | !!~(current.models || current.values()).map(scan).indexOf(true)
33 | );
34 | })(root);
35 | };
36 |
37 | exports["createFactory"] = function(SourceEntity){
38 | return SourceEntity.extend({
39 | initialize: function(){
40 | // Hook for all event, rely them to "sink" to deliver to all registered observers
41 | this.on('all', _.partial(sink, this));
42 |
43 | // Call "initialize" on the source entity
44 | SourceEntity.prototype.initialize.call(this);
45 | },
46 | backscatterOn: function(handler){
47 | var _this = this,
48 | wrapper = function(target){
49 | // Validate if the ancestry chain is valid
50 | validateAncestry(_this, target) && handler.apply(undefined, _.toArray(arguments))
51 | };
52 |
53 | wrapper._handler = handler;
54 | dispatchers.push(wrapper);
55 | return handler;
56 | },
57 | backscatterOff: function(handler){
58 | var index = _.pluck(dispatchers, '_handler').indexOf(handler);
59 | dispatchers.splice(index, ~index ? 1 : 0);
60 | return handler;
61 | }
62 | });
63 | };
64 |
65 | return _.extend(exports, {
66 | Model: exports["createFactory"](Backbone.Model),
67 | Collection: exports["createFactory"](Backbone.Collection)
68 | });
69 |
70 | });
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "backscatter",
3 | "version": "1.0.4",
4 | "description": "A reactive library for Backbone",
5 | "main": "./lib/backscatter.js",
6 | "dependencies": {
7 | "backbone": "^1.2.3",
8 | "underscore": "^1.8.3"
9 | },
10 | "devDependencies": {},
11 | "scripts": {
12 | "test": "echo \"Error: no test specified\" && exit 1"
13 | },
14 | "repository": {
15 | "type": "git",
16 | "url": "git+https://github.com/tweinfeld/backscatter.git"
17 | },
18 | "keywords": [
19 | "reactive",
20 | "backbone",
21 | "trigger",
22 | "event",
23 | "changes",
24 | "refresh",
25 | "react"
26 | ],
27 | "author": "Tal Weinfeld",
28 | "license": "ISC",
29 | "bugs": {
30 | "url": "https://github.com/tweinfeld/backscatter/issues"
31 | },
32 | "homepage": "https://github.com/tweinfeld/backscatter#readme"
33 | }
34 |
--------------------------------------------------------------------------------