PageSourceSearch

https://www.jynarque.com/modules/custom/otsuka_ds/otsuka_ds_tagging/js/form.js

js jynarque.com collected 2026-10-08 00:24:37 UTC 13,326 bytes, 424 lines download raw bytes

1// v1.6
2(($, Drupal, once) => {
3  let ajaxBeforeRedirectProcessed = [];
4  let beforeRedirectProcessed = [];
5
6  Drupal.behaviors.analyticsHelpersForm = {
7    attach: (context) => {
8      Drupal.analytics = Drupal?.analytics ?? {};
9      Drupal.analytics.formData = Drupal.analytics?.formData ?? {};
10
11      Drupal.analytics.initForm = (formsList) => {
12        formsList.forEach((options) => {
13          if (options?.externalForm) {
14            // Wait for form and handle, and track.
15            waitForElement(options.formSelector, () => {
16              handleForm(options);
17            });
18          } else {
19            handleForm(options);
20          }
21
22          Drupal.analytics.lsNewPageConfirmation.check(options);
23        });
24      }
25
26      function waitForElement(selector, callback) {
27        const $body = $('body');
28
29        const observer = new MutationObserver((mutationsList, observerInstance) => {
30          if ($body.find(selector).length) {
31            callback($body.find(selector));
32          }
33        });
34
35        observer.observe(document.body, { childList: true, subtree: true });
36      }
37
38      /**
39       * Form handler.
40       *
41       * @param {Object} options
42       */
43      function handleForm(options) {
44        $(once('form-listener', options.formSelector, document)).each(() => {
45          const form = getForm(options);
46          const $form = $(form);
47          const $submitButton = $form.find(options?.submitButtonSelector ?? 'input[type="submit"]');
48
49          form.setAttribute('data-form-name', options.formName);
50          form.setAttribute('data-form-type', options.formType);
51
52          $submitButton.addClass('data-analytics-form-submit data-analytics-click-link');
53
54          $submitButton.each(function () {
55            let name = Drupal.t('Submit');
56            if (this.value) {
57              name = this.value;
58            } else if (this.innerText) {
59              name = this.innerText;
60            }
61
62            Drupal.analytics.addDataAnalyticsLink(this, {
63              name: name,
64            });
65          });
66
67          if (options?.addCustomClasses) {
68            for (const [key, value] of Object.entries(options.addCustomClasses)) {
69              $form.find(key).addClass(value);
70            }
71          }
72
73          // Check result on a new page after redirect.
74          if (options?.newPageConfirmation) {
75            window.onload = () => collectAndExecute(options);
76            Drupal.analytics.lsNewPageConfirmation.init(form, options);
77          }
78
79          $submitButton.on('click', () => {
80            Drupal.analytics.formData = formValues(form);
81            collectAndExecute(options);
82          });
83          $submitButton.on('keydown', (event) => {
84            if (event.key === 'Enter') {
85              Drupal.analytics.formData = formValues(form);
86              collectAndExecute(options);
87            }
88          });
89        });
90      }
91
92      /**
93       * Get the form element.
94       *
95       * @param {Object} options
96       * @return {Element}
97       */
98      function getForm(options) {
99        let form = document.querySelector(`${options.formSelector}`);
100        if (form.tagName.toLowerCase() !== 'form') {
101          form = document.querySelector(`${options.formSelector} form`);
102        }
103        return form;
104      }
105
106      /**
107       * Collect all function calls for delay timeout and execute with final
108       * results.
109       *
110       * @param {(Object|undefined)} options
111       * @returns
112       */
113      function collectAndExecute(options = undefined) {
114        const delay = options?.delay ?? 100;
115
116        if (options?.formCompleteBeforeRedirect) {
117          if (options?.ajax) {
118            $(document).off('ajaxComplete').on('ajaxComplete', (event, xhr, settings) => {
119              setTimeout(() => formError(options), delay);
120            });
121            formCompleteBeforeAjaxRedirect(options);
122
123            if (options?.jsValidation) {
124              setTimeout(() => formError(options), delay);
125            }
126          } else {
127            if (options?.jsValidation) {
128              formCompleteBeforeRedirect(options);
129            }
130          }
131        } else {
132          if (options?.ajax) {
133            $(document).off('ajaxComplete').on('ajaxComplete', (event, xhr, settings) => {
134              setTimeout(() => checkForm(options), delay);
135            });
136          }
137
138          if (options?.jsValidation) {
139            setTimeout(() => checkForm(options), delay);
140          }
141        }
142
143        if (!options?.ajax && !options?.jsValidation) {
144          setTimeout(() => checkForm(options), delay);
145        }
146      }
147
148      /**
149       * Check form.
150       *
151       * @param {Object} options
152       */
153      function checkForm(options) {
154        const errors = formError(options);
155        if (!errors) {
156          formComplete(options);
157        }
158      }
159
160      /**
161       * Trigger form_complete event and redirect after delay for Ajax form.
162       *
163       * @param {Object} options
164       */
165      function formCompleteBeforeAjaxRedirect(options) {
166        if (!ajaxBeforeRedirectProcessed.includes(options.formSelector)) {
167          const redirectDelay = options?.formCompleteBeforeRedirectSettings?.delay ?? 2000;
168          const originalWebformRefresh = Drupal.AjaxCommands.prototype.webformRefresh;
169
170          Drupal.AjaxCommands.prototype.webformRefresh = function (ajax, response, status) {
171            const self = this;
172
173            formComplete(options);
174
175            setTimeout(() => {
176              // Call the original webformRefresh command to proceed with the default behavior.
177              originalWebformRefresh.call(self, ajax, response, status);
178            }, redirectDelay);
179          };
180
181          ajaxBeforeRedirectProcessed = [ ...ajaxBeforeRedirectProcessed, options.formSelector ];
182        }
183      }
184
185      /**
186       * Trigger form_complete event and redirect after delay for regular form.
187       *
188       * @param {Object} options
189       */
190      function formCompleteBeforeRedirect(options) {
191        if (!beforeRedirectProcessed.includes(options.formSelector)) {
192          const form = getForm(options);
193          form.addEventListener('submit', function(event) {
194            event.preventDefault();
195
196            setTimeout(() => {
197              const errors = formError(options);
198              if (!errors) {
199                formComplete(options);
200
201                const redirectDelay = options?.formCompleteBeforeRedirectSettings?.delay ?? 2000;
202
203                setTimeout(() => {
204                  this.submit();
205                }, redirectDelay);
206              }
207            }, options?.delay ?? 100);
208          });
209
210          beforeRedirectProcessed = [ ...beforeRedirectProcessed, options.formSelector ];
211        }
212      }
213
214      /**
215       * Collect form values.
216       *
217       * @param {Element} form
218       */
219      function formValues(form) {
220        const elements = form.elements;
221        const result = {};
222
223        for (let i = 0; i < elements.length; i++) {
224          const element = elements[i];
225          const name = element.name;
226
227          // Skip elements without a name or disabled elements
228          if (!name || element.disabled) continue;
229
230          switch (element.type) {
231            case 'checkbox':
232              result[name] = element.checked;
233              break;
234            case 'radio':
235              if (element.checked) {
236                result[name] = element.value;
237              } else if (!(name in result)) {
238                result[name] = null;
239              }
240              break;
241            case 'select-multiple':
242              result[name] = Array.from(element.selectedOptions).map(option => option.value);
243              break;
244            default:
245              result[name] = element.value;
246          }
247        }
248        return result;
249      }
250
251      /**
252       * Find form.
253       *
254       * @param {Element} element
255       */
256      function findForm(element) {
257        while (true) {
258          element = element.parentElement;
259          const form = element.querySelector('form');
260          if (form) return form;
261        }
262      }
263
264      /**
265       * Through event of form successful submission.
266       *
267       * @param {Object} options
268       */
269      function formComplete(options) {
270        const getData = () => {
271          const data = {
272            form_name: options.formName,
273            form_type: options.formType,
274          };
275
276          if (options?.formFieldPopulated) {
277            const formFieldPopulated = [];
278            options.formFieldPopulated.forEach((field) => {
279              if (Drupal.analytics.formData[field]) {
280                formFieldPopulated.push(`${field}:${Drupal.analytics.formData[field]}`);
281              }
282            });
283            if (formFieldPopulated.length) {
284              data.form_field_populated = formFieldPopulated.join();
285            }
286          }
287
288          return data;
289        };
290
291        if (options?.resultSelector) {
292          const selector = `${options.formSelector} ${options.resultSelector}`;
293          const $formResultEl = $(`${selector}:visible`);
294
295          if ($formResultEl.length) {
296            const form = findForm($formResultEl[0]);
297            const results = formValues(form);
298            const data = getData();
299
300            if (options?.formResult) {
301              data.form_result = $(form).find(options.formResult).first().text();
302            }
303
304            $(document).trigger('form_complete', { data });
305          }
306        } else if (options?.formCompleteBeforeRedirect) {
307          const data = getData();
308          $(document).trigger('form_complete', { data });
309        }
310      }
311
312      /**
313       * Through event of form error submission.
314       *
315       * @param {Object} options
316       * @return boolean
317       */
318      function formError(options) {
319        if (options?.errorSelector) {
320          const $errors = $(options.formSelector).find(options.errorSelector);
321          let errors = [];
322
323          $errors.each(function () {
324            const $item = $(this);
325            const item = $item[0];
326            const isErrorVisible = $item.is(':visible');
327            let errorMessage;
328
329            if (isErrorVisible) {
330              if (item.tagName.toLowerCase() === 'select') {
331                for (const option of item.options) {
332                  if (option.defaultSelected) {
333                    errorMessage = option.text;
334                    break;
335                  }
336                }
337              } else {
338                errorMessage = item.innerText.replace(/["'`\n\r\t\0\v]/g, '');
339              }
340              if (!errorMessage) {
341                errorMessage = item.getAttribute('placeholder');
342              }
343              errorMessage = errorMessage?.trim();
344
345              let name = '';
346              const $formItem = $item.closest('.form-item');
347              const $fieldset = $item.closest('fieldset');
348
349              if ($formItem.hasClass('js-form-type-checkbox')) {
350                name = $formItem.find('input').attr('name');
351                errorMessage ??= Drupal.t('Required');
352              } else if ($fieldset.hasClass('radios--wrapper')) {
353                name = $fieldset.find('input').attr('name');
354                errorMessage ??= Drupal.t('Required');
355              } else if (!options?.simpleErrorsFormat) {
356                name = $item.parent().find('input, select, textarea').attr('name');
357              }
358
359              if (errorMessage) {
360                if (name) {
361                  errors.push(`${name}^${errorMessage}`);
362                } else {
363                  errors.push(errorMessage);
364                }
365              }
366            }
367          });
368
369          if (options?.captcha) {
370            const $captchaErrorWrapper = $(options.formSelector).find(options.captcha.errorSelector);
371            if ($captchaErrorWrapper.length) {
372              errors.push(options.captcha.errorMessage);
373            }
374          }
375
376          if (errors.length) {
377            errors = [...new Set(errors)]; // Remove duplicates
378            const $form = $(options.formSelector);
379
380            let step;
381            if (options?.stepsSelectors) {
382              for (const [key, stepSelector] of Object.entries(options.stepsSelectors)) {
383                const $stepWrapper = $form.find(`${stepSelector}:visible`);
384
385                if ($stepWrapper.length) {
386                  step = key;
387                  break;
388                }
389              }
390            }
391
392            // Replace errors if it's needed.
393            if (options?.replaceErrors) {
394              options.replaceErrors.forEach((replace) => {
395                errors = errors.map(error => {
396                  return error === replace.from ? replace.to : error;
397                });
398              });
399            }
400
401            const data = {
402              form_name: options.formName,
403              form_type: options.formType,
404              form_error_messages: errors,
405            };
406
407            if (step) {
408              data.form_step = step;
409            }
410
411            $(document).trigger('form_error', { data });
412
413            Drupal.analytics.lsNewPageConfirmation.deleteKey(options);
414          }
415
416          return (!!errors.length);
417        }
418
419        // No errors.
420        return false;
421      }
422    }
423  };
424})(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.