1/** 2 * @file 3 * auto_submit.js 4 * 5 * Provides a "form auto-submit" feature for the Better Exposed Filters module. 6 */ 7 8(function ($, Drupal, once) { 9 10 /** 11 * To make a form auto submit, all you have to do is 3 things:. 12 * 13 * Use the "better_exposed_filters/auto_submit" js library. 14 * 15 * On gadgets, you want to auto-submit when changed, add the 16 * data-bef-auto-submit attribute. With FAPI, add: 17 * @code 18 * '#attributes' => array('data-bef-auto-submit' => ''), 19 * @endcode 20 * 21 * If you want to have auto-submit for every form element, add the 22 * data-bef-auto-submit-full-form to the form. With FAPI, add: 23 * @code 24 * '#attributes' => array('data-bef-auto-submit-full-form' => ''), 25 * @endcode 26 * 27 * If you want to exclude a field from the bef-auto-submit-full-form auto 28 * submission, add an attribute of data-bef-auto-submit-exclude to the form 29 * element. With FAPI, add: 30 * @code 31 * '#attributes' => array('data-bef-auto-submit-exclude' => ''), 32 * @endcode 33 * 34 * Finally, you have to identify which button you want clicked for autosubmit. 35 * The behavior of this button will be honored if it's ajax or not: 36 * @code 37 * '#attributes' => array('data-bef-auto-submit-click' => ''), 38 * @endcode 39 * 40 * Currently only 'select', 'radio', 'checkbox' and 'textfield' types are 41 * supported. We probably could use additional support for HTML5 input types. 42 */ 43 Drupal.behaviors.betterExposedFiltersAutoSubmit = { 44 attach: function (context, settings) { 45 46 let selectors = ''; 47 if (drupalSettings.better_exposed_filters && drupalSettings.better_exposed_filters.auto_submit_sort_only
47) { 48 selectors = 'form[data-bef-auto-submit-sort-only] .form-item-sort-order'; 49 } 50 else { 51 // When exposed as a block, the form #attributes are moved from the form 52 // to the block element, thus the second selector. 53 // @see \Drupal\block\BlockViewBuilder::preRender 54 selectors = 'form[data-bef-auto-submit-full-form], [data-bef-auto-submit-full-form] form, [data-bef-auto-submit]'; 55 } 56 57 $(selectors, context).addBack(selectors).find('input:text:not(.hasDatepicker), textarea, select, input:radio, input:checkbox').each(function () { 58 const $el = $(this); 59 const $tagName = $el[0].tagName; 60 if ($tagName === 'SELECT') { 61 setFocus($el); 62 } 63 else if (['radio', 'checkbox'].includes($el.attr('type'))) { 64 setFocus($el); 65 } 66 else { 67 setFocus($el); 68 } 69 }); 70 71 function setFocus($el) { 72 const observer = new IntersectionObserver((entries) => { 73 74 entries.forEach(entry => { 75 if (entry.isIntersecting) { 76 // Get the stored selector and cursor position from settings. 77 const lastTriggeredSelector = settings.bef_autosubmit_triggered_selector; 78 const lastCursorPosition = settings.bef_autosubmit_cursor_position; 79 const currentSelector = $el.attr('data-drupal-selector'); 80 81 if (lastTriggeredSelector && currentSelector) { 82 // Extract the base selector without the hash suffix 83 const lastTriggeredBase = lastTriggeredSelector.replace(/-[a-z0-9]{11}$/, ''); 84 const currentBase = currentSelector.replace(/-[a-z0-9]{11}$/, ''); 85 86 // Match if the base selectors are the same, or if current element is within 87 // the same fieldset as the triggered element 88 if (currentBase === lastTriggeredBase || 89 currentSelector.startsWith(lastTriggeredBase + '-')) { 90 $el.focus(); 91 // Restore cursor position for text inputs. 92 if ($el.is(':text, textarea') && typeof lastCursorPosition === 'number') { 93 try { 94 $el[0].setSelectionRange(lastCursorPosition, lastCursorPosition); 95 } 96 catch (e) { 97 // Some input types don't support setSelectionRange. 98 } 99 } 100 } 101 } 102 // Disconnect observer after processing. 103 observer.disconnect(); 104 } 105 }); 106 }); 107 108 observer.observe($el.get(0)); 109 } 110 111 // The change event bubbles, so we only need to bind it to the outer form 112 // in case of a full form, or a single element when specified explicitly. 113 $(selectors, context).addBack(selectors).each(function (i, e) { 114 const $e = $(e); 115 // Store the current form. 116 let $needsAutoSubmit = $e; 117 118 // Retrieve the autosubmit delay for this particular form.
119 let autoSubmitDelay = 500; 120 if (e.hasAttribute('data-bef-auto-submit-delay')) { 121 autoSubmitDelay = $e.data('bef-auto-submit-delay'); 122 } 123 else if (!e.hasAttribute('data-bef-auto-submit-full-form')) { 124 // Find the container but skip checking if this element already is 125 // 'form[data-bef-auto-submit-full-form]'. 126 const $container = $e.closest('[data-bef-auto-submit-full-form]'); 127 if ($container.length && $container.get(0).hasAttribute('data-bef-auto-submit-delay')) { 128 $needsAutoSubmit = $container; 129 autoSubmitDelay = $container.data('bef-auto-submit-delay'); 130 } 131 } 132 133 // Ensure that have a delay. 134 autoSubmitDelay = autoSubmitDelay || 500; 135 // Separate debounce instance for date inputs change event. 136 let dateInputChangeDebounce = Drupal.debounce(triggerSubmit, autoSubmitDelay, false); 137 138 // Attach event listeners. 139 $(once('bef-auto-submit', $needsAutoSubmit)) 140 // On change, trigger the submit immediately unless it's a date 141 // input which emits change event as soon as the date value is 142 // valid, even if user is still typing. 143 .on('change', function (e) { 144 return e.target.type === 'date' 145 ? dateInputChangeDebounce(e) 146 : triggerSubmit(e); 147 }) 148 // On keyup, wait for a specified number of milliseconds before 149 // triggering autosubmit. Each new keyup event resets the timer. 150 .on('keyup', Drupal.debounce(triggerSubmit, autoSubmitDelay, false)); 151 }); 152 153 /** 154 * Triggers form autosubmit when conditions are right. 155 * 156 * - Checks first that the element that was the target of the triggering 157 * event is `:text` or `textarea`, but is not `.hasDatePicker`. 158 * - Checks that the keycode of the keyup was not in the list of ignored 159 * keys (navigation keys etc.). 160 * 161 * @param {object} e - The triggering event. 162 */ 163 function triggerSubmit(e) { 164 // e.keyCode: key. 165 const ignoredKeyCodes = [ 166 16, // Shift. 167 17, // Ctrl. 168 18, // Alt. 169 20, // Caps lock. 170 33, // Page up. 171 34, // Page down. 172 35, // End. 173 36, // Home. 174 37, // Left arrow. 175 38, // Up arrow. 176 39, // Right arrow. 177 40, // Down arrow. 178 9, // Tab. 179 13, // Enter. 180 27, // Esc. 181 32, // Space. 182 ]; 183 184 // Triggering element. 185 const $target = $(e.target); 186 const $form = e.target.form ? $(e.target.form) : $target.closest('form'); 187 const $submit = $form.find('[data-bef-auto-submit-click]').first(); 188 const mediaQuery = $target.closest('form').data('bef-auto-submit-media-query'); 189 190 // Don't submit when the document doesn't match the media query string. 191 if (mediaQuery && !matchMedia(mediaQuery).matches) { 192 return true; 193 } 194 195 // Don't submit on changes to excluded elements,submit elements, or select2 autocomplete. 196 if ($target.is('[data-bef-auto-submit-exclude], :submit, .select2-search__field, .chosen-search-input')) { 197 return true; 198 } 199 200 // Submit only if this is a non-datepicker textfield and if the 201 // incoming keycode is not one of the excluded values, and it has a 202 // minimum character length. 203 let textfieldMinimumLength = $target.closest('form').data('bef-auto-submit-minimum-length') || 1; 204 let inputTextLength = $target.val().length; 205 let textfieldHasMinimumLength = ($target.is(':text:not(.hasDatepicker), textarea') && inputTextLength >= textfieldMinimumLength) || $target.is('[type="checkbox"], [type="radio"]'); 206 if ( 207 (textfieldHasMinimumLength || inputTextLength === 0) 208 && $.inArray(e.keyCode, ignoredKeyCodes) === -1 209 && !e.altKey 210 && !e.ctrlKey 211 && !e.shiftKey 212 ) { 213 $submit.click(); 214 } 215 // Only trigger submit if a change was the trigger (no keyup). 216 else if (e.type === 'change') { 217 let target = $target.is(':text:not(.hasDatepicker), textarea'); 218 if (target && textfieldHasMinimumLength) { 219 $submit.click(); 220 } 221 else if (!target) { 222 $submit.click(); 223 } 224 } 225 226 // Store the selector string (not the element) so it persists after AJAX refresh. 227 settings.bef_autosubmit_triggered_selector = $target.attr('data-drupal-selector'); 228 // Store cursor position for text inputs to restore after AJAX. 229 if ($target.is(':text, textarea')) { 230 try { 231 settings.bef_autosubmit_cursor_position = $target[0].selectionStart; 232 } 233 catch (e) { 234 settings.bef_autosubmit_cursor_position = $target.val().length; 235 } 236 } 237 } 238 }, 239 }; 240 241}(jQuery, Drupal, once));
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.