How to load an additional script for a block in the block editor?

I'm developing a dynamic block that prints the following markup (for example) using the ServerSideRender component:

div class=accordion
    h3 class=accordion-titleA title/h3
    div class=accordion-contentLorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat./div
/div

To fully work this block needs the Accordion component from jQuery UI:

accordion-jquery-start.js file:

( function( $ ) {

    $( function() {
        
        $( '.accordion' ).accordion( {
            header: 'h3',
            heightStyle: 'content',
            animate: 100,
            collapsible: true,
            active: false
        } );

    } );

} )( jQuery );

I'm registering my dynamic block using:

register_block_type( 'my/accordion-block', array(
    'style'           = 'accordion-block-css',
    'editor_script'   = 'accordion-block-js',
    'render_callback' = 'render_block',
);

To load the additional script shown above in the block editor, I'm using the enqueue_block_editor_assets action:

function my_enqueue_block_editor_assets() {
    wp_enqueue_script( 'accordion-jquery-start', plugins_url( '/assets/js/accordion-jquery-start.js', __FILE__ ), array( 'jquery', 'jquery-ui-accordion', 'accordion-block-js' ), false, true );
}
add_action( 'enqueue_block_editor_assets', 'my_enqueue_block_editor_assets' );

Almost everything works: the block is registered and prints the correct markup and all of the needed scripts and styles get enqueued. BUT, for some reason, the accordion function from jQuery UI doesn't get attached to the .accordion class of my block in the block editor, so the accordion effect doesn't work. But, there's no console errors anywhere. It seems that the accordion-jquery-start.js script is run before the block itself is fully loaded.

Any ideas? Should I load the accordion-jquery-start.js script differently?

Topic block-editor jquery-ui Wordpress javascript

Category Web


BUT, for some reason, the accordion function from jQuery UI doesn't get attached to the .accordion class of my block in the block editor, so the accordion effect doesn't work.

The ServerSideRender component uses the REST API to fetch the dynamic block output, hence the above issue happens because by the time the DOM is ready (which is when jQuery.ready()'s callbacks run), the REST API AJAX call is not yet resolved or may have not even started, therefore the accordion div isn't yet attached to the DOM.

Should I load the accordion-jquery-start.js script differently?

Yes, kind of — ServerSideRender doesn't (yet) provide a "on-content-loaded" or "on-AJAX-complete/resolved" event that we could listen to, so you'd need to manually check if your div is already in the DOM and if so, run the $( '.accordion' ).accordion().

And in the modern world of JS, an easy way to do that is by using the MutationObserver API.

Please check the MDN web docs (see link above) for more details about what is the mutation observer API, but in specific to Gutenberg or the block editor, and a dynamic block, here's a working example you can try and learn from. :-)

So in my index.js file, I got these: ( the console.log() are just for debugging purposes, so don't forget to remove them later )

  1. Load WordPress dependencies:

    import { registerBlockType } from '@wordpress/blocks';
    import { useRef, useLayoutEffect } from '@wordpress/element';
    import { useBlockProps } from '@wordpress/block-editor';
    import ServerSideRender from '@wordpress/server-side-render';
    
  2. The MutationObserver callback:

    // This function checks if the accordion div has been attached to the DOM, and
    // if so, initializes jQuery accordion on that very div. So this function can be
    // placed at the top in your file (before you call registerBlockType()).
    function initAccordion( mutations ) {
        for ( let mutation of mutations ) {
            if ( 'childList' === mutation.type && mutation.addedNodes[0] &&
                // Good, the added node has an accordion div.
                jQuery( '.accordion', mutation.addedNodes[0] ).length >= 1
            ) {
                // Convert the div to a jQuery accordion.
                jQuery( mutation.addedNodes[0] ).accordion( {
                    header: 'h3',
                    heightStyle: 'content',
                    animate: 100,
                    collapsible: true,
                    active: false
                } );
                console.log( 'accordion initialized' );
            }
        }
    }
    
  3. Now here's my block type:

    registerBlockType( 'my/accordion-block', {
        apiVersion: 2,
        title: 'My Accordion Block',
        category: 'widgets',
        edit() {
            // Create a ref for the block container.
            const ref = useRef( null );
    
            // Initialize the mutation observer once the block is rendered.
            useLayoutEffect( () => {
                let observer;
    
                if ( ref.current ) {
                    observer = new MutationObserver( initAccordion );
    
                    // Observe DOM changes in our block container only.
                    observer.observe( ref.current, {
                        childList: true,
                        subtree: true,
                    } );
                }
    
                // Cleanup function which stops the mutation observer.
                return () => {
                    if ( observer ) {
                        observer.disconnect();
                        console.log( 'observer disconnected' );
                    }
                };
            }, [] );
    
            // Pass the ref to the block container.
            // Note: { ref } is short for { ref: ref }
            const blockProps = useBlockProps( { ref } );
    
            return (
                <div { ...blockProps }>
                    <ServerSideRender block="my/accordion-block" />
                </div>
            );
        }
    } );
    

About

Geeks Mental is a community that publishes articles and tutorials about Web, Android, Data Science, new techniques and Linux security.