Démo sur jsFiddle.net et code source de la demo ici.
Sources du plug-in similarelements.js sous Git.
L’idée de départ est de pouvoir piloter de manière dynamique un certain nombre d’éléments de la page qui déclenchent une même action. L’action sera déclenchée une seule fois, mais tous les éléments pourront être mis à jour. On peut exécuter de manière séparée du code pour l’élément activé et les éléments similaires non-activés qui doivent quand-même faire l’objet d’une mise à jour.
Utilisation du plug-in
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// On déclare un tableau (array) contenant l'ensemble des éléments similaires susceptibles d'être présents dans le DOM. const $_BUTTONS_TOGGLEALLDETAILS = [$('#offCanvas_toggleAllOrderDetail'), $('#inPage_toggleAllOrderDetail'), $('#inPage2_toggleAllOrderDetail')]; // On initie le plug-in. $(document).similarElements({ arrayOfSimilarElements: $_BUTTONS_TOGGLEALLDETAILS, triggerEvent: 'click', triggeredElement: function() { // Le code à exécuter pour l'élément "triggeré". }, nonTriggeredElement: function() { // Le code à exécuter pour le(s) autre(s) élément(s) ("non-triggeré(s)"). } }); |
Source du plug-in
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 |
/** * "Similar Elements" jQuery plug-in * @author: Frank LANG (https://github.com/franklang/) * * Usage: $(document).similarElements( <array of similar elements>, <jQuery event ('click' is default)> ); * <array of similar elements> parameter is mandatory. * ALL elements within <array of similar elements> MUST trigger the EXACT SAME code. */ ;( function ( $, window, undefined ) { $.fn.similarElements = function(options) { var defaults = { arrayOfSimilarElements: null, triggerEvent: 'click', // Définition de 2 fonctions anonymes vides qui serviront à passer // du code sous forme de callback pour le "triggeredElement" et pour // le(s) "nonTriggeredElement" au moment de l'initialisation du plugin. // https://learn.jquery.com/plugins/advanced-plugin-concepts/#provide-callback-capabilities triggeredElement: function() {}, nonTriggeredElement: function() {} } var settings = $.extend( {}, defaults, options); // Vérifier la présence effective, dans le DOM, de chaque élément // déclaré dans le tableau [arrayOfSimilarElements]... // https://stackoverflow.com/questions/50955692/javascript-check-if-html-contains-an-item-from-an-array const MATCHED_ELEMENTS = []; options.arrayOfSimilarElements.forEach( (el) => { const MATCH = $(el); // ...et stocker ceux qui sont présents dans un nouveau tableau <MATCHED_ELEMENTS>. if (MATCH && MATCH.length != 0) { MATCHED_ELEMENTS.push(el); } }); console.log('Trouvé(s) dans le DOM: ' + MATCHED_ELEMENTS.length + ' élement(s) qui matche(nt) sur un total de ' + options.arrayOfSimilarElements.length + '.'); // Si au moins 2 éléments déclarés dans le tableau [options.arrayOfSimilarElements] sont présents dans le DOM: if (MATCHED_ELEMENTS.length >= 2) { console.log('Nous avons au moins 2 éléments déclarés dans le tableau [options.arrayOfSimilarElements] qui sont présents dans le DOM!'); console.log('L_utilisation de ce plugin est justifiée.'); // On boucle sur les éléments stockés dans le tableau <MATCHED_ELEMENTS>. // https://stackoverflow.com/questions/21070431/how-to-loop-over-an-array-and-add-jquery-click-events for (let s in MATCHED_ELEMENTS) { let MATCHED_ELEMENTS_s = MATCHED_ELEMENTS[s]; (function( triggeredElement ){ // Pour l'élément du tableau <MATCHED_ELEMENTS> qui a été utilisé // pour déclancher l'événement <options.triggerEvent> (et UNIQUEMENT pour celui-ci): triggeredElement.on(options.triggerEvent, function(){ // Le callback pour le "triggeredElement": // === Your custom code outside this plugin, within the triggeredElement() function === // Do something with triggered element of options.arrayOfSimilarElements. options.triggeredElement.call( triggeredElement ); // === end: Your custom code. === // On filtre les éléments du tableau <MATCHED_ELEMENTS> pour // y retirer l'élément déclancheur de l'événement <options.triggerEvent>. // On se retrouve avec un nouveau tableau <FILTERED_MATCHED_ELEMENTS> // qui contient uniquement les éléments du tableau de départ qui sont // présents dans le DOM mais qui n'ont pas été utilisés. const FILTERED_MATCHED_ELEMENTS = MATCHED_ELEMENTS.filter(function( value, index, arr ) { return value != triggeredElement; }); // Pour ce(s) élément(s) qui n'ont PAS déclanché l'événement <options.triggerEvent> // (et UNIQUEMENT ceux-ci): FILTERED_MATCHED_ELEMENTS.forEach(function( item ) { // Le callback pour le(s) "nonTriggeredElement": // === Your custom code outside this plugin, within the nonTriggeredElement() function === // Do something with non-triggered element(s) of options.arrayOfSimilarElements. options.nonTriggeredElement.call( item ); // === end: Your custom code. === }); }); }(MATCHED_ELEMENTS_s)); } } // Si 1 seul élément parmi ceux déclarés dans le tableau [options.arrayOfSimilarElements] est présent dans le DOM: else if (MATCHED_ELEMENTS.length === 1) { console.log('1 seul élément parmi ceux déclarés dans le tableau [options.arrayOfSimilarElements] est présent dans le DOM.'); console.log('Vous ne devriez probablement pas utiliser ce plugin si vous ne gérez qu_1 seul élément...'); $(MATCHED_ELEMENTS[0]).on(options.triggerEvent, function(){ // Le callback pour le "triggeredElement": // === Your custom code outside this plugin, within the triggeredElement() function === // Do something with triggered element of options.arrayOfSimilarElements. options.triggeredElement.call( $(this) ); // === end: Your custom code. === }); } // Si AUCUN élément parmi ceux déclarés dans le tableau [options.arrayOfSimilarElements] n'est présent dans le DOM: else { console.log('AUCUN élément parmi ceux déclarés dans le tableau [options.arrayOfSimilarElements] n_est présent dans le DOM.'); console.log('Vous ne devriez probablement pas utiliser ce plugin...'); return false; } }; }( jQuery, window )); |
ATTENTION: en dessous de cette ligne, mes notes sur le projet en cours. Si vous souhaitez utiliser ce plug-in en production, utilisez la source git et référez-vous aux notes au-dessus de cette ligne. Merci.
ATTENTION: code expérimental. Ce code fonctionne très bien mais ne peut pas forcément être réutilisé comme un plugin mais plutôt comme un pattern…
Pour être vraiment utile et ré-utilisable à la volée, ce plugin devrait donner la possibilité d’exécuter du code externe à sa source lors de son initialisation un peu comme dans enquire JS qui permet d’exécuter du code si certaines conditions prévues par le plugin (match, unmatch, setup, deferSetup, destroy
) sont remplies.
Dans le cas de silimarElements.js, les conditions seraient triggeredElement, nonTriggeredElement
, mais je dois encore creuser ça…
Utilisation
Dans l’exemple, j’agis sur plusieurs boutons tous présents dans la même page et qui effectuent tous la même action. Cependant, l’action prévue au click ne sera exécutée qu’une fois mais les libellés des autres boutons proposant la même action auront aussi besoin d’être mis à jour.
Ce plugin se veut (une fois terminé) assez souple pour pouvoir gérer autre chose que des boutons ou des événements click. N’importe quel event jQuery peut être utilisé en argument.
L’idée de départ est de pouvoir piloter de manière dynamique un certain nombre d’éléments de la page qui déclenchent une même action. L’action sera déclenchée une seule fois, mais tous les éléments pourront être mis à jour.
L’argument scopeWithinDOM
est probablement amené à disparaître car il pourra être défini à l’initialisation du plug-in, en dehors de son code source.
1 2 3 4 5 6 7 8 9 10 11 12 |
<button class="btn btn-secondary btn-ToggleAll-orderDetail" role="button" id="offCanvas_toggleAllOrderDetail" class="toggleAll--orderDetail" data-expanded="false"> <span class="hide-all"><i class="fas fa-minus"></i> Masquer </span><span class="show-all"><i class="fas fa-plus"></i> Afficher </span> tous les détails </button> <button class="btn btn-sm btn-secondary btn-ToggleAll-orderDetail" role="button" id="inPage_toggleAllOrderDetail" class="toggleAll--orderDetail" data-expanded="false"> <span class="hide-all"><i class="fas fa-minus"></i> Masquer </span><span class="show-all"><i class="fas fa-plus"></i> Afficher </span> tous les détails </button> <div id="tableOrder"> // <field of action> du plugin: // au click sur l'un des boutons déclarés dans l'array <array of similar elements>, // c'est UNIQUEMENT le code enfant de cet élément div#tableOrder qui sera impacté. </div> |
1 2 3 4 5 6 7 |
const $_TABLE_ORDER = $('#tableOrder'), // Le bouton "afficher/masquer tous les détails" est présent 2 fois dans la page. // Lorsque l'utilisateur clique sur l'un des deux, il faut pouvoir gérer l'impact sur l'autre (switcher le texte afficher/masquer). // On commence par initier un tableau qui contient les IDs de chaque bouton présent dans le DOM qui déclenche une action similaire dans la page: $_BUTTONS_TOGGLEALLDETAILS = ['#offCanvas_toggleAllOrderDetail', '#inPage_toggleAllOrderDetail']; $(document).similarElements( $_BUTTONS_TOGGLEALLDETAILS, $_TABLE_ORDER, 'click' ); |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 |
/** * "Similar Elements" jQuery plug-in * @author: Frank LANG (https://github.com/franklang/) * * Usage: $(document).similarElements( <array of similar elements>, <field of action>, <jQuery event> ); * ALL arguments are mandatory. * ALL elements within <array of similar elements> MUST trigger the same code. */ ;( function ( $, window, undefined ) { $.fn.similarElements = function( arrayOfSimilarElements, scopeWithinDOM, triggerEvent ) { // Vérifier la présence effective, dans le DOM, de chaque élément // déclaré dans le tableau [arrayOfSimilarElements]... // https://stackoverflow.com/questions/50955692/javascript-check-if-html-contains-an-item-from-an-array const MATCHED_ELEMENTS = []; arrayOfSimilarElements.forEach( (el) => { const MATCH = document.querySelectorAll(el); // ...et stocker ceux qui sont présents dans un nouveau tableau <MATCHED_ELEMENTS>. if (MATCH && MATCH.length != 0) { MATCHED_ELEMENTS.push(el); } }); console.log('Found: ' + MATCHED_ELEMENTS.length + ' matched element(s) over ' + arrayOfSimilarElements.length ); console.log(MATCHED_ELEMENTS); // Si au moins 2 éléments déclarés dans le tableau [arrayOfSimilarElements] sont présents dans le DOM: if (MATCHED_ELEMENTS.length >= 2) { console.log('Nous avons au moins 2 éléments déclarés dans le tableau [arrayOfSimilarElements] qui sont présents dans le DOM!'); // On boucle sur les éléments stockés dans le tableau <MATCHED_ELEMENTS>. // https://stackoverflow.com/questions/21070431/how-to-loop-over-an-array-and-add-jquery-click-events for (let s in MATCHED_ELEMENTS) { let MATCHED_ELEMENTS_s = MATCHED_ELEMENTS[s]; (function( triggeredElement ){ // Pour l'élément du tableau <MATCHED_ELEMENTS> qui a été utilisé // pour déclancher l'événement <triggerEvent> (et UNIQUEMENT pour celui-ci): $(triggeredElement).on(triggerEvent, function(){ // On exécute le code suivant: // === Your custom code here: === // Do something with triggered element of arrayOfSimilarElements. $(this).toggleAllDetail( scopeWithinDOM ); // === end: Your custom code here. === // On filtre les éléments du tableau <MATCHED_ELEMENTS> pour // y retirer l'élément déclancheur de l'événement <triggerEvent>. // On se retrouve avec un nouveau tableau <FILTERED_MATCHED_ELEMENTS> // qui contient uniquement les éléments du tableau de départ qui sont // présents dans le DOM mais qui n'ont pas été utilisés. const FILTERED_MATCHED_ELEMENTS = MATCHED_ELEMENTS.filter(function( value, index, arr ) { return value != triggeredElement; }); // Pour ce(s) élément(s) qui n'ont PAS déclanché l'événement <triggerEvent> // (et UNIQUEMENT ceux-ci): FILTERED_MATCHED_ELEMENTS.forEach(function( item ) { // On exécute le code suivant: // === Your custom code here: === // Do something with non-triggered element(s) of arrayOfSimilarElements. let dataExpandedCurrentValue = $(item).attr('data-expanded'); if(dataExpandedCurrentValue === 'true'){ $(item).attr('data-expanded', 'false'); } else if(dataExpandedCurrentValue === 'false'){ $(item).attr('data-expanded', 'true'); } // === end: Your custom code here. === }); }); }(MATCHED_ELEMENTS_s)); } } // Si 1 seul élément parmi ceux déclarés dans le tableau [arrayOfSimilarElements] est présent dans le DOM: else if (MATCHED_ELEMENTS.length === 1) { console.log('1 seul élément parmi ceux déclarés dans le tableau [arrayOfSimilarElements] est présent dans le DOM.'); $(MATCHED_ELEMENTS[0]).on(triggerEvent, function(){ // === Your custom code here: === $(this).toggleAllDetail( scopeWithinDOM ); // === end: Your custom code here. === }); } // Si AUCUN élément parmi ceux déclarés dans le tableau [arrayOfSimilarElements] n'est présent dans le DOM: else { console.log('AUCUN élément parmi ceux déclarés dans le tableau [arrayOfSimilarElements] n_est présent dans le DOM.'); return false; } }; }( jQuery, window )); |
Code source de la demo
1 2 3 4 5 6 7 8 9 10 11 12 |
<p>Hit F12 to see console logs!</p> <section id="example01"> <button id="btn01" class="btn" type="button" data-color="pink"> Switch background color to <span class="text-green">green</span><span class="text-pink">pink</span> </button><br /> <button id="btn02" class="btn" type="button" data-color="pink"> Switch background color to <span class="text-green">green</span><span class="text-pink">pink</span> </button><br /> <button id="btn03" class="btn" type="button" data-color="pink"> Switch background color to <span class="text-green">green</span><span class="text-pink">pink</span> </button> </section> |
1 2 3 4 5 6 7 8 9 10 11 12 13 |
.btn { margin-bottom: 10px; &[data-color="pink"] { .text-green { display: none; } } &[data-color="green"] { .text-pink { display: none; } } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
let currentColor = 'pink'; $.fn.switchBackgroundColor = function() { console.log('old color was ' + currentColor); if ( currentColor === 'pink' ) { $('body').css('background', 'green'); currentColor = 'green'; } else { $('body').css('background', 'pink'); currentColor = 'pink'; } console.log('updated color is ' + currentColor); } $.fn.switchButtonsDataColorValue = function() { const $_THIS = $(this); if( currentColor === 'pink' ) { $_THIS.attr('data-color', 'pink'); } else { $_THIS.attr('data-color', 'green'); } } $('body').css('background', currentColor); const btnArray = [$('#btn01'), $('#btn02'), $('#btn03')]; $(document).similarElements({ arrayOfSimilarElements: btnArray, triggerEvent: 'click', triggeredElement: function() { console.log('triggeredElement: ' + $(this).attr('id')); $(this).switchBackgroundColor(); $(this).switchButtonsDataColorValue(); }, nonTriggeredElement: function() { console.log('nonTriggeredElement: ' + $(this).attr('id')); $(this).switchButtonsDataColorValue(); } }); |