La liaison de données bidirectionnelle fait référence à la possibilité de lier les modifications apportées aux propriétés d'un objet aux modifications apportées à l'interface utilisateur, et vice versa. En d'autres termes, si nous avons un objet utilisateur et un attribut name, une fois que nous attribuons une nouvelle valeur à user.name, le nouveau nom sera affiché sur l'interface utilisateur. De même, si l'interface utilisateur contient une zone de saisie pour le nom de l'utilisateur, la saisie d'une nouvelle valeur devrait entraîner la modification en conséquence de la propriété de nom de l'objet utilisateur.
De nombreux clients de framework JS populaires tels que Ember.js, Angular.js ou KnockoutJS ont implémenté la liaison de données bidirectionnelle dans leurs dernières fonctionnalités. Cela ne signifie pas qu’il est difficile de le mettre en œuvre à partir de zéro, ni que l’adoption de ces cadres est la seule option lorsque ces fonctions sont nécessaires. L'idée ci-dessous est en fait très basique et peut être considérée comme un plan en 3 étapes :
Nous avons besoin d'un moyen de lier les éléments et les attributs de l'interface utilisateur les uns aux autres
Nous devons surveiller les changements dans les propriétés et les éléments de l'interface utilisateur
Nous devons informer tous les objets et éléments liés des changements
Il existe encore de nombreuses façons de mettre en œuvre l'idée ci-dessus. Un moyen simple et efficace consiste à utiliser le mode PubSub. L'idée est simple : nous utilisons des attributs de données pour lier le code HTML, et tous les objets JavaScript et éléments DOM liés ensemble s'abonnent à un objet PubSub. Tant qu'un objet JavaScript ou un élément d'entrée HTML écoute les modifications de données, l'événement lié à l'objet PubSub sera déclenché et d'autres objets et éléments liés apporteront les modifications correspondantes.
Utilisez jQuery pour réaliser une implémentation simple
Pour l'abonnement et la publication d'événements DOM, il est très simple de l'implémenter avec jQuery. Ensuite, nous utiliserons Jquery, comme le suivant :
.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 change events on elements with the data-binding attribute and proxy // them to the PubSub, so that the change is "broadcasted" to all connected objects jQuery( document ).on( "change", "[data-" + data_attr + "]", function( evt ) { var $input = jQuery( this ); pubSub.trigger( message, [ $input.data( data_attr ), $input.val() ] ); }); // PubSub propagates changes to all bound elements, 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("input, textarea, select") ) { $bound.val( new_val ); } else { $bound.html( new_val ); } }); }); return pubSub; }
Pour l'implémentation ci-dessus, voici la méthode d'implémentation la plus simple d'un modèle utilisateur :
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 the PubSub binder.on( uid + ":change", function( evt, attr_name, new_val, initiator ) { if ( initiator !== user ) { user.set( attr_name, new_val ); } }); return user; }
Maintenant, si nous voulons lier les attributs du modèle utilisateur à l'interface utilisateur, il nous suffit de lier les attributs de données appropriés aux éléments HTML correspondants.
// javascript var user = new User( 123 ); user.set( "name", "Wolfgang" ); // html <input type="number" data-bind-123="name" />
De cette façon, la valeur d'entrée sera automatiquement mappée à l'attribut de nom de l'objet utilisateur, et vice versa
Idem. Cette simple implémentation est terminée !
Pas besoin d'implémentation de jQuery
Dans la plupart des projets actuels, jQuery est probablement déjà utilisé, donc l'exemple ci-dessus est tout à fait acceptable. Cependant, que se passe-t-il si nous devons essayer d'aller à l'autre extrême et également supprimer la dépendance à jQuery ? Eh bien, ce n'est pas si difficile à prouver (d'autant plus que nous limitons la prise en charge à IE 8 et versions ultérieures). En fin de compte, nous devons implémenter un PubSub personnalisé en utilisant du javascript normal et conserver les événements DOM :
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 = , 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, // IE 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 { // IE 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 = , 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; }
Le modèle peut rester le même sauf pour appeler la méthode de déclenchement jQuery dans le setter. L'appel de la méthode trigger sera remplacé par l'appel de la méthode submit de notre PubSub personnalisé avec des caractéristiques différentes :
// 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 ); } } // ... }
Nous avons une fois de plus obtenu les résultats souhaités avec moins d'une centaine de lignes de JavaScript pur maintenable.
Le contenu ci-dessus est un tutoriel sur la liaison de données bidirectionnelle js. J'espère qu'il sera utile à tout le monde.