PageSourceSearch

https://www.kickitout.org/modules/contrib/better_exposed_filters/js/auto_submit.js?tlvi6d

js kickitout.org collected 2026-10-02 02:53:05 UTC 9,743 bytes, 241 lines download raw bytes

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.