Translated from: http://www.sellarafaeli.com/blog/native_javascript_data_binding
Two-way data-binding is such a important feature-align your JS models with your HTML view @ all times, to reduce boile Rplate coding and enhance UX. We'll observe the ways of doing this using native JavaScript, with the no Frameworks-one with revolutionary technology (OB Ject.observe), and one with an original concept (overriding Get/set). Spoiler alert-the second one is better. See TL;DR at bottom.
1:object.observe && Dom.onchange
Object.observe () is the new kid on the block. This native JS Ability-well, actually it's a future ability since it's only proposed for ES7, but it ' s already[ !] Available in the current stable chrome-allows for reactive updates to changes to a JS object. Or in simple english-a callback run whenever a object (' s properties) change (s).
An idiomatic usage could is:
Log = Console.loguser = {}object.observe (user, function (changes) { Changes.foreach (change) { User.fullname = User.firstname + "" + User.lastname; }); user.firstname = ' Bill '; user.lastname = ' Clinton '; user.fullname//' Bill Clinton '
This was already pretty cool and allows reactive programming within js-keeping everything up-to-date by push.
But let's take it to the next level:
<input id= "foo" >user = {};d IV = $ ("#foo"); Object.observe (user, function (changes) { Changes.foreach ( function (change) { var fullName = (User.firstname | | "") + "" + (User.lastname | | ""); Div.text (FullName); }); User.firstname = ' Bill '; user.lastname = ' Clinton ';d iv.text ()//bill Clinton
Jsfiddle
cool! We just got Model-to-view databinding! Let's DRY ourselves with a helper function.
<input id= "foo" >function Bindobjproptodomelem (obj, property, Domelem) { object.observe (obj, function ( Changes) { Changes.foreach (change) { $ (domelem). Text (Obj[property]); });} ); user = {};bindobjproptodomelem (user, ' name ', $ ("#foo")); user.name = ' William ' $ ("#foo"). Text ()//' William '
Jsfiddle
sweet!
Now-the other-the-around-binding a DOM elem to a JS value. A pretty good solution could is a simple use of the JQuery ' s . change (http://api.jquery.com/change/):
<input id= "foo" >$ ("#foo"). Val (""); function Binddomelemtoobjprop (Domelem, obj, PropertyName) { $ (domelem ). Change (function () { Obj[propertyname] = $ (Domelem). Val (); Alert ("User.Name is now" +user.name);
Jsfiddle
That was pretty awesome. To wrap up, in practice you could combine the and a single function to create a two-way data-binding:
function Bindobjproptodomelem (obj, property, Domelem) { object.observe (obj, function (changes) { Changes.foreach (function (change) { $ (domelem). Text (Obj[property]);});} ); function Binddomelemtoobjprop (obj, PropertyName, Domelem) { $ (domelem). Change (function () { obj[propertyname ] = $ (Domelem). Val (); Console.log ("obj is", obj); }); function Bindmodelview (obj, property, Domelem) { Bindobjproptodomelem (obj, property, Domelem) Binddomelemtoobjprop (obj, PropertyName, Domelem)}
Take note to use the correct DOM manipulation in case of a two-way binding, since different DOM elements (input, Div, text area, select) answer to different semantics (text, Val). Also take note this two-way data-binding is not always necessary– "output" elements rarely need view-to-model binding and "Input" elements rarely need model-to-view binding. But Wait–there ' s more:
2:go deeper:changing ' get ' and ' set '
We can do even better than the above. Some issues with We above implementation is the using. Change breaks on modifications that don ' t trigger JQuery ' s "Chang E "event-for example, DOM changes via the code, e.g. on the above code the following wouldn ' t work:
We'll discuss a more radical way-to override the definition of getters and setters. This feels less ' safe ' since we is not merely observing, we'll be a overriding the most basic of language functionality, Get/setting a variable. However, this bit of metaprogramming would allow us great powers, as we'll quickly see.
So, what if we could override getting and setting values of objects? After all, that's exactly what's data-binding is. Turns out the using Object.defineProperty()
we can in fact does exactly that.
We used to has the old, non-standard, deprecated-on-the-same-and now we have the new-cool (and most-importantly, standard)-the-the-u Sing Object.defineProperty
, as so:
user = {}namevalue = ' Joe '; object.defineproperty (user, ' name ', { get:function () {return namevalue}, set:functi On (newvalue) {namevalue = newvalue;}, configurable:true//to Enable redefining the property later}); User.Name//joe User.Name = ' Bob ' user.name//bobnamevalue//bob
OK, so-User.Name is a alias for Namevalue. But we can does more than just redirect the variable to being used-we can use it to create an alignment between the model and The view. Observe:
<input id= "foo" >object.defineproperty (user, ' name ', { get:function () {return document.getElementById (" Foo "). Value}, set:function (newvalue) {document.getElementById (" foo "). Value = NewValue;}, configurable: True//to Enable redefining the property later});
user.name
is now binded to the input #foo
. This was a very concise expression of ' binding ' at a native level-by defining (or extending) the native Get/set. Since the implementation is so concise, one can easily extend/modify this code to custom situation-binding only Get/set Or extending either one of the them, for example to enable binding of the other data types.
As usual we make sure to DRY ourselves with something like:
function Bindmodelinput (obj, property, Domelem) { object.defineproperty (obj, property, { get:function ()} { return domelem.value; }, set:function (newvalue) {domelem.value = newvalue;}, configurable:true });}
Usage
user = {};inputelem = document.getElementById ("foo"), Bindmodelinput (user, ' name ', inputelem); user.name = "Joe"; Alert (" Input value was now "+inputelem.value"//input was now ' Joe '; inputelem.value = ' Bob '; alert ("User.Name are Now" +user.name)// Model is now ' Bob ';
Jsfiddle
Note the above still uses ' domelem.value ' and so would still work only on <input>
elements. (This can is extended and abstracted away within the bindmodelinput, to identify the appropriate DOM type and use the Corr Ect method to set its ' value ').
Discussion:
- DefineProperty is available in pretty much every browser.
- It is worth mentioning so in the above implementation, THE&NBSP; View is now the ' single point of truth ' (at least, to a certain perspective). This is generally unremarkable (since, the point of two-way data-binding means equivalency. However on a principle level this may make some uncomfortable, and in some cases the May has actual effect-for example in C ASE of a removal of the DOM element, would our model would essentially is rendered useless? The answer is no, it would not. our
bindmodelinput
creates a closure over Domelem
, keeping it in Memory-and Perserving the behavior a la binding with the Model-even if the DOM element is removed. Thus the model lives on, even if the view is removed. Naturally the reverse is also true-if the model is removed, the view still functions just fine. Understanding these internals could prove important in extreme cases of refreshing both the data and the view.
Using such a bare-hands approach presents many benefits over using a-framework such as Knockout or Angular for Data-bindin G, such as:
- Understanding:once the source code of the data-binding is in your own hands, can better understand it and modify it T O Your own use-cases.
- Performance:don ' t bind everything and the kitchen sink, only any need, thus avoiding performance hits at large Numbe RS of observables.
- Avoiding lock-in:being able to perform data-binding yourself are of course immensely powerful, if you ' re not in a framewor K that supports.
One weakness is the since this was not a ' true ' binding (there was no ' dirty checking ' going on), some cases would fail-up Dating the view won't ' trigger ' anything in the model, so for example trying to ' sync ' both DOM elements via the Vie W would fail. That is, the binding of the elements to the same model would only be refresh both elements correctly when the model is ' touched '. This can is amended by adding a custom ' toucher ':
<input id= ' input1 ' >//<input id= ' input2 ' >input1 = document.getElementById (' input1 ') Input2 = document.getElementById (' input2 ') user = {}object.defineproperty (user, ' name ', { get:function ()} {return Input1.value; }, set:function (newvalue) {input1.value = newvalue; input2.value = newvalue;}, configurable:true}); Input1.onchange = function () {user.name = User.Name}//sync both inputs.
TL;DR:
Create A to data-binding between model and view with native JavaScript as such:
function Bindmodelinput (obj, property, Domelem) { object.defineproperty (obj, property, { get:function ()} { return domelem.value; }, set:function (newvalue) {domelem.value = newvalue;}, configurable:true });} <input id= "foo" >user = {}bindmodelinput (user, ' name ', document.getElementById (' foo ')); Hey presto, we now have two-way data binding.
Thanks for reading. Comments at discussion in Reddit or at [email protected]
"Go" Native JavaScript data-binding