Synapse
Data Binding For The Rest Of Us
Learn
Background
Synapse is a JavaScript data binding library. The API was written
                    to work all-in-code, that is, it does not depend on
                    any template language (e.g. Handlebars) or special attributes (e.g.
                    data-bind) to work.
The API design focus has been placed on making the requirements and options of data binding transparent for developers. There should not be any magic with data binding, since that magic can quickly turn opaque when debugging. Being pragmatic about the design enables Synapse to be a modular foundation for writing additional layers of abstraction on top (such as integration with a template language).
Fundamentally, there are two parties involved: subjects and observers. Keep the following in mind.
Any object can be an observer. It simply waits for the subject's state to change and then does something as a result. In the context of data binding, it may store the subject's state that changed or perform an action as a result of the change.
Subjects on the other hand must have a mechanism for being aware of actions
                    or events that are performed on itself. In the case of the DOM, when a user
                    clicks a button, the onclick event is triggered on that DOM
                    element. These actions can cause a cascade of events all of which can be used
                    to respond to the state changes.
Channels
Synapse provides a mechanism for defining a communication channel
                    between two objects. In order for two objects to communicate, there
                    are three components necessary for A
                    → B:
- The event that will trigger when A's state changes
- The function to call on/for Athat returns a representation of the changed state (typically the data that has changed)
- The function to call on/for Bthat handles this received data fromA
The channel can be defined with respect to either the subject A
                    or the observer B depending on the system. In either case, whenever
                    a change in state occurs in A, B (and all other
                    observers of A) will be notified.
var A = $('input'),
    B = $('span');
A.bind('keyup', function() {
    B.text(A.val());
});
// Synapse abstracts away the need to write the spaghetti
// code above. The following are functionally equivalent
Synapse(A).notify(B);
Synapse(B).observe(A);
                
            Interfaces
The above examples explain the most simple interactions between two objects. But how is data returned from or sent to each object if they are of different types?
Synapse interfaces provide a way to generically get and
                    set properties
                    on supported objects. A Synapse object has get and
                    set methods which allow for a consistent API regardless of the
                    underlying object type.
The properties that are utilized may or may not cleaning
                    map to the underlying object's properties (or methods). For example,
                    an available interface for the jQuery hook is value. As
                    implied on the right, doing A.set('value', 5) actually calls
                    a.val(5).
// Using Synapse as a constructor exposes the interface
// methods. We are interfacing three different types of
// objects: a DOM element (using the jQuery hook), a Backbone
// model instance and an object primitive.
var a = jQuery('<input value="hello world!">'),
    b = new Backbone.Model(),
    c = {foo: 5};
var A = new Synapse(a),
    B = new Synapse(b),
    C = new Synapse(c);
// These get/set 'interfaces' roughly map the the underlying
// object's API
B.set('message', A.get('value'));
A.set('value', C.get('foo'));
C.set('bar', 10);
b.get('message');   // 'hello world!'
a.val();            // '5'
c.bar;              // 10
                
            Get Started
License
Synapse is released under the BSD 2-clause license. See the LICENSE file for details.
Dependencies
The core Synapse has no dependencies
Download
Right-click and save-as
- Development Version (0.5.1) - 12K
- Production Version (0.5.1) - 1.8K minified, gzipped
| Hook Name | Development | Production | Dependencies | 
|---|---|---|---|
| Object | Download | Download | (none) | 
| jQuery | Download | Download | jQuery | 
| Zepto | Download | Download | Zepto | 
| Backbone Model | Download | Download | Backbone | 
| Backbone View | Download | Download | Backbone | 
Universal Module Definition (UMD)
Synapse supports the UMD. It can load in the CommonJS/Node environment, via AMD in the browser or as a traditional script.
require(['synapse', 'synapse/jquery'], function(Synapse, jQueryHook) {
    Synapse.hooks.push(jQueryHook);
});
                
            APIs
Constructor
Synapse can be used as a constructor or a
                    function. Using it as a constructor wraps
                    the object and exposes the full Synapse Object API. Using it
                    as a function will augment the passed in
                    object with a limited API.
                    
