You have a legacy site running jQuery. You want reactivity without ripping anything out. Drop in Wildflower, keep your jQuery code, and they share the same DOM peacefully. The three zones below are live proofs.
This console is itself a Wildflower component reading entries from a store.
When jQuery handlers call wildflower.getStore('demoLog').add(...),
the list re-renders reactively. No bridge code, no event bus.
jQuery binds .on('click') to the orange outer panel.
Wildflower binds data-action to the inner buttons.
Click each button and watch the log.
Default
jQuery + WF both fire
·
With data-event-stop
only WF fires
Why this matters. In v1.1, Wildflower lets DOM events bubble
naturally, so existing jQuery delegation keeps working. When you genuinely
want to swallow an event, opt in with data-event-stop.
<div id="bubble-outer">
<!-- jQuery handler attached in app.js: -->
<!-- $('#bubble-outer').on('click', () => demoLog.add('jQuery', 'outer click')); -->
<button data-action="defaultClick">Click me (default)</button>
<button data-action="stoppedClick" data-event-stop>Click me (data-event-stop)</button>
</div>
jQuery moves, fades, and wraps Wildflower-managed elements. Data bindings survive every mutation. The count field on each row updates in place wherever jQuery has dragged the row to.
The honest caveat. Like Vue, React, and Svelte, Wildflower delegates list-item
click handlers to the list parent. If jQuery's .appendTo() moves a row out of its
data-list parent without updating state, the per-item +1
button stops firing on that orphaned row; the click never reaches a delegated handler. Top-level
bindings (data-bind="count") still update because they're tracked by element reference,
not DOM position; Bump all proves it.
The fix: bridge pattern. Drive the move from state, not from the DOM. The
jQuery: relocate to second list button shifts the first item from
items into relocatedItems. Wildflower re-renders both lists; the relocated
row lives in #stress-relocation-target, which has its own data-list and
its own delegated click handler, so the +1 button keeps working. The
Kanban demo uses this pattern with SortableJS:
the library moves DOM during a drag, the onEnd callback updates the store, Wildflower
replays the move, click handlers work everywhere.
jQuery DOM mutations vs. state-driven decoration. Wildflower treats each
data-list as its own scope: a cross-list state move destroys the source
element and creates fresh from the template at the destination. So jQuery DOM mutations
(.wrap(), .addClass(), inline styles) applied to elements between
renders won't survive a cross-list move; there's no reactive hook for WF to know about them.
For decoration that needs to survive moves, drive it from state. The Decorate
(state-driven) button toggles a decorated property on each item;
the template binds it via data-bind-class="{ 'jq-decorated': decorated }". The class
is part of the reactive contract now. WF applies it on every render, including freshly-created
elements at a destination list.
// [Bump all (state mutation)]
// State mutation reaches every row regardless of where jQuery has put it:
this.items.forEach(it => it.count++);
this.relocatedItems.forEach(it => it.count++);
// [jQuery: relocate to second list]
// Bridge pattern: drive the move from state. WF re-renders the row into
// #stress-relocation-target's own data-list, where its +1 button works.
// If the moving row happens to be wrapped in foreign DOM, unwrap that one
// row first so its wrapper doesn't strand as an empty div in the source
// list. Other wrapped rows are left alone:
const moved = this.items[0];
const $movedEl = $('#stress-list-host .item').first();
if ($movedEl.parent('.jquery-wrapper').length) $movedEl.unwrap();
this.items.shift();
this.relocatedItems.push(moved);
// [jQuery: hide().fadeIn()]
// Run a jQuery animation across all items. Display flips, listeners survive:
$('#stress-list-host .item, #stress-relocation-target .item').hide().fadeIn(450);
// [jQuery: .wrap()]
// Wrap adds a foreign parent div WF doesn't track. The moving row's
// wrapper is scrubbed inside jqRelocate (above); wrappers on rows that
// don't move stay intact:
$('#stress-list-host .item, #stress-relocation-target .item')
.wrap('<div class="jquery-wrapper"></div>');
// [Decorate (state-driven)]
// State-driven decoration: class is part of the reactive contract, so WF
// applies it on every render including freshly-created elements after a move:
//
// template: <div class="item" data-bind-class="{ 'jq-decorated': decorated }">
// action: this.items.forEach(it => it.decorated = true);
// this.relocatedItems.forEach(it => it.decorated = true);
// [(not a button, illustrative anti-pattern)]
// Don't expect jQuery DOM mutation to survive a cross-list move. WF creates a
// fresh element at the destination from the template and there's no reactive
// hook for it to reapply this class:
$('#stress-list-host .item, #stress-relocation-target .item').addClass('jq-decorated');
No npm. No webpack. No JSX, no SFC, no virtual DOM. Two script tags, one HTML file.
Save the snippet below as test.html, open it in a browser, done.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>jQuery + WildflowerJS</title>
<script src="https://code.jquery.com/jquery-4.0.0.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/wildflowerjs@1/dist/wildflower.mini.min.js"></script>
</head>
<body>
<div data-component="hello">
<h1 data-bind="msg"></h1>
<button data-action="shout">Shout</button>
</div>
<script>
// Wildflower-side: reactive component
wildflower.component('hello', {
state: { msg: 'Hello' },
shout() { this.msg = this.msg.toUpperCase() + '!'; }
});
// jQuery-side: same DOM, no wrapper plugins, no bridge
$(function () {
$('h1').css('color', '#5fa6a6');
});
</script>
</body>
</html>
Wildflower is the reactivity engine. jQuery is the legacy you don't have to delete. Use both, on the same page, on the same elements. BFFs.