Creating a Custom Gutenberg Block for an Owl Carousel

In this blog post, we will walk you through the process of creating a custom Gutenberg block for an Owl Carousel using WordPress block development. This block allows users to create dynamic carousels with various configuration options such as autoplay, loop, item count, and more. Additionally, we will explore how to create carousel items as child blocks, making the carousel fully customizable within the block editor. Let’s break down the code step-by-step.

Importing Dependencies

// Importing the uuid library for generating unique IDs
import { v4 as uuidv4 } from 'uuid';

We start by importing the uuidv4 function from the uuid library. This will be used to generate a unique ID for each carousel instance, ensuring that multiple carousels on the same page do not interfere with each other.

Defining the Main Carousel Block

export default function gutenbergBlocksOwlCarousel(wp) {
    const { registerBlockType } = wp.blocks;
    const { InnerBlocks, InspectorControls } = wp.blockEditor;
    const { PanelBody, ToggleControl, RangeControl } = wp.components;
    const { useEffect } = wp.element;

    registerBlockType('my-plugin/owl-carousel', {
        title: 'Owl Carousel',
        icon: 'images-alt2',
        category: 'layout',
        supports: {
            align: ['wide', 'full'],
        },
        attributes: {
            autoplay: { type: 'boolean', default: true },
            loop: { type: 'boolean', default: true },
            items: { type: 'number', default: 3 },
            margin: { type: 'number', default: 10 },
            carouselId: { type: 'string', default: '' },
            showNav: { type: 'boolean', default: true },
            showDots: { type: 'boolean', default: true },
            fadeEffect: { type: 'boolean', default: false },
            slideInterval: { type: 'number', default: 5 },
            autoplayHoverPause: { type: 'boolean', default: true },
            mouseDrag: { type: 'boolean', default: true },
            touchDrag: { type: 'boolean', default: true },
        },

Here, we define the main carousel block with various attributes. These attributes control the behavior of the carousel, such as whether it should autoplay, loop, and how many items should be visible at once.

Edit Function: Handling Carousel Settings

edit({ attributes, setAttributes, clientId }) {
    const { autoplay, loop, items, margin, carouselId, showNav, showDots, fadeEffect, slideInterval, autoplayHoverPause, mouseDrag, touchDrag } = attributes;

    useEffect(() => {
        if (!carouselId) {
            const generatedId = `owl-carousel-${uuidv4().replace(/[^a-zA-Z0-9-_]/g, '').substr(0, 8)}`;
            setAttributes({ carouselId: generatedId });
        }

        const innerBlocks = wp.data.select('core/block-editor').getBlocks(clientId);
        if (innerBlocks.length === 0) {
            const { replaceInnerBlocks } = wp.data.dispatch('core/block-editor');
            const block = wp.blocks.createBlock('my-plugin/owl-carousel-item');
            replaceInnerBlocks(clientId, [block], false);
        }
    }, [clientId]);

The edit function defines how the block behaves and appears in the editor. Here, we use the useEffect hook to generate a unique ID for the carousel if it doesn’t already have one. This ensures that each carousel block has a unique identifier. Additionally, if no child blocks (carousel items) are present, one is added by default.

Inspector Controls: Carousel Settings Panel

    return (
        <>
            <InspectorControls>
                <PanelBody title="Carousel Settings">
                    <ToggleControl
                        label="Autoplay"
                        checked={autoplay}
                        onChange={(value) => setAttributes({ autoplay: value })}
                    />
                    <ToggleControl
                        label="Loop"
                        checked={loop}
                        onChange={(value) => setAttributes({ loop: value })}
                    />
                    <RangeControl
                        label="Items to Show"
                        value={items}
                        onChange={(value) => setAttributes({ items: value })}
                        min={1}
                        max={10}
                    />
                    <RangeControl
                        label="Margin between Items (px)"
                        value={margin}
                        onChange={(value) => setAttributes({ margin: value })}
                        min={0}
                        max={50}
                    />
                    <ToggleControl
                        label="Show Navigation"
                        checked={showNav}
                        onChange={(value) => setAttributes({ showNav: value })}
                    />
                    <ToggleControl
                        label="Show Dots"
                        checked={showDots}
                        onChange={(value) => setAttributes({ showDots: value })}
                    />
                    <ToggleControl
                        label="Fade Effect"
                        checked={fadeEffect}
                        onChange={(value) => setAttributes({ fadeEffect: value })}
                    />
                    <RangeControl
                        label="Slide Interval (seconds)"
                        value={slideInterval}
                        onChange={(value) => setAttributes({ slideInterval: value })}
                        min={1}
                        max={20}
                    />
                    <ToggleControl
                        label="Pause on Hover"
                        checked={autoplayHoverPause}
                        onChange={(value) => setAttributes({ autoplayHoverPause: value })}
                    />
                    <ToggleControl
                        label="Enable Mouse Drag"
                        checked={mouseDrag}
                        onChange={(value) => setAttributes({ mouseDrag: value })}
                    />
                    <ToggleControl
                        label="Enable Touch Drag"
                        checked={touchDrag}
                        onChange={(value) => setAttributes({ touchDrag: value })}
                    />
                </PanelBody>
            </InspectorControls>
            <div id={carouselId} className="owl-carousel d-block">
                <InnerBlocks allowedBlocks={['my-plugin/owl-carousel-item']} />
            </div>
        </>
    );
},

The InspectorControls component renders a settings panel in the block editor sidebar. Here, we provide various options to customize the carousel, such as toggles for autoplay, loop, navigation, and dots, as well as range controls for the number of items and margin between items. These settings give the user complete control over the appearance and behavior of the carousel.

Save Function: Frontend Output

        save({ attributes }) {
            const { autoplay, loop, items, margin, carouselId, showNav, showDots, fadeEffect, slideInterval, autoplayHoverPause, mouseDrag, touchDrag } = attributes;

            return (
                <div
                    id={carouselId}
                    className={`owl-carousel ${fadeEffect ? 'owl-fade' : ''}`}
                    data-autoplay={autoplay}
                    data-loop={loop}
                    data-items={items}
                    data-margin={margin}
                    data-nav={showNav}
                    data-dots={showDots}
                    data-fade={fadeEffect}
                    data-interval={slideInterval * 1000}
                    data-autoplay-hover-pause={autoplayHoverPause}
                    data-mouse-drag={mouseDrag}
                    data-touch-drag={touchDrag}
                >
                    <InnerBlocks.Content />
                </div>
            );
        },
    });
}

