Skip to content
59 changes: 54 additions & 5 deletions js/fieldmanager-loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,71 @@
* Ensures that metaboxes have loaded before initializing functionality.
* @param {function} callback - The callback function to execute when the DOM is ready.
*/
function fmLoadModule( callback ) {
function fmLoadModule( callback ) {
/**
* Wraps the provided callback to add check for the Gutenberg editor. If this
* is called within the Gutenberg editor, then the callback function will trigger
* after the metaboxes are initialized.
*/
const wrappedCallback = () => {
if (document.querySelector('.block-editor-page')) {
let pollAttemptsRemaining = 5;
let isSubscribed = false;

wp.data.subscribe(() => {
if ( ! isSubscribed && wp.data.select( 'core/edit-post' ).areMetaBoxesInitialized() ) {
// Ensure new intervals aren't created on subsequent state updates.
isSubscribed = true;

/**
* `areMetaBoxesInitialized` is called immediately before the
* `MetaBoxesArea` component is rendered, which is where the metabox
* HTML is moved from the hidden div and into the main form element.
*
* This means we need to set a polling function that checks for the
* existence of the markup in the DOM before we run our callbacks.
*
* @link https://github.com/WordPress/gutenberg/blob/019d0a1b1883a5c3e5c9cdecc60bd5e546b60a1b/packages/edit-post/src/components/meta-boxes/index.js#L38-L45
* @link https://github.com/WordPress/gutenberg/blob/d39949a3b9dc8e12d5f5d33b9091f14b93b37c8a/packages/edit-post/src/components/meta-boxes/meta-boxes-area/index.js#L34-L36
*/
const pollForMetaBoxes = setInterval(() => {
let callbackExecuted = false;

// Decrement the counter so we can limit the number of tries.
pollAttemptsRemaining--;

if ( document.querySelector('.edit-post-meta-boxes-area__container') ) {
callback();
callbackExecuted = true;
}

// Make sure we clear the callback interval.
if (callbackExecuted || ! pollAttemptsRemaining ) {
clearInterval(pollForMetaBoxes);
}
}, 100);
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we feel this is always going to be sufficient? It's only as little as a half a second -- but, if loading the meta boxes takes longer than that, I expect it would be blocking the event loop, so that's probably moot?

Using timeouts or intervals still feels like a flawed pattern to me. For what it's worth, setInterval doesn't appear once in the GB codebase. And as far as I could tell with a quick pass, setTimeout was only used in cases like animations, flashes, etc.

Further, is an interval even necessary? Once subscribed, won't this repeatedly get executed during ~any activity in the editor? And to that point, should this get properly unsubscribed once successfully executed? Does GB subscribe work like redux subscribe in that you get an unsubscriber and can unsubscribe? I actually don't know.

Copy link
Contributor

Choose a reason for hiding this comment

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

}
});
} else {
callback();
}
};

if ( 'object' === typeof wp && 'function' === typeof wp.domReady ) {
wp.domReady( callback );
wp.domReady( wrappedCallback );
} else if ( jQuery ) {
jQuery( document ).ready( callback );
jQuery( document ).ready( wrappedCallback );
} else {
// Shim wp.domReady.
if (
document.readyState === 'complete' || // DOMContentLoaded + Images/Styles/etc loaded, so we call directly.
document.readyState === 'interactive' // DOMContentLoaded fires at this point, so we call directly.
) {
callback();
wrappedCallback();
return;
}

// DOMContentLoaded has not fired yet, delay callback until then.
document.addEventListener( 'DOMContentLoaded', callback );
document.addEventListener( 'DOMContentLoaded', wrappedCallback );
}
}