1/* global jQuery */ 2/* global Drupal */ 3/* global drupalSettings */ 4/* global once */ 5(function ($, Drupal, drupalSettings, window) { 6 7 Drupal.behaviors.ws_ajax_search__ajax_search_block = { 8 attach: function (context, settings) { 9 10 // Valeurs de la configuration. 11 let { items_per_page, display_results_total, track_with_matomo, no_results_text, show_more_text } = drupalSettings.ws_ajax_search.ajax_search_block; 12 // Ãléments HTML de la recherche. 13 let $body = $('body', context); 14 let $inputSearch = $('.ajax-search-input', context); 15 let $wrapperResults = $('.results-wrapper', context); 16 let $divResults = $('.ajax-search-results', context); 17 let $closeResults = $('.ajax-search-close-results', context); 18 19 // Corrige les ids dupliques du champ de recherche et leurs labels. 20 $(once('ajax-search-block-input-id-fix', $('[id="ajax-search-input"]', context))).each(function (index) { 21 const inputId = 'ajax-search-input-' + (index + 1); 22 $(this).attr('id', inputId); 23 $(once('ajax-search-block-label-for-fix', $('label[for="ajax-search-input"]', $(this).parent()))).attr('for', inputId); 24 }); 25 26 // Décalage (nombre d'éléments à sauter). 27 let offset = 0; 28 // Gestion des appels simultanés (recherche et suivi Matomo). 29 let rejectLastSearchPromise, rejectLastMatomoPromise = undefined; 30 31 $(once('ajax-search-block-init', $body)).each(function () { 32 33 // Ouverture/fermeture du container des résultats de recherche. 34 $inputSearch.on('input', function () { 35 $body.trigger('ajaxSearch:open'); 36 }); 37 $closeResults.on('click', function (e) { 38 e.preventDefault(); 39 if (display_results_total) { 40 $body.trigger('ajaxSearch:hideResultsTotal'); 41 } 42 $body.trigger('ajaxSearch:close'); 43 }); 44 45 // Perte du focus lors de l'appui sur Entrée. 46 $inputSearch.on('keyup', function (e) { 47 if (e.keyCode === 13) { 48 document.activeElement.blur(); 49 } 50 }); 51 52 // Saisie dans le champ de recherche. 53 $inputSearch.on('input', function () { 54 let keyword = $(this).val(); 55 // Mise à jour de tous les champs de la page (s'il y en a plusieurs). 56 $inputSearch.val(keyword); 57 if (keyword === '') { 58 // Champ vide : vidage et fermeture des résultats. 59 $divResults.empty(); 60 $('.ajax-search-show-more', context).remove(); 61 if (rejectLastSearchPromise) { 62 rejectLastSearchPromise(); 63 } 64 if (rejectLastMatomoPromise) { 65 rejectLastMatomoPromise(); 66 } 67 if (display_results_total) { 68 $body.trigger('ajaxSearch:hideResultsTotal'); 69 } 70 $body.trigger('ajaxSearch:close'); 71 } 72 else { 73 // Nouvelle recherche : Rà Z du décalage. 74 offset = 0; 75 let parameters = { 76 q: Drupal.checkPlain(keyword), 77 lang: drupalSettings.path.currentLanguage, 78 offset, 79 }; 80 // Récupération et affichage des résultats. 81 doSearch(parameters); 82 } 83 }); 84 85 // Clic sur le lien "Voir plus". 86 $body.on('click', '.ajax-search-show-more', function (e) { 87 e.preventDefault(); 88 let keyword = $inputSearch.val(); 89 let parameters = { 90 q: Drupal.checkPlain(keyword), 91 lang: drupalSettings.path.currentLanguage, 92 offset, 93 }; 94 // Récupération et affichage des résultats. 95 doSearch(parameters); 96 }); 97 98 }); 99 100 function doSearch(parameters) { 101 // Si la précédente recherche n'a pas eu le temps d'aboutir, 102 // alors on rejette la promesse qui avait été lancée en même temps 103 // (ce qui va interrompre le traitement de la précédente recherche). 104 if (rejectLastSearchPromise) { 105 rejectLastSearchPromise(); 106 } 107 // Lancement de 2 promesses en même temps : 108 // - La recherche, 109 // - Une promesse vide, dont on stocke la fonction de rejet. 110 // La première des deux à être résolue continue le script. 111 Promise.race([
112 $.get('/' + drupalSettings.path.pathPrefix + 'recherche-ajax', parameters), 113 new Promise((resolve, reject) => { 114 rejectLastSearchPromise = reject; 115 }) 116 ]) 117 .then((data) => { 118 // Si la recherche a eu le temps d'aboutir (= elle n'a pas été 119 // interrompue par un autre appel), on affiche les résultats. 120 displayResults(data, offset); 121 }) 122 .catch(() => { 123 // Dans le cas où la recherche a été interrompue, on ne fait rien 124 // (en attendant que la nouvelle recherche aboutisse). 125 }); 126 } 127 128 function displayResults(data, reqOffset) { 129 // Décalage nul = nouvelle recherche. 130 if (reqOffset === 0) { 131 // Suppression des anciens résultats. 132 $divResults.empty(); 133 // Aucun résultat. 134 if (!data.total) { 135 $divResults.append('<div class="ajax-search-no-results">' + no_results_text + '</div>'); 136 } 137 // Suivi de la recherche via Matomo, si activé. 138 if (track_with_matomo && window._paq !== undefined) { 139 // Search keyword searched for. 140 let keyword = $inputSearch.val(); 141 // Search category selected in your search engine. If you do not need this, set to false. 142 let category = false; 143 // Number of results on the Search results page. Zero indicates a 'No Result Search Keyword'. Set to false if you don't know. 144 let searchCount = data.total; 145 // Déclenchement du suivi. 146 doMatomoTrack(keyword, category, searchCount); 147 } 148 // Lancement d'un événement avec le nombre total de résultats. 149 if (display_results_total) { 150 $body.trigger('ajaxSearch:showResultsTotal', data.total); 151 } 152 } 153 // Suppression du lien "Voir plus". 154 $('.ajax-search-show-more', context).remove(); 155 // Affichage des nouveaux résultats. 156 if (data.results.length) { 157 data.results.forEach(function (result) { 158 $divResults.append(result.entity); 159 }); 160 // Si nécessaire, Mà J du décalage et rajout du lien "Voir plus". 161 if (data.results.length === items_per_page) { 162 offset = reqOffset + items_per_page; 163 $divResults.after('<a href="#" class="ajax-search-show-more">' + show_more_text + '</a>'); 164 } 165 } 166 } 167 168 function doMatomoTrack(keyword, category, searchCount) { 169 // @see doSearch() 170 if (rejectLastMatomoPromise) { 171 rejectLastMatomoPromise(); 172 } 173 Promise.race([ 174 // Délai de 2 secondes avant le suivi Matomo. 175 new Promise((resolve, reject) => { 176 setTimeout(resolve, 2000); 177 }), 178 new Promise((resolve, reject) => { 179 rejectLastMatomoPromise = reject; 180 }) 181 ]) 182 .then((data) => { 183 // Transformation du mot-clé. 184 // En minuscules. 185 keyword = keyword.toLowerCase(); 186 // Sans accents. 187 const replacements = [ 188 ['a', /[à âä]/g], 189 ['e', /[éèêë]/g], 190 ['i', /[îï]/g], 191 ['o', /[ôö]/g], 192 ['u', /[ùûü]/g], 193 ['c', /ç/g] 194 ]; 195 keyword = replacements.reduce(function (accumulator, [letter, regex]) { 196 return accumulator.replace(regex, letter); 197 }, keyword); 198 // Déclenchement de l'événement de suivi. 199 window._paq.push(['trackSiteSearch', keyword, category, searchCount]); 200 }) 201 .catch(() => { 202 }); 203 204 } 205 206 } 207 }; 208 209})(jQuery, Drupal, drupalSettings, this);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.