PageSourceSearch

https://pouillelescoteaux.fr/_assets/6b4f2a2766cf7ae23f682cf290eb0…/PowermailCondition.js?1788344467

js pouillelescoteaux.fr collected 2026-10-02 20:04:22 UTC 11,049 bytes, 348 lines download raw bytes

1class PowermailCondition {
2  'use strict';
3
4  /**
5   * Form element (filled via constructor)
6   */
7  #form;
8
9  /**
10   * Is the form a morestep form?
11   */
12  #isMoreStepForm;
13
14  /**
15   * Selector for fields to be excluded from being sent to backend.
16   * Might be useful for file upload fields.
17   *
18   * @type {string}
19   */
20  #excludedFieldsSelector = 'data-powermail-cond-excluded-fields';
21
22  constructor(form) {
23    this.#form = form;
24    this.#form.powermailConditions = this;
25    this.#isMoreStepForm = this.#form.classList.contains('powermail_morestep');
26  }
27
28  initialize = function () {
29    const that = this;
30
31    let formUid = this.#form.querySelector('input.powermail_form_uid').value;
32    let formActionSelector = '#form-' + formUid + '-actions';
33
34    if (document.querySelector(formActionSelector) === null) {
35      // Loading conditions via AJAX
36      that.#sendFormValuesToPowermailCond();
37    } else {
38      // Using prerendered conditions
39      let actions = JSON.parse(document.querySelector(formActionSelector).textContent);
40      that.#processActions(actions);
41    }
42
43    that.#fieldListener();
44    that.#submitListener();
45  }
46
47  /**
48   * Prevents a specific race condition when submitting forms
49   *
50   * Technical background:
51   * When a user's first action is to focus a field, enter a value, and immediately
52   * click submit, it can cause problems. This sequence triggers #sendFormValuesToPowermailCond
53   * and #enableAllFields, but the form submission cancels the network request to the
54   * condition endpoint. As a result, all fields get enabled and transmitted for processing.
55   *
56   * How it works:
57   * This listener checks if any form element is currently focused. If so, it:
58   * 1. Blurs the focused input first
59   * 2. Waits a brief moment (50ms)
60   * 3. Then submits the form
61   *
62   * This ensures field processing completes properly before submission, avoiding the race condition.
63   */
64  #submitListener() {
65    // don't setup race-condition listener for AJAX forms
66    if (this.#form.getAttribute("data-powermail-ajax") === "true") {
67      return;
68    }
69
70    this.#form.addEventListener('submit', (event) => {
71      event.preventDefault();
72
73      if (document.activeElement && document.activeElement.tagName) {
74        const activeElement = document.activeElement;
75        const tagName = activeElement.tagName.toLowerCase();
76        if ((tagName === 'input' || tagName === 'textarea' || tagName === 'select') &&
77          this.#form.contains(activeElement)) {
78          activeElement.blur();
79
80          setTimeout(() => {
81            if (this.#hasValidationErrors()) {
82              return;
83            }
84
85            this.#form.submit();
86          }, 50);
87          return;
88        }
89      }
90
91      if (this.#hasValidationErrors()) {
92        return;
93      }
94
95      this.#form.submit();
96    });
97  }
98
99  /**
100   * Check if the form has any validation errors, covering both native HTML5
101   * validation and powermail's JavaScript validation.
102   *
103   * Note: reportValidity() alone is not sufficient because it always returns
104   * true when the form has a "novalidate" attribute (i.e. when native
105   * validation is disabled via TypoScript validation.native = 0).
106   */
107  #hasValidationErrors() {
108    if (!this.#form.reportValidity()) {
109      return true;
110    }
111    if (this.#form.classList.contains('powermail_form_error')) {
112      return true;
113    }
114    return false;
115  }
116
117  #fieldListener() {
118    const that = this;
119    const fields = this.#getFieldsFromForm();
120    fields.forEach((field) => {
121      field.addEventListener('change', function(event) {
122        that.#sendFormValuesToPowermailCond();
123      })
124    });
125  }
126
127  #sendFormValuesToPowermailCond () {
128    const that = this;
129    that.#enableAllFields();
130    const dataToSend = new FormData(this.#form);
131
132    if (this.#form.hasAttribute(this.#excludedFieldsSelector)) {
133      // gather fields that should be excluded
134      const excludedFields = this.#form.querySelectorAll(this.#form.getAttribute(this.#excludedFieldsSelector));
135      excludedFields.forEach(e => {
136        // and remove them from the payload being sent to the backend
137        if (e.hasAttribute('name')) {
138          dataToSend.delete(e.getAttribute('name'));
139        }
140      });
141    }
142
143    fetch(this.#getAjaxUri(), {body: dataToSend, method: 'post'})
144      .then((resp) => resp.json())
145      .then(function(data) {
146        if (data.loops > data.loopLimit) {
147          console.log('Too much loops reached by parsing conditions and rules. Check for c
147onflicting conditions.');
148        } else {
149          that.#processActions(data);
150        }
151      })
152      .catch(function(error) {
153        console.log(error);
154      });
155  };
156
157  #processActions(data) {
158    if (data.todo !== undefined) {
159      for (let formUid in data.todo) {
160        for (let pageUid in data.todo[formUid]) {
161
162          // do actions with whole pages
163          if (data.todo[formUid][pageUid]['#action'] === 'hide') {
164            if (this.#isMoreStepForm) {
165              PowermailCondition.hideElement(this.#getMoreStepToggleByUid(pageUid));
166            }
167
168            this.#hidePage(this.#getFieldsetByUid(pageUid));
169          }
170          if (data.todo[formUid][pageUid]['#action'] === 'un_hide') {
171            if (this.#isMoreStepForm) {
172              PowermailCondition.showElement(this.#getMoreStepToggleByUid(pageUid));
173            }
174            this.#showPage(this.#getFieldsetByUid(pageUid));
175          }
176
177          // do actions with single fields
178          for (var fieldMarker in data.todo[formUid][pageUid]) {
179            if (data.todo[formUid][pageUid][fieldMarker]['#action'] === 'hide') {
180              this.#hideField(fieldMarker);
181            }
182            if (data.todo[formUid][pageUid][fieldMarker]['#action'] === 'un_hide') {
183              this.#showField(fieldMarker);
184            }
185          }
186        }
187      }
188    }
189    let fieldsets = this.#form.querySelectorAll('.powermail_fieldset');
190    fieldsets.forEach(function(fieldset) {
191      if (window.getComputedStyle(fieldset).visibility === 'hidden') {
192        // Making initially invisible fieldset visible
193        fieldset.style.visibility = 'visible';
194        fieldset.style.opacity = 1;
195      }
196    });
197  };
198
199  #enableAllFields() {
200    const fields = this.#form.querySelectorAll('[disabled="disabled"]');
201    fields.forEach((field) => {
202      field.removeAttribute('disabled');
203    });
204  };
205
206  #getFieldsFromForm() {
207    return this.#form.querySelectorAll(
208      'input:not([data-powermail-validation="disabled"]):not([type="hidden"]):not([type="submit"])'
209      + ', textarea:not([data-powermail-validation="disabled"])'
210      + ', select:not([data-powermail-validation="disabled"])'
211    );
212  };
213
214  #getAjaxUri() {
215    const container = document.querySelector('[data-condition-uri]');
216    if (container === null) {
217      console.log('Tag with data-condition-uri not found. Maybe TypoScript was not included.');
218    }
219    return container.getAttribute('data-condition-uri');
220  };
221
222  #showField(fieldMarker) {
223    let wrappingContainer = this.#getWrappingContainerByMarkerName(fieldMarker);
224    if (wrappingContainer !== null) {
225      PowermailCondition.showElement(wrappingContainer);
226    }
227    let field = this.#getFieldByMarker(fieldMarker);
228    if (field !== null) {
229      field.removeAttribute('disabled');
230      this.#rerequireField(field);
231    }
232  };
233
234  #hideField(fieldMarker) {
235    let wrappingContainer = this.#getWrappingContainerByMarkerName(fieldMarker);
236    if (wrappingContainer !== null) {
237      PowermailCondition.hideElement(wrappingContainer);
238    }
239    let field = this.#getFieldByMarker(fieldMarker);
240    if (field !== null) {
241      field.setAttribute('disabled', 'disabled');
242      this.#derequireField(field);
243    }
244  };
245
246  #showPage(page) {
247    page.classList.remove('powermail-cond-hidden');
248    if (!this.#isMoreStepForm) {
249      PowermailCondition.showElement(page);
250    }
251  };
252
253  #hidePage(page) {
254    if (this.#isMoreStepForm) {
255      page.classList.add('powermail-cond-hidden');
256    } else {
257      PowermailCondition.hideElement(page);
258    }
259  };
260
261  #derequireField(field) {
262    if (field.hasAttribute('required') || field.hasAttribute('data-powermail-required')) {
263      field.removeAttribute('required');
264      field.removeAttribute('data-powermail-required');
265      field.setAttribute('data-powermailcond-required', 'required');
266    }
267  };
268
269  #rerequireField(field) {
270    if (field.getAttribute('data-powermailcond-required') === 'required') {
271      if (this.#isHtml5ValidationActivated() || this.#isPowermailValidationActivated()) {
272        field.setAttribute('required', 'required')
273      }
274    }
275    field.removeAttribute('data-powermailcond-required');
276  };
277
278  #isPowermailValidationActivated() {
279    return this.#form.getAttribute('data-powermail-validate') === 'data-powermail-validate';
280  };
281
282  #isHtml5ValidationActivated() {
283    return this.#form.getAttribute('data-validate') === 'html5';
284  };
285
286  #getWrappingContainerByMarkerName(fieldMarker) {
287    let wrappingContainer = this.#getFieldwrappingContainerByMarker(fieldMarker);
288    if (wrappingContainer !== null) {
289      return wrappingContainer;
290    }
291
292    let field = this.#getFieldByMarker(fieldMarker);
293    if (field !== null) {
294      let wrappingContainer = field.closest('.powermail_fieldwrap');
295      if (wrappingContainer !== null) {
296        return wrappingContainer;
297      }
298    }
299
300    console.log('Error: Could not find field by fieldMarker "' + fieldMarker + '"');
301    return null;
302  };
303
304  #getFieldByMarker(fieldMarker) {
305    let fieldName = 'tx_powermail_pi1[field][' + fieldMarker + ']';
306    return this.#form.querySelector('[name="' + fieldName +  '"]:not([type="hidden"])') ||
307      this.#form.querySelector('[name="' + fieldName +  '[]"]');
308  };
309
310  #getFieldsetByUid(pageUid) {
311    return this.#form.querySelector('.powermail_fieldset_' + pageUid);
312  };
313
314  #getMoreStepToggleByUid(pageUid) {
315    return this.#form.querySelector(`.btn[data-powermail-fieldset="${pageUid}"]`)
316  }
317
318  #getFieldwrappingContainerByMarker(fieldMarker) {
319    return this.#form.querySelector('.powermail_fieldwrap_' + fieldMarker);
320  };
321
322  static hideElement(element) {
323    if (element !== null) {
324      element.classList.add('powermail-cond-hidden');
325      element.style.display = 'none';
326    }
327  }
328
329  static showElement(element) {
330    if (element !== null) {
331      element.classList.remove('powermail-cond-hidden');
332      element.style.display = '';
333    }
334  }
335}
336
337// We use "pageshow" instead of ready/DOMContentLoaded because this event
338// specifically handles the backward/forward-navigation cache (bfcac
338he)
339// of browsers, so when someone returns to a already filled out form,
340// the values get checked properly instead of sendFormValuesToPowermailCond
341// receiving a practically empty initial form state.
342window.addEventListener('pageshow', () => {
343  const forms = document.querySelectorAll('.powermail_form');
344  forms.forEach(function(form) {
345    let powermailConditions = new PowermailCondition(form);
346    powermailConditions.initialize();
347  });
348});

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.