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
- Create a JetFormBuilder form containing a WYSIWYG field.
- Render that form inside a JetEngine Listing Grid item template (in my case via the Listing Grid widget in Elementor).
- Load the page on the front end.
- 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:
- Initialize later. Defer WYSIWYG initialization until after the listing grid has finished rendering and re-arranging the DOM, rather than on document ready.
- 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.
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 andbodyis emptied. TinyMCE's JS instance survives this, so it keeps reporting as healthy while the visible editor is a dead shell.Steps to reproduce
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:
Key points:
tinymce.get(id)returns a valid object — the instance exists, which is why "is TinyMCE initialized?" checks pass while the editor is dead.designMode: offand an emptybody— the iframe's document was reloaded after TinyMCE configured it.Confirming the mechanism
The broken state can be reproduced deterministically on a working editor by re-parenting the wrapper:
After running this, the editor shows exactly the same symptoms:
alive: true,designMode: off, emptybody, 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:
tinymce.get(id)is not a sufficient health check — the instance survives the reset. The reliable check is on the iframe's document: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). ReusingtinyMCEPreInit.mceInit[id]preserves the intended toolbar:This has to be re-run on initial load, after AJAX listing renders, and on user interaction as a safety net.
Environment
<3.6.5.1 ><3.8.14.1 >Additional notes
The console also logs
Permissions policy violation: unload is not allowed in this document.fromtinymce.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 restrictingunloadhandlers.