0

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

<div class="accordion">
    <h3 class="accordion-title">A title</h3>
    <div class="accordion-content">Lorem 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?

2 Answers 2

3

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>
            );
        }
    } );
    
2
  • Wow, so much complexity for a simple jQuery call! Thanks for your answer! In the end, it might be better to just replicate the PHP code that prints the markup in JS and put that in the edit() function instead of using the ServerSideRender component. But, this is not that easy either.
    – leemon
    Jul 4, 2021 at 16:19
  • 1
    Yes I agreed :), but here's something that you might want to try/check - with your original code, you'd only need to edit accordion-jquery-start.js.
    – Sally CJ
    Jul 5, 2021 at 2:22
1

A bit late, but mabe will help someone :)

I fixed this with a simple setInterval() that clears itself as soon element is on page:

(function($){
    var blockSliderInterval = setInterval(function(){
        //console.log('blockSliderInterval');
        initBlock()
    }, 200);
        
    function initBlock(){
        if( $('.image-slider__wrapper').length ){
            $('.image-slider__wrapper').slick({
                slidesToShow: 1,
                slidesToScroll: 1,
                infinite: true,
                dots: true,
                draggable: true,
                swipe: true,
                arrows: false,
                autoplay: true,
                autoplaySpeed: 4000,
            });

            //console.log('clearInterval blockSliderInterval');
            clearInterval(blockSliderInterval);
        }
    }

}(window.jQuery));

Oh, and my block loading js with block added only, so theres no infinite loop if block not added at all :)

1
  • Works perfectly! Thanks for sharing. Apr 20, 2023 at 18:49

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.