Skip to content

WYSIWYG field becomes unusable (read-only) when the form is rendered inside a JetEngine Listing Grid #698

Description

@wasoo123

WYSIWYG field becomes unusable (read-only) when the form is rendered inside a JetEngine Listing Grid

Summary

When a form containing a WYSIWYG field is rendered inside a JetEngine Listing Grid, the editor intermittently renders visually — toolbar, iframe and all — but is completely unusable: clicking into the content area does not place a caret and typing does nothing. Reloading the page a few times eventually produces a working editor, which makes this look random from a user's point of view.

The root cause is not random. The editor is initialized correctly, but its <iframe> is re-parented in the DOM afterwards. When an iframe is moved in the DOM, the browser reloads its inner document from scratch — the editable mode is lost and body is emptied. TinyMCE's JS instance survives this, so it keeps reporting as healthy while the visible editor is a dead shell.

Steps to reproduce

  1. Create a JetFormBuilder form containing a WYSIWYG field.
  2. Render that form inside a JetEngine Listing Grid item template (in my case via the Listing Grid widget in Elementor).
  3. Load the page on the front end.
  4. Click into the WYSIWYG content area and try to type.

Because this is a race condition, it does not fail on every load. Reload repeatedly until a failing render appears.

Expected behaviour

The WYSIWYG field accepts input on every page load.

Actual behaviour

The editor renders but is read-only. No caret, no typing, toolbar buttons have no effect. A hard reload sometimes fixes it, with no other change.

Diagnosis

Collected from the browser console while the editor was in the broken state:

tinymce loaded:            object
live instances:            1
textareas found:           1
duplicate IDs:             1
id:                        wp_editor_20278_instructions
alive (tinymce.get):       true
iframe present:            true
iframe readyState:         complete
iframe designMode:         off      <-- editor is not editable
iframe body innerHTML:     ""       <-- inner document was reset
element at click point:    <iframe id="wp_editor_20278_instructions_ifr">

Key points:

  • No duplicate IDs — only one editor instance exists, so this is not an ID collision from repeated listing items.
  • tinymce.get(id) returns a valid object — the instance exists, which is why "is TinyMCE initialized?" checks pass while the editor is dead.
  • designMode: off and an empty body — the iframe's document was reloaded after TinyMCE configured it.
  • The iframe is the topmost element at the click coordinates, so this is not an overlay or z-index issue.

Confirming the mechanism

The broken state can be reproduced deterministically on a working editor by re-parenting the wrapper:

var wrap = document.querySelector('.wp-editor-wrap');
var box  = document.createElement('div');
wrap.parentNode.insertBefore(box, wrap);
box.appendChild(wrap);   // moving the iframe reloads its document

After running this, the editor shows exactly the same symptoms: alive: true, designMode: off, empty body, and typing is impossible. This strongly suggests that in the failing loads some script (the listing grid / Elementor building its layout) moves the form's DOM subtree after TinyMCE has initialized.

Suggested fix

Two directions, either of which would resolve it:

  1. Initialize later. Defer WYSIWYG initialization until after the listing grid has finished rendering and re-arranging the DOM, rather than on document ready.
  2. Detect and recover. Re-initialize the editor if its iframe document has been reset. Note that tinymce.get(id) is not a sufficient health check — the instance survives the reset. The reliable check is on the iframe's document:
function isBroken(id) {
  var ed  = tinymce.get(id);
  var ifr = document.getElementById(id + '_ifr');
  if (!ed || !ifr) return true;
  var d = ifr.contentDocument;
  if (!d || !d.body) return true;
  return d.designMode !== 'on' && d.body.contentEditable !== 'true';
}

Current workaround

Re-initializing the editor works, but only if the original WordPress configuration is reused. Calling wp.editor.initialize(id, {...}) with a hand-written config produces a stripped-down toolbar (roughly 5 buttons instead of the full set). Reusing tinyMCEPreInit.mceInit[id] preserves the intended toolbar:

var conf = tinyMCEPreInit.mceInit[id];      // original WP config
var copy = Object.assign({}, conf);
copy.selector = '#' + id;
delete copy.elements;
copy.init_instance_callback = function (editor) {
  editor.setContent(previousContent);       // preserve user input
};
tinymce.get(id).remove();
tinymce.init(copy);

This has to be re-run on initial load, after AJAX listing renders, and on user interaction as a safety net.

Environment

  • JetFormBuilder: <3.6.5.1 >
  • JetEngine: <3.8.14.1 >
  • Elementor: 3.35.9
  • TinyMCE: 49110-20250317 (bundled with WP)
  • Browser: Chrome

Additional notes

The console also logs Permissions policy violation: unload is not allowed in this document. from tinymce.min.js. This appears in both working and broken states and does not seem related to this bug, but it may be worth addressing separately since Chrome has been restricting unload handlers.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions