How to build custom Elementor custom widgets extensions utilizing modern Heartbeat API schemas
Leveraging WordPress Heartbeat API for Dynamic Elementor Widgets
The WordPress Heartbeat API, while often associated with auto-saves and post locking, offers a powerful, albeit underutilized, mechanism for real-time data synchronization within the Elementor editor. This allows for the creation of highly dynamic custom widgets that can update their content or behavior based on external data sources or user interactions without requiring a full page reload. This post details how to architect and implement such extensions, focusing on efficient data handling and modern API schema design.
Understanding the Heartbeat API Flow
The Heartbeat API operates on a client-server model. The browser periodically sends requests (heartbeats) to the server, and the server can respond with data or instructions. Elementor hooks into this process to manage its own editor state. We can tap into this same channel to push custom data to the Elementor editor or to trigger actions on the client-side based on server-side events.
Registering Custom Heartbeat Routes
To send custom data, we need to register new routes within the Heartbeat API. This is done using the heartbeat_send filter. It’s crucial to namespace your data to avoid conflicts with core WordPress or other plugin data. We’ll define a custom route, say my_custom_widget_data, which will carry our widget-specific information.
PHP Implementation for Heartbeat Data Registration
<?php
/**
* Plugin Name: My Elementor Custom Widgets
* Description: Adds custom widgets with Heartbeat API integration.
* Version: 1.0.0
* Author: Your Name
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Register custom heartbeat data.
*
* @param array $response The response data.
* @param array $data The incoming heartbeat data.
* @return array Modified response data.
*/
function my_elementor_custom_widgets_heartbeat_send( $response, $data ) {
// Check if our custom route is requested.
if ( isset( $data['my_custom_widget_data'] ) ) {
// Fetch or generate your dynamic data here.
// For demonstration, let's simulate fetching product stock.
$product_stock = rand( 10, 100 ); // Replace with actual stock retrieval logic.
$response['my_custom_widget_data'] = array(
'stock_level' => $product_stock,
'last_updated' => time(),
);
}
return $response;
}
add_filter( 'heartbeat_send', 'my_elementor_custom_widgets_heartor_send', 10, 2 );
/**
* Enqueue scripts for Elementor editor.
*/
function my_elementor_custom_widgets_enqueue_editor_scripts() {
wp_enqueue_script(
'my-elementor-custom-widgets-editor',
plugin_dir_url( __FILE__ ) . 'assets/js/editor-script.js',
array( 'jquery', 'heartbeat' ), // Depend on jQuery and Heartbeat
'1.0.0',
true
);
}
add_action( 'elementor/editor/after_enqueue_scripts', 'my_elementor_custom_widgets_enqueue_editor_scripts' );
In this PHP snippet:
- We define a function
my_elementor_custom_widgets_heartbeat_sendthat hooks intoheartbeat_send. - Inside the function, we check if our custom key
my_custom_widget_datais present in the incoming heartbeat data. This signifies that the client is interested in our data. - We then prepare an array with our dynamic data (e.g.,
stock_level) and add it to the response array under our custom key. - The
my_elementor_custom_widgets_enqueue_editor_scriptsfunction ensures our JavaScript file is loaded only within the Elementor editor and correctly declares dependencies onjqueryandheartbeat.
Client-Side JavaScript for Data Consumption
On the client-side, within the Elementor editor, we need to listen for Heartbeat API events and process the data received. We’ll use the heartbeat-send event to request our data and the heartbeat-tick event to receive and process it.
JavaScript Implementation (editor-script.js)
(function($) {
$(document).ready(function() {
var editor = false;
// Check if we are in the Elementor editor
if (typeof elementor !== 'undefined' && elementor.isEditMode()) {
editor = true;
}
if (!editor) {
return; // Exit if not in Elementor editor
}
// Request custom data on heartbeat send
$(document).on('heartbeat-send', function(e, data) {
// Request our custom data
data.my_custom_widget_data = true;
});
// Process received heartbeat data
$(document).on('heartbeat-tick', function(e, data) {
if (data.my_custom_widget_data) {
var widgetData = data.my_custom_widget_data;
console.log('Received custom widget data:', widgetData);
// Now, update your Elementor widgets based on this data.
// This is where you'd target specific widgets and update their content.
// Example: Update a widget's text based on stock level.
// You'll need to identify your widget instances.
// For instance, if your widget has a specific CSS class or data attribute.
// Example: Find all widgets with a specific data attribute and update them.
// This requires your custom widget to have a way to be identified.
// Let's assume your widget has a data-widget-id attribute.
// You would typically find widgets using Elementor's internal API or DOM traversal.
// A more robust approach would involve Elementor's widget API if available for dynamic updates.
// For direct DOM manipulation (use with caution):
$('.my-dynamic-widget[data-widget-id]').each(function() {
var widgetElement = $(this);
var widgetId = widgetElement.data('widget-id'); // Assuming your widget stores its ID
// Find the specific element within the widget to update
var stockDisplay = widgetElement.find('.stock-level-display'); // Assuming a class for stock display
if (stockDisplay.length && widgetData.stock_level !== undefined) {
stockDisplay.text('Stock: ' + widgetData.stock_level);
// You might also want to add classes for styling based on stock
if (widgetData.stock_level < 10) {
stockDisplay.addClass('low-stock').removeClass('in-stock');
} else {
stockDisplay.addClass('in-stock').removeClass('low-stock');
}
}
});
}
});
// Optional: Set heartbeat interval if needed, but Elementor usually manages this.
// wp.heartbeat.interval( 15000 ); // e.g., 15 seconds
});
})(jQuery);
In this JavaScript:
- We first ensure the code only runs within the Elementor editor.
- On
heartbeat-send, we add our custom keymy_custom_widget_data: trueto the data object. This tells the server we want to receive data for this key. - On
heartbeat-tick, we check ifdata.my_custom_widget_dataexists. If it does, we process the received data. - The example demonstrates how you might find specific widgets in the DOM (using hypothetical selectors like
.my-dynamic-widget[data-widget-id]) and update their content (e.g., a.stock-level-displayelement) based on the receivedstock_level. - Important: The DOM manipulation part is a simplified example. For robust solutions, you’d ideally interact with Elementor’s widget rendering API if it provides hooks for dynamic content updates. Otherwise, careful DOM traversal and manipulation are required, ensuring your selectors are specific and resilient to Elementor’s internal DOM structure changes.
Creating the Custom Elementor Widget
Now, let’s define a basic Elementor widget that can display this dynamic data. This widget will need a placeholder element in its template that our JavaScript can target.
PHP Widget Class
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class My_Dynamic_Stock_Widget extends \Elementor\Widget_Base {
public function get_name() {
return 'my-dynamic-stock-widget';
}
public function get_title() {
return esc_html__( 'Dynamic Stock Widget', 'my-elementor-custom-widgets' );
}
public function get_icon() {
return 'eicon-cart'; // Elementor icon class
}
public function get_categories() {
return array( 'my-custom-widgets' ); // Custom category
}
protected function _register_controls() {
$this->start_controls_section(
'content_section',
array(
'label' => esc_html__( 'Content', 'my-elementor-custom-widgets' ),
'tab' => \Elementor\Controls_Manager::TAB_CONTENT,
)
);
$this->add_control(
'stock_label',
array(
'label' => esc_html__( 'Stock Label', 'my-elementor-custom-widgets' ),
'type' => \Elementor\Controls_Manager::TEXT,
'default' => esc_html__( 'Current Stock:', 'my-elementor-custom-widgets' ),
'placeholder' => esc_html__( 'Enter your stock label', 'my-elementor-custom-widgets' ),
)
);
$this->end_controls_section();
}
protected function render() {
$settings = $this->get_settings_for_display();
$stock_label = $settings['stock_label'];
// The data will be populated by JavaScript in the editor.
// For the frontend render, you might fetch data directly or use a placeholder.
// For simplicity, we'll use a placeholder here and rely on JS for editor preview.
// On the frontend, you'd typically fetch this data via AJAX or a separate Heartbeat call if needed.
?>
<div class="my-dynamic-widget" data-widget-id="get_id() ); ?>">
<span class="stock-label"><?php echo esc_html( $stock_label ); ?></?php echo esc_html( $stock_label ); ?></span>
<span class="stock-level-display">Loading stock...</span>
</div>
<?php
}
protected function _content_template() {
// This template is used for the Elementor editor preview.
// It will be rendered using JavaScript and can directly access data
// that the Heartbeat API pushes to the client.
?>
<div class="my-dynamic-widget" data-widget-id="<%= getId() %>">
<span class="stock-label"><%= getSettings('stock_label') %></span>
<span class="stock-level-display">Loading stock...</span>
</div>
<?php
}
// Ensure the widget is registered. This would typically be in your main plugin file.
// public function __construct( array $data = [], array $args = null ) {
// parent::__construct( $data, $args );
// // Register widget scripts/styles if needed
// wp_register_script( 'my-elementor-custom-widgets-editor', plugin_dir_url( __FILE__ ) . 'assets/js/editor-script.js', array( 'jquery', 'heartbeat' ), '1.0.0', true );
// }
}
In this widget class:
- We define the widget’s name, title, icon, and category.
- The
_register_controlsmethod sets up a simple text control for the stock label. - The
rendermethod generates the HTML for the frontend. It includes adata-widget-idattribute for JavaScript targeting and astock-level-displayspan where the stock will be shown. - The
_content_templatemethod is crucial for the Elementor editor. It uses Underscore.js templating syntax (e.g.,<%= ... %>) to render the widget’s structure. This template is what the JavaScript will interact with to update the display. - The
data-widget-idattribute is dynamically set using<%= getId() %>in the template, which is essential for our JavaScript to target specific widget instances.
Integrating the Widget and Scripts
To make this work, ensure:
- Your PHP widget class is correctly registered with Elementor. This usually involves hooking into
elementor/widgets/widgets_registered. - The
editor-script.jsfile is enqueued for the Elementor editor, as shown in the PHP Heartbeat registration section. - The Heartbeat API registration function is also included in your plugin’s main file or an included file.
Registering the Widget
<?php
// In your main plugin file (e.g., my-elementor-custom-widgets.php)
// ... (previous Heartbeat and enqueue code) ...
use Elementor\Plugin;
class My_Elementor_Custom_Widgets_Plugin {
public function __construct() {
add_action( 'elementor/widgets/widgets_registered', array( $this, 'register_widgets' ) );
// Ensure Elementor is active before trying to register widgets
add_action( 'plugins_loaded', array( $this, 'init' ) );
}
public function init() {
// Add custom widget category
add_action( 'elementor/elements_categories_registered', array( $this, 'add_elementor_category' ) );
}
public function add_elementor_category( $elements_manager ) {
$elements_manager->add_category(
'my-custom-widgets',
array(
'title' => esc_html__( 'My Custom Widgets', 'my-elementor-custom-widgets' ),
)
);
}
public function register_widgets() {
// Include the widget file
require_once( __DIR__ . '/widgets/my-dynamic-stock-widget.php' );
// Register the widget
Plugin::instance()->widgets_manager->register_widget_type( new My_Dynamic_Stock_Widget() );
}
}
// Instantiate the plugin
new My_Elementor_Custom_Widgets_Plugin();
This setup ensures that your custom widget is recognized by Elementor and placed into a dedicated category. The init method with plugins_loaded hook is a standard practice to ensure Elementor is ready before attempting to add categories or widgets.
Advanced Considerations and Best Practices
- Data Schema Design: For complex data, consider a JSON schema for your Heartbeat data. This makes parsing and validation on the client-side more predictable.
- Performance: Heartbeat requests can increase server load. Optimize your server-side data fetching. Cache data where possible. Consider adjusting the heartbeat interval if frequent updates aren’t strictly necessary (though Elementor often manages this intelligently).
- Error Handling: Implement robust error handling in both PHP and JavaScript. Log errors server-side and provide user-friendly feedback client-side if data fails to load or update.
- Security: Always sanitize and validate data. Use nonces for critical operations if your Heartbeat data triggers server-side actions beyond simple data retrieval.
- Frontend vs. Editor: Remember that the Heartbeat API primarily synchronizes data within the Elementor editor. For live frontend updates, you’ll typically need AJAX, WebSockets, or a different approach. However, data fetched via Heartbeat in the editor can inform the initial render of the widget on the frontend if you choose to pass it along or re-fetch it.
- Elementor API: Explore Elementor’s JavaScript API for more integrated ways to update widget content rather than direct DOM manipulation. This can lead to more stable and future-proof extensions.
By integrating the WordPress Heartbeat API with custom Elementor widgets, you can create highly interactive and dynamic editing experiences. This approach allows for real-time feedback and data synchronization, enhancing the usability and power of your custom Elementor extensions for e-commerce and beyond.