ampersand-dom-bindings
Part of the Ampersand.js toolkit for building clientside applications.
Takes binding declarations as described below and returns key-tree-store of functions that can be used to apply those bindings to a DOM tree.
ampersand-view use this for declarative bindings.
The returned functions should be called with these arguments: The root element, the current value of the property, and a name for the binding types where that is relevant.
install
npm install ampersand-dom-bindings
Binding types
text
sets/maintains textContent
of selected element. treats undefined
, null
, and NaN
as ''
'model.key': {
type: 'text',
selector: '.someSelector'
}
class
sets and maintains single class as string that matches value of property
- handles removing previous class if there was one
- treats
undefined
, null
, and NaN
as ''
(empty string).
'model.key': {
type: 'class',
selector:
}
attribute
sets the whole attribute to match value of property. treats undefined
, null
, and NaN
as ''
(empty string). name
can also be an array to set multiple attributes to the same value.
'model.key': {
type: 'attribute',
selector: '#something',
name: 'width'
}
value
sets the value of the element to match value of the property. works well for input
, select
, and textarea
elements. treats undefined
, null
, and NaN
as ''
(empty string).
note: The binding will only be applied if the element is not currently in focus. This is done by checking to see if the element is the document.activeElement
first. The reason it works this way is because if you've set up two-way data bindings you get a circular event: the input changes, which sets the bound model property, which in turn updates the value of the input. This might sound OK but results in the cursor always jumping to the end of the input/textarea. So if you're editing the middle of a bound text field, the cursor keeps jumping to the end. We avoid this by making sure it's not already in focus thus avoiding the bad loop.
'model.key': {
type: 'value',
selector: '#something',
}
booleanClass
add/removes class based on boolean interpretation of property name. name
, yes
, or no
can also be an array of class names where all the values will be toggled.
'model.active': {
type: 'booleanClass',
selector: '#something',
name: 'active'
yes: 'active',
no: 'not-active'
}
booleanAttribute
toggles whole attribute on the element (think checked
) based on boolean interpretation of property name. name
can also be an array of attribute names where all the values will be toggled.
'model.isAwesome': {
type: 'booleanAttribute',
selector: '#something',
name: 'checked'
}
toggle
toggles existence of entire element (uses a comment node as placeholder if gone) based on boolean interpretation of property name.
'model.key': {
type: 'toggle',
selector: '#something'
}
'model.key': {
type: 'toggle',
yes: '#true_case',
no: '#false_case'
}
switch
Toggles existence of multiple items based on value of property.
'model.activetab': {
type: 'switch',
cases: {
'edit': '#edit_tab',
'new': '#new_tab',
'details': '#details_tab'
}
}
switchClass
Toggles existence of a class on multiple elements based on value of property.
'model.key': {
type: 'switchClass',
name: 'is-active',
cases: {
'edit': '#edit_tab',
'new': '#new_tab',
'details': '#details_tab'
}
}
innerHTML
renders innerHTML, can be a string or DOM, based on property value of model
'model.key': {
type: 'innerHTML',
selector: '#something'
}
custom functions
type
can also be a function. It will be run for each matching el
with the
value
and previousValue
of the property.
'model.key': {
type: function (el, value, previousValue) {
},
selector: '#something',
}
Handling multiple bindings for a given key
If given an array, then treat each contained item as separate binding
'model.key': [
{
type: 'booleanClass',
selector: '#something',
name: 'active'
},
{
type: 'attribute',
selector: '#something',
name: 'width'
}
]
The attribute
, booleanAttribute
and booleanClass
types also accept an array for the name
property (and yes
/no
for booleanClass
). All the values in the array will be set the same as if each were bound separately.
'model.key': {
type: 'attribute',
selector: '#avatar',
name: ['height', 'width']
}
binding using data-hook
attribute
We've started using this convention a lot, rather than using classes and IDs in JS to select elements within a view, we use the data-hook
attribute. This lets designers edit templates without fear of breaking something by changing a class. It works wonderfully, but the only thing that sucks about that is the syntax of attribute selectors: [data-hook=some-hook]
is a bit annoying to type a million types, and also in JS-land when coding and we see [
we always assume arrays.
So for each of these bindings you can either use selector
or hook
, so these two would be equivalent:
'model.key': {
selector: '[data-hook=my-element]'
}
'model.key': {
hook: 'my-element'
}
handling simplest cases: text
'model.key': '#something'
'model.key': {
hook: 'hook-name'
}
real life example
var View = require('ampersand-view');
var templates = require('../templates');
module.exports = View.extend({
template: templates.includes.app,
bindings: {
'model.client_name': {
hook: 'name'
},
'model.logo_uri': {
type: 'attribute',
name: 'src',
hook: 'icon'
}
}
});
other benefits
Previously after having given views the ability to have their own properties (since view inherits from state) it was awkward to bind those to the DOM. Also, for binding things that were not just this.model
the syntax had to change.
Now this is fairly simple/obvious:
module.exports = View.extend({
template: templates.includes.app,
props: {
activetab: 'string',
person: 'state',
meeting: 'state'
},
bindings: {
'activetab': {
type: 'switch',
case: {
'edit': '#edit_tab',
'new': '#new_tab',
'details': '#details_tab'
}
},
'person.full_name': '[data-hook=name]',
'meeting.subject': '[data-hook=subject]'
}
});
changelog
- 3.3.1 - Fix issues with yes/no handling in boolean class. Add lots of tests.
license
MIT