I am trying to extend the Gutenberg core/gallery block to add some new functionality to my image gallery on the frontend. I have created a separate plugin to handle this task. I have registered my script with the following code:

function mt_enqueue_gutenberg(){

  wp_enqueue_script(
        'mt-gallery-block',
        plugins_url( 'gallery-block.js',__FILE__),
        array( 'wp-blocks', 'wp-i18n', 'wp-element' )
    );

    //Script and stylesheet loaded on frontend only
    if(!is_admin()){
      wp_enqueue_script(
        'mt-gallery-frontend-script',
        plugins_url( 'gallery.js', __FILE__),
        array( 'mt-gallery-block' )
      );

      wp_enqueue_style(
        'mt-gallery-frontend-style',
        plugins_url( 'gallery.css', __FILE__),
        array( 'mt-gallery-block' )
      );
    }
}

add_action( 'enqueue_block_editor_assets', 'mt_enqueue_gutenberg');

In my gallery-block.js file I am using the blocks.getSaveElement filter to edit the block’s HTML-output to the frontend.

//Filter hook
wp.hooks.addFilter(
    'blocks.getSaveElement',
    'wpse-298225',
    mt_gallerySaveElement
)

function mt_gallerySaveElement( element, blockType, attributes ) {
    //returns the element without changing it, if it is not the gallery block
    if ( blockType.name !== 'core/gallery' ) {
        return element;
    }

    //Creates div element with nested img's
    var newElement = wp.element.createElement(
        'div',
        {
            'className': 'gallery-grid',
            'data-total-slides': attributes.images.length
        },
        //Loops through each image in the gallery and creates an img-element
        attributes.images.map(
            function( image, index ) {
              return wp.element.createElement(
                        'img',
                        {
                            'src': image.url,
                            'alt': image.alt,
                            'className': 'gallery-item',
                            'data-slide-no': index,
                            'data-caption': image.caption[0]
                        }
                    )

              }

        )
    )

    return newElement
}

Everything works as it is supposed to except when I reload the Gutenberg editor. When I reload the page I get a validation error and I am not allowed to edit the gallery block. (See screenshots below)

Gallery block - before and after reload

Below I have provided a screenshot of the console log after I reloaded the page:

Validation error - console log

I am pretty sure the error occurs because my output does not correspond to the expected structure of the gallery, and thus WordPress does not see any images inside the gallery block when I reload the page.

So, my question is: How can I fix this validation error?

Assuming the error is caused by the difference between my HTML structure and the expected gallery structure: How can I make Gutenberg recognize my HTML structure as a gallery?

Any suggestions would be highly appreciated. 😊

  • 3
    This is expected behavior - if you modify the HTML of a block, it won't be recognized as the original block. Rather than trying to manipulate a core block it seems like unregistering the core block and registering a new replacement block in its place would be a safer approach - that way you're ensuring users of the site use your particular customized gallery, which will validate because it defines its own HTML structure. – WebElaine Sep 10 at 13:49
up vote 1 down vote accepted

Answering my own question:

As suggested by WebElaine I created a completely new block to fix the problem. To make this task easier, I used create-guten-block by Ahmad Awais, which is pretty easy to set up.

In init.php I made sure to enqueue my JavaScript and extra CSS for the frontend. This should be straight forward. I recommend reading the Learning Gutenberg series on css-tricks.com. It was a great help for me getting started.

The important part is how I created the gallery block. In block.js (created by create-guten-block) I have destructured MediaUpload from wp.editor, and Button from wp.components.

const { MediaUpload } = wp.editor; //Import MediaUpload from wp.editor
const { Button } = wp.components; //Import Button from wp.components

I register the block with a title, an icon, a category, some keywords and an images attribute with the type array. This will contain the images from the gallery

 registerBlockType( 'cgb/block-my-test-block', {
    title: __( 'MT - Gallery' ), // Block title.
    icon: 'format-gallery', // Block icon from Dashicons → https://developer.wordpress.org/resource/dashicons/.
    category: 'common', // Block category
    keywords: [ //Keywords
        __('materialtheme'),
        __('photos'),
        __('images')
    ],
    attributes: { //Attributes
        images : { //Images array
            type: 'array',
        }
},

My edit function (the part of the block shown in the editor) looks like this:

edit({ attributes, className, setAttributes }) {

        //Destructuring the images array attribute
        const {images = []} = attributes;


        // This removes an image from the gallery
        const removeImage = (removeImage) => {
                    //filter the images
                    const newImages = images.filter( (image) => {
                        //If the current image is equal to removeImage the image will be returnd
                        if(image.id != removeImage.id) {
                            return image;
                        }
                    });

                    //Saves the new state
                    setAttributes({
                        images:newImages,
                    })
        }


        //Displays the images
        const displayImages = (images) => {
            return (
                //Loops throug the images
                images.map( (image) => {
                    return (
                    <div className="gallery-item-container">
                            <img className='gallery-item' src={image.url} key={ images.id } />
                            <div className='remove-item' onClick={() => removeImage(image)}><span class="dashicons dashicons-trash"></span></div>
                            <div className='caption-text'>{image.caption[0]}</div>
                    </div>
                    )
                })

            )
        }

        //JSX to return
        return (
            <div>
                <div className="gallery-grid">
                    {displayImages(images)}
                </div>
                <br/>
                <MediaUpload
                        onSelect={(media) => {setAttributes({images: [...images, ...media]});}}
                        type="image"
                        multiple={true}
                        value={images}
                        render={({open}) => (
                            <Button className="select-images-button is-button is-default is-large" onClick={open}>
                                Add images
                            </Button>
                        )}
                    />
            </div>

        );
    },

Here I make use of the MediaUpload and Button object. Notice that I have set multiple to true, which allows me to select multiple images. To select multiple images this way, you must hold down CTRL like you would do selecting multiple files in File Explorer (on Windows).

In my save function I map the images and add the desired attributes to the img-tag.

save({attributes}) {
    //Destructuring the images array attribute
    const { images = [] } = attributes;

    // Displays the images
    const displayImages = (images) => {
        return (
            images.map( (image,index) => {
                return (
                        <img
                            className='gallery-item'
                            key={images.id}
                            src={image.url}
                            data-slide-no={index}
                            data-caption={image.caption[0]}
                            alt={image.alt}
                            />
                )
            })
        )
    }

    //JSX to return
    return (
        <div>
        <div className="gallery-grid" data-total-slides={images.length}>{ displayImages(images) }</div>
        </div>
    );

},

My complete code from block.js looks like this:

/**
 * BLOCK: my-test-block
 *
 * Registering a basic block with Gutenberg.
 * Simple block, renders and saves the same content without any interactivity.
 */

import './style.scss';
import './editor.scss';

const { __ } = wp.i18n; // Import __() from wp.i18n
const { registerBlockType } = wp.blocks; // Import registerBlockType() from wp.blocks
const { MediaUpload } = wp.editor; //Import MediaUpload from wp.editor
const { Button } = wp.components; //Import Button from wp.components


/**
 * Register: aa Gutenberg Block.
 *
 * Registers a new block provided a unique name and an object defining its
 * behavior. Once registered, the block is made editor as an option to any
 * editor interface where blocks are implemented.
 *
 * @link https://wordpress.org/gutenberg/handbook/block-api/
 * @param  {string}   name     Block name.
 * @param  {Object}   settings Block settings.
 * @return {?WPBlock}          The block, if it has been successfully
 *                             registered; otherwise `undefined`.
 */

registerBlockType( 'cgb/block-my-test-block', {
    title: __( 'MT - Gallery' ), // Block title.
    icon: 'format-gallery', // Block icon from Dashicons → https://developer.wordpress.org/resource/dashicons/.
    category: 'common', // Block category
    keywords: [ //Keywords
        __('materialtheme'),
        __('photos'),
        __('images')
    ],
    attributes: { //Attributes
        images : { //Images array
            type: 'array',
        }
},

    /**
     * The edit function describes the structure of your block in the context of the editor.
     * This represents what the editor will render when the block is used.
     *
     * The "edit" property must be a valid function.
     *
     * @link https://wordpress.org/gutenberg/handbook/block-api/block-edit-save/
     */
    edit({ attributes, className, setAttributes }) {

        //Destructuring the images array attribute
        const {images = []} = attributes;


        // This removes an image from the gallery
        const removeImage = (removeImage) => {
                    //filter the images
                    const newImages = images.filter( (image) => {
                        //If the current image is equal to removeImage the image will be returnd
                        if(image.id != removeImage.id) {
                            return image;
                        }
                    });

                    //Saves the new state
                    setAttributes({
                        images:newImages,
                    })
        }


        //Displays the images
        const displayImages = (images) => {
            return (
                //Loops throug the images
                images.map( (image) => {
                    return (
                    <div className="gallery-item-container">
                            <img className='gallery-item' src={image.url} key={ images.id } />
                            <div className='remove-item' onClick={() => removeImage(image)}><span class="dashicons dashicons-trash"></span></div>
                            <div className='caption-text'>{image.caption[0]}</div>
                    </div>
                    )
                })

            )
        }

        //JSX to return
        return (
            <div>
                <div className="gallery-grid">
                    {displayImages(images)}
                </div>
                <br/>
                <MediaUpload
                        onSelect={(media) => {setAttributes({images: [...images, ...media]});}}
                        type="image"
                        multiple={true}
                        value={images}
                        render={({open}) => (
                            <Button className="select-images-button is-button is-default is-large" onClick={open}>
                                Add images
                            </Button>
                        )}
                    />
            </div>

        );
    },

    /**
     * The save function defines the way in which the different attributes should be combined
     * into the final markup, which is then serialized by Gutenberg into post_content.
     *
     * The "save" property must be specified and must be a valid function.
     *
     * @link https://wordpress.org/gutenberg/handbook/block-api/block-edit-save/
     */
    save({attributes}) {
        //Destructuring the images array attribute
        const { images = [] } = attributes;

        // Displays the images
        const displayImages = (images) => {
            return (
                images.map( (image,index) => {
                    return (
                            <img
                                className='gallery-item'
                                key={images.id}
                                src={image.url}
                                data-slide-no={index}
                                data-caption={image.caption[0]}
                                alt={image.alt}
                                />
                    )
                })
            )
        }

        //JSX to return
        return (
            <div>
            <div className="gallery-grid" data-total-slides={images.length}>{ displayImages(images) }</div>
            </div>
        );

    },
} );

After adding some styling my gallery looks like this in the editor:

enter image description here

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

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