Skip to content

Commit 0be8a11

Browse files
committed
Implement reactive data binding mechanism for ldview
This commit introduces a reactive data binding system that enables automatic DOM updates when data changes, similar to modern frameworks like Vue.js. ## Core Changes ### 1. New ldreactive Library (src/ldreactive.ls) - Independent reactive data binding library - Uses ES6 Proxy for dependency tracking - Supports deep object/array tracking - Event-driven architecture (on/fire pattern) - Batch update support - Property exclusion mechanism Features: - `get()` - Get reactive proxy - `raw()` - Get raw data without tracking - `set(data)` - Set/update data - `track(name, fn)` - Track dependencies - `untrack(name)` - Stop tracking - `watch(key, callback)` - Watch specific properties - `batch(fn)` - Batch multiple updates ### 2. ldview Integration (src/ldview.ls) - Seamless integration with ldreactive - Auto-detect reactive context - Automatic handler re-rendering on data changes - Per-handler reactive control via `reactive: false` - Support for all handler types (handler, text, attr, style) Usage: ```javascript const state = new ldview.reactive({ count: 0 }); new ldview({ root: '#app', ctx: state, handler: { counter: ({node, ctx}) => node.textContent = ctx.count } }); state.get().count++; // Auto re-renders counter handler ``` ### 3. Build System Updates - Updated build script to compile ldreactive - Generate both regular and minified versions - Copy to web assets directory ### 4. Documentation & Tests - REACTIVE.md - Comprehensive documentation with examples - test-ldreactive.js/html - Standalone ldreactive tests (13 tests) - test-integration.js/html - Integration tests (10 tests) - All tests passing ✓ ## Technical Details - Deep property tracking (e.g., `user.profile.name`) - Array mutation tracking (push, pop, splice, etc.) - WeakMap-based proxy caching for performance - Event system for change notifications - No breaking changes to existing ldview API - Backward compatible (reactive is opt-in) ## Browser Support Requires ES6 Proxy support: - Chrome 49+, Firefox 18+, Safari 10+, Edge 12+ - Not compatible with IE11 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent 5a5b374 commit 0be8a11

File tree

16 files changed

+2352
-33
lines changed

16 files changed

+2352
-33
lines changed

REACTIVE.md

Lines changed: 419 additions & 0 deletions
Large diffs are not rendered by default.

