PluginProbe
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More / trunk
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More vtrunk
6.35 6.34 6.33.1 6.33 6.32.1 6.32 6.31 6.25 6.25.1 6.26 6.26.1 6.27 6.28 6.29 6.3 6.3.1 6.3.2 6.30 6.4 6.4.1 6.4.2 6.5 6.5.1 6.5.2 6.5.3 All 141 releases
← All changes | square/js/frontend.js +279 -3 6.32.1 → trunk View file →
@@ -31,8 +31,15 @@
31 31 }
32 32
33 33 const card = await payments.card();
34 34 const cardStyle = frmSquareVars.style;
35 +
36 + // Never attach while the card element is hidden (e.g. by conditional
37 + // logic). Square measures the container on attach, and a hidden
38 + // container measures as zero-size, so the card form renders with the
39 + // wrong height. Wait until the element is visible before attaching.
40 + await waitForVisibleCardElement( cardElement );
41 +
35 42 await card.attach( '.frm-card-element' );
36 43
37 44 card.configure( { style: cardStyle } );
38 45
@@ -59,8 +66,14 @@
59 66 if ( thisForm ) {
60 67 if ( squareCardElementIsComplete ) {
61 68 enableSubmit();
62 69 } else {
70 + if ( squareIsConditionallyDisabled( thisForm ) ) {
71 + running = 0;
72 + enableSubmit();
73 + return;
74 + }
75 +
63 76 disableSubmit( thisForm );
64 77 }
65 78 }
66 79 } );
@@ -79,9 +92,9 @@
79 92 enableSubmit();
80 93 } else {
81 94 cardFields.postalCode = false;
82 95 squareCardElementIsComplete = false;
83 - disableSubmit();
96 + disableSubmit( thisForm );
84 97 }
85 98 } );
86 99
87 100 return card;
@@ -87,9 +100,92 @@
87 100 return card;
88 101 }
89 102
90 103 /**
104 + * Resolve once the card element is visible (has a layout box).
105 + * A width of zero means the element or one of its ancestors is hidden,
106 + * usually by conditional logic setting display: none.
107 + *
108 + * @since 6.34
109 + *
110 + * @param {HTMLElement} cardElement
111 + * @return {Promise<void>}
112 + */
113 + function waitForVisibleCardElement( cardElement ) {
114 + return new Promise( resolve => {
115 + if ( cardElement.getBoundingClientRect().width > 0 ) {
116 + resolve();
117 + return;
118 + }
119 +
120 + const form = cardElement.closest( 'form' );
121 + const observer = new MutationObserver( () => {
122 + if ( cardElement.getBoundingClientRect().width > 0 ) {
123 + observer.disconnect();
124 + resolve();
125 + }
126 + } );
127 +
128 + // Conditional logic toggles inline styles on field and section
129 + // containers, so watch the whole form for attribute changes and
130 + // re-check the card element's visibility on each change.
131 + observer.observe( form || document.body, {
132 + attributes: true,
133 + attributeFilter: [ 'style', 'class' ],
134 + subtree: true
135 + } );
136 + } );
137 + }
138 +
139 + /**
140 + * Check if a Square card element is conditionally hidden.
141 + * If it is, we should not be disabling the submit button.
142 + *
143 + * @since 6.34
144 + *
145 + * @param {HTMLElement} form
146 + *
147 + * @return {boolean} True if the field is conditionally hidden, false otherwise.
148 + */
149 + function squareIsConditionallyDisabled( form ) {
150 + const fieldContainer = getPaymentElementFieldContainer( form );
151 + if ( ! fieldContainer ) {
152 + return false;
153 + }
154 +
155 + // Field is conditionally hidden.
156 + if ( 'none' === fieldContainer.style.display ) {
157 + return true;
158 + }
159 +
160 + // Section parent is conditionally hidden.
161 + const parentSection = fieldContainer.closest( '.frm_section_heading' );
162 + return parentSection && 'none' === parentSection.style.display;
163 + }
164 +
165 + /**
166 + * Try to get the field container for a Square card payment element.
167 + * The field container is checked to determine if the field is conditionally hidden or not.
168 + *
169 + * @param {HTMLElement} form
170 + *
171 + * @return {HTMLElement|null} The field container element or null if not found.
172 + */
173 + function getPaymentElementFieldContainer( form ) {
174 + const paymentElement = form.querySelector( '.frm-card-element' );
175 + if ( ! paymentElement ) {
176 + return null;
177 + }
178 + return paymentElement.parentElement.closest( '.frm_form_field' );
179 + }
180 +
181 + /**
91 182 * Enable the submit button for the form.
183 + *
184 + * Square being ready is only half of what enables the button. When the submit
185 + * button has conditional logic of its own that is not satisfied, it stays
186 + * disabled no matter what state the card is in, so every caller is checked
187 + * here rather than at the individual call sites.
92 188 */
93 189 function enableSubmit() {
94 190 if ( running > 0 ) {
95 191 return;
@@ -94,8 +190,12 @@
94 190 if ( running > 0 ) {
95 191 return;
96 192 }
97 193
194 + if ( submitButtonIsConditionallyDisabled( getFormIdForForm( thisForm ) ) ) {
195 + return;
196 + }
197 +
98 198 thisForm.classList.add( 'frm_loading_form' );
99 199 frmFrontForm.removeSubmitLoading( jQuery( thisForm ), 'enable', 0 );
100 200
101 201 // Trigger custom event for other scripts to hook into
@@ -221,13 +321,22 @@
221 321 const cardContainer = document.querySelector( '.frm-card-element' );
222 322 if ( cardContainer ) {
223 323 thisForm = cardContainer.closest( 'form' );
224 324 if ( thisForm ) {
225 - // Initially disable the submit button until card is valid
226 - disableSubmit( thisForm );
325 + listenForFieldMutations( thisForm );
326 + listenForSubmitButtonMutations( thisForm );
227 327
328 + if ( ! squareIsConditionallyDisabled( thisForm ) ) {
329 + // Initially disable the submit button until card is valid
330 + disableSubmit( thisForm );
331 + }
332 +
228 333 // Add event listener for form submission
229 334 thisForm.addEventListener( 'submit', function( event ) {
335 + if ( squareIsConditionallyDisabled( thisForm ) ) {
336 + return;
337 + }
338 +
230 339 event.preventDefault();
231 340 event.stopPropagation();
232 341
233 342 if ( ! squareCardElementIsComplete ) {
@@ -313,8 +422,175 @@
313 422 }
314 423 displayPaymentFailure( e.message );
315 424 }
316 425 }
426 + }
427 +
428 + /**
429 + * Possibly toggle on and off the submit button when a Stripe Link payment field is conditionally shown or hidden.
430 + *
431 + * @since 6.34
432 + *
433 + * @param {HTMLElement} form
434 + * @return {void}
435 + */
436 + function listenForFieldMutations( form ) {
437 + const fieldContainer = getPaymentElementFieldContainer( form );
438 + if ( ! fieldContainer ) {
439 + return;
440 + }
441 +
442 + observeAttributeMutations( fieldContainer, handleMutation );
443 +
444 + const section = fieldContainer.closest( '.frm_section_heading' );
445 + if ( section ) {
446 + observeAttributeMutations( section, handleMutation );
447 + }
448 +
449 + /**
450 + * Handle a style attribute change for either a payment field container
451 + * or the field container of its parent section.
452 + *
453 + * @param {MutationRecord} mutation
454 + * @return {void}
455 + */
456 + function handleMutation( mutation ) {
457 + if ( mutation.attributeName !== 'style' ) {
458 + return;
459 + }
460 +
461 + const isFieldVisible = 'none' !== mutation.target.style.display;
462 +
463 + // If field is hidden, enable submit (field is conditionally not required)
464 + if ( ! isFieldVisible ) {
465 + thisForm = form;
466 + running = 0;
467 + enableSubmit();
468 + return;
469 + }
470 +
471 + // Field is now visible, recalculate size and check validation
472 + if ( cardGlobal ) {
473 + cardGlobal.recalculateSize();
474 + }
475 +
476 + const shouldEnable = squareCardElementIsComplete || squareIsConditionallyDisabled( form );
477 + if ( ! shouldEnable ) {
478 + disableSubmit( form );
479 + return;
480 + }
481 +
482 + thisForm = form;
483 + running = 0;
484 + enableSubmit();
485 + }
486 + }
487 +
488 + /**
489 + * Keep the submit button disabled while the Square card is not ready.
490 + *
491 + * Conditional logic on the submit button enables it as soon as its own
492 + * conditions are met, with no knowledge of the payment field. Watch for that
493 + * and disable it again until the card details are complete.
494 + *
495 + * @since 6.34
496 + *
497 + * @param {HTMLElement} form
498 + * @return {void}
499 + */
500 + function listenForSubmitButtonMutations( form ) {
501 + const submitButton = form.querySelector( '.frm_final_submit' );
502 + if ( ! submitButton ) {
503 + return;
504 + }
505 +
506 + observeAttributeMutations( submitButton, mutation => {
507 + if ( mutation.attributeName !== 'disabled' || submitButton.disabled ) {
508 + return;
509 + }
510 +
511 + if ( squareCardElementIsComplete || squareIsConditionallyDisabled( form ) ) {
512 + // Either the card is filled in or it is conditionally hidden, so
513 + // there is nothing left for Square to wait on.
514 + return;
515 + }
516 +
517 + disableSubmit( form );
518 + } );
519 + }
520 +
521 + /**
522 + * @param {HTMLElement} element
523 + * @param {Function} mutationHandler
524 + *
525 + * @return {void}
526 + */
527 + function observeAttributeMutations( element, mutationHandler ) {
528 + const observer = new MutationObserver(
529 + mutations => {
530 + mutations.forEach( mutationHandler );
531 + }
532 + );
533 + observer.observe(
534 + element,
535 + { attributes: true }
536 + );
537 + }
538 +
539 + /**
540 + * Check if the submit button is conditionally disabled.
541 + * This is required for Stripe link so the button does not get enabled at the wrong time after completing the Stripe elements.
542 + *
543 + * @since 6.34
544 + *
545 + * @param {string} formId
546 + *
547 + * @return {boolean} True if the submit button is conditionally disabled, false otherwise.
548 + */
549 + function submitButtonIsConditionallyDisabled( formId ) {
550 + if ( ! submitButtonIsConditionallyNotAvailable( formId ) ) {
551 + return false;
552 + }
553 +
554 + // __FRMRULES is only defined when conditional logic is on the page.
555 + const submitRules = typeof __FRMRULES === 'undefined' ? undefined : __FRMRULES[ `submit_${ formId }` ];
556 +
557 + return Boolean( submitRules ) && 'disable' === submitRules.hideDisable;
558 + }
559 +
560 + /**
561 + * Check submit button is conditionally "hidden". This is also used for the enabled check and is used in submitButtonIsConditionallyDisabled.
562 + *
563 + * @since 6.34
564 + *
565 + * @param {string} formId
566 + *
567 + * @return {boolean} True if the submit button is conditionally not available, false otherwise.
568 + */
569 + function submitButtonIsConditionallyNotAvailable( formId ) {
570 + const hideFields = document.getElementById( `frm_hide_fields_${ formId }` );
571 + if ( ! hideFields ) {
572 + return false;
573 + }
574 +
575 + // The value is a JSON array of every container conditional logic has
576 + // hidden, for example ["frm_field_25_container","frm_form_16_container
577 + // .frm_final_submit"], so match the quoted entry anywhere in it. Matching
578 + // the array brackets too would only find the submit button when it is the
579 + // single hidden entry, and it never is once the payment field has
580 + // conditional logic of its own.
581 + return hideFields.value.includes( `"frm_form_${ formId }_container .frm_final_submit"` );
582 + }
583 +
584 + /**
585 + * Check a form's form_id input for a form ID value.
586 + *
587 + * @param {HTMLElement} form
588 + *
589 + * @return {number} The form ID.
590 + */
591 + function getFormIdForForm( form ) {
592 + return parseInt( form.querySelector( '[name="form_id"]' ).value );
317 593 }
318 594
319 595 document.addEventListener( 'DOMContentLoaded', async function() {
320 596 if ( ! window.Square ) {