Havenlytics ships a first-class Elementor integration that registers a dedicated widget category and three property widgets. This guide documents how the integration boots, what each widget renders, how assets are loaded conditionally, and how to register your own Elementor widget following the exact pattern used in the plugin.
Note: The integration only loads when Elementor is active. Everything below is gated behind a
class_exists( '\Elementor\Plugin' )check, so it never runs — and never errors — on sites without Elementor.
How the integration boots
There are two cooperating wiring paths, and both register the same three widgets into the same havenlytics category (Elementor’s register() is idempotent by slug, so this is safe):
- Primary path — In
havenlytics.php, onplugins_loadedpriority20, if\Elementor\Pluginexists (orELEMENTOR_VERSIONis defined), the plugin requiresincludes/Integrations/Elementor/Bootstrap.phpand callsBootstrap::get_instance(). The bootstrap constructor hookselementor/init(priority 5),elementor/widgets/register, and the frontend/editor/preview asset-enqueue hooks. Onelementor/initpriority999,promote_havenlytics_category_first()reorders the category to the top of the Elementor panel. - Secondary path —
HvnlyNab\Integrations\Elementor\ElementorIntegrationis registered as a frontend service and also registers the category and the same three widgets, enforcing a minimum Elementor version.
The Havenlytics widget category
All widgets appear under a single Elementor category:
| Property | Value |
|---|---|
| Category slug | havenlytics |
| Title | Havenlytics |
| Icon | eicon-home |
| Registered on | elementor/elements/categories_registered |
The category is promoted to the top of the Elementor panel, and the editor panel icon is overridden with the Havenlytics SVG (havenlytics-icon20x20.svg).
The three property widgets
Each widget extends \Elementor\Widget_Base, returns ['havenlytics'] from get_categories(), and uses the icon eicon-gallery-grid.
HVN: Property Archive — hvnly_all_properties
File: includes/Integrations/Elementor/Widgets/HvnlyAllPropertiesWidget.php. Renders the full property archive UI (search filters, optional filter sidebar, view controls, the property loop via hvnly_render_property_card(), and pagination). It swaps $wp_query for a PropertyQueryExecutor query built by PropertyQueryArgsBuilder::merge_elementor_widget_context(), then restores it. It localizes the hvnlyElementor JavaScript global for AJAX load-more.
| Control group | Controls (defaults) |
|---|---|
| Display | show_filter_sidebar (yes), show_top_search (yes), default_view (grid / list / map), sidebar_position (left / right), columns (1–4, default 2) |
| Query | posts_per_page (4, range 1–100), orderby (date / title / price_low / price_high / rand), featured_only (no) |
| Default Filters | default_department (SELECT2 from hvnly_prop_depts), default_min_price, default_max_price, default_bedrooms, default_bathrooms |
| Style | brand_color (#6C60FE → --hvnly-brand-primary), secondary_color (#764ba2) |
HVN: Property Agents — hvnly_property_agents
File: includes/Integrations/Elementor/Widgets/HvnlyPropertyAgentsWidget.php. Renders the property-archive/partials/agents-archive template (the same output as the [hvnly_property_agents] shortcode) by querying the hvnly_agent CPT.
- Display:
show_header(yes),title(“Real Estate Agents”),subtitle,show_search(yes),show_view_controls(yes),posts_per_page(12, range 1–48),columns(1–4, default 4),default_view(grid / list) - Query:
orderby(title / date),order(ASC / DESC)
HVN: Property Agency — hvnly_property_agencies
File: includes/Integrations/Elementor/Widgets/HvnlyPropertyAgenciesWidget.php. Renders property-archive/partials/agencies-archive via AgencyArchiveQuery. Same control shape as the agents widget, but orderby is (name / date) and the default title is “Real Estate Agencies”.
Conditional asset loading
Widget CSS/JS is only enqueued when it is actually needed. The bootstrap checks whether the current post’s _elementor_data contains the widget slug, or whether the request is in the Elementor editor/preview context, via should_load_*_widget_assets(), page_has_elementor_widget(), and is_elementor_editor_context(). Widgets also declare get_style_depends() / get_script_depends() and fall back to Bootstrap::enqueue_*_assets_for_render() inside render(). Elementor assets live under assets/frontend/elementor/css/ and assets/frontend/elementor/js/.
Performance: Because assets are gated on the presence of the widget in the page, adding these widgets to a page never loads their CSS/JS globally — keep this behaviour when adding your own widget by wiring a
should_load_*gate.
AJAX load-more
The archive widget’s “load more” uses the AJAX action hvnly_elementor_load_more_properties, handled by includes/Integrations/Elementor/AjaxHandler.php and protected by the hvnly_ajax_request nonce. The query args are filterable via hvnly_elementor_load_more_query_args.
Register a new Elementor widget
Follow the existing pattern to add your own widget under the Havenlytics category:
<?php
namespace HvnlyNab\Integrations\Elementor\Widgets;
use Elementor\Widget_Base;
use Elementor\Controls_Manager;
class HvnlyMyWidget extends Widget_Base {
public function get_name() {
return 'hvnly_my_widget'; // unique slug
}
public function get_title() {
return 'HVN: My Widget'; // follow the "HVN: ..." convention
}
public function get_icon() {
return 'eicon-gallery-grid';
}
public function get_categories() {
return array( 'havenlytics' ); // place it in the Havenlytics category
}
protected function register_controls() {
$this->start_controls_section( 'content', array(
'label' => 'Content',
'tab' => Controls_Manager::TAB_CONTENT,
) );
$this->add_control( 'posts_per_page', array(
'label' => 'Posts per page',
'type' => Controls_Manager::NUMBER,
'default' => 6,
) );
$this->end_controls_section();
}
protected function render() {
$settings = $this->get_settings_for_display();
// Reuse the Havenlytics render pipeline, e.g. hvnly_render_property_card().
}
}
Common mistake: Registering the widget in only one place. You must add your
file => classentry to bothBootstrap::register_widgets()andElementorIntegration::register_widgets(), or the widget will be missing depending on which path fires.
Best practices
- Reuse the Havenlytics render helpers (
hvnly_render_property_card(),PropertyQueryArgsBuilder) instead of writing your own query/markup, so your widget inherits the Card Builder layout and filters. - Always restore
$wp_queryafter swapping it inrender()to avoid “content bleeding” into other widgets on the page. - Prefix widget slugs with
hvnly_and titles withHVN:for consistency.
Security notes
Security: Escape all control values on output (
esc_attr(),esc_html()) and verify thehvnly_ajax_requestnonce in any custom AJAX endpoint your widget calls. Never trustget_settings_for_display()values as safe HTML.
Common mistakes
- Assuming the widgets appear without Elementor active — they do not; the whole integration is gated on Elementor being present.
- Enqueuing assets globally instead of gating them on widget presence.
- Forgetting that the archive widget swaps the main query — heavy logic in
render()runs on every page that embeds it.