build

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,25 @@
11
#!/usr/bin/env bash
22
rm -rf dist
33
mkdir -p dist
4+
5+
echo "build src/ldreactive.ls -> dist/ldreactive.js ..."
6+
./node_modules/.bin/lsc -cp --no-header src/ldreactive.ls > dist/ldreactive.js
7+
48
echo "build src/ldview.ls -> dist/index.js ..."
59
./node_modules/.bin/lsc -cp --no-header src/ldview.ls > dist/index.js
10+
11+
echo "minifying ldreactive.js ..."
12+
./node_modules/.bin/uglifyjs dist/ldreactive.js -m -c > dist/ldreactive.min.js
13+
614
echo "minifying index.js ..."
715
./node_modules/.bin/uglifyjs dist/index.js -m -c > dist/index.min.js
16+
817
echo "copy ldview.pug to dist/ ..."
918
cp src/ldview.pug dist/index.pug
19+
1020
echo "copy files to web ..."
1121
rm -rf web/static/assets/lib/ldview/dev
1222
mkdir -p web/static/assets/lib/ldview/dev
1323
cp dist/* web/static/assets/lib/ldview/dev
24+
1425
echo "done."

dist/index.js

Lines changed: 57 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
(function(){
2-
var setEvtHandler, ldview;
2+
var setEvtHandler, ldreactive, ldview;
33
setEvtHandler = function(d, k, f){
44
return d.node.addEventListener(k, function(evt){
55
return f(import$({
66
evt: evt
77
}, d));
88
});
99
};
10+
ldreactive = (typeof module != 'undefined' && module !== null) && (typeof require != 'undefined' && require !== null)
11+
? require('./ldreactive.js')
12+
: typeof window != 'undefined' && window !== null ? window.ldreactive : void 8;
1013
ldview = function(opt){
1114
var names, i$, ref$, k, v, len$, list, j$, len1$, it, res$, this$ = this;
1215
opt == null && (opt = {});
@@ -20,6 +23,17 @@
2023
if (opt.context) {
2124
console.warn('[ldview] `context` is deprecated. use `ctx` instead.');
2225
}
26+
this._reactive = null;
27+
this._reactiveEnabled = false;
28+
if (this._ctx && ldreactive && (this._ctx instanceof ldreactive || this._ctx._isReactiveContext)) {
29+
this._reactive = this._ctx;
30+
this._reactiveEnabled = true;
31+
this._reactive.on('change', function(key, value, oldValue, dependents){
32+
if (dependents && dependents.length > 0) {
33+
return this$.render(dependents);
34+
}
35+
});
36+
}
2337
this.attr = opt.attr || {};
2438
this.style = opt.style || {};
2539
this.handler = opt.handler || {};
@@ -368,18 +382,24 @@
368382
});
369383
},
370384
_render: function(n, d, i, b, e, initOnly){
371-
var c, init, handler, text, attr, style, action, ref$, k, v, f, results$ = [];
372-
c = typeof this._ctx === 'function'
373-
? c = this._ctx({
374-
node: this.root,
375-
ctxs: this._ctxs,
376-
views: this.views
377-
})
378-
: this._ctx;
385+
var c, reactiveAllowed, init, handler, text, attr, style, action, ref$, textVal, attrVal, k, v, styleVal, f, results$ = [];
386+
c = this._reactiveEnabled
387+
? this._reactive.get()
388+
: typeof this._ctx === 'function'
389+
? this._ctx({
390+
node: this.root,
391+
ctxs: this._ctxs,
392+
views: this.views
393+
})
394+
: this._ctx;
379395
d.ctx = c;
380396
d.context = c;
381397
d.ctxs = this._ctxs;
382398
d.views = this.views;
399+
reactiveAllowed = this._reactiveEnabled;
400+
if (b && typeof b.reactive !== 'undefined') {
401+
reactiveAllowed = b.reactive;
402+
}
383403
if (b) {
384404
if (b.view) {
385405
init = function(arg$){
@@ -424,19 +444,40 @@
424444
return Promise.resolve((d.inited || (d.inited = {}))[n]);
425445
}
426446
if (handler) {
427-
handler(d);
447+
if (reactiveAllowed && this._reactive) {
448+
this._reactive.track(n, function(){
449+
return handler(d);
450+
});
451+
} else {
452+
handler(d);
453+
}
428454
}
429455
if (text) {
430-
d.node.textContent = typeof text === 'function' ? text(d) : text;
456+
textVal = typeof text === 'function' ? reactiveAllowed && this._reactive
457+
? this._reactive.track(n, function(){
458+
return text(d);
459+
})
460+
: text(d) : text;
461+
d.node.textContent = textVal;
431462
}
432463
if (attr) {
433-
for (k in ref$ = attr(d) || {}) {
464+
attrVal = reactiveAllowed && this._reactive
465+
? this._reactive.track(n, function(){
466+
return attr(d);
467+
})
468+
: attr(d);
469+
for (k in ref$ = attrVal || {}) {
434470
v = ref$[k];
435471
d.node.setAttribute(k, v);
436472
}
437473
}
438474
if (style) {
439-
for (k in ref$ = style(d) || {}) {
475+
styleVal = reactiveAllowed && this._reactive
476+
? this._reactive.track(n, function(){
477+
return style(d);
478+
})
479+
: style(d);
480+
for (k in ref$ = styleVal || {}) {
440481
v = ref$[k];
441482
d.node.style[k] = v;
442483
}
@@ -614,6 +655,9 @@
614655
}
615656
return a;
616657
};
658+
if (ldreactive) {
659+
ldview.reactive = ldreactive;
660+
}
617661
if (typeof module != 'undefined' && module !== null) {
618662
module.exports = ldview;
619663
}

dist/index.min.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)