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-action for click/input events
  • data-bind-class for dynamic classes
  • data-bind-style for dynamic styles
  • data-model for 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:
  1. Get raw element: const el = this.$el(selector).el
  2. Initialize library: this.libInstance = SomeLibrary(el, options)
  3. Bridge events to state: onChange: (val) => this.value = val
  4. 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

CategoryMethods
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

MethodReturnsDescription
.elElement|nullGet first raw DOM element (or null if empty)
.lengthNumberNumber of matched elements
.get(index)ElementGet raw DOM element at index
.first()WrapperWrapped first element
.last()WrapperWrapped last element
.each(fn)WrapperIterate with callback(el, index)

Classes

MethodReturnsDescription
.addClass(names)WrapperAdd one or more classes (space-separated)
.removeClass(names)WrapperRemove one or more classes
.toggleClass(name)WrapperToggle a class on/off
.hasClass(name)BooleanCheck if first element has class

Attributes & Data

MethodReturnsDescription
.attr(name)StringGet attribute value
.attr(name, value)WrapperSet attribute value
.data(key)StringGet data attribute value
.data(key, value)WrapperSet data attribute value

Content & Values

MethodReturnsDescription
.text()StringGet text content
.text(value)WrapperSet text content
.html()StringGet HTML content
.html(value)WrapperSet HTML content
.val()StringGet input value
.val(value)WrapperSet input value (triggers input event)

Styles & Display

MethodReturnsDescription
.css(prop, value)WrapperSet a single CSS property
.css(object)WrapperSet multiple CSS properties
.show()WrapperShow element (display: '')
.hide()WrapperHide element (display: none)

Events

MethodReturnsDescription
.on(event, handler)WrapperAttach event listener (auto-cleanup on destroy)
.off(event)WrapperRemove all handlers for event type
.off(event, handler)WrapperRemove specific handler
.trigger(event)WrapperDispatch custom event

Traversal

MethodReturnsDescription
.find(selector)WrapperFind descendants matching selector
.parent()WrapperGet parent element (within component)
.closest(selector)WrapperFind closest ancestor (within component)
.children()WrapperGet direct children
.siblings()WrapperGet sibling elements

Predicates & Utilities

MethodReturnsDescription
.is(selector)BooleanCheck if first element matches selector
.hasClass(name)BooleanCheck if first element has class
.focus()WrapperFocus the first element
.blur()WrapperRemove focus from the first element
.remove()WrapperRemove elements from DOM