Home  >  Article  >  Web Front-end  >  Easily implement two-way binding of javascript data_javascript skills

Easily implement two-way binding of javascript data_javascript skills

WBOY
WBOYOriginal
2016-05-16 15:32:411329browse

Two-way data binding means that when the properties of an object change, the corresponding UI can be changed at the same time, and vice versa. In other words, if we have a user object that has a name property, whenever you set a new value to user.name, the UI will display the new value. Likewise, if the UI contains an input box for the user's name, entering a new value will cause the user object's name property to change accordingly.

Many popular javascript frameworks, like Ember.js, Angular.js or KnockoutJS, promote two-way data binding as one of their main features. This does not mean that implementing it from scratch is difficult, nor does it mean that using these frameworks is our only option when we need this functionality. The underlying idea inside is actually quite basic, and its implementation can be summarized in the following three points:

  • We need a way to determine which UI element is bound to which property.
  • We need to monitor changes in properties and UI
  • We need to propagate changes to all bound objects and UI elements.

Although there are many ways to achieve these points, a simple and efficient way is that we implement it through the publish-subscriber pattern. The method is simple: we can use the customized data attribute as the attribute that needs to be bound in the HTML code. All JavaScript objects and DOM elements that are bound together will subscribe to this publish-subscribe object. Any time we detect a change in either a Javascript object or an HTML input element, we pass the event proxy to the publish-subscribe object, and then pass and broadcast all changes that occur in the bound objects and elements through it. go out.

A simple example implemented with jQuery

It is quite simple and straightforward to implement what we discussed above through jQuery, because as a popular library, it allows us to easily subscribe to and publish DOM events, and we can also customize one:

function DataBinder(object_id){
  // Use a jQuery object as simple PubSub
  var pubSub=jQuery({});

  // We expect a `data` element specifying the binding
  // in the form:data-bind-<object_id>="<property_name>"
  var data_attr="bind-"+object_id,
    message=object_id+":change";

  // Listen to chagne events on elements with data-binding attribute and proxy
  // then to the PubSub, so that the change is "broadcasted" to all connected objects
  jQuery(document).on("change","[data-]"+data_attr+"]",function(eve){
    var $input=jQuery(this);

    pubSub.trigger(message,[$input.data(data_attr),$input.val()]);
  });

  // PubSub propagates chagnes to all bound elemetns,setting value of
  // input tags or HTML content of other tags
  pubSub.on(message,function(evt,prop_name,new_val){
    jQuery("[data-"+data_attr+"="+prop_name+"]").each(function(){
      var $bound=jQuery(this);

      if($bound.is("")){
        $bound.val(new_val);
      }else{
        $bound.html(new_val);
      }
    });
  });
  return pubSub;
}

As for JavaScript objects, here is an example of a minimal user data model implementation:

function User(uid){
  var binder=new DataBinder(uid),
    
    user={
      attributes:{},
      // The attribute setter publish changes using the DataBinder PubSub
      set:function(attr_name,val){
        this.attributes[attr_name]=val;
        binder.trigger(uid+":change",[attr_name,val,this]);
      },

      get:function(attr_name){
        return this.attributes[attr_name];
      },
    
      _binder:binder
    };

  // Subscribe to PubSub
  binder.on(uid+":change",function(evt,attr_name,new_val,initiator){
    if(initiator!==user){
      user.set(attr_name,new_val);
    }
  });

  return user;
}

Now, whenever we want to bind the properties of an object to the UI, we just set the appropriate data attribute on the corresponding HTML element.

// javascript 
var user=new User(123);
user.set("name","Wolfgang");

// html
<input type="number" data-bind-123="name" />

Value changes in the input box will automatically be mapped to the user's name attribute, and vice versa. You're done!

An implementation that does not require jQuery

Most projects nowadays generally use jQuery, so the above example is completely acceptable. But what if we need to be completely independent of jQuery? Well, in fact it is not difficult to do (especially when we only provide support for IE8 and above). Finally, we just have to observe DOM events through the publish-subscriber pattern.

function DataBinder( object_id ) {
 // Create a simple PubSub object
 var pubSub = {
  callbacks: {},

  on: function( msg, callback ) {
   this.callbacks[ msg ] = this.callbacks[ msg ] || [];
   this.callbacks[ msg ].push( callback );
  },

  publish: function( msg ) {
   this.callbacks[ msg ] = this.callbacks[ msg ] || []
   for ( var i = 0, len = this.callbacks[ msg ].length; i < len; i++ ) {
    this.callbacks[ msg ][ i ].apply( this, arguments );
   }
  }
 },

 data_attr = "data-bind-" + object_id,
 message = object_id + ":change",

 changeHandler = function( evt ) {
  var target = evt.target || evt.srcElement, // IE8 compatibility
    prop_name = target.getAttribute( data_attr );

  if ( prop_name && prop_name !== "" ) {
   pubSub.publish( message, prop_name, target.value );
  }
 };

 // Listen to change events and proxy to PubSub
 if ( document.addEventListener ) {
  document.addEventListener( "change", changeHandler, false );
 } else {
  // IE8 uses attachEvent instead of addEventListener
  document.attachEvent( "onchange", changeHandler );
 }

 // PubSub propagates changes to all bound elements
 pubSub.on( message, function( evt, prop_name, new_val ) {
var elements = document.querySelectorAll("[" + data_attr + "=" + prop_name + "]"),
  tag_name;

for ( var i = 0, len = elements.length; i < len; i++ ) {
 tag_name = elements[ i ].tagName.toLowerCase();

 if ( tag_name === "input" || tag_name === "textarea" || tag_name === "select" ) {
  elements[ i ].value = new_val;
 } else {
  elements[ i ].innerHTML = new_val;
 }
}
 });

 return pubSub;
}

The data model can remain unchanged, except for the call to the trigger method in jQuery in the setter, which can be replaced by our customized publish method in PubSub.

// In the model's setter:
function User( uid ) {
 // ...

 user = {
  // ...
  set: function( attr_name, val ) {
     this.attributes[ attr_name ] = val;
     // Use the `publish` method
     binder.publish( uid + ":change", attr_name, val, this );
  }
 }

 // ...
}

We explained it through examples, and once again achieved the results we wanted with less than a hundred lines of maintainable pure JavaScript. We hope it will be helpful to everyone in realizing two-way binding of JavaScript data.

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn