PageSourceSearch

https://bccjapan.com/sites/default/files/js/optimized/js_KnxXGkJpH…lACUEgmHwWKANB-eHK69VWAaKOj7Cr4.js?v=10.5.4

js bccjapan.com collected 2026-09-24 21:06:36 UTC 11,128 bytes, 337 lines download raw bytes

1/* Source and licensing information for the line(s) below can be found at https://bccjapan.com/core/misc/form.js. */
2/**
3 * @file
4 * Form features.
5 */
6
7/**
8 * Triggers when a value in the form changed.
9 *
10 * The event triggers when content is typed or pasted in a text field, before
11 * the change event triggers.
12 *
13 * @event formUpdated
14 */
15
16/**
17 * Triggers when a click on a page fragment link or hash change is detected.
18 *
19 * The event triggers when the fragment in the URL changes (a hash change) and
20 * when a link containing a fragment identifier is clicked. In case the hash
21 * changes due to a click this event will only be triggered once.
22 *
23 * @event formFragmentLinkClickOrHashChange
24 */
25
26(function ($, Drupal, debounce) {
27  /**
28   * Retrieves the summary for the first element.
29   *
30   * @return {string}
31   *   The text of the summary.
32   */
33  $.fn.drupalGetSummary = function () {
34    const callback = this.data('summaryCallback');
35
36    if (!this[0] || !callback) {
37      return '';
38    }
39
40    const result = callback(this[0]);
41
42    return result ? result.trim() : '';
43  };
44
45  /**
46   * Sets the summary for all matched elements.
47   *
48   * @param {function} callback
49   *   Either a function that will be called each time the summary is
50   *   retrieved or a string (which is returned each time).
51   *
52   * @return {jQuery}
53   *   jQuery collection of the current element.
54   *
55   * @fires event:summaryUpdated
56   *
57   * @listens event:formUpdated
58   */
59  $.fn.drupalSetSummary = function (callback) {
60    const self = this;
61
62    // To facilitate things, the callback should always be a function. If it's
63    // not, we wrap it into an anonymous function which just returns the value.
64    if (typeof callback !== 'function') {
65      const val = callback;
66      callback = function () {
67        return val;
68      };
69    }
70
71    return (
72      this.data('summaryCallback', callback)
73        // To prevent duplicate events, the handlers are first removed and then
74        // (re-)added.
75        .off('formUpdated.summary')
76        .on('formUpdated.summary', () => {
77          self.trigger('summaryUpdated');
78        })
79        // The actual summaryUpdated handler doesn't fire when the callback is
80        // changed, so we have to do this manually.
81        .trigger('summaryUpdated')
82    );
83  };
84
85  /**
86   * Prevents consecutive form submissions of identical form values.
87   *
88   * Repetitive form submissions that would submit the identical form values
89   * are prevented, unless the form values are different to the previously
90   * submitted values.
91   *
92   * This is a simplified re-implementation of a user-agent behavior that
93   * should be natively supported by major web browsers, but at this time, only
94   * Firefox has a built-in protection.
95   *
96   * A form value-based approach ensures that the constraint is triggered for
97   * consecutive, identical form submissions only. Compared to that, a form
98   * button-based approach would (1) rely on [visible] buttons to exist where
99   * technically not required and (2) require more complex state management if
100   * there are multiple buttons in a form.
101   *
102   * This implementation is based on form-level submit events only and relies
103   * on jQuery's serialize() method to determine submitted form values. As such,
104   * the following limitations exist:
105   *
106   * - Event handlers on form buttons that preventDefault() do not receive a
107   *   double-submit protection. That is deemed to be fine, since such button
108   *   events typically trigger reversible client-side or server-side
109   *   operations that are local to the context of a form only.
110   * - Changed values in advanced form controls, such as file inputs, are not
111   *   part of the form values being compared between consecutive form submits
112   *   (due to limitations of jQuery.serialize()). That is deemed to be
113   *   acceptable, because if the user forgot to attach a file, then the size of
114   *   HTTP payload will most likely be small enough to be fully passed to the
115   *   server endpoint within seconds, or even milliseconds. If a user
116   *   mistakenly attached a wrong file and is technically versed enough to
117   *   cancel the form submission (and HTTP payload) in order to attach a
118   *   different file, then that edge-case is not supported here.
119   *
120   * Lastly, all forms submitted via HTTP GET are idempotent by definition of
121   * HTTP standards, so excluded in this implementation.
122   *
123   * @type {Drupal~behavior}
124   */
125  Drupal.behaviors.formSingleSubmit = {
126    attach() {
127      function onFormSubmit(e) {
128        const $form = $(e.currentTarget);
129        const formValues = new URLSearchParams(
130          new FormData(e.target),
131        ).toString();
132        const previousValues = $form.attr('data-drupal-form-submit-last');
133        if (previousValues === formValues) {
134          e.preventDefault();
135        } else {
136          $form.attr('data-drupal-form-submit-last', formValues);
137        }
138      }
139
140      $(once('form-single-submit', 'body')).on(
141        'submit.singleSubmit',
142        'form:not([method~="GET"])',
143        onFormSubmit,
144      );
145    },
146  };
147
148  /**
149   * Sends a 'formUpdated' event each time a form element is modified.
150   *
151   * @param {HTMLElement} element
152   *   The element to trigger a form updated event on.
153   *
154   * @fires event:formUpdated
155   */
156  function triggerFormUpdated(element) {
157    $(element).trigger('formUpdated');
158  }
159
160  /**
161   * Collects the IDs of all form fields in the given form.
162   *
163   * @param {HTMLFormElement} form
164   *   The form element to search.
165   *
166   * @return {Array}
167   *   Array of IDs for form fields.
168   */
169  function fieldsList(form) {
170    // We use id to avoid name duplicates on radio fields and filter out
171    // elements with a name but no id.
172    return [].map.call(form.querySelectorAll('[name][id]'), (el) => el.id);
173  }
174
175  /**
176   * Triggers the 'formUpdated' event on form elements when they are modified.
177   *
178   * @type {Drupal~behavior}
179   *
180   * @prop {Drupal~behaviorAttach} attach
181   *   Attaches formUpdated behaviors.
182   * @prop {Drupal~behaviorDetach} detach
183   *   Detaches formUpdated behaviors.
184   *
185   * @fires event:formUpdated
186   */
187  Drupal.behaviors.formUpdated = {
188    attach(context) {
189      const $context = $(context);
190      const contextIsForm = context.tagName === 'FORM';
191      const $forms = $(
192        once('form-updated', contextIsForm ? $context : $context.find('form')),
193      );
194      let formFields;
195
196      if ($forms.length) {
197        // Initialize form behaviors, use $.makeArray to be able to use native
198        // forEach array method and have the callback parameters in the right
199        // order.
200        $.makeArray($forms).forEach((form) => {
201          const events = 'change.formUpdated input.formUpdated ';
202          const eventHandler = debounce((event) => {
203            triggerFormUpdated(event.target);
204          }, 300);
205          formFields = fieldsList(form).join(',');
206
207          form.setAttribute('data-drupal-form-fields', formFields);
208          $(form).on(events, eventHandler);
209        });
210      }
211      // On ajax requests context is the form element.
212      if (contextIsForm) {
213        formFields = fieldsList(context).join(',');
214        // @todo replace with form.getAttribute() when #1979468 is in.
215        const currentFields = $(context).attr('data-drupal-form-fields');
216        // If there has been a change in the fields or their order, trigger
217        // formUpdated.
218        if (formFields !== currentFields) {
219          triggerFormUpdated(context);
220        }
221      }
222    },
223    detach(context, settings, trigger) {
224      const $context = $(context);
225      const contextIsForm = context.tagName === 'FORM';
226      if (trigger === 'unload') {
227        once
228          .remove(
229            'form-updated',
230            contextIsForm ? $context : $context.find('form'),
231          )
232          .forEach((form) => {
233            form.removeAttribute('data-drupal-form-fields');
234            $(form).off('.formUpdated');
235          });
236      }
237    },
238  };
239
240  /**
241   * Prepopulate form fields with information from the visitor browser.
242   *
243   * @type {Drupal~behavior}
244   *
245   * @prop {Drupal~behaviorAttach} attach
246   *   Attaches the behavior for filling user info from browser.
247   */
248  Drupal.behaviors.fillUserInfoFromBrowser = {
249    attach(context, settings) {
250      const userInfo = ['name', 'mail', 'homepage'];
251      const $forms = $(
252        once('user-info-from-browser', '[data-user-info-from-browser]'),
253      );
254      if ($forms.length) {
255        userInfo.forEach((info) => {
256          const $element = $forms.find(`[name=${info}]`);
257          const browserData = localStorage.getItem(`Drupal.visitor.${info}`);
258          if (!$element.length) {
259            return;
260          }
261          const emptyValue = $element[0].value === '';
262          const defaultValue =
263            $element.attr('data-drupal-default-value') === $element[0].value;
264          if (browserData && (emptyValue || defaultValue)) {
265            $element.each(function (index, item) {
266              item.value = browserData;
267            });
268          }
269        });
270      }
271      $forms.on('submit', () => {
272        userInfo.forEach((info) => {
273          const $element = $forms.find(`[name=${info}]`);
274          if ($element.length) {
275            localStorage.setItem(`Drupal.visitor.${info}`, $element[0].value);
276          }
277        });
278      });
279    },
280  };
281
282  /**
283   * Sends a fragment interaction event on a hash change or fragment link click.
284   *
285   * @param {jQuery.Event} e
286   *   The event triggered.
287   *
288   * @fires event:formFragmentLinkClickOrHashChange
289   */
290  const handleFragmentLinkClickOrHashChange = (e) => {
291    let url;
292    if (e.type === 'click') {
293      url = e.currentTarget.location
294        ? e.currentTarget.location
295        : e.currentTarget;
296    } else {
297      url = window.location;
298    }
299    const hash = url.hash.substring(1);
300    if (hash) {
301      const $target = $(`#${hash}`);
302      $('body').trigger('formFragmentLinkClickOrHashChange', [$target]);
303
304      /**
305       * Clicking a fragment link or a hash change should focus the target
306       * element, but event timing issues in multiple browsers require a timeout.
307       */
308      setTimeout(() => $target.trigger('focus'), 300);
309    }
310  };
311
312  const debouncedHandleFragmentLinkClickOrHashChange = debounce(
313    handleFragmentLinkClickOrHashChange,
314    300,
315    true,
316  );
317
318  // Binds a listener to handle URL fragment changes.
319  $(window).on(
320    'hashchange.form-fragment',
321    debouncedHandleFragmentLinkClickOrHashChange,
322  );
323
324  /**
325   * Binds a listener to handle clicks on fragment links and absolute URL links
326   * containing a fragment, this is needed next to the hash change listener
327   * because clicking such links doesn't trigger a hash change when the fragment
328   * is already in the URL.
329   */
330  $(document).on(
331    'click.form-fragment',
332    'a[href*="#"]',
333    debouncedHandleFragmentLinkClickOrHashChange,
334  );
335})(jQuery, Drupal, Drupal.debounce);
336
337/* Source and licensing information for the above line(s) can be found at https://bccjapan.com/core/misc/form.js. */

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.