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!