Step-by-Step Guide to building a custom dynamic lead collector block for Gutenberg using HTMX dynamic attributes
Gutenberg Block Architecture for Dynamic Lead Collection
Building a custom Gutenberg block for dynamic lead collection requires a robust architecture that leverages modern web technologies for a seamless user experience. This guide focuses on creating a block that can submit lead data without a full page reload, enhancing conversion rates. We’ll utilize HTMX for its ability to trigger server-side requests directly from HTML attributes, simplifying client-side JavaScript and enabling dynamic updates.
Setting Up the WordPress Plugin and Block Structure
First, we need a basic WordPress plugin structure to house our Gutenberg block. This involves creating a main plugin file and a directory for our block’s assets.
Plugin File (`dynamic-lead-collector.php`)
<?php
/**
* Plugin Name: Dynamic Lead Collector
* Description: A custom Gutenberg block for dynamic lead collection using HTMX.
* Version: 1.0.0
* Author: Your Name
* License: GPL-2.0+
* License URI: http://www.gnu.org/licenses/gpl-2.0.txt
* Text Domain: dynamic-lead-collector
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Register the block.
*/
function dlc_register_block() {
// Automatically load dependencies and version
$asset_file = include( plugin_dir_path( __FILE__ ) . 'build/index.asset.php');
wp_register_script(
'dlc-block-editor-script',
plugins_url( 'build/index.js', __FILE__ ),
$asset_file['dependencies'],
$asset_file['version']
);
wp_register_style(
'dlc-block-editor-style',
plugins_url( 'build/index.css', __FILE__ ),
array( 'wp-edit-blocks' ),
$asset_file['version']
);
register_block_type( 'dlc/lead-collector', array(
'editor_script' => 'dlc-block-editor-script',
'editor_style' => 'dlc-block-editor-style',
'render_callback' => 'dlc_render_lead_collector_block',
'attributes' => array(
'formTitle' => array(
'type' => 'string',
'default' => 'Get in Touch',
),
'submitButtonText' => array(
'type' => 'string',
'default' => 'Submit',
),
),
) );
}
add_action( 'init', 'dlc_register_block' );
/**
* Server-side rendering callback for the block.
*
* @param array $attributes Block attributes.
* @return string HTML output of the block.
*/
function dlc_render_lead_collector_block( $attributes ) {
$form_title = $attributes['formTitle'] ?? 'Get in Touch';
$submit_button_text = $attributes['submitButtonText'] ?? 'Submit';
// Enqueue HTMX script for frontend
wp_enqueue_script( 'htmx-script', 'https://unpkg.com/[email protected]', array(), '1.9.10', true );
ob_start();
?>
'Invalid input. Please provide a valid name and email.' ), 400 );
wp_die();
}
// In a real-world scenario, you would save this to the database, send an email, etc.
// For this example, we'll just simulate a success message.
$response_message = sprintf(
'Thank you, %s! Your message has been received.
',
esc_html( $name )
);
echo $response_message; // HTMX will swap this into the hx-target element.
wp_die(); // This is required for AJAX to work.
}
add_action( 'wp_ajax_dlc_submit_lead', 'dlc_ajax_submit_lead' );
add_action( 'wp_ajax_nopriv_dlc_submit_lead', 'dlc_ajax_submit_lead' ); // For logged-out users
// Add nonce to the form dynamically if needed for more robust security
// For simplicity in this example, we rely on sanitization and basic checks.
// A more advanced approach would involve dynamically adding a nonce field.
This PHP file registers the block, defines its attributes (like form title and button text), and sets up a server-side rendering callback. Crucially, it enqueues the HTMX library and defines an AJAX action hook for processing lead submissions.
Gutenberg Block Editor Implementation (JavaScript/React)
Next, we’ll create the JavaScript file that defines the block’s behavior within the Gutenberg editor. This uses React and the WordPress Block Editor API.
Block Editor Script (`src/index.js`)
import { registerBlockType } from '@wordpress/blocks';
import { useBlockProps, InspectorControls } from '@wordpress/block-editor';
import { PanelBody, TextControl } from '@wordpress/components';
import './style.scss'; // For frontend styles
import './editor.scss'; // For editor styles
const Edit = ( { attributes, setAttributes } ) => {
const blockProps = useBlockProps();
const { formTitle, submitButtonText } = attributes;
const onChangeFormTitle = ( newTitle ) => {
setAttributes( { formTitle: newTitle } );
};
const onChangeSubmitButtonText = ( newText ) => {
setAttributes( { submitButtonText: newText } );
};
// Preview of the form in the editor
return (
<>
<InspectorControls>
<PanelBody title="Lead Collector Settings" initialOpen={ true }>
<TextControl
label="Form Title"
value={ formTitle }
onChange={ onChangeFormTitle }
/>
<TextControl
label="Submit Button Text"
value={ submitButtonText }
onChange={ onChangeSubmitButtonText }
/>
</PanelBody>
</InspectorControls>
<div { ...blockProps } className="dlc-lead-collector">
<h2>{ formTitle || 'Get in Touch' }</h2>
<div className="dlc-form-group">
<label>Name:</label>
<input type="text" placeholder="Enter your name" disabled />
</div>
<div className="dlc-form-group">
<label>Email:</label>
<input type="email" placeholder="Enter your email" disabled />
</div>
<div className="dlc-form-group">
<label>Message:</label>
<textarea rows="4" placeholder="Your message" disabled></textarea>
</div>
<button type="button" disabled>{ submitButtonText || 'Submit' }</button>
<p>(Form submission is disabled in the editor preview)</p>
</div>
</>
);
};
registerBlockType( 'dlc/lead-collector', {
apiVersion: [ 2, 2 ],
title: 'Dynamic Lead Collector',
icon: 'email-alt', // WordPress dashicon
category: 'widgets',
attributes: {
formTitle: {
type: 'string',
default: 'Get in Touch',
},
submitButtonText: {
type: 'string',
default: 'Submit',
},
},
edit: Edit,
save: () => null, // We use a server-side render_callback, so save returns null.
} );
This script registers the block, defines its editable attributes using `InspectorControls` (for the sidebar settings), and provides a visual representation in the editor. The `save: () => null` is crucial because the block’s output is handled entirely by the PHP `render_callback` on the frontend.
Styling the Lead Collector Block
We need some basic CSS for both the editor and the frontend to make the form presentable.
Editor Styles (`src/editor.scss`)
.wp-block-dlc-lead-collector {
border: 1px dashed #ccc;
padding: 15px;
background-color: #f9f9f9;
margin-bottom: 20px;
h2 {
margin-top: 0;
color: #333;
}
.dlc-form-group {
margin-bottom: 15px;
label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
input[type="text"],
input[type="email"],
textarea {
width: 100%;
padding: 8px;
border: 1px solid #ddd;
box-sizing: border-box;
}
textarea {
resize: vertical;
}
}
button {
padding: 10px 15px;
background-color: #0073aa;
color: white;
border: none;
cursor: pointer;
&:disabled {
background-color: #ccc;
}
}
}
Frontend Styles (`src/style.scss`)
.dlc-lead-collector {
border: 1px solid #e0e0e0;
padding: 25px;
background-color: #ffffff;
border-radius: 8px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
max-width: 500px;
margin: 20px auto;
font-family: sans-serif;
h2 {
text-align: center;
margin-top: 0;
margin-bottom: 25px;
color: #333;
font-size: 1.8em;
}
.dlc-form-group {
margin-bottom: 20px;
label {
display: block;
margin-bottom: 8px;
font-weight: 600;
color: #555;
}
input[type="text"],
input[type="email"],
textarea {
width: 100%;
padding: 12px;
border: 1px solid #ccc;
border-radius: 4px;
box-sizing: border-box;
font-size: 1em;
&:focus {
border-color: #0073aa;
outline: none;
box-shadow: 0 0 0 2px rgba(0, 115, 170, 0.2);
}
}
textarea {
resize: vertical;
min-height: 100px;
}
}
button {
display: block;
width: 100%;
padding: 12px 20px;
background-color: #0073aa;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 1.1em;
font-weight: bold;
transition: background-color 0.3s ease;
&:hover {
background-color: #005177;
}
}
.dlc-response-message {
margin-top: 20px;
padding: 15px;
border-radius: 4px;
text-align: center;
font-size: 1em;
& > p {
margin: 0;
}
}
}
These SCSS files will be compiled into CSS during the build process.
Build Process and Plugin Activation
To compile the JavaScript and SCSS, you’ll need Node.js and npm (or yarn) installed. We’ll use `@wordpress/scripts` for a streamlined build process.
`package.json` Configuration
{
"name": "dynamic-lead-collector",
"version": "1.0.0",
"description": "A custom Gutenberg block for dynamic lead collection using HTMX.",
"main": "build/index.js",
"scripts": {
"build": "wp-scripts build",
"start": "wp-scripts start"
},
"keywords": [
"wordpress",
"gutenberg",
"block",
"htmx",
"lead collection"
],
"author": "Your Name",
"license": "GPL-2.0-or-later",
"devDependencies": {
"@wordpress/scripts": "^26.10.0"
}
}
Run the following commands in your plugin’s root directory:
npm install npm run build
This will create the `build/` directory containing `index.js`, `index.css`, and `index.asset.php`. Activate the “Dynamic Lead Collector” plugin in your WordPress admin area.
Implementing HTMX Dynamic Attributes
The core of the dynamic behavior lies in the HTMX attributes added to the form in the PHP `dlc_render_lead_collector_block` function:
<form id="dlc-lead-form"
hx-post=""
hx-target="#dlc-response-message"
hx-swap="innerHTML"
hx-trigger="submit">
<input type="hidden" name="action" value="dlc_submit_lead">
<!-- ... form fields ... -->
</form>
<div id="dlc-response-message"></div>
hx-post: Specifies the URL to send the POST request to. We use `admin-ajax.php` for WordPress AJAX requests.hx-target: Defines the HTML element that will receive the response from the server. Here, it’s the `div` with the ID `dlc-response-message`.hx-swap: Determines how the response content is placed into the target element.innerHTMLreplaces the existing content.hx-trigger="submit": Configures HTMX to trigger the request when the form is submitted.<input type="hidden" name="action" value="dlc_submit_lead">: This hidden input is essential for WordPress AJAX to identify which function to call.
When the form is submitted:
- HTMX intercepts the submit event.
- It sends a POST request to `wp-admin/admin-ajax.php` with the form data.
- The `action=dlc_submit_lead` parameter tells WordPress to execute our `dlc_ajax_submit_lead` function.
- Our PHP function processes the data, generates a response message (e.g., “Thank you!”), and echoes it.
- HTMX receives this response and injects it into the element with `id=”dlc-response-message”`.
Enhancing Security and User Feedback
While the example includes basic sanitization (`sanitize_text_field`, `sanitize_email`, `sanitize_textarea_field`), robust security is paramount. For AJAX requests, WordPress typically uses nonces. Although HTMX doesn’t natively send a nonce field, you could dynamically add one using JavaScript before submission or rely on the `check_ajax_referer` within your PHP function if you were to manually add a nonce field to the form.
The `hx-target` and `hx-swap` attributes provide immediate visual feedback to the user. Instead of a full page refresh, they see the success or error message appear directly below the form, creating a smoother, more app-like experience.
Advanced Considerations and Next Steps
For production environments, consider:
- Data Persistence: Integrate with WordPress custom post types, user meta, or a dedicated database table to store lead information.
- Email Notifications: Send lead details to an administrator’s email address using `wp_mail()`.
- Error Handling: Implement more sophisticated error handling on both the client (HTMX) and server (PHP) sides. Display specific error messages for invalid fields.
- Form Validation: While basic HTML5 validation is present, consider server-side validation for critical data integrity.
- Rate Limiting: Protect against brute-force submissions by implementing rate limiting on the AJAX endpoint.
- Accessibility: Ensure the form and feedback messages are accessible to users with disabilities.
- Internationalization: Use WordPress internationalization functions (`__`, `_e`, etc.) for all user-facing strings.
- Dynamic Content Loading: HTMX can also be used to load different parts of the form or response based on user interaction, further enhancing dynamism. For instance, `hx-get` could load a follow-up question after initial submission.
By combining Gutenberg’s block architecture with HTMX’s declarative approach to AJAX, you can build highly interactive and efficient components for your WordPress site without complex JavaScript frameworks.