Building Reusable Components in Gutenberg with Vue: A Comprehensive Guide
Gutenberg, the WordPress block editor, offers a powerful platform for building dynamic and engaging website content. While WordPress offers a wide range of built-in blocks, sometimes you need to create custom blocks to meet specific needs. This is where the integration of Vue.js comes into play, allowing you to build highly reusable and interactive components within Gutenberg.
This comprehensive guide will take you through the process of building reusable Gutenberg blocks with Vue.js, covering everything from setup to deployment.
1. Setting the Stage: Project Setup and Dependencies
Before diving into code, we need to set up our project environment.
1.1. Project Setup:
First, create a new WordPress plugin directory within your /wp-content/plugins
folder. For this guide, we’ll name it "gutenberg-vue-components." Inside, create a src
directory and two files:
index.js
: This is the main file that will be loaded by WordPress.gutenberg-vue-block.js
: This will contain our Vue component code.
1.2. Essential Dependencies:
Install the following packages:
npm install vue vue-loader @wordpress/scripts @wordpress/block-editor @wordpress/components @wordpress/data
These packages are vital for:
- vue: The core Vue.js library.
- vue-loader: Allows you to import and process Vue components within Webpack.
- @wordpress/scripts: Provides Webpack configuration and tools for building Gutenberg blocks.
- @wordpress/block-editor: Offers Gutenberg-specific APIs and components.
- @wordpress/components: Provides pre-built components like buttons, inputs, and more.
- @wordpress/data: Provides a mechanism for accessing and managing WordPress data.
2. Crafting Our First Reusable Component:
Let’s create a simple "Quote" block using Vue.
2.1. Defining the Vue Component:
In gutenberg-vue-block.js
, create a Vue component:
import { registerBlockType } from '@wordpress/blocks';
import {
InspectorControls,
RichText,
useBlockProps,
BlockControls,
ToolbarButton
} from '@wordpress/block-editor';
import { PanelBody, TextControl, ToggleControl } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
const QuoteBlock = () => {
const blockProps = useBlockProps();
const [quoteText, setQuoteText] = useState('');
const [author, setAuthor] = useState('');
const [showAuthor, setShowAuthor] = useState(true);
const handleQuoteChange = (newQuote) => {
setQuoteText(newQuote);
};
const handleAuthorChange = (newAuthor) => {
setAuthor(newAuthor);
};
const toggleAuthor = () => {
setShowAuthor(!showAuthor);
};
return (
<div {...blockProps}>
<BlockControls>
<ToolbarButton
icon="format-quote"
label={__("Quote Settings")}
onClick={toggleAuthor}
/>
</BlockControls>
<InspectorControls>
<PanelBody title={__("Quote Settings")}>
<TextControl
label={__("Quote Text")}
value={quoteText}
onChange={handleQuoteChange}
/>
<TextControl
label={__("Author")}
value={author}
onChange={handleAuthorChange}
/>
<ToggleControl
label={__("Show Author")}
checked={showAuthor}
onChange={toggleAuthor}
/>
</PanelBody>
</InspectorControls>
<div className="quote-container">
<RichText
tagName="blockquote"
value={quoteText}
onChange={handleQuoteChange}
/>
{showAuthor && <footer>
<cite>{author}</cite>
</footer>}
</div>
</div>
);
};
registerBlockType('my-plugin/quote-block', {
title: __('Quote Block'),
icon: 'format-quote',
category: 'common',
edit: QuoteBlock,
save: (props) => {
const { attributes } = props;
return (
<blockquote className="wp-block-quote">
<p>{attributes.quoteText}</p>
{attributes.showAuthor &&
<footer>
<cite>{attributes.author}</cite>
</footer>
}
</blockquote>
);
},
});
Explanation:
- We use
registerBlockType
from@wordpress/blocks
to register our block. - The
edit
prop defines our Vue component as the block’s edit view. - The
save
prop defines the block’s output when saved. useBlockProps
provides props for styling and attributes.InspectorControls
allows adding settings within the sidebar.PanelBody
groups settings into a panel.TextControl
creates input fields for text.ToggleControl
creates a switch for toggling options.RichText
provides a WYSIWYG editor for the quote text.BlockControls
adds controls to the block toolbar.- We use state management for the quote text, author, and show author options.
2.2. Building the Block’s Frontend (Save Function):
The save
function determines how the block will be rendered on the frontend. In this example, we create a standard HTML blockquote structure.
2.3. Loading the Script:
In your index.js
, import the Vue component and initialize it using Webpack:
import './style.css';
import './gutenberg-vue-block.js';
const { registerPlugin } = wp.plugins;
const { __ } = wp.i18n;
registerPlugin('my-plugin/gutenberg-vue-components', {
render: () => {
return null;
},
});
3. Expanding Functionality: Building More Complex Components
Now, let’s create a reusable "Call to Action" block.
3.1. The "Call to Action" Component:
In gutenberg-vue-block.js
, create a new Vue component:
import { registerBlockType } from '@wordpress/blocks';
import {
InspectorControls,
RichText,
useBlockProps,
BlockControls,
ToolbarButton,
AlignmentToolbar
} from '@wordpress/block-editor';
import { PanelBody, TextControl, SelectControl, ToggleControl } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
const CallToActionBlock = () => {
const blockProps = useBlockProps();
const [title, setTitle] = useState('');
const [buttonText, setButtonText] = useState('');
const [linkUrl, setLinkUrl] = useState('');
const [alignment, setAlignment] = useState('center');
const handleTitleChange = (newTitle) => {
setTitle(newTitle);
};
const handleButtonTextChange = (newButtonText) => {
setButtonText(newButtonText);
};
const handleLinkUrlChange = (newLinkUrl) => {
setLinkUrl(newLinkUrl);
};
const handleAlignmentChange = (newAlignment) => {
setAlignment(newAlignment);
};
return (
<div {...blockProps}>
<BlockControls>
<ToolbarButton
icon="align-left"
label={__("Align Left")}
onClick={() => handleAlignmentChange('left')}
isActive={alignment === 'left'}
/>
<ToolbarButton
icon="align-center"
label={__("Align Center")}
onClick={() => handleAlignmentChange('center')}
isActive={alignment === 'center'}
/>
<ToolbarButton
icon="align-right"
label={__("Align Right")}
onClick={() => handleAlignmentChange('right')}
isActive={alignment === 'right'}
/>
</BlockControls>
<InspectorControls>
<PanelBody title={__("Call to Action Settings")}>
<TextControl
label={__("Title")}
value={title}
onChange={handleTitleChange}
/>
<TextControl
label={__("Button Text")}
value={buttonText}
onChange={handleButtonTextChange}
/>
<TextControl
label={__("Link URL")}
value={linkUrl}
onChange={handleLinkUrlChange}
/>
<SelectControl
label={__("Alignment")}
value={alignment}
onChange={handleAlignmentChange}
options={[
{ label: __("Left"), value: 'left' },
{ label: __("Center"), value: 'center' },
{ label: __("Right"), value: 'right' },
]}
/>
</PanelBody>
</InspectorControls>
<div className={`cta-container ${alignment}`}>
{title && <h2 className="cta-title">{title}</h2>}
<a href={linkUrl} className="cta-button">{buttonText}</a>
</div>
</div>
);
};
registerBlockType('my-plugin/call-to-action-block', {
title: __('Call to Action Block'),
icon: 'admin-post',
category: 'common',
edit: CallToActionBlock,
save: (props) => {
const { attributes } = props;
return (
<div className={`cta-container ${attributes.alignment}`}>
{attributes.title && <h2 className="cta-title">{attributes.title}</h2>}
<a href={attributes.linkUrl} className="cta-button">{attributes.buttonText}</a>
</div>
);
},
});
3.2. Building the Block’s Frontend (Save Function):
The save
function constructs the frontend HTML for the call-to-action block based on the attributes. We use classes to apply styling for alignment.
4. Adding Dynamic Behavior: Interacting with WordPress Data
Let’s create a block that dynamically displays a list of recent posts using Vue.js and the WordPress data API.
4.1. The "Recent Posts" Component:
import { registerBlockType } from '@wordpress/blocks';
import { InspectorControls, useBlockProps } from '@wordpress/block-editor';
import { PanelBody, TextControl } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import { useSelect } from '@wordpress/data';
const RecentPostsBlock = () => {
const blockProps = useBlockProps();
const [postCount, setPostCount] = useState(3);
const handlePostCountChange = (newPostCount) => {
setPostCount(newPostCount);
};
const { getPosts } = useSelect((select) => ({
getPosts: select('core').getPosts,
}), []);
const posts = getPosts({
per_page: postCount,
});
return (
<div {...blockProps}>
<InspectorControls>
<PanelBody title={__("Recent Posts Settings")}>
<TextControl
label={__("Number of Posts")}
value={postCount}
onChange={handlePostCountChange}
type="number"
/>
</PanelBody>
</InspectorControls>
<ul>
{posts.map((post) => (
<li key={post.id}>
<a href={post.link}>
{post.title.rendered}
</a>
</li>
))}
</ul>
</div>
);
};
registerBlockType('my-plugin/recent-posts-block', {
title: __('Recent Posts Block'),
icon: 'post-formats',
category: 'common',
edit: RecentPostsBlock,
save: (props) => {
const { attributes } = props;
return (
<ul>
{attributes.postCount > 0 &&
<p>{__("This block will display recent posts on the frontend.")}</p>
}
</ul>
);
},
});
4.2. Explanation:
- We use
useSelect
to access thecore
store and get the list of posts. - The
getPosts
function fetches the recent posts with the specifiedper_page
count. - The
posts
array is then mapped to display each post title in a list. - The
save
function includes placeholder text for the frontend, as the recent posts are dynamically loaded on the frontend.
5. Integrating with External APIs and Custom Data
We can go beyond WordPress data and fetch information from external APIs or custom data sources using the fetch
API or libraries like axios
.
5.1. The "Weather" Component (using OpenWeatherMap API):
import { registerBlockType } from '@wordpress/blocks';
import { InspectorControls, useBlockProps } from '@wordpress/block-editor';
import { PanelBody, TextControl } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import { useState, useEffect } from 'react';
const WeatherBlock = () => {
const blockProps = useBlockProps();
const [city, setCity] = useState('');
const [weatherData, setWeatherData] = useState(null);
const handleCityChange = (newCity) => {
setCity(newCity);
};
useEffect(() => {
const apiKey = 'YOUR_API_KEY'; // Replace with your OpenWeatherMap API key
const apiUrl = `https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${apiKey}&units=metric`;
if (city) {
fetch(apiUrl)
.then(response => response.json())
.then(data => setWeatherData(data))
.catch(error => console.error('Error fetching weather data:', error));
}
}, [city]);
return (
<div {...blockProps}>
<InspectorControls>
<PanelBody title={__("Weather Settings")}>
<TextControl
label={__("City")}
value={city}
onChange={handleCityChange}
/>
</PanelBody>
</InspectorControls>
{weatherData && (
<div>
<h2>{weatherData.name}</h2>
<p>Temperature: {weatherData.main.temp}°C</p>
<p>Description: {weatherData.weather[0].description}</p>
</div>
)}
</div>
);
};
registerBlockType('my-plugin/weather-block', {
title: __('Weather Block'),
icon: 'cloud',
category: 'common',
edit: WeatherBlock,
save: (props) => {
const { attributes } = props;
return (
<div>
{attributes.city && <p>{__("This block will display weather data on the frontend.")}</p>}
</div>
);
},
});
5.2. Explanation:
- The
WeatherBlock
component fetches weather data from the OpenWeatherMap API using theuseEffect
hook. - The
city
state is used to store the city name entered by the user. - The
weatherData
state stores the fetched weather data. - The
save
function includes placeholder text for the frontend, as the weather data is dynamically loaded on the frontend.
6. Deploying Your Gutenberg Blocks
To deploy your Gutenberg blocks, follow these steps:
6.1. Build the Plugin:
Run npm run build
in the plugin directory to generate the production-ready code. This creates a build
folder containing the compiled files.
6.2. Activate the Plugin:
Go to the Plugins section in your WordPress dashboard and activate the "gutenberg-vue-components" plugin.
6.3. Use Your Blocks:
You can now use your reusable blocks in any post or page within your WordPress editor.
7. Best Practices and Tips
- Use CSS Modules or SCSS: For consistent styling, use CSS Modules or SCSS for component-specific styles.
- Component Reusability: Design your components to be as reusable as possible, keeping them modular and self-contained.
- State Management: Consider using a state management solution like Vuex for more complex applications.
- Testing: Write unit tests for your components to ensure they function correctly.
- Accessibility: Follow accessibility guidelines to ensure your blocks are usable by everyone.
Conclusion
By integrating Vue.js with Gutenberg, you can unlock a new level of interactivity and functionality within the WordPress block editor. This guide has provided a foundation for building reusable components, fetching data dynamically, and creating engaging user experiences. With these tools and best practices, you can build custom Gutenberg blocks that are both powerful and aesthetically pleasing.
Remember, this is just a starting point. Explore the vast potential of Vue.js and Gutenberg to create truly unique and engaging website content.
Leave a Reply