DOM Helpers
The this.$el helper provides jQuery-like DOM access for cases where declarative attributes aren't enough, primarily third-party library integration.
When to Use
this.$el
Use $el for:
- Third-party libraries (Flatpickr, Chart.js, SortableJS)
- Events the framework doesn't handle (drag, resize, scroll)
- Programmatic focus management
Prefer declarative:
data-actionfor click/input eventsdata-bind-classfor dynamic classesdata-bind-stylefor dynamic stylesdata-modelfor form inputs
Primary Use Case: Third-Party Libraries
Most libraries need a raw DOM element to initialize. Use .el to get it:
Live Demo: Chart.js Sales Dashboard
Open Full Example
The Code
<!-- Include Chart.js from CDN -->
<script defer src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<div data-component="sales-chart">
<div class="chart-container">
<canvas></canvas>
</div>
<button data-action="addMonth">Add Month</button>
<button data-action="toggleType">Toggle Bar/Line</button>
<div>Total: $<span data-bind="total"></span></div>
</div>
wildflower.component('sales-chart', {
state: {
labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'],
sales: [120, 190, 150, 220, 180, 250],
chartType: 'bar'
},
computed: {
total() { return this.sales.reduce((a, b) => a + b, 0) }
},
init() {
// Get raw DOM element for Chart.js
const canvas = this.$el('canvas').el;
this._chart = new Chart(canvas, {
type: this.chartType,
data: {
labels: this.labels.slice(),
datasets: [{
label: 'Sales ($)',
data: this.sales.slice(),
borderColor: '#4361ee',
backgroundColor: 'rgba(67, 97, 238, 0.3)',
fill: true
}]
},
options: { responsive: true, maintainAspectRatio: false }
});
},
addMonth() {
const months = ['Jan','Feb','Mar','Apr','May','Jun',
'Jul','Aug','Sep','Oct','Nov','Dec'];
this.labels.push(months[this.labels.length % 12]);
this.sales.push(Math.round(100 + Math.random() * 200));
// Update Chart.js with new data
this._chart.data.labels = this.labels.slice();
this._chart.data.datasets[0].data = this.sales.slice();
this._chart.update('none');
},
toggleType() {
this.chartType = this.chartType === 'bar' ? 'line' : 'bar';
this._chart.destroy();
this.init(); // Recreate with new type
},
destroy() {
// Always clean up library instances
if (this._chart) this._chart.destroy();
}
});
The Pattern:
- Get raw element:
const el = this.$el(selector).el - Initialize library:
this.libInstance = SomeLibrary(el, options) - Bridge events to state:
onChange: (val) => this.value = val - Clean up in
destroy():this.libInstance.destroy()
More Library Examples
// Chart.js
init() {
const canvas = this.$el('.chart-canvas').el;
this.chart = new Chart(canvas, {
type: 'bar',
data: this.chartData
});
}
// SortableJS
init() {
const list = this.$el('.sortable-list').el;
this.sortable = Sortable.create(list, {
onEnd: (evt) => {
// Reorder state array to match new DOM order
const item = this.items.splice(evt.oldIndex, 1)[0];
this.items.splice(evt.newIndex, 0, item);
}
});
}
// Tippy.js tooltips
init() {
this.$el('[data-tooltip]').each((el) => {
tippy(el, { content: el.dataset.tooltip });
});
}
See the Third-Party Libraries guide for complete integration patterns.
Secondary: Events Not Covered by data-action
Use $el for events like mouseenter, mouseleave, dragstart, scroll, or resize:
<div data-component="hover-card">
<div class="p-4 border rounded">
<h5>Hover over me</h5>
<p class="details" style="display: none;">
Extra details shown on hover!
</p>
</div>
</div>
wildflower.component('hover-card', {
state: {},
init() {
// mouseenter/mouseleave aren't available via data-action
this.$el('.border')
.on('mouseenter', () => {
this.$el('.details').show();
this.$el('.border').css('background-color', 'var(--code-bg)');
})
.on('mouseleave', () => {
this.$el('.details').hide();
this.$el('.border').css('background-color', '');
});
}
// Event handlers auto-cleanup on component destroy
});
Live Preview
Auto-Cleanup: Event handlers attached with
$el().on() are automatically removed when the component is destroyed, so nothing leaks and there is no cleanup to write.
Secondary: Focus Management
Programmatically focus inputs, useful for modals, forms, and accessibility:
wildflower.component('search-modal', {
state: { isOpen: false },
open() {
this.isOpen = true;
// Focus the input after the modal opens
setTimeout(() => {
this.$el('.search-input').focus();
}, 100);
},
handleKeydown(event) {
if (event.key === 'Escape') {
this.isOpen = false;
}
}
});
The val() Reactivity Bridge
When setting input values programmatically, val() dispatches an input event to keep data-model in sync:
// This updates both the DOM and the reactive state
this.$el('input[data-model="search"]').val('new search term');
// Equivalent to:
// 1. element.value = 'new search term'
// 2. element.dispatchEvent(new Event('input', { bubbles: true }))
Selection Basics
| Call | Returns | Use Case |
|---|---|---|
this.$el('.selector') |
Wrapper | Select elements within component |
this.$el('.selector').el |
Element or null | Get raw element for third-party libs |
this.$el() |
Wrapper | Select component's root element |
this.$el(domElement) |
Wrapper | Wrap an existing element |
Component Boundary: Traversal methods (
.parent(), .closest()) cannot escape the component element. This prevents accidental manipulation of parent components.
API Reference
| Category | Methods |
|---|---|
| Element Access | .el (raw element), .get(i), .length, .first(), .last(), .each(fn) |
| Classes | .addClass(), .removeClass(), .toggleClass(), .hasClass() |
| Styles | .css(prop, val), .css({...}), .show(), .hide() |
| Content | .text(), .html(), .val() (triggers input event), .attr(), .data() |
| Events | .on(event, fn) (auto-cleanup), .off(), .trigger() |
| Traversal | .find(), .parent(), .closest(), .children(), .siblings() |
| Predicates | .is(selector), .hasClass() |
| Utilities | .focus(), .blur(), .remove() |
Full API Details (click to expand)
Selection & Access
| Method | Returns | Description |
|---|---|---|
.el | Element|null | Get first raw DOM element (or null if empty) |
.length | Number | Number of matched elements |
.get(index) | Element | Get raw DOM element at index |
.first() | Wrapper | Wrapped first element |
.last() | Wrapper | Wrapped last element |
.each(fn) | Wrapper | Iterate with callback(el, index) |
Classes
| Method | Returns | Description |
|---|---|---|
.addClass(names) | Wrapper | Add one or more classes (space-separated) |
.removeClass(names) | Wrapper | Remove one or more classes |
.toggleClass(name) | Wrapper | Toggle a class on/off |
.hasClass(name) | Boolean | Check if first element has class |
Attributes & Data
| Method | Returns | Description |
|---|---|---|
.attr(name) | String | Get attribute value |
.attr(name, value) | Wrapper | Set attribute value |
.data(key) | String | Get data attribute value |
.data(key, value) | Wrapper | Set data attribute value |
Content & Values
| Method | Returns | Description |
|---|---|---|
.text() | String | Get text content |
.text(value) | Wrapper | Set text content |
.html() | String | Get HTML content |
.html(value) | Wrapper | Set HTML content |
.val() | String | Get input value |
.val(value) | Wrapper | Set input value (triggers input event) |
Styles & Display
| Method | Returns | Description |
|---|---|---|
.css(prop, value) | Wrapper | Set a single CSS property |
.css(object) | Wrapper | Set multiple CSS properties |
.show() | Wrapper | Show element (display: '') |
.hide() | Wrapper | Hide element (display: none) |
Events
| Method | Returns | Description |
|---|---|---|
.on(event, handler) | Wrapper | Attach event listener (auto-cleanup on destroy) |
.off(event) | Wrapper | Remove all handlers for event type |
.off(event, handler) | Wrapper | Remove specific handler |
.trigger(event) | Wrapper | Dispatch custom event |
Traversal
| Method | Returns | Description |
|---|---|---|
.find(selector) | Wrapper | Find descendants matching selector |
.parent() | Wrapper | Get parent element (within component) |
.closest(selector) | Wrapper | Find closest ancestor (within component) |
.children() | Wrapper | Get direct children |
.siblings() | Wrapper | Get sibling elements |
Predicates & Utilities
| Method | Returns | Description |
|---|---|---|
.is(selector) | Boolean | Check if first element matches selector |
.hasClass(name) | Boolean | Check if first element has class |
.focus() | Wrapper | Focus the first element |
.blur() | Wrapper | Remove focus from the first element |
.remove() | Wrapper | Remove elements from DOM |