var object = $('input');
var wrapped = new Synapse(object);
Synapse(object);
                
            Object API
There two primary sets of methods, those that creates a channel between two objects and those that control the channel flow.
Properties
- raw- a reference to the original object. Note, if the hook this object is used with has a- coerceObjectmethod defined,- rawwill be the object that is returned from that function.
wrapped.raw; // reference to `object`
Channel Methods
- observe- opens a one-way channel between this object and a subject where this object observes state changes of the subject.
- notify- opens a one-way channel between this object and an observer where this object notifies it's state changes to the observer.
- syncWith- opens a two-way channel between this object and another where each acts as a subject and observer.
wrapped.observe(subject, [options]); wrapped.notify(observer, [options]); wrapped.syncWith(other);
Channel Configuration
- event- the event that will trigger the subject to push it's interface value to all observers. this is variable across hooks. this is optional only if the subject's hook has a- detectEventmethod and can be successfully detected.
- subjectInterface- the interface identifier of the subject for a given channel or a function. this varies across hooks. this is optional only if the subject's hook has a- detectInterfacemethod or the observer's hook has a- detectOtherInterfacemethod and can be successfully detected.
- observerInterface- the interface identifier of the observer for a given channel or a function. this varies across hooks. this is optional only if the observer's hook has a- detectInterfacemethod or the subject's hook has a- detectOtherInterfacemethod and can be successfully detected.
- converter- an optional function or method name on the observer that will take the value(s) provided by the subject and return a value prior to calling- observer.set(...).
- triggerOnBind- if true, when the channel is initially created the subject will be triggered using the event supplied or detected.
// available configuration options and their defaults
var options = {
    event: null,
    subjectInterface: null,
    observerInterface: null,
    converter: null,
    triggerOnBind: true
};
// the default, pass in a configuration options object defining
// any or all of the options
wrapped.observe(subject, [options]);
// shorthand method signatures
// no options passed, all `null` options will attempt to be detected
wrapped.observe(subject);
// define the observer and subject interfaces
wrapped.observe(subject, [subjectInterface], [observerInterface]);
// define just a converter
wrapped.observe(subject, [converter]);
                
            Channel Flow Methods