In the save function, we define the HTML structure and data attributes for the carousel on the frontend. These data attributes are used by the Owl Carousel JavaScript library to initialize and configure the carousel. The InnerBlocks.Content component outputs the content of the carousel items.

Defining the Carousel Item Block

export default function gutenbergBlocksOwlCarouselItem(wp) {
    const { registerBlockType } = wp.blocks;
    const { InnerBlocks, useBlockProps, InspectorControls } = wp.blockEditor;
    const { PanelBody, ToggleControl } = wp.components;
    const { useEffect } = wp.element;
    const { select, dispatch } = wp.data;

    registerBlockType('my-plugin/owl-carousel-item', {
        title: 'Owl Carousel Item',
        icon: 'image-flip-horizontal',
        category: 'layout',
        parent: ['my-plugin/owl-carousel'],
        supports: {
            reusable: false,
        },
        attributes: {
            visibleInAdmin: {
                type: 'boolean',
                default: false,
            },
            isFirstItem: {
                type: 'boolean',
                default: false,
            },
        },

The carousel item block is defined here, with attributes such as visibleInAdmin and isFirstItem. These attributes help manage the visibility of the item in the editor and determine whether it is the first item in the carousel.

Edit Function: Carousel Item Behavior

edit({ attributes, setAttributes, clientId }) {
    const { visibleInAdmin } = attributes;
    const blockProps = useBlockProps({
        className: `item ${visibleInAdmin ? 'active' : ''}`,
        style: {
            display: visibleInAdmin ? 'block' : 'none',
        },
    });

    useEffect(() => {
        const parentBlockId = select('core/block-editor').getBlockRootClientId(clientId);
        const siblingBlocks = select('core/block-editor').getBlocks(parentBlockId);

        if (siblingBlocks.length > 0 && siblingBlocks[0].clientId === clientId) {
            setAttributes({ isFirstItem: true });
        } else {
            setAttributes({ isFirstItem: false });
        }

        if (visibleInAdmin) {
            siblingBlocks.forEach((block) => {
                if (block.clientId !== clientId) {
                    dispatch('core/block-editor').updateBlockAttributes(block.clientId, {
                        visibleInAdmin: false,
                    });
                }
            });
        }
    }, [visibleInAdmin, clientId]);

    return (
        <>
            <InspectorControls>
                <PanelBody title="Carousel Item Settings">
                    <ToggleControl
                        label="Visible in Editor"
                        checked={visibleInAdmin}
                        onChange={(value) => setAttributes({ visibleInAdmin: value })}
                    />
                </PanelBody>
            </InspectorControls>
            <div {...blockProps}>
                <InnerBlocks />
            </div>
        </>
    );
},

The edit function of the carousel item block manages its visibility in the editor. It ensures that only one item is visible at a time, helping users focus on one item while editing.

Save Function: Carousel Item Frontend Output

        save({ attributes }) {
            const { isFirstItem } = attributes;

            const blockProps = useBlockProps.save({
                className: `item ${isFirstItem ? 'active' : ''}`,
            });

            return (
                <div {...blockProps}>
                    <InnerBlocks.Content />
                </div>
            );
        },
    });
}

