Problem / Motivation

Two separate bugs produce user-visible breakage when this module is installed on Drupal 10/11 sites.


Bug 1 — Ajax dialog forms (Views UI, Media library, etc.) render a white-screen page containing raw JSON in a <textarea>

Saving a dialog-based form — the most common case being any Views UI configuration dialog — results in a white-screen page whose only content is something like:

[{"command":"closeDialog","selector":"#drupal-modal",...}]

displayed inside a <textarea>.

Root cause (two contributing code paths)

(a) once() ignores the Drupal behavior context

In cv.jquery.validate.js, the attach callback uses:

once('cvJqueryValidate', 'body form').forEach(function(element) {
  $(element).validate(drupalSettings.cvJqueryValidateOptions);
});

'body form' ignores the context argument that Drupal's behavior system passes to attach. This means every form in the entire document — including forms injected into dialogs by Ajax — is discovered and initialised with jQuery Validate on every behavior attach.

Drupal's behavior API requires selectors to be scoped to context so that only newly-inserted DOM is processed each time.

(b) Drupal.Ajax.prototype.beforeSubmit patch is not guarded against dialog forms

The module monkey-patches Drupal.Ajax.prototype.beforeSubmit to call $(this.$form).validate() and $(this.$form).valid() before Ajax submission. When a Views UI dialog button is activated, jQuery Validate's .valid() triggers an internal validation pass that can fire a native form.submit() — bypassing Drupal's Ajax entirely.

The result is a full browser navigation to the Ajax endpoint. Drupal's AjaxResponseSubscriber detects the text/html Accept header (the browser sends this for navigations) and wraps the JSON response in <textarea> tags (see AjaxResponseSubscriber::onResponse()). Because there is no longer an XHR handler to unwrap and execute the commands, the browser renders the raw <textarea>JSON</textarea> as a white page.

Removing clientside_validation resolves the issue instantly, confirming it as the source.


Bug 2 — hook_clientside_validation_should_validate logic is inverted

In clientside_validation.module, the clientside_validation_should_validate() function contains:

if ($should_validate && !$hook($element, $form_state, $form_id) === FALSE) {
  $should_validate = FALSE;
}

Due to PHP operator precedence, ! binds before ===, so this evaluates as:

if ($should_validate && (bool)$hook(...) === TRUE) {
  $should_validate = FALSE;
}

This is the exact inverse of the documented intent. Any module implementing hook_clientside_validation_should_validate and returning FALSE to opt an element out of validation will instead have validation enabled for that element. Returning TRUE (or any truthy value) will disable validation.

This bug is silent as long as no module implements the hook, but renders the opt-out API completely unusable.


Steps to reproduce (Bug 1)

  1. Install clientside_validation and clientside_validation_jquery on a Drupal 10 or 11 site.
  2. Ensure Views and Views UI are enabled and at least one View exists.
  3. Navigate to Administration → Structure → Views and open any View for editing.
  4. Click any dialog-triggering link, e.g. Add field, Add filter criterion, or Add sort criterion.
  5. Select an option and click Apply (or Apply and continue) in the dialog.

Expected result: The dialog closes and the View preview refreshes normally.

Actual result: The page goes white and the only visible content is a <textarea> containing raw JSON Ajax command data.


Proposed resolution

Three targeted changes across two files, included in the attached patch.

clientside_validation_jquery/js/cv.jquery.validate.js

1. Scope the once() call to the Drupal behavior context instead of 'body form', and skip forms inside Drupal-managed dialogs:

- once('cvJqueryValidate', 'body form').forEach(function(element) {
-   $(element).validate(drupalSettings.cvJqueryValidateOptions);
- });
+ once('cvJqueryValidate', 'form', context).forEach(function(element) {
+   if ($(element).closest('.ui-dialog-content, [data-drupal-dialog-content]').length) {
+     return;
+   }
+   $(element).validate(drupalSettings.cvJqueryValidateOptions);
+ });

2. Guard the Drupal.Ajax.prototype.beforeSubmit patch so it never intercepts forms inside Drupal-managed dialogs:

+ var isDialogForm = typeof this.$form !== 'undefined' &&
+   this.$form.closest('.ui-dialog-content, [data-drupal-dialog-content]').length > 0;
- if (typeof this.$form !== 'undefined' && (validateAll === 1 || ...)) {
+ if (typeof this.$form !== 'undefined' && !isDialogForm && (validateAll === 1 || ...)) {

clientside_validation.module

3. Fix the inverted operator precedence in clientside_validation_should_validate():

- if ($should_validate && !$hook($element, $form_state, $form_id) === FALSE) {
+ if ($should_validate && $hook($element, $form_state, $form_id) === FALSE) {

Acceptance criteria

  • Saving a Views UI dialog (Add field, Add filter, Add sort, etc.) closes the dialog and refreshes the preview — no white-screen, no raw JSON visible in the browser.
  • The same holds for any other Drupal core or contrib dialog that uses ui-dialog-content or data-drupal-dialog-content (e.g. Media library, Layout Builder).
  • A module implementing hook_clientside_validation_should_validate and returning FALSE successfully prevents clientside validation from being applied to the targeted element.
  • A module implementing hook_clientside_validation_should_validate and returning TRUE (or nothing) does not suppress validation.
  • Clientside validation still fires normally on non-dialog forms (node add/edit, webforms, etc.).
  • The once() call no longer processes forms outside the current behavior context, preventing duplicate jQuery Validate initialisation on forms already processed in a previous attach cycle.
  • The attached patch applies cleanly to the 4.1.2 release tag.

Affected versions: 4.1.2 (and likely all 4.x releases)

Related Drupal APIs:

  • AjaxResponseSubscriber::onResponse() — server-side behaviour that wraps JSON in <textarea> when Accept: text/html is detected
  • Drupal.behaviors API — attach(context, settings) contract requiring selectors scoped to context
  • hook_clientside_validation_should_validate — documented in clientside_validation.api.php

Comments

philip_stier created an issue. See original summary.

philip_stier’s picture

Patch for the resolution