Skip to content

Instantly share code, notes, and snippets.

@rlauck
Forked from 140bytes/LICENSE.txt
Last active December 25, 2015 17:29
Show Gist options
  • Save rlauck/7013399 to your computer and use it in GitHub Desktop.
Save rlauck/7013399 to your computer and use it in GitHub Desktop.

dataBind

Bi-directional data-binding method that ties properties on an object model to DOM elements.

Pass a POJO (Plain Old Javascript Object) as a model, a property name and a list of DOM elements and they will be bound for time and all eternity... or until you leave the page. Updates to the model property will be reflected in the DOM and changing bound form fields will update the model.

This currently weighs in at 206 bytes, but I think it could still be golfed further. A decent chuck is dedicated to handling the logic around dealing with checkbox and radio inputs and there is probably a better way to organize it to avoid/reuse all the inner functions.

Note that this uses the Object.watch method which is only implemented in firefox. A polyfill is provided and is tested to work on the latest Chrome.

Ideas for further enhancement:

  • Use Object.observe instead of watch
  • Use DOM Mutation Observers instead of onchange/onkeyup handlers (cant happen in under 140 though, too verbose)
  • Handle binding to calculated properties, arrays and nested objects
  • Add unbind functionality and clean up

For more information

See the 140byt.es site for a showcase of entries (built itself using 140-byte entries!), and follow @140bytes on Twitter.

To learn about byte-saving hacks for your own code, or to contribute what you've learned, head to the wiki.

140byt.es is brought to you by Jed Schmidt, with help from Alex Kloss. It was inspired by work from Thomas Fuchs and Dustin Diaz.

function(
a, // the model object
b, // the property to bind on the model
c // array of DOM elements to bind to
){
a.watch(b, // Object.watch is mozilla-specific but a polyfill is included for other browsers
function( // handler called by watch before the property value changes
d, // the property name, not currently used
e, // the old value, placeholder
f // the new value
){
e='value', // constant for attribute access
c.map(function(g){ // loop through each DOM element as g
g[ // determine which DOM attribute to set
e in g // IF the element has a value attribute...
&& ( g.checked = // AND the checked attribute is true after the following assignment...
!/[ox]$/.test(g.type) // checkbox and radio are the only input types that end in 'o' or 'x'
|| f==g[e] // coerce f to string and compare with the current attribute value
|| f===!0 // if the model is storing true, then it overrides any value comparison for checkbox/radio
)
?e // THEN set the value attribute for a form element
:'innerHTML' // ELSE set the innerHTML for any other element/node
]=f,
g.onchange=function(){ // also assigning onkeyup gives feedback as you type but I'm over my byte limit
a[b]=g.checked&&g[e] // if the checked property is true, store the DOM value attribute otherwise false (for checkboxes)
}
})
}),
a[b]=a[b] // reset the property to trigger watch and set initial values from the model
}
function(a,b,c){a.watch(b,function(d,e,f){e='value',c.map(function(g){g[e in g&&(g.checked=!/[ox]$/.test(g.type)||f==g[e]||f===!0)?e:'innerHTML']=f,g.onchange=function(){a[b]=g.checked&&g[e]}})}),a[b]=a[b]}
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
Version 2, December 2004
Copyright (C) 2011 YOUR_NAME_HERE <YOUR_URL_HERE>
Everyone is permitted to copy and distribute verbatim or modified
copies of this license document, and changing it is allowed as long
as the name is changed.
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. You just DO WHAT THE FUCK YOU WANT TO.
{
"name": "dataBind",
"description": "Bi-directional data-binding method that ties properties on an object model to DOM elements",
"keywords": [
"data-bind",
"dom",
"html",
"bind",
"model"
]
}
/*
* object.watch polyfill
*
* 2012-04-03
*
* By Eli Grey, http://eligrey.com Public Domain. NO WARRANTY EXPRESSED OR
* IMPLIED. USE AT YOUR OWN RISK.
*/
// object.watch
if (!Object.prototype.watch) {
Object.defineProperty(Object.prototype, "watch", {
enumerable : false,
configurable : true,
writable : false,
value : function(prop, handler) {
var oldval = this[prop], newval = oldval, getter = function() {
return newval;
}, setter = function(val) {
oldval = newval;
return newval = handler.call(this, prop, oldval, val);
};
if (delete this[prop]) { // can't watch constants
Object.defineProperty(this, prop, {
get : getter,
set : setter,
enumerable : true,
configurable : true
});
}
}
});
}
// object.unwatch
if (!Object.prototype.unwatch) {
Object.defineProperty(Object.prototype, "unwatch", {
enumerable : false,
configurable : true,
writable : false,
value : function(prop) {
var val = this[prop];
delete this[prop]; // remove accessors
this[prop] = val;
}
});
}
<!doctype html>
<html lang="en">
<head>
<title>data-binding test</title>
</head>
<body>
<fieldset><legend>Text Input</legend>
<div><label>Label: </label><span class="text"></span></div>
<div><input class="text"></div>
<div><textarea class="text"></textarea></div>
<div><input class="text" readonly></div>
</fieldset>
<fieldset><legend>Checkbox</legend>
<div><label>Label: </label><span class="check"></span></div>
<div>
<input type="checkbox" class="check">
</div>
<div>
<input type="checkbox" class="check">
</div>
</fieldset>
<fieldset><legend>Radio</legend>
<div><label>Label: </label><span class="radio"></span></div>
<fieldset><legend>Group #1</legend>
<div>
<input type="radio" name="group1" value="1" class="radio">
<label>Value: 1</label>
</div>
<div>
<input type="radio" name="group1" value="2" class="radio">
<label>Value: 2</label>
</div>
</fieldset>
<fieldset><legend>Group #2</legend>
<div>
<input type="radio" name="group2" value="0" class="radio">
<label>Value: 0</label>
</div>
<div>
<input type="radio" name="group2" value="1" class="radio">
<label>Value: 1</label>
</div>
<div>
<input type="radio" name="group2" value="2" class="radio">
<label>Value: 2</label>
</div>
</fieldset>
</fieldset>
<fieldset><legend>Select</legend>
<div><label>Label: </label><span class="select"></span></div>
<div>
<select class="select">
<option>Option #1</option>
<option>Option #2</option>
<option>Option #3</option>
</select>
</div>
<div>
<select class="select">
<option>Option #1</option>
<option>Option #2</option>
<option>Option #3</option>
</select>
</div>
</fieldset>
<fieldset><legend>HTML5 Inputs</legend>
<div><label>Label: </label><span class="other"></span></div>
<div>
<label>Range</label>
<input type="range" class="other" min="0" max="100">
</div>
<div>
<label>Number</label>
<input type="number" class="other" min="0" max="100">
</div>
<div>
<label>Progress</label>
<progress max="100" class="other"></progress>
</div>
</fieldset>
<!-- Object.watch polyfill -->
<script src="polyfill.js"></script>
<script type="text/javascript">
// helper
var getEls = function(clazz){
return [].slice.call(document.getElementsByClassName(clazz));
};
var dataBind = function(a,b,c){a.watch(b,function(d,e,f){e='value',c.map(function(g){g[e in g&&(g.checked=!/[ox]$/.test(g.type)||f==g[e]||f===!0)?e:'innerHTML']=f,g.onchange=function(){a[b]=g.checked&&g[e]}})}),a[b]=a[b]};
// default values for the model
var model = {
text: "abcd",
check: true,
radio: 2,
select: "Option #2",
other: 10
};
dataBind(model, "text", getEls("text"));
dataBind(model, "check", getEls("check"));
dataBind(model, "radio", getEls("radio"));
dataBind(model, "select", getEls("select"));
dataBind(model, "other", getEls("other"));
</script>
</body>
</html>
@rlauck
Copy link
Author

rlauck commented Oct 22, 2013

Looks like you are missing a closing paren near the end. The map call serves a dual purpose to create a closure on 'g' for the onchange handler. Maybe there is some way to shave a few off with Function.bind()...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment