Question

I've written the following custom block that allows a user to create a simple slider and add an image that is then wrapped inside a slider item element. I've opted to make this a single block rather than using nested blocks (e.g. a slider item block and parent slider) because I don't want them wrapping in additional HTML when inside the block editor and instead want it to be a single block of images (like the gallery block).

(function(blocks, editor, element, blockEditor, components) {
var el = element.createElement;
var MediaUpload = editor.MediaUpload;

blocks.registerBlockType('theme/slider', {
        title: 'Slider',
        icon: 'universal-access-alt',
        category: 'layout',
        example: {},
        attributes: {
            mediaID: {
                type: 'number',
            },
            mediaURL: {
                type: 'string',
                source: 'attribute',
                selector: 'img',
                attribute: 'src',
            },
        },
        edit: function(props) {
            function onSelectImage(media) {
                return props.setAttributes({
                    mediaURL: media.url,
                    mediaID: media.id,
                });
            };

            return el('div', {
                    className: 'slider'
                },
                el('div', {
                        className: 'slider-item'
                    },
                    el(MediaUpload, {
                        onSelect: onSelectImage,
                        allowedTypes: 'image',
                        value: props.attributes.mediaID,
                        render: function(obj) {
                            return el(
                                components.Button, {
                                    className: props.attributes.mediaID ?
                                        'image-button' :
                                        'button button-large',
                                    onClick: obj.open,
                                },
                                !props.attributes.mediaID ?
                                'Upload Image' :
                                el('img', {
                                    src: props.attributes.mediaURL
                                })
                            );
                        },
                    }),
                }),
        );
    },
    save: function(props) {
        return el('div', {
                className: 'slider'
            }, el('div', {
                className: 'slider-item'
            }, (props.attributes.mediaURL ? el('img', {
                src: props.attributes.mediaURL
            }) : el('div')))
        },
    });
});
})(window.wp.blocks, window.wp.editor, window.wp.element, window.wp.blockEditor, window.wp.components);

However I'm not sure how to make both of those attributes mediaID and mediaURL repeatable?

Was it helpful?

Solution

There are other changes you need to make in your code, but as for the main one — making the mediaID and mediaURL be repeatable, the (core) gallery block did it by setting query as the attribute source and array as the attribute type.

So I would do the same and for example you can set the attribute name to images:

// Define the attribute:
attributes: {
    images: {
        type: 'array',
        source: 'query',
        selector: '.slider-item',
        default: [],

        // The following means in each .slider-item element in the saved markup,
        // the attribute value is read from the data-id or src attribute of the
        // img element in the .slider-item element. And yes of course, you can
        // change the selector to 'a', '.some-class' or something else.
        query: {
            mediaID: {
                type: 'number',
                source: 'attribute',
                attribute: 'data-id',
                selector: 'img',
            },
            mediaURL: {
                type: 'string',
                source: 'attribute',
                attribute: 'src',
                selector: 'img',
            },
        },
    },
}

/* And the attribute value would look like:
images: [
  { mediaID: 1, mediaURL: 'https://example.com/wp-content/uploads/2021/04/image.png' },
  { mediaID: 2, mediaURL: 'https://example.com/wp-content/uploads/2021/04/image2.png' },
  ...
]
*/

Then in your MediaUpload element, you would want to set the gallery and multiple properties to true (to allow multiple image selections), and in your onSelectImage() function, you can set the attribute value (which is an array) like so:

props.setAttributes( {
    images: items.map( item => {
        return {
            mediaID: parseInt( item.id, 10 ),
            mediaURL: item.url,
        };
    } ),
} );

Try My Block/Code

So it uses JSX and ESNext, but the save() output is identical to the one in the question, and you can find the code here. :)

Licensed under: CC-BY-SA with attribution
Not affiliated with wordpress.stackexchange
scroll top