- stopObserving- stops an object from observing all objects it is currently observing and removes all event handlers referencing this object. if a- Synapseobject is passed, only observing of that object will be stopped.
- pauseObserving- same as- stopObservingexcept event handlers are retained to allow for resuming observing. if a- Synapseobject is passed, only observing of that object will be paused.
- resumeObserving- all paused obversing is resumed. if a- Synapseobject is passed, only observing of that object will be resumed.
- stopNotifying- stops an object from notifying all objects it is currently notifying and removes all event handlers referencing those object. if a- Synapseobject is passed, only notifying of that object will be stopped.
- pauseNotifying- same as- stopNotifyingexcept event handlers are retained to allow for resuming notifying. if a- Synapseobject is passed, only notifying of that object will be paused.
- resumeNotifying- all paused notifying is resumed. if an existing- Synapseis object passed, only notifying of that object will be resumed.
wrapped.stopObserving([subject]); wrapped.pauseObserving([subject]); wrapped.resumeObserving([subject]); wrapped.stopNotifying([observer]); wrapped.pauseNotifying([observer]); wrapped.resumeNotifying([observer]);
Interface Methods
- get- returns a representation of the object's data for the given interface.
- set- sets a value relative to the given interface for the object.
wrapped.get(interface); wrapped.set(interface, value);
Hook API
Synapse relies on hooks being defined to perform the under-the-hood operations. The Synapse constructor is simply an abstraction for these hooks which are defined on a per-object-type basis. Depending on the specificity of the type, objects can fallback to less specific hooks to have at least minimal functionality.
Base
A hook is simply an object. To make it distributable, it is recommended to define it as a standalone module. The base parts of a hook include.
- typeName- name of the object type this hook represents
- checkObjectType- a method which checks if the object is applicable for this hook
define(function() {
    return {
        // the name of the type this hook represents
        typeName: 'The Awesome Type',
        // checks whether the object is the correct type for this hook
        checkObjectType: function(object) {},
                
            Interfaces
The following methods are required for an object to an observer.
- getHandler- a method which takes an object and an interface identifier and returns a value
- setHandler- a method which takes an object, an interface identifier, and a value and sets the value
        ...
        // takes an object and a interface identifier and returns a value
        getHandler: function(object, interface) {},
        // takes an object, inteface identifier and value and sets the value
        setHandler: function(object, interface, value) {},
        ...
                
            Events
The following methods are required for an object to a subject.
- onEventHandler- a method which attaches an event handler to an object
- offEventHandler- a method which detaches an event handler from an object
- triggerEventHandler- a method which triggers and event on an object
        ...
        // takes an object, event and handler and attaches the handler
        onEventHandler: function(object, event, handler) {},
        // take an object, event and handler and detaches the handler
        offEventHandler: function(object, event, handler) {},
        // takes an object and triggers an event on it
        triggerEventHandler: function(object, event) {},
        ...
                
            Helpers
The following methods are optional, but may be useful.
- coerceObject- takes the raw object passed in to- Synapseand coerces the object into a more useful object (think CSS selector → jQuery object)
        ...
        // takes the raw object passed in to Synapse and coerces the object
        // into a more useful object (think CSS selector --> jQuery object)
        coerceObject: function(object) {},
        ...
            Detection
These methods add much of desired magic for automatic binding between objects.
- detectInterface- detects an appropriate interface for the given object
- detectOtherInterface- detects an appropriate interface for the other object involved (either subject or observer)
- detectEvent- (subjects only) detects an appropriate event for the given object which will trigger the downstream notification to observers
        ...
        // detects an appropriate interface for the given object
        detectInterface: function(object) {},
        // detects an appropriate interface for the other object involved
        // (either subject or observer)
        detectOtherInterface: function(object) {},
        // detects an appropriate event for the given object which will
        // trigger the downstream notification to observers
        detectEvent: function(object, [interface]) {}
    };
});
                
            Supported Hooks
Plain Objects
| Supported Types | Plain objects are typically just
                    object primitives, e.g. {}, or objects created from theObjectconstructor, e.g.new Object(). | 
|---|---|
| Events | These objects are assumed to not have support for events and thus can only be observers. As a result, using this hook alone is pretty useless since it has nothing to observe.. it is a lonely object. | 
| Interfaces | The interfaces available for plain objects are simply, their properties and methods. Methods always take precedence over properties. | 
Sanitize Plain Objects
Synapse uses an internal expando property to keep track of
                        all managed objects to prevent defining duplicate channels.
                        This means plain objects will obtain a property that looks something
                        like Synapse0508635553480125964. It may be desirable for
                        downstream processing to remove this property. Synapse.expando
                        contains the property name so the property can be removed.
var obj = {};
// wrapped, expando property is added to the object
var wrapped = new Synapse(obj);
console.log(obj);
{
    Synapse0508635553480125964: 29
}
// The expando property ensures if the object gets wrapped again,
// it won't created a new object
var wrapped2 = new Synapse(obj);
wrapped2 === wrapped; // true
// It is recommended to create a copy of the object to perform
// post-processing (delete the expando property)
// using Underscore
var clone = _.clone(obj);
// or using jQuery
var clone = $.extend({}, obj);
delete clone[Synapse.expando];
// do downstream processing with the clone..
                    jQuery
| Supported Types | jQuery objects, CSS selectors, and DOM elements. | 
|---|---|
| Events | DOM events via jQuery's on,off,
                                andtriggermethods | 
| Interfaces | jQuery provides a very nice API which encapsulates cross-browser inconsistencies with a few other niceties. Full list below. | 
Hook Extensions
In addition to implementing the Hook API discussed above, the jQuery hook exposes additional configurable components for customizing the default behavior when interfacing with jQuery objects.
- jQueryHook.interfaces- contains the registry of all available interfaces for jQuery objects. code
- jQueryHook.domEvents- an array of- [selector, event]pairs used by- detectEvent. code
- jQueryHook.elementInterfaces- an array of- [selector, interface]pairs used by- detectInterface. code
- jQueryHook.elementBindAttributes- an array of element attributes used by- detectOtherInterface. code
define(['synapse/jquery'], function(jQueryHook) {
    // The interface registry object
    jQueryHook.interfaces.registry
    // Register a new interface (name must be unique)
    jQueryHook.interfaces.register({
        name: 'awesomness',
        get: function() { ... },
        set: function() { ... }
    });
    // Unregister interface
    jQueryHook.interfaces.unregister('awesomness');
});
                
            Simple Interfaces
Most of the simple interfaces are value-based or toggle-like properties.
| text | ||
| html | ||
| value | ||
| enabled | ||
| disabled | ||
| checked | ||
| visible | ||
| hidden | 
Compound Interfaces
Compound intefaces map more directly to the underlying object API and exposes more granular control.
Compound interface identifiers are delimited by a period (.). The first key is the reference interface and typically maps to a method on the object in which it itself takes a key (thus being compound).
| prop.value | ||
| attr.href | Variable Link | |
| css.color | Color | |
| data.role | Nothing to show, but for the curious
                                    open up the console and type document.ex3.data('role') | |
| class.stop | DON'T ENTER A VALUE! | 
Zepto
| Supported Types | Zepto arrays, CSS selectors, and DOM elements. | 
|---|---|
| Events | DOM events via Zepto's bind,unbind,
                                andtriggermethods | 
| Interfaces | The Zepto hook implements the same interfaces as the jQuery
                                    hook with the exception of the prop.<key>compound interface since Zepto does not differentiate between
                                    properties and attributes in the API. | 
Backbone View
| Supported Types | Backbone.Viewobjects | 
|---|---|
| Events | Similar to the jQuery hook under the hood, therefore using the DOM events mentioned above. | 
| Interfaces | Similar to the jQuery hook under the
                                    hood, therefore all interfaces above work for these objects. In
                                    addition, interface identifier that reference a method name on
                                    the Backbone.Viewobject directly may be used. Note,
                                    this will take precedence over the above jQuery interfaces of the
                                    same name. | 
Backbone Model
| Supported Types | Backbone.Modelobjects | 
|---|---|
| Events | Uses the built-in event system Backbone provides | 
| Interfaces | Similar to the plain objects above, the interface identifiers
                                    correspond to properties and methods. Under the hood, getting
                                    and setting the properties will be done via the object's
                                    native getandsetmethods (this ensures
                                    when a property is set, the change events fire). | 
Change Log
0.5.1
- Diff- Fix clean up of channels when either stopNotifyingorstopObserving
- Allow functions to be passed in as interfaces
- Converter functions are now passed the value, the observer and the observer's interface
0.5
- Diff- Remove hooksdirectory, so hooks can be required usingrequire(['synapse', 'synapse/jquery'], ... );
- Method addHooksandclearHookshave been removed in favor of exposing the rawSynapse.hooksarray. Add or remove hooks by using the native array methods such aspushandshift.
- Add expando property to prevent duplication of channels between objects.
                        This will reduce the number of objects initialized. The passed in object will receive
                        a property that looks like Synapse0508635553480125964. Caution should be taken when using Synapse with plain objects to prevent exposing this property during downstream processing. See the above warning.
- Refresh documentation, simplify style.
0.4.2
- Diff- The simple classinterface has been removed in favor of an existential compoundclassinterface.
0.4
- Diff- The method synchas been renamed tosyncWithto not conflict with Backbone'sModel.syncmethod when the object is augmented.
0.3.2
- Diff- The cssinterface has been renamed toclassand demoted to a simple interface. As a result, thestyleinterface has been renamed tocssfor consistency with jQuery and Zepto.
0.3
- Diff- Breaking change, the delimiter between compond interface keys is now a period (.) rather than a colon (:). This mentally maps better to it's multi-method/property traversal on the object.
0.2
- Diff- Breaking change, the delimiter between compond interface keys is now a period (.) rather than a colon (:). This mentally maps better to it's multi-method/property traversal on the object.
0.1
- Diff- Initial release