tendon
v0.1.0
Published
Two way data bindings for Backbone and the DOM
Downloads
3
Readme
Tendon.js
EXPERIMENTAL LIBRARY - NOT PRODUCTION READY
Two way data binding for Backbone and the DOM
Tired of writing tons of repetative Backbone code to update the DOM? Tired of wiring up DOM listeners to update your Backbone models? Well then this might be what you're looking for!
This library has 3 primary purposes.
- Update the DOM when the JS data changes
- Update the JS when the DOM data changes
- Handle templating because thats how I roll
This library takes a slightly different approach for data bindings, in that nearly all of the configuration is in the HTML markup itself. The primary reason for this is that it provided me a way to keep my JS very lightweight and straight forward, as well as allow me to rapidly change the UI/UX of an application without having to rewrite my entire JS app.
It may be somewhat of an anti-pattern to put so much control in HTML, but hey, this library is mainly about rendering and keeping state in sync. If you prefer putting your HTML inside your JS and then embedded JS inside of that HTML, maybe React is for you.
Table of Contents
Install
This is a client side library, but still exports to Node.js if you are doing some fancy backend stuff. Writing the obligatory install steps, but if you've made it this far you probably know what to do here.
With npm
npm install tendon
With bower
bower install tendon
Or download the JS files directly and load into the browser.
<script src="path/to/tendon.js"></script>
Usage
Generally, there shouldn't be much JS interaction with the library, it is meant to do as much as it can automatically for you based on the HTML markup it finds within the element you give it.
var Model = Backbone.Model.extend({
defaults: {
menuItems: ['Home', 'About', 'Contact']
, activeItem: null
, locked: 'locked'
}
})
var View = Backbone.View.extend({
el: 'body'
, initialize: function() {
// All template rendering will use this, as well as any subscribe
// or publish events specified in the markup
var context = {
view: this
, model: this.model
, store: window.localStorage
}
// The initialize of Tendon is going to find all relevant elements and do
// what the markup attributes say
this.tendon = new Tendon(this.$el, context)
// You can also listen for specific changes if you want to some extra fancy
// stuff. Think of things like JSONView or Highlight.js calls.
this.tendon.on('updateElement:elementID', function($element, content) {
})
// Backbone-esque version of above
this.tendon.on('updateElement', function($element, content) {
})
}
})
// Such app creation, new models, wow.
var app = new View({
model: new Model()
})
Examples
1 Way Binding - Contents
Lets start off small and create a 1 way binding to update the contents of a div. We need to know what events trigger a DOM update, and how to get the data for the update.
- Listen to the model event
change:username
- Update content with the model
username
property
<div
tendon-subscribe="model.change:username"
tendon-set="model:username"
></div>
You can specify multiple subscriptions, and use custom functions for the value
<div
tendon-subscribe="model.sync,model.change:property,state.change:username change:group"
tendon-set="model:someFunction"
></div>
1 Way Binding - Attribute
Here we have an element that we only want to update the class attribute on. This
is a one-way binding as there is no tendon-publish
attribute and no way to get
a value. The attributes here do the following:
- Bind content changes to an attribute,
class
in this case, usingtendon-set-attribute
- Use the specific value,
model:status
akamodel.get('status')
, usingtendon-set
- Trigger a content update on
model.change:locked
akamodel.on('change:status')
Using this setup, anytime the model's status
attribute changes, the DOM element
will update its class
attribute with that value.
<div
tendon-set="model:status"
tendon-set-attribute="class"
tendon-subscribe="model.change:status"
/>
Here we are going to use templates and publishing to setup two way binding.
- Listen to the
model
events;sync
,change:menuItems
, andchange:activeItem
- Update the innerHTML content using the results of the template from
tendon-template
attribute. The template will be called with thecontext
provided on initialization - Listen to all child
li
element changes via thetendon-listen
attribute - Publish any changes found by setting the
model:activeItem
prop, akamodel.set('activeItem')
- Trigger an render on initialization via
tendon-auto-render
<script type="text/html" id="my-list-template">
{{ var active = __.model.get('activeItem') }}
{{ __.model.get('menuItems').forEach(function(item) { }}
<li {{= (item === cur) ? 'class="active"' : '' }}>{{ item }}</li>
{{ }) }}
</script>
<ul
tendon-subscribe="model.sync, model.change:menuItems, model.change:activeItem"
tendon-auto-render="true"
tendon-template="script#my-list-template"
tendon-listen="li"
tendon-publish="model:activeItem"
><!-- filed in by Tendon using template above --></ul>
HTML Attribute Options
tendon-subscribe
{String} list of model events to listen to in the formatmodel.event:property
tendon-publish
{String} list of model events to publish HTML changes totendon-auto-render
{Boolean} flag to signal initial rendering call, use if the data already exists in thecontext
and the page was not bootstrapped with contenttendon-template
{String} jQuery selector for underscore template, if set this will be run with the providedcontext
and set as the inner HTMLtendon-set
{String} model attribute or method to be used as direct valuetendon-set-attribute
{String} element attribute to assign value to, innerHTML is set if not specifiedtendon-listen
{String} jQuery selector to specify a child element(s) to listen for changes, instead of the current elementtendon-uuid
{String} internally set UUID to identify source of HTML update events
Events
init:before
init
init:after
change:html:id
change:html
change:js
API
The methods here are mainly all to be used internally, they aren't named fairly well at the moment and will most likely be changed.
new Tendon($selector, context, options)
Class constructor, automatically creates all DOM and Backbone event bindings for
any element found within the $selector
containing tendon-
attributes.
$selector
- jQuery element or string selector, this is the DOM contextcontext
- JS context objectoptions
- option overrides, merged withTendon.defaults
described below
var App = Backbone.View.extend({
initialize: function() {
var context = {
view: this
}
this.tendon = new Tendon(this.$el, context)
}
})
Tendon.defaults
prefix
- HTML attribute prefixdebug
- boolean to toggle debug loggingtemplateSettings
- custom underscore template settings
{
prefix: 'tendon-'
, debug: false
, templateSettings: {
variable: '__'
, evaluate: /\{\{(.+?)\}\}/g
, interpolate: /\{\{=(.+?)\}\}/g
, escape: /\{\{-(.+?)\}\}/g
}
}
instance.setup()
Search the current $selector
for any elements with attributes matching any of
the adapters in use. Create event proxies to run the adapters.
tendon.setup()
Adapters
get
- event -
init:before
- attr -
tendon-get
- value - any jQuery object method or prop. (ex:
data
,val
,html
) - autoload -
true
Description
template
- event -
change:js
- attr -
tendon-template
- value - jQuery selector of underscore template
- autoload -
true
Description
set
- event -
change:js
- related - set-attribute
- attr -
tendon-set
- value - context method or property (ex:
obj.prop
,model.get:prop
,obj.method:arg1,arg2
) - autoload -
true
Description
set-attribute
- required adapter - set
- event -
change:js
- attr -
tendon-set
- value - element attribute (ex:
class
,data-thing
,selected
) - autoload -
true
Description
auto-render
Description
uuid
- event -
init
- attr -
tendon-uuid
- value - underscore uuid with prefix (ex:
tendon-25
) - autoload -
true
Description
publish
- event -
change:html
- attr -
tendon-publish
- value -
Description
listen
- event -
init
- attr -
tendon-listen
- value - child element selector (optional, default current el)
- autoload -
true
Description
subscribe
- event -
init
- attr -
tendon-subscribe
- value -
- autoload -
true
Description
License
(The MIT License)
Copyright (c) 2015 Beau Sorensen
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.