Skip to content

Try to replace more jQuery in formidable.js#2628

Merged
Crabcyborg merged 6 commits into
masterfrom
try_to_replace_more_jquery
Dec 4, 2025
Merged

Try to replace more jQuery in formidable.js#2628
Crabcyborg merged 6 commits into
masterfrom
try_to_replace_more_jquery

Conversation

@Crabcyborg
Copy link
Copy Markdown
Contributor

@Crabcyborg Crabcyborg commented Dec 4, 2025

@Crabcyborg Crabcyborg added this to the 6.26 milestone Dec 4, 2025
@Crabcyborg Crabcyborg marked this pull request as ready for review December 4, 2025 16:40
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Dec 4, 2025

Walkthrough

Refactors js/formidable.js to replace jQuery DOM selection/manipulation with native DOM APIs, updates enableSaveDraft and enableSubmitButton parameter types to accept HTMLElements (and jQuery for save-draft), and adds null-safety throughout form-related flows.

Changes

Cohort / File(s) Summary
jQuery → Native DOM refactor
js/formidable.js
Replaced jQuery selections/manipulation with native APIs (querySelector, querySelectorAll, closest, classList, getComputedStyle); switched iterations to forEach and native loops; added null checks and guards; updated error rendering to use native field containers; adjusted scroll/margin calculations and captcha/turnstile lookups.
API signature updates
js/formidable.js
Changed enableSubmitButton($form) parameter to {HTMLElement} $form; updated enableSaveDraft($form) parameter to accept `{jQuery
Loading / script cleanup
js/formidable.js
Rewrote submit/loading indicator removal and script end-marker removal to use document.querySelectorAll and per-element .remove() loops instead of jQuery collections and .remove() calls.
Field/value handling & validation
js/formidable.js
Replaced jQuery .val(), .hasClass(), and array checks with native value, getAttribute, classList and null-safe checks; adjusted file input handling and validation flows to use resolved DOM elements.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Pay attention to:
    • Callers of enableSubmitButton / enableSaveDraft to ensure compatible argument types.
    • Recaptcha/Turnstile resolution and per-form lifecycle (attach/detach) logic.
    • Script removal and loading indicator behavior to confirm parity with previous jQuery logic.
    • Scroll/margin calculations using getComputedStyle for potential off-by-layout differences.

Possibly related PRs

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 2 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 64.29% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ❓ Inconclusive The PR title 'Try to replace more jQuery' is vague and generic, using imprecise language that doesn't convey specific details about what is being replaced or improved in the codebase. Provide a more specific title that describes the actual changes, such as 'Replace jQuery DOM operations with native equivalents in formidable.js' to clearly indicate the scope and nature of the modifications.
Description check ❓ Inconclusive The PR description only references a related issue without explaining any details about the actual changes made in this pull request. Expand the description to explain what changes were made and why they are needed, beyond just linking to an issue.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch try_to_replace_more_jquery

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (4)
js/formidable.js (4)

171-186: Clarify and harden enableSaveDraft input handling

Accepting both jQuery objects and HTMLElements here is good and consistent with other helpers. To make this more defensive against unexpected inputs (e.g. being passed a NodeList or null), consider also guarding on the presence of querySelectorAll:

function enableSaveDraft( $form ) {
-	const form = $form instanceof jQuery ? $form.get( 0 ) : $form;
-	if ( ! form ) {
+	const form = $form instanceof jQuery ? $form.get( 0 ) : $form;
+	if ( ! form || ! form.querySelectorAll ) {
 		return;
 	}

This keeps current behaviour but avoids a possible TypeError if a non-element slips through.


515-523: DOM-based getFileVals is fine; you can short‑circuit for clarity/perf

The new selector set and DOM usage looks equivalent to the previous jQuery version given how fileID is constructed. To avoid walking the whole NodeList once a non-empty value is found, you could use a simple loop with early return:

function getFileVals( fileID ) {
-	let val = '';
-	const fileFields = document.querySelectorAll(
-		'input[name="file' + fileID + '"], input[name="file' + fileID + '[]"], input[name^="item_meta[' + fileID + ']"]'
-	);
-
-	fileFields.forEach( function( field ) {
-		if ( val === '' ) {
-			val = field.value;
-		}
-	} );
-	return val;
+	const fileFields = document.querySelectorAll(
+		'input[name="file' + fileID + '"], input[name="file' + fileID + '[]"], input[name^="item_meta[' + fileID + ']"]'
+	);
+
+	for ( const field of fileFields ) {
+		if ( field.value !== '' ) {
+			return field.value;
+		}
+	}
+	return '';
}

Behaviour is unchanged (still effectively returns the first non-empty value) but avoids unnecessary iterations.


1096-1101: removeAddedScripts still depends on jQuery at call site

Using document.querySelectorAll with a forEach to remove the end markers is a good jQuery reduction. Note that formContainer.nextUntil(...).remove(); still assumes formContainer is a jQuery object, which is true in the current call (replaceContent = jQuery( object ).closest( '.frm_forms' )). If you plan to keep refactoring jQuery out, this helper is a good candidate to fully convert to DOM traversal, or at least document that formContainer must be jQuery.


1314-1328: removeSubmitLoading now affects all loading forms — confirm this is desired

The new implementation ignores the first argument and operates on every .frm_loading_form in the document, re-enabling submit/save-draft for all of them when enable === 'enable'. That’s arguably simpler, but it’s a behavioural change if multiple forms can be in a loading state concurrently.

If simultaneous AJAX submissions for multiple forms are possible, you may want to scope this to the specific form that initiated the call (using the first parameter) and only fall back to querySelectorAll when no form is provided.

If only one loading form is ever expected, this change is fine but it’s worth making that assumption explicit in a comment.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fdbce3e and d1f9b09.

📒 Files selected for processing (1)
  • js/formidable.js (10 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Cypress
🔇 Additional comments (4)
js/formidable.js (4)

317-341: Switching validateField to pass containers directly looks correct

Passing fieldContainer (an HTMLElement from closest) directly into removeFieldError and addFieldError matches their signatures, which already accept either jQuery or DOM and normalize internally. This simplifies the call sites without changing behaviour.


1772-1776: Native querySelectorAll + jQuery blur trigger is consistent

The init logic that finds input/textarea[onblur] via document.querySelectorAll and then calls jQuery( field ).trigger( 'blur' ) for empty fields keeps the previous behaviour while reducing jQuery usage. No issues spotted here.


2044-2048: Using getComputedStyle for margins matches previous jQuery semantics

Switching from jQuery’s .css('margin-top') to getComputedStyle( document.documentElement/body ).marginTop keeps the behaviour (string values like "0px" that are then parseInt’d) while removing the jQuery dependency. Looks good.


1872-1887: Normalize object to an HTMLElement before calling .closest

submitFormManual now calls object.closest( '.frmapi-form' ), which assumes object is an HTMLElement. Elsewhere in this module, form arguments commonly accept either jQuery or DOM (and normalize via instanceof jQuery). If any external code calls frmFrontForm.submitFormManual( e, jQuery( form ) ), this will throw a TypeError.

You can make this robust while preserving current behaviour by normalizing once at the top:

submitFormManual: function( e, object ) {
+	const form = object instanceof jQuery ? object.get( 0 ) : object;
+	if ( ! form ) {
+		return;
+	}
+
-	if ( document.body.classList.contains( 'wp-admin' ) && ! object.closest( '.frmapi-form' ) ) {
+	if ( document.body.classList.contains( 'wp-admin' ) && ! form.closest( '.frmapi-form' ) ) {
 		return;
 	}

…and keep passing the original object to frmProForm.submitAllowed, validateFormSubmit, and hasInvisibleRecaptcha, which already handle both jQuery and DOM.

Since submitFormManual is part of the public frmFrontForm API, this normalization guards against third‑party usage patterns.

Comment thread js/formidable.js
@Crabcyborg Crabcyborg changed the title Try to replace more jquery Try to replace more jQuery Dec 4, 2025
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
js/formidable.js (1)

141-149: Fix enableSubmitButton JSDoc/param mismatch and accept jQuery|HTMLElement consistently

The JSDoc uses $form while the function parameter is form, which is why ESLint reports both “expected @param names to be form” and “missing JSDoc @param form”. Also, unlike disableSubmitButton/enableSaveDraft, this now only works with a plain DOM element, which is a potential breaking change if anything still calls it with a jQuery object.

You can align types, fix lint, and keep backward‑compatibility by normalizing like the other helpers:

 /**
- * Enable the submit button for a given jQuery form object
+ * Enable the submit button for a given form object.
  *
  * @since 2.03.02
  *
- * @param {HTMLElement} $form
- *
- * @return {void}
- */
-function enableSubmitButton( form ) {
-	form.querySelectorAll( 'input[type="submit"], input[type="button"], button[type="submit"]' ).forEach(
+ * @param {jQuery|HTMLElement} form
+ * @return {void}
+ */
+function enableSubmitButton( form ) {
+	const formEl = form instanceof jQuery ? form.get( 0 ) : form;
+	if ( ! formEl ) {
+		return;
+	}
+	formEl.querySelectorAll( 'input[type="submit"], input[type="button"], button[type="submit"]' ).forEach(
 		button => button.disabled = false
 	);
 }

This should clear the jsdoc errors and keep existing jQuery call sites working.

♻️ Duplicate comments (1)
js/formidable.js (1)

1851-1859: Guard against missing form in reCAPTCHA callbacks

If the reCAPTCHA markup isn’t found or the element has been removed before the callback fires, object will be null, and submitFormNow( object ) will throw when it tries to access object.className.

Adding a simple null‑check keeps normal flow intact and avoids hard failures in edge cases (same issue as previously noted in this file):

afterSingleRecaptcha: function() {
-	const recaptcha = document.querySelector( '.frm-show-form .g-recaptcha' );
-	const object = recaptcha ? recaptcha.closest( 'form' ) : null;
-	frmFrontForm.submitFormNow( object );
+	const recaptcha = document.querySelector( '.frm-show-form .g-recaptcha' );
+	const object = recaptcha ? recaptcha.closest( 'form' ) : null;
+	if ( object ) {
+		frmFrontForm.submitFormNow( object );
+	}
},

afterRecaptcha: function( _, formID ) {
-	const object = document.querySelector( '#frm_form_' + formID + '_container form' );
-	frmFrontForm.submitFormNow( object );
+	const object = document.querySelector( '#frm_form_' + formID + '_container form' );
+	if ( object ) {
+		frmFrontForm.submitFormNow( object );
+	}
},

This prevents a null dereference if the form container can’t be found.

🧹 Nitpick comments (1)
js/formidable.js (1)

1095-1099: Consider scoping .frm_end_ajax_* lookup to the container for robustness

removeAddedScripts now queries document.querySelectorAll( '.frm_end_ajax_' + formID ), then uses formContainer.nextUntil( '.frm_end_ajax_' + formID ) from the (jQuery) container and removes all matched end markers globally.

If the same formID can appear multiple times on a page (e.g., duplicated form output), this will remove all matching end markers across the document, not just within this container’s context. If that’s not desired, consider scoping the selector:

-function removeAddedScripts( formContainer, formID ) {
-	const endReplace = document.querySelectorAll( '.frm_end_ajax_' + formID );
+function removeAddedScripts( formContainer, formID ) {
+	const containerEl = formContainer instanceof jQuery ? formContainer.get( 0 ) : formContainer;
+	const endReplace = containerEl
+		? containerEl.parentNode.querySelectorAll( '.frm_end_ajax_' + formID )
+		: document.querySelectorAll( '.frm_end_ajax_' + formID );

This keeps behavior local while still falling back to the global query if needed.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d1f9b09 and df9975a.

📒 Files selected for processing (1)
  • js/formidable.js (12 hunks)
🧰 Additional context used
🪛 GitHub Actions: Inspections
js/formidable.js

[error] 136-136: Missing JSDoc @param "form" declaration. (jsdoc/require-param)

🪛 GitHub Check: Run ESLint
js/formidable.js

[failure] 141-141:
Expected @param names to be "form". Got "$form"

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Cypress
🔇 Additional comments (9)
js/formidable.js (9)

169-183: enableSaveDraft DOM/jQuery handling looks solid

Normalizing $form to a DOM node and guarding against a missing form before iterating .frm_save_draft elements is correct and consistent with disableSaveDraft. No issues here.


334-338: Passing raw fieldContainer into error helpers is safe and simplifies things

Switching to pass the HTMLElement (fieldContainer) directly into removeFieldError / addFieldError is fine since both helpers already unwrap jQuery when needed. This reduces jQuery usage without changing behavior.


423-435: checkRequiredField value normalization preserves jQuery .val() semantics

The new handling for val === null and non‑string values (arrays from multi‑selects) correctly normalizes to a single string, keeping the “empty vs non‑empty” logic intact. Behavior matches the prior jQuery .val() usage.


513-520: getFileVals jQuery→DOM refactor looks correct

Using document.querySelectorAll over the three input name patterns and returning the first non‑empty field.value mirrors the original jQuery .each behavior. Straightforward and safe change.


1317-1325: Global removeSubmitLoading behavior is coherent with new usage

Iterating over all .frm_loading_form elements, removing loading classes, firing frmEndFormLoading, and (when enable === 'enable') calling enableSubmitButton/enableSaveDraft per form aligns with how removeSubmitLoading is now called (sometimes with a specific form, sometimes with no arg from enableSubmitButtonOnBackButtonPress). The processesRunning guard is preserved. Looks good.


1677-1684: Back/forward cache handler correctly re‑enables submit buttons

Using document.querySelectorAll( '.frm_loading_form' ) and calling enableSubmitButton( form ) on each when event.persisted is true is consistent with the updated enableSubmitButton signature, and the subsequent removeSubmitLoading() safely clears loading state without re‑enabling drafts a second time. No issues.


1770-1773: Native selector + jQuery blur trigger is a clean hybrid

The new init logic that uses document.querySelectorAll to find .frm-show-form inputs/textarea with onblur and triggers jQuery( field ).trigger( 'blur' ) only when field.value === '' preserves previous behavior while reducing reliance on jQuery for selection. Looks correct.


1871-1895: Admin‑page early return refactor is equivalent

Replacing the jQuery check:

  • jQuery( 'body' ).hasClass( 'wp-admin' )
  • ! jQuery( object ).closest( '.frmapi-form' ).length

with:

if ( document.body.classList.contains( 'wp-admin' ) && ! object.closest( '.frmapi-form' ) ) {
    return;
}

preserves the original intent (skip front‑end submission handling for admin forms that aren’t .frmapi-form) while avoiding jQuery selection. Safe and readable change.


2042-2046: Using getComputedStyle for margins in scrollMsg is fine

Switching from jQuery to:

m = getComputedStyle( document.documentElement ).marginTop;
b = getComputedStyle( document.body ).marginTop;

and subtracting their parsed integer values from newPos preserves the previous offset‑adjustment behavior. Margin values are returned as CSS length strings (e.g., "0px"), so parseInt is safe here.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
js/formidable.js (1)

1847-1856: The previous review comment about null guards is still valid.

The reCAPTCHA callbacks afterSingleRecaptcha and afterRecaptcha still lack null guards before calling submitFormNow. If the DOM has changed or the form cannot be located, submitFormNow(null) will throw when accessing object.className at line 1897.

Apply the previously suggested fix:

 afterSingleRecaptcha: function() {
 	const recaptcha = document.querySelector( '.frm-show-form .g-recaptcha' );
 	const object = recaptcha ? recaptcha.closest( 'form' ) : null;
+	if ( object ) {
 		frmFrontForm.submitFormNow( object );
+	}
 },

 afterRecaptcha: function( _, formID ) {
 	const object = document.querySelector( '#frm_form_' + formID + '_container form' );
+	if ( object ) {
 		frmFrontForm.submitFormNow( object );
+	}
 },
🧹 Nitpick comments (4)
js/formidable.js (4)

138-146: Consider accepting jQuery objects for backward compatibility.

Unlike enableSaveDraft (lines 172-176), enableSubmitButton no longer handles jQuery objects. This is a breaking change for any external code calling this function with a jQuery-wrapped form.

Apply this diff to match the pattern used in enableSaveDraft:

-function enableSubmitButton( form ) {
+function enableSubmitButton( form ) {
+	form = form instanceof jQuery ? form.get( 0 ) : form;
+	if ( ! form ) {
+		return;
+	}
 	form.querySelectorAll( 'input[type="submit"], input[type="button"], button[type="submit"]' ).forEach(
 		button => button.disabled = false
 	);
 }

420-420: Replace jQuery .val() with native DOM.

This line still uses jQuery for value retrieval, which is inconsistent with the PR's goal of replacing jQuery with native DOM APIs.

Apply this diff to use native DOM:

-		val = jQuery( field ).val();
+		if ( field.tagName === 'SELECT' && field.multiple ) {
+			val = Array.from( field.selectedOptions ).map( opt => opt.value ).filter( v => v !== '' );
+			if ( val.length === 0 ) {
+				val = '';
+			} else if ( val.length === 1 ) {
+				val = val[0];
+			}
+		} else {
+			val = field.value || '';
+		}

1091-1096: Refactor to eliminate jQuery nextUntil.

This function receives a jQuery object and uses jQuery's nextUntil method (line 1094), which is inconsistent with the PR's refactoring goal.

Apply this diff to use native DOM:

 function removeAddedScripts( formContainer, formID ) {
+	const container = formContainer instanceof jQuery ? formContainer.get( 0 ) : formContainer;
+	if ( ! container ) {
+		return;
+	}
 	const endReplace = document.querySelectorAll( '.frm_end_ajax_' + formID );
 	if ( endReplace.length ) {
-		formContainer.nextUntil( '.frm_end_ajax_' + formID ).remove();
+		let sibling = container.nextElementSibling;
+		while ( sibling && ! sibling.classList.contains( 'frm_end_ajax_' + formID ) ) {
+			const next = sibling.nextElementSibling;
+			sibling.remove();
+			sibling = next;
+		}
 		endReplace.forEach( el => el.remove() );
 	}
 }

1767-1771: Complete the jQuery replacement.

While the field selection uses native DOM, the blur event is still triggered using jQuery. Consider using native APIs for consistency.

Apply this diff to use native event triggering:

 		document.querySelectorAll( '.frm-show-form input[onblur], .frm-show-form textarea[onblur]' ).forEach( function( field ) {
 			if ( field.value === '' ) {
-				jQuery( field ).trigger( 'blur' );
+				field.dispatchEvent( new Event( 'blur', { bubbles: true } ) );
 			}
 		} );
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between df9975a and dfaa5ed.

📒 Files selected for processing (1)
  • js/formidable.js (13 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
js/formidable.js (1)
stripe/js/frmstrp.js (3)
  • form (640-640)
  • form (745-745)
  • fieldContainer (941-941)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Cypress
🔇 Additional comments (6)
js/formidable.js (6)

43-48: LGTM!

The jQuery-to-native-element resolution is correctly implemented.


510-518: LGTM!

The refactoring from jQuery to native DOM APIs is correctly implemented with proper iteration and value access.


331-331: LGTM!

The calls to removeFieldError and addFieldError correctly pass native DOM elements, and both functions properly handle the conversion from jQuery or native at their entry points.

Also applies to: 334-334


2039-2040: LGTM!

The refactoring from jQuery's .css() to native getComputedStyle is correctly implemented.


166-181: LGTM!

The function properly handles both jQuery and native DOM elements with appropriate null safety and uses native DOM APIs for the implementation.


1868-1868: LGTM!

The use of native closest() method is correct.

Comment thread js/formidable.js
@Crabcyborg Crabcyborg merged commit 45b5d6d into master Dec 4, 2025
16 checks passed
@Crabcyborg Crabcyborg deleted the try_to_replace_more_jquery branch December 4, 2025 17:03
@Crabcyborg Crabcyborg changed the title Try to replace more jQuery Try to replace more jQuery in formidable.js Dec 4, 2025
stephywells pushed a commit that referenced this pull request Apr 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant