├── 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 | --------------------------------------------------------------------------------