In the save function of the carousel item block, we add the active class to the first item to ensure it is displayed when the carousel loads.

Complete Code

Here’s the complete code for both the carousel and carousel item blocks:

import { v4 as uuidv4 } from 'uuid';

export default function gutenbergBlocksOwlCarousel(wp) {
    const { registerBlockType } = wp.blocks;
    const { InnerBlocks, InspectorControls } = wp.blockEditor;
    const { PanelBody, ToggleControl, RangeControl } = wp.components;
    const { useEffect } = wp.element;

    registerBlockType('my-plugin/owl-carousel', {
        title: 'Owl Carousel',
        icon: 'images-alt2',
        category: 'layout',
        supports: {
            align: ['wide', 'full'],
        },
        attributes: {
            autoplay: {
                type: 'boolean',
                default: true,
            },
            loop: {
                type: 'boolean',
                default: true,
            },
            items: {
                type: 'number',
                default: 3,
            },
            margin: {
                type: 'number',
                default: 10,
            },
            carouselId: {
                type: 'string',
                default: '',
            },
            showNav: {
                type: 'boolean',
                default: true,
            },
            showDots: {
                type: 'boolean',
                default: true,
            },
            fadeEffect: {
                type: 'boolean',
                default: false,
            },
            slideInterval: {
                type: 'number',
                default: 5,
            },
            autoplayHoverPause: {
                type: 'boolean',
                default: true,
            },
            mouseDrag: {
                type: 'boolean',
                default: true,
            },
            touchDrag: {
                type: 'boolean',
                default: true,
            },
        },
        edit({ attributes, setAttributes, clientId }) {
            const { autoplay, loop, items, margin, carouselId, showNav, showDots, fadeEffect, slideInterval, autoplayHoverPause, mouseDrag, touchDrag } = attributes;

            useEffect(() => {
                if (!carouselId) {
                    const generatedId = `owl-carousel-${uuidv4().replace(/[^a-zA-Z0-9-_]/g, '').substr(0, 8)}`;
                    setAttributes({ carouselId: generatedId });
                }

                // Ensure an owl-carousel-item is added by default when the block is first inserted
                const innerBlocks = wp.data.select('core/block-editor').getBlocks(clientId);
                if (innerBlocks.length === 0) {
                    const { replaceInnerBlocks } = wp.data.dispatch('core/block-editor');
                    const block = wp.blocks.createBlock('my-plugin/owl-carousel-item');
                    replaceInnerBlocks(clientId, [block], false);
                }
            }, [clientId]);

            return (
                <>
                    <InspectorControls>
                        <PanelBody title="Carousel Settings">
                            <ToggleControl
                                label="Autoplay"
                                checked={autoplay}
                                onChange={(value) => setAttributes({ autoplay: value })}
                            />
                            <ToggleControl
                                label="Loop"
                                checked={loop}
                                onChange={(value) => setAttributes({ loop: value })}
                            />
                            <RangeControl
                                label="Items to Show"
                                value={items}
                                onChange={(value) => setAttributes({ items: value })}
                                min={1}
                                max={10}
                            />
                            <RangeControl
                                label="Margin between Items (px)"
                                value={margin}
                                onChange={(value) => setAttributes({ margin: value })}
                                min={0}
                                max={50}
                            />
                            <ToggleControl
                                label="Show Navigation"
                                checked={showNav}
                                onChange={(value) => setAttributes({ showNav: value })}
                            />
                            <ToggleControl
                                label="Show Dots"
                                checked={showDots}
                                onChange={(value) => setAttributes({ showDots: value })}
                            />
                            <ToggleControl
                                label="Fade Effect"
                                checked={fadeEffect}
                                onChange={(value) => setAttributes({ fadeEffect: value })}
                            />
                            <RangeControl
                                label="Slide Interval (seconds)"
                                value={slideInterval}
                                onChange={(value) => setAttributes({ slideInterval: value })}
                                min={1}
                                max={20}
                            />
                            <ToggleControl
                                label="Pause on Hover"
                                checked={autoplayHoverPause}
                                onChange={(value) => setAttributes({ autoplayHoverPause: value })}
                            />
                            <ToggleControl
                                label="Enable Mouse Drag"
                                checked={mouseDrag}
                                onChange={(value) => setAttributes({ mouseDrag: value })}
                            />
                            <ToggleControl
                                label="Enable Touch Drag"
                                checked={touchDrag}
                                onChange={(value) => setAttributes({ touchDrag: value })}
                            />
                        </PanelBody>
                    </InspectorControls>
                    <div id={carouselId} className="owl-carousel d-block">
                        <InnerBlocks allowedBlocks={['my-plugin/owl-carousel-item']} />
                    </div>
                </>
            );
        },
        save({ attributes }) {
            const { autoplay, loop, items, margin, carouselId, showNav, showDots, fadeEffect, slideInterval, autoplayHoverPause, mouseDrag, touchDrag } = attributes;

            return (
                <div
                    id={carouselId}
                    className={`owl-carousel ${fadeEffect ? 'owl-fade' : ''}`}
                    data-autoplay={autoplay}
                    data-loop={loop}
                    data-items={items}
                    data-margin={margin}
                    data-nav={showNav}
                    data-dots={showDots}
                    data-fade={fadeEffect}
                    data-interval={slideInterval * 1000} // Convert seconds to milliseconds
                    data-autoplay-hover-pause={autoplayHoverPause}
                    data-mouse-drag={mouseDrag}
                    data-touch-drag={touchDrag}
                >
                    <InnerBlocks.Content />
                </div>
            );
        },
    });
}
export default function gutenbergBlocksOwlCarouselItem(wp) {
    const { registerBlockType } = wp.blocks;
    const { InnerBlocks, useBlockProps, InspectorControls } = wp.blockEditor;
    const { PanelBody, ToggleControl } = wp.components;
    const { useEffect } = wp.element;
    const { select, dispatch } = wp.data;

    registerBlockType('my-plugin/owl-carousel-item', {
        title: 'Owl Carousel Item',
        icon: 'image-flip-horizontal',
        category: 'layout',
        parent: ['my-plugin/owl-carousel'],
        supports: {
            reusable: false,
        },
        attributes: {
            visibleInAdmin: {
                type: 'boolean',
                default: false,
            },
            isFirstItem: {
                type: 'boolean',
                default: false,
            },
        },
        edit({ attributes, setAttributes, clientId }) {
            const { visibleInAdmin } = attributes;
            const blockProps = useBlockProps({
                className: `item ${visibleInAdmin ? 'active' : ''}`,
                style: {
                    display: visibleInAdmin ? 'block' : 'none',
                },
            });

            useEffect(() => {
                const parentBlockId = select('core/block-editor').getBlockRootClientId(clientId);
                const siblingBlocks = select('core/block-editor').getBlocks(parentBlockId);

                // Determine if this block is the first in the parent carousel
                if (siblingBlocks.length > 0 && siblingBlocks[0].clientId === clientId) {
                    setAttributes({ isFirstItem: true });
                } else {
                    setAttributes({ isFirstItem: false });
                }

                // Ensure only one carousel item is visible in the editor at a time
                if (visibleInAdmin) {
                    siblingBlocks.forEach((block) => {
                        if (block.clientId !== clientId) {
                            dispatch('core/block-editor').updateBlockAttributes(block.clientId, {
                                visibleInAdmin: false,
                            });
                        }
                    });
                }
            }, [visibleInAdmin, clientId]);

            return (
                <>
                    <InspectorControls>
                        <PanelBody title="Carousel Item Settings">
                            <ToggleControl
                                label="Visible in Editor"
                                checked={visibleInAdmin}
                                onChange={(value) => setAttributes({ visibleInAdmin: value })}
                            />
                        </PanelBody>
                    </InspectorControls>
                    <div {...blockProps}>
                        <InnerBlocks />
                    </div>
                </>
            );
        },
        save({ attributes }) {
            const { isFirstItem } = attributes;

            // Apply 'active' class to the first item
            const blockProps = useBlockProps.save({
                className: `item ${isFirstItem ? 'active' : ''}`,
            });

            return (
                <div {...blockProps}>
                    <InnerBlocks.Content />
                </div>
            );
        },
    });
}
$('.owl-carousel').each(function () {
    const $carousel = $(this);
    const hasAutoplay = $carousel.data('autoplay');
    const hasLoop = $carousel.data('loop');
    const numberOfItems = parseInt($carousel.data('items'), 10) || 1;
    const setMargin = $carousel.data('margin');
    const showNav = $carousel.data('nav');
    const showDots = $carousel.data('dots');
    const fadeEffect = $carousel.data('fade');
    const slideInterval = parseInt($carousel.data('interval'), 10) || 5000; // Default to 5 seconds
    const isAutoplayHoverPause = $carousel.data('autoplay-hover-pause');
    const hasMouseDrag = $carousel.data('mouse-drag');
    const hasTouchDrag = $carousel.data('touch-drag');
    $carousel.owlCarousel({
        autoplay: hasAutoplay,
        loop: hasLoop,
        items: numberOfItems,
        margin: setMargin,
        nav: showNav,
        dots: showDots,
        autoplayTimeout: slideInterval, // Set the interval between slides
        animateOut: fadeEffect ? 'fadeOut' : '', // Apply fade effect if enabled
        autoplayHoverPause: isAutoplayHoverPause,
        mouseDrag: hasMouseDrag,
        touchDrag: hasTouchDrag,
    });
});

In this blog post, we’ve covered the creation of a custom Gutenberg block for an Owl Carousel. We explored how to set up the block attributes, handle editing and saving, and manage carousel items. The final code snippet encapsulates the entire carousel and item blocks, ready to be used in your WordPress projects. Stay tuned for more posts in this series as we continue to explore and create powerful custom blocks!