@gogocat/data-bind 1.11.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.editorconfig +14 -14
- package/.vscode/launch.json +12 -12
- package/CONFIGURATION.md +294 -0
- package/REACTIVE_MODE.md +553 -0
- package/README.md +266 -829
- package/babel.config.json +30 -0
- package/dist/js/_escape.d.ts +14 -0
- package/dist/js/_escape.d.ts.map +1 -0
- package/dist/js/applyBinding.d.ts +11 -0
- package/dist/js/applyBinding.d.ts.map +1 -0
- package/dist/js/attrBinding.d.ts +12 -0
- package/dist/js/attrBinding.d.ts.map +1 -0
- package/dist/js/binder.d.ts +67 -0
- package/dist/js/binder.d.ts.map +1 -0
- package/dist/js/changeBinding.d.ts +19 -0
- package/dist/js/changeBinding.d.ts.map +1 -0
- package/dist/js/commentWrapper.d.ts +39 -0
- package/dist/js/commentWrapper.d.ts.map +1 -0
- package/dist/js/config.d.ts +55 -0
- package/dist/js/config.d.ts.map +1 -0
- package/dist/js/createBindingOption.d.ts +32 -0
- package/dist/js/createBindingOption.d.ts.map +1 -0
- package/dist/js/createEventBinding.d.ts +10 -0
- package/dist/js/createEventBinding.d.ts.map +1 -0
- package/dist/js/cssBinding.d.ts +15 -0
- package/dist/js/cssBinding.d.ts.map +1 -0
- package/dist/js/dataBind.js +2772 -2519
- package/dist/js/dataBind.min.js +8 -1
- package/dist/js/dataBind.min.js.map +1 -1
- package/dist/js/domWalker.d.ts +9 -0
- package/dist/js/domWalker.d.ts.map +1 -0
- package/dist/js/forOfBinding.d.ts +12 -0
- package/dist/js/forOfBinding.d.ts.map +1 -0
- package/dist/js/hoverBinding.d.ts +13 -0
- package/dist/js/hoverBinding.d.ts.map +1 -0
- package/dist/js/ifBinding.d.ts +12 -0
- package/dist/js/ifBinding.d.ts.map +1 -0
- package/dist/js/index.d.ts +10 -0
- package/dist/js/index.d.ts.map +1 -0
- package/dist/js/modelBinding.d.ts +12 -0
- package/dist/js/modelBinding.d.ts.map +1 -0
- package/dist/js/postProcess.d.ts +3 -0
- package/dist/js/postProcess.d.ts.map +1 -0
- package/dist/js/pubSub.d.ts +11 -0
- package/dist/js/pubSub.d.ts.map +1 -0
- package/dist/js/reactiveProxy.d.ts +28 -0
- package/dist/js/reactiveProxy.d.ts.map +1 -0
- package/dist/js/renderForOfBinding.d.ts +8 -0
- package/dist/js/renderForOfBinding.d.ts.map +1 -0
- package/dist/js/renderIfBinding.d.ts +22 -0
- package/dist/js/renderIfBinding.d.ts.map +1 -0
- package/dist/js/renderIteration.d.ts +16 -0
- package/dist/js/renderIteration.d.ts.map +1 -0
- package/dist/js/renderTemplate.d.ts +14 -0
- package/dist/js/renderTemplate.d.ts.map +1 -0
- package/dist/js/renderTemplatesBinding.d.ts +19 -0
- package/dist/js/renderTemplatesBinding.d.ts.map +1 -0
- package/dist/js/showBinding.d.ts +13 -0
- package/dist/js/showBinding.d.ts.map +1 -0
- package/dist/js/switchBinding.d.ts +13 -0
- package/dist/js/switchBinding.d.ts.map +1 -0
- package/dist/js/textBinding.d.ts +13 -0
- package/dist/js/textBinding.d.ts.map +1 -0
- package/dist/js/types/_escape.d.ts +14 -0
- package/dist/js/types/_escape.d.ts.map +1 -0
- package/dist/js/types/applyBinding.d.ts +11 -0
- package/dist/js/types/applyBinding.d.ts.map +1 -0
- package/dist/js/types/attrBinding.d.ts +12 -0
- package/dist/js/types/attrBinding.d.ts.map +1 -0
- package/dist/js/types/binder.d.ts +67 -0
- package/dist/js/types/binder.d.ts.map +1 -0
- package/dist/js/types/changeBinding.d.ts +19 -0
- package/dist/js/types/changeBinding.d.ts.map +1 -0
- package/dist/js/types/commentWrapper.d.ts +39 -0
- package/dist/js/types/commentWrapper.d.ts.map +1 -0
- package/dist/js/types/config.d.ts +55 -0
- package/dist/js/types/config.d.ts.map +1 -0
- package/dist/js/types/createBindingOption.d.ts +32 -0
- package/dist/js/types/createBindingOption.d.ts.map +1 -0
- package/dist/js/types/createEventBinding.d.ts +10 -0
- package/dist/js/types/createEventBinding.d.ts.map +1 -0
- package/dist/js/types/cssBinding.d.ts +15 -0
- package/dist/js/types/cssBinding.d.ts.map +1 -0
- package/dist/js/types/domWalker.d.ts +9 -0
- package/dist/js/types/domWalker.d.ts.map +1 -0
- package/dist/js/types/forOfBinding.d.ts +12 -0
- package/dist/js/types/forOfBinding.d.ts.map +1 -0
- package/dist/js/types/hoverBinding.d.ts +13 -0
- package/dist/js/types/hoverBinding.d.ts.map +1 -0
- package/dist/js/types/ifBinding.d.ts +12 -0
- package/dist/js/types/ifBinding.d.ts.map +1 -0
- package/dist/js/types/index.d.ts +10 -0
- package/dist/js/types/index.d.ts.map +1 -0
- package/dist/js/types/modelBinding.d.ts +12 -0
- package/dist/js/types/modelBinding.d.ts.map +1 -0
- package/dist/js/types/postProcess.d.ts +3 -0
- package/dist/js/types/postProcess.d.ts.map +1 -0
- package/dist/js/types/pubSub.d.ts +11 -0
- package/dist/js/types/pubSub.d.ts.map +1 -0
- package/dist/js/types/reactiveProxy.d.ts +28 -0
- package/dist/js/types/reactiveProxy.d.ts.map +1 -0
- package/dist/js/types/renderForOfBinding.d.ts +8 -0
- package/dist/js/types/renderForOfBinding.d.ts.map +1 -0
- package/dist/js/types/renderIfBinding.d.ts +22 -0
- package/dist/js/types/renderIfBinding.d.ts.map +1 -0
- package/dist/js/types/renderIteration.d.ts +16 -0
- package/dist/js/types/renderIteration.d.ts.map +1 -0
- package/dist/js/types/renderTemplate.d.ts +14 -0
- package/dist/js/types/renderTemplate.d.ts.map +1 -0
- package/dist/js/types/renderTemplatesBinding.d.ts +19 -0
- package/dist/js/types/renderTemplatesBinding.d.ts.map +1 -0
- package/dist/js/types/showBinding.d.ts +13 -0
- package/dist/js/types/showBinding.d.ts.map +1 -0
- package/dist/js/types/switchBinding.d.ts +13 -0
- package/dist/js/types/switchBinding.d.ts.map +1 -0
- package/dist/js/types/textBinding.d.ts +13 -0
- package/dist/js/types/textBinding.d.ts.map +1 -0
- package/dist/js/types/types.d.ts +111 -0
- package/dist/js/types/types.d.ts.map +1 -0
- package/dist/js/types/util.d.ts +119 -0
- package/dist/js/types/util.d.ts.map +1 -0
- package/dist/js/types.d.ts +111 -0
- package/dist/js/types.d.ts.map +1 -0
- package/dist/js/util.d.ts +119 -0
- package/dist/js/util.d.ts.map +1 -0
- package/eslint.config.js +124 -0
- package/examples/DBMONSTER_COMPARISON.md +123 -0
- package/examples/afterRenderDemo.html +119 -0
- package/examples/bootstrap/css/animate.css +1579 -1579
- package/examples/bootstrap/css/bootstrap.min.css +6 -6
- package/examples/bootstrap/css/homeservices.css +378 -390
- package/examples/bootstrap/css/open-iconic.css +511 -511
- package/examples/bootstrap/fonts/open-iconic.svg +543 -543
- package/examples/bootstrap/js/compMessageDialog.js +20 -19
- package/examples/bootstrap/js/compSearchBar.js +12 -19
- package/examples/bootstrap/js/compSearchResults.js +50 -46
- package/examples/bootstrap/js/featureAdsResult.json +65 -65
- package/examples/bootstrap/js/searchResult.json +57 -57
- package/examples/bootstrap.html +343 -332
- package/examples/css/baseTodo.css +141 -141
- package/examples/css/dbMonsterStyles.css +27 -27
- package/examples/css/indexTodo.css +374 -374
- package/examples/dbmonsterForOfReactive.html +40 -0
- package/examples/dbmonsterReact.html +19 -0
- package/examples/forOfBindingSimpleDebug.html +45 -0
- package/examples/form.html +20 -4
- package/examples/globalConfig.html +131 -0
- package/examples/js/afterRenderDemo.js +190 -0
- package/examples/js/appTodo.js +46 -46
- package/examples/js/attrBindingDemo.js +2 -2
- package/examples/js/dbMonApp.js +24 -26
- package/examples/js/dbMonAppReact.jsx +79 -0
- package/examples/js/dbMonAppReactive.js +28 -0
- package/examples/js/fiberDemo.js +4 -4
- package/examples/js/filtersDemo.js +8 -8
- package/examples/js/forOfDemo.js +7 -9
- package/examples/js/forOfDemoComplex.js +44 -17
- package/examples/js/form.js +44 -12
- package/examples/js/globalConfig.js +117 -0
- package/examples/js/ifBindingDemo.js +16 -16
- package/examples/js/reactiveDemo.js +119 -0
- package/examples/js/switchBindingDemo.js +8 -8
- package/examples/react-dbmonster/dist/bundle.js +43 -0
- package/examples/react-dbmonster/package-lock.json +537 -0
- package/examples/react-dbmonster/package.json +16 -0
- package/examples/react-dbmonster/src/index.jsx +80 -0
- package/examples/reactiveDemo.html +127 -0
- package/examples/refreshRateTest.html +75 -75
- package/index.html +841 -0
- package/package.json +31 -34
- package/rollup.config.js +79 -36
- package/src/{_escape.js → _escape.ts} +19 -17
- package/src/applyBinding.ts +179 -0
- package/src/{attrBinding.js → attrBinding.ts} +14 -13
- package/src/binder.ts +289 -0
- package/src/changeBinding.ts +93 -0
- package/src/{commentWrapper.js → commentWrapper.ts} +33 -30
- package/src/config.ts +107 -0
- package/src/createBindingOption.ts +91 -0
- package/src/createEventBinding.ts +88 -0
- package/src/{cssBinding.js → cssBinding.ts} +13 -11
- package/src/{domWalker.js → domWalker.ts} +44 -30
- package/src/{forOfBinding.js → forOfBinding.ts} +4 -3
- package/src/hoverBinding.ts +84 -0
- package/src/{ifBinding.js → ifBinding.ts} +14 -12
- package/src/index.ts +53 -0
- package/src/{modelBinding.js → modelBinding.ts} +11 -9
- package/src/postProcess.ts +22 -0
- package/src/{pubSub.js → pubSub.ts} +24 -15
- package/src/reactiveProxy.ts +285 -0
- package/src/{renderForOfBinding.js → renderForOfBinding.ts} +55 -33
- package/src/{renderIfBinding.js → renderIfBinding.ts} +45 -20
- package/src/renderIteration.ts +53 -0
- package/src/renderTemplate.ts +165 -0
- package/src/renderTemplatesBinding.ts +73 -0
- package/src/{showBinding.js → showBinding.ts} +4 -3
- package/src/{switchBinding.js → switchBinding.ts} +18 -15
- package/src/{textBinding.js → textBinding.ts} +5 -4
- package/src/types.ts +124 -0
- package/src/util.ts +810 -0
- package/test/css/reporter.css +9 -9
- package/test/fixtures/dataBindBootstrap.html +2 -2
- package/test/fixtures/formBindings.html +9 -1
- package/test/globals.d.ts +19 -0
- package/test/helpers/testHelper.js +46 -11
- package/test/mocks/featureAdsResult.json +65 -65
- package/test/mocks/searchResult.json +57 -57
- package/test/specs/{attrBinding.spec.js → attrBinding.spec.ts} +103 -106
- package/test/specs/{binder.spec.js → binder.spec.ts} +29 -27
- package/test/specs/blurBinding.spec.ts +60 -0
- package/test/specs/chainableUse.spec.ts +125 -0
- package/test/specs/clickBinding.spec.ts +194 -0
- package/test/specs/{cssBinding.spec.js → cssBinding.spec.ts} +72 -79
- package/test/specs/{dataBindBootstrap.spec.js → dataBindBootstrap.spec.ts} +332 -313
- package/test/specs/{filter.spec.js → filter.spec.ts} +75 -76
- package/test/specs/{forOfBinding.spec.js → forOfBinding.spec.ts} +208 -219
- package/test/specs/formBinding.spec.ts +272 -0
- package/test/specs/ifBinding.spec.ts +165 -0
- package/test/specs/{nestedComponent.spec.js → nestedComponent.spec.ts} +88 -88
- package/test/specs/reactiveProxy.spec.ts +465 -0
- package/test/specs/{showBinding.spec.js → showBinding.spec.ts} +148 -149
- package/test/specs/{switchBinding.spec.js → switchBinding.spec.ts} +172 -173
- package/test/specs/templateBinding.spec.ts +273 -0
- package/test/specs/{textBinding.spec.js → textBinding.spec.ts} +47 -48
- package/test/tsconfig.json +31 -0
- package/test-output.txt +200 -0
- package/test-reactive.html +224 -0
- package/tsconfig.json +28 -0
- package/vendors/lodash.custom.js +4577 -4577
- package/vendors/lodash.custom.min.js +45 -45
- package/vitest.config.js +27 -0
- package/.eslintrc.js +0 -1
- package/.grunt/grunt-contrib-jasmine/boot.js +0 -161
- package/.grunt/grunt-contrib-jasmine/dist/js/dataBind.js +0 -9
- package/.grunt/grunt-contrib-jasmine/grunt-template-jasmine-istanbul/reporter.js +0 -23
- package/.grunt/grunt-contrib-jasmine/jasmine-html.js +0 -853
- package/.grunt/grunt-contrib-jasmine/jasmine.css +0 -271
- package/.grunt/grunt-contrib-jasmine/jasmine.js +0 -9761
- package/.grunt/grunt-contrib-jasmine/jasmine_favicon.png +0 -0
- package/.grunt/grunt-contrib-jasmine/json2.js +0 -489
- package/.grunt/grunt-contrib-jasmine/reporter.js +0 -107
- package/coverage/coverage.json +0 -1
- package/coverage/lcov/lcov-report/base.css +0 -213
- package/coverage/lcov/lcov-report/index.html +0 -93
- package/coverage/lcov/lcov-report/js/dataBind.js.html +0 -6596
- package/coverage/lcov/lcov-report/js/index.html +0 -93
- package/coverage/lcov/lcov-report/prettify.css +0 -1
- package/coverage/lcov/lcov-report/prettify.js +0 -1
- package/coverage/lcov/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov/lcov-report/sorter.js +0 -158
- package/coverage/lcov/lcov.info +0 -1991
- package/eslintrc.json +0 -40
- package/examples/bootstrap/js/bootstrap.min.js +0 -6
- package/examples/bootstrap/js/popper.min.js +0 -5
- package/examples/bootstrap/js/searchSuggestion.js +0 -58
- package/examples/bootstrap/js/typeahead.jquery.js +0 -1538
- package/gruntfile.js +0 -92
- package/gulpfile.js +0 -32
- package/src/binder.js +0 -422
- package/src/changeBinding.js +0 -57
- package/src/config.js +0 -65
- package/src/createBindingOption.js +0 -66
- package/src/createEventBinding.js +0 -46
- package/src/eventSystem.js +0 -46
- package/src/hoverBinding.js +0 -57
- package/src/index.js +0 -26
- package/src/renderTemplate.js +0 -128
- package/src/util.js +0 -648
- package/test/specs/blurBinding.spec.js +0 -57
- package/test/specs/formBinding.spec.js +0 -292
- package/test/specs/ifBinding.spec.js +0 -169
- package/test/specs/templateBinding.spec.js +0 -117
- package/vendors/jasmine-jquery.js +0 -841
- package/vendors/jquery-3.2.1.min.js +0 -4
package/REACTIVE_MODE.md
ADDED
|
@@ -0,0 +1,553 @@
|
|
|
1
|
+
# Reactive Rendering Mode
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
DataBind.js uses **automatic reactive rendering by default** using JavaScript Proxies. This modern feature eliminates the need for manual `render()` calls, making your code cleaner and less error-prone.
|
|
6
|
+
|
|
7
|
+
**Reactive mode is now the default!** No configuration needed.
|
|
8
|
+
|
|
9
|
+
## Quick Start
|
|
10
|
+
|
|
11
|
+
### Default (Reactive Mode) ✨
|
|
12
|
+
|
|
13
|
+
```javascript
|
|
14
|
+
let viewModel = {
|
|
15
|
+
name: 'John',
|
|
16
|
+
items: [1, 2, 3]
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
// Reactive mode is the default!
|
|
20
|
+
// The viewModel is automatically wrapped in a reactive Proxy
|
|
21
|
+
const component = dataBind.init(element, viewModel);
|
|
22
|
+
|
|
23
|
+
// Access the reactive viewModel from the component
|
|
24
|
+
viewModel = component.viewModel;
|
|
25
|
+
|
|
26
|
+
// Just modify data - renders automatically!
|
|
27
|
+
viewModel.name = 'Jane';
|
|
28
|
+
viewModel.items.push(4);
|
|
29
|
+
|
|
30
|
+
// NO render() call needed - automatic! ✨
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Or use the component's viewModel directly:
|
|
34
|
+
|
|
35
|
+
```javascript
|
|
36
|
+
const viewModel = {
|
|
37
|
+
name: 'John',
|
|
38
|
+
items: [1, 2, 3]
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const component = dataBind.init(element, viewModel);
|
|
42
|
+
|
|
43
|
+
// Use component.viewModel for reactive updates
|
|
44
|
+
component.viewModel.name = 'Jane';
|
|
45
|
+
component.viewModel.items.push(4);
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Manual Mode (Opt-out)
|
|
49
|
+
|
|
50
|
+
```javascript
|
|
51
|
+
const viewModel = {
|
|
52
|
+
name: 'John',
|
|
53
|
+
items: [1, 2, 3]
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
// Explicitly disable reactive mode if needed
|
|
57
|
+
const component = dataBind.init(element, viewModel, {
|
|
58
|
+
reactive: false // Opt-out of reactive mode
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// Must call render manually
|
|
62
|
+
viewModel.name = 'Jane';
|
|
63
|
+
viewModel.items.push(4);
|
|
64
|
+
component.render();
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Features
|
|
68
|
+
|
|
69
|
+
### ✅ Automatic Rendering
|
|
70
|
+
|
|
71
|
+
Any change to the viewModel automatically triggers a render:
|
|
72
|
+
|
|
73
|
+
```javascript
|
|
74
|
+
const component = dataBind.init(element, viewModel); // Reactive by default!
|
|
75
|
+
|
|
76
|
+
// All of these trigger automatic renders (using component.viewModel):
|
|
77
|
+
component.viewModel.name = 'New Name';
|
|
78
|
+
component.viewModel.age = 25;
|
|
79
|
+
component.viewModel.items.push('new item');
|
|
80
|
+
component.viewModel.nested.property = 'value';
|
|
81
|
+
delete component.viewModel.oldProperty;
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
**Important:** Always use `component.viewModel` for updates. The original `viewModel` variable points to the unwrapped object and won't trigger renders.
|
|
85
|
+
|
|
86
|
+
### ✅ Deep Reactivity
|
|
87
|
+
|
|
88
|
+
Nested objects and arrays are automatically reactive:
|
|
89
|
+
|
|
90
|
+
```javascript
|
|
91
|
+
const viewModel = {
|
|
92
|
+
user: {
|
|
93
|
+
profile: {
|
|
94
|
+
name: 'John',
|
|
95
|
+
settings: {
|
|
96
|
+
theme: 'dark'
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
todos: [
|
|
101
|
+
{text: 'Task 1', done: false},
|
|
102
|
+
{text: 'Task 2', done: false}
|
|
103
|
+
]
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const component = dataBind.init(element, viewModel, {reactive: true});
|
|
107
|
+
|
|
108
|
+
// All automatically trigger renders:
|
|
109
|
+
component.viewModel.user.profile.name = 'Jane'; // Deep nested
|
|
110
|
+
component.viewModel.user.profile.settings.theme = 'light'; // Very deep nested
|
|
111
|
+
component.viewModel.todos[0].done = true; // Array element property
|
|
112
|
+
component.viewModel.todos.push({text: 'Task 3', done: false}); // Array mutation
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### ✅ Automatic Batching
|
|
116
|
+
|
|
117
|
+
Multiple changes are automatically batched into a single render via `requestAnimationFrame`:
|
|
118
|
+
|
|
119
|
+
```javascript
|
|
120
|
+
const component = dataBind.init(element, viewModel, {reactive: true});
|
|
121
|
+
|
|
122
|
+
// All 5 changes result in only 1 render!
|
|
123
|
+
component.viewModel.name = 'John';
|
|
124
|
+
component.viewModel.age = 30;
|
|
125
|
+
component.viewModel.email = 'john@example.com';
|
|
126
|
+
component.viewModel.items.push('A');
|
|
127
|
+
component.viewModel.items.push('B');
|
|
128
|
+
|
|
129
|
+
// Debouncing via RAF ensures optimal performance
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### ✅ Array Methods Support
|
|
133
|
+
|
|
134
|
+
All array mutating methods are automatically detected:
|
|
135
|
+
|
|
136
|
+
```javascript
|
|
137
|
+
const component = dataBind.init(element, viewModel, {reactive: true});
|
|
138
|
+
|
|
139
|
+
// All trigger automatic renders:
|
|
140
|
+
component.viewModel.items.push(4);
|
|
141
|
+
component.viewModel.items.pop();
|
|
142
|
+
component.viewModel.items.shift();
|
|
143
|
+
component.viewModel.items.unshift(0);
|
|
144
|
+
component.viewModel.items.splice(1, 1);
|
|
145
|
+
component.viewModel.items.sort();
|
|
146
|
+
component.viewModel.items.reverse();
|
|
147
|
+
component.viewModel.items.fill(0);
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### ✅ Backward Compatible
|
|
151
|
+
|
|
152
|
+
Reactive mode is **opt-in**. Existing code continues to work without changes:
|
|
153
|
+
|
|
154
|
+
```javascript
|
|
155
|
+
// Manual mode (default) - existing code works as-is
|
|
156
|
+
const component1 = dataBind.init(element, viewModel);
|
|
157
|
+
viewModel.name = 'John';
|
|
158
|
+
component1.render(); // Still need manual render
|
|
159
|
+
|
|
160
|
+
// Reactive mode - new way
|
|
161
|
+
const component2 = dataBind.init(element, viewModel, {reactive: true});
|
|
162
|
+
viewModel.name = 'Jane'; // Automatic render
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## API
|
|
166
|
+
|
|
167
|
+
### Options
|
|
168
|
+
|
|
169
|
+
```typescript
|
|
170
|
+
interface BinderOptions {
|
|
171
|
+
reactive?: boolean; // Enable reactive mode (default: false)
|
|
172
|
+
trackChanges?: boolean; // Track which properties changed (default: false)
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### Usage
|
|
177
|
+
|
|
178
|
+
```javascript
|
|
179
|
+
dataBind.init(element, viewModel, {
|
|
180
|
+
reactive: true, // Enable reactive rendering
|
|
181
|
+
trackChanges: false // Optional: track changed paths (future feature)
|
|
182
|
+
});
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### Accessing Original ViewModel
|
|
186
|
+
|
|
187
|
+
If you need the original non-proxied viewModel:
|
|
188
|
+
|
|
189
|
+
```javascript
|
|
190
|
+
const component = dataBind.init(element, viewModel, {reactive: true});
|
|
191
|
+
|
|
192
|
+
// Access via component
|
|
193
|
+
const original = component.originalViewModel;
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
## Examples
|
|
197
|
+
|
|
198
|
+
### Example 1: Simple Form
|
|
199
|
+
|
|
200
|
+
```html
|
|
201
|
+
<div data-bind-comp="form">
|
|
202
|
+
<p>Name: <span data-bind-text="name"></span></p>
|
|
203
|
+
<p>Email: <span data-bind-text="email"></span></p>
|
|
204
|
+
<button id="updateBtn">Update</button>
|
|
205
|
+
</div>
|
|
206
|
+
|
|
207
|
+
<script>
|
|
208
|
+
const viewModel = {
|
|
209
|
+
name: '',
|
|
210
|
+
email: ''
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
const component = dataBind.init(
|
|
214
|
+
document.querySelector('[data-bind-comp="form"]'),
|
|
215
|
+
viewModel,
|
|
216
|
+
{reactive: true}
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
component.render().then(() => {
|
|
220
|
+
document.getElementById('updateBtn').addEventListener('click', () => {
|
|
221
|
+
component.viewModel.name = 'John Doe';
|
|
222
|
+
component.viewModel.email = 'john@example.com';
|
|
223
|
+
// Automatic render! No manual call needed.
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
</script>
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### Example 2: Todo List
|
|
230
|
+
|
|
231
|
+
```html
|
|
232
|
+
<div data-bind-comp="todos">
|
|
233
|
+
<ul>
|
|
234
|
+
<li data-bind-for="todo of todos">
|
|
235
|
+
<span data-bind-text="todo.text"></span>
|
|
236
|
+
</li>
|
|
237
|
+
</ul>
|
|
238
|
+
<button id="addBtn">Add Todo</button>
|
|
239
|
+
</div>
|
|
240
|
+
|
|
241
|
+
<script>
|
|
242
|
+
const viewModel = {
|
|
243
|
+
todos: [
|
|
244
|
+
{text: 'Learn reactive mode'},
|
|
245
|
+
{text: 'Build awesome apps'}
|
|
246
|
+
]
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
const component = dataBind.init(
|
|
250
|
+
document.querySelector('[data-bind-comp="todos"]'),
|
|
251
|
+
viewModel,
|
|
252
|
+
{reactive: true}
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
component.render().then(() => {
|
|
256
|
+
let counter = 1;
|
|
257
|
+
document.getElementById('addBtn').addEventListener('click', () => {
|
|
258
|
+
component.viewModel.todos.push({
|
|
259
|
+
text: `New todo ${counter++}`
|
|
260
|
+
});
|
|
261
|
+
// Automatic render!
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
</script>
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### Example 3: Real-time Data Updates
|
|
268
|
+
|
|
269
|
+
```javascript
|
|
270
|
+
const viewModel = {
|
|
271
|
+
searchResults: []
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
const component = dataBind.init(element, viewModel, {reactive: true});
|
|
275
|
+
|
|
276
|
+
// Simulate real-time updates
|
|
277
|
+
setInterval(() => {
|
|
278
|
+
component.viewModel.searchResults = fetchLatestData();
|
|
279
|
+
// Automatic render! Perfect for real-time apps.
|
|
280
|
+
}, 1000);
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
## Performance
|
|
284
|
+
|
|
285
|
+
### Benchmarks
|
|
286
|
+
|
|
287
|
+
Reactive mode has **identical performance** to manual mode because:
|
|
288
|
+
|
|
289
|
+
1. Uses the same `debounceRaf` mechanism for batching
|
|
290
|
+
2. Proxies have minimal overhead in modern browsers
|
|
291
|
+
3. Only one render per RAF cycle regardless of changes
|
|
292
|
+
|
|
293
|
+
**Performance comparison** (1000 property changes):
|
|
294
|
+
|
|
295
|
+
| Mode | Renders | Time |
|
|
296
|
+
|------|---------|------|
|
|
297
|
+
| Manual (unbatched) | 1000 | ~500ms |
|
|
298
|
+
| Manual (batched) | 1 | ~16ms |
|
|
299
|
+
| Reactive (automatic) | 1 | ~16ms |
|
|
300
|
+
|
|
301
|
+
Reactive mode automatically achieves optimal batching!
|
|
302
|
+
|
|
303
|
+
### When to Use Reactive Mode
|
|
304
|
+
|
|
305
|
+
**✅ Use Reactive Mode When:**
|
|
306
|
+
- Building interactive UIs with frequent updates
|
|
307
|
+
- Working with real-time data
|
|
308
|
+
- Prototyping and rapid development
|
|
309
|
+
- You want cleaner, more maintainable code
|
|
310
|
+
- Working with complex nested data structures
|
|
311
|
+
|
|
312
|
+
**❌ Stick with Manual Mode When:**
|
|
313
|
+
- You need fine-grained control over rendering
|
|
314
|
+
- Working in environments without Proxy support (IE11)
|
|
315
|
+
- Very large objects with thousands of properties
|
|
316
|
+
- Performance profiling shows Proxy overhead
|
|
317
|
+
|
|
318
|
+
## Browser Support
|
|
319
|
+
|
|
320
|
+
Reactive mode requires **JavaScript Proxy** support:
|
|
321
|
+
|
|
322
|
+
- ✅ Chrome 49+
|
|
323
|
+
- ✅ Firefox 18+
|
|
324
|
+
- ✅ Safari 10+
|
|
325
|
+
- ✅ Edge 12+
|
|
326
|
+
- ✅ Node.js 6+
|
|
327
|
+
- ❌ IE11 (falls back to manual mode with console warning)
|
|
328
|
+
|
|
329
|
+
### Fallback Behavior
|
|
330
|
+
|
|
331
|
+
If Proxy is not supported, reactive mode automatically falls back to manual mode:
|
|
332
|
+
|
|
333
|
+
```javascript
|
|
334
|
+
const component = dataBind.init(element, viewModel, {reactive: true});
|
|
335
|
+
|
|
336
|
+
// In IE11:
|
|
337
|
+
// Console warning: "Reactive mode requires Proxy support. Falling back to manual mode."
|
|
338
|
+
// component.isReactive === false
|
|
339
|
+
// Manual render() calls required
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
## Migration Guide
|
|
343
|
+
|
|
344
|
+
### Migrating Existing Code
|
|
345
|
+
|
|
346
|
+
1. **Add reactive option** to `dataBind.init()`:
|
|
347
|
+
```javascript
|
|
348
|
+
// Before
|
|
349
|
+
const component = dataBind.init(element, viewModel);
|
|
350
|
+
|
|
351
|
+
// After
|
|
352
|
+
const component = dataBind.init(element, viewModel, {reactive: true});
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
2. **Remove manual render calls**:
|
|
356
|
+
```javascript
|
|
357
|
+
// Before
|
|
358
|
+
viewModel.name = 'John';
|
|
359
|
+
component.render();
|
|
360
|
+
|
|
361
|
+
// After
|
|
362
|
+
viewModel.name = 'John';
|
|
363
|
+
// That's it!
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
3. **Keep render().then()** for callbacks:
|
|
367
|
+
```javascript
|
|
368
|
+
// Still works in reactive mode
|
|
369
|
+
component.render().then(() => {
|
|
370
|
+
console.log('Render complete');
|
|
371
|
+
});
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
### Gradual Migration
|
|
375
|
+
|
|
376
|
+
You can migrate components one at a time:
|
|
377
|
+
|
|
378
|
+
```javascript
|
|
379
|
+
// Component A - still manual
|
|
380
|
+
const componentA = dataBind.init(elementA, viewModelA);
|
|
381
|
+
|
|
382
|
+
// Component B - now reactive
|
|
383
|
+
const componentB = dataBind.init(elementB, viewModelB, {reactive: true});
|
|
384
|
+
|
|
385
|
+
// Component C - still manual
|
|
386
|
+
const componentC = dataBind.init(elementC, viewModelC);
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
## Advanced Topics
|
|
390
|
+
|
|
391
|
+
### Debugging
|
|
392
|
+
|
|
393
|
+
Check if reactive mode is enabled:
|
|
394
|
+
|
|
395
|
+
```javascript
|
|
396
|
+
const component = dataBind.init(element, viewModel, {reactive: true});
|
|
397
|
+
|
|
398
|
+
console.log(component.isReactive); // true
|
|
399
|
+
console.log(component.originalViewModel); // Access original object
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
### Working with Monitoring
|
|
403
|
+
|
|
404
|
+
If using performance monitoring, you can still hook into renders:
|
|
405
|
+
|
|
406
|
+
```javascript
|
|
407
|
+
const component = dataBind.init(element, viewModel, {reactive: true});
|
|
408
|
+
|
|
409
|
+
setInterval(() => {
|
|
410
|
+
viewModel.data = generateRandomData();
|
|
411
|
+
|
|
412
|
+
// Hook into the automatic render for monitoring
|
|
413
|
+
component.render().then(() => {
|
|
414
|
+
Monitoring.renderRate.ping();
|
|
415
|
+
});
|
|
416
|
+
}, 1000);
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
### Future Features (trackChanges)
|
|
420
|
+
|
|
421
|
+
Future versions will support selective re-rendering based on changed paths:
|
|
422
|
+
|
|
423
|
+
```javascript
|
|
424
|
+
const component = dataBind.init(element, viewModel, {
|
|
425
|
+
reactive: true,
|
|
426
|
+
trackChanges: true // Future: enables smart re-rendering
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
// Future: Only re-render affected parts of DOM
|
|
430
|
+
viewModel.user.name = 'John'; // Only updates elements bound to user.name
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
## Comparison with Other Frameworks
|
|
434
|
+
|
|
435
|
+
### Vue 3
|
|
436
|
+
|
|
437
|
+
DataBind reactive mode uses similar Proxy-based approach:
|
|
438
|
+
|
|
439
|
+
```javascript
|
|
440
|
+
// Vue 3
|
|
441
|
+
const data = reactive({name: 'John'});
|
|
442
|
+
data.name = 'Jane'; // Auto-updates UI
|
|
443
|
+
|
|
444
|
+
// DataBind
|
|
445
|
+
const component = dataBind.init(el, {name: 'John'}, {reactive: true});
|
|
446
|
+
viewModel.name = 'Jane'; // Auto-updates UI
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
### MobX
|
|
450
|
+
|
|
451
|
+
Similar automatic tracking and rendering:
|
|
452
|
+
|
|
453
|
+
```javascript
|
|
454
|
+
// MobX
|
|
455
|
+
const data = observable({name: 'John'});
|
|
456
|
+
autorun(() => render(data));
|
|
457
|
+
data.name = 'Jane'; // Auto re-runs
|
|
458
|
+
|
|
459
|
+
// DataBind
|
|
460
|
+
const component = dataBind.init(el, {name: 'John'}, {reactive: true});
|
|
461
|
+
viewModel.name = 'Jane'; // Auto re-renders
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
## Troubleshooting
|
|
465
|
+
|
|
466
|
+
### Changes Not Detected
|
|
467
|
+
|
|
468
|
+
**Problem**: Changes to viewModel don't trigger renders
|
|
469
|
+
|
|
470
|
+
**Solutions**:
|
|
471
|
+
1. **Most Common Issue**: You're modifying the original viewModel instead of the proxied one!
|
|
472
|
+
```javascript
|
|
473
|
+
// ❌ WRONG - modifying original viewModel
|
|
474
|
+
const viewModel = {name: 'John'};
|
|
475
|
+
const component = dataBind.init(element, viewModel, {reactive: true});
|
|
476
|
+
viewModel.name = 'Jane'; // Won't trigger render!
|
|
477
|
+
|
|
478
|
+
// ✅ CORRECT - modifying proxied viewModel
|
|
479
|
+
const viewModel = {name: 'John'};
|
|
480
|
+
const component = dataBind.init(element, viewModel, {reactive: true});
|
|
481
|
+
component.viewModel.name = 'Jane'; // Will trigger render!
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
2. Ensure reactive mode is enabled:
|
|
485
|
+
```javascript
|
|
486
|
+
dataBind.init(element, viewModel, {reactive: true});
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
3. Check browser supports Proxy:
|
|
490
|
+
```javascript
|
|
491
|
+
console.log(typeof Proxy !== 'undefined');
|
|
492
|
+
console.log(component.isReactive); // Should be true
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
### Performance Issues
|
|
496
|
+
|
|
497
|
+
**Problem**: App feels slow with reactive mode
|
|
498
|
+
|
|
499
|
+
**Solutions**:
|
|
500
|
+
1. Check if you have circular references in viewModel
|
|
501
|
+
2. Avoid very deep nesting (>10 levels)
|
|
502
|
+
3. Consider manual mode for very large objects (>10000 properties)
|
|
503
|
+
|
|
504
|
+
### Multiple Renders
|
|
505
|
+
|
|
506
|
+
**Problem**: Multiple renders triggered unexpectedly
|
|
507
|
+
|
|
508
|
+
**Solution**: This is normal - debouncing ensures only 1 render per RAF cycle. Use browser DevTools to profile actual renders.
|
|
509
|
+
|
|
510
|
+
## FAQs
|
|
511
|
+
|
|
512
|
+
**Q: Does reactive mode impact performance?**
|
|
513
|
+
A: No. It uses the same RAF-based debouncing as manual mode. Proxy overhead is minimal in modern browsers.
|
|
514
|
+
|
|
515
|
+
**Q: Can I mix reactive and manual mode?**
|
|
516
|
+
A: Yes! You can have some components in reactive mode and others in manual mode.
|
|
517
|
+
|
|
518
|
+
**Q: What about IE11 support?**
|
|
519
|
+
A: Reactive mode automatically falls back to manual mode in IE11 with a console warning.
|
|
520
|
+
|
|
521
|
+
**Q: Can I call render() manually in reactive mode?**
|
|
522
|
+
A: Yes! Manual render() calls still work. They're debounced so no performance penalty.
|
|
523
|
+
|
|
524
|
+
**Q: Do I need to change my HTML templates?**
|
|
525
|
+
A: No! Templates work exactly the same in both modes.
|
|
526
|
+
|
|
527
|
+
**Q: Can I disable reactive mode after initialization?**
|
|
528
|
+
A: Currently no. Choose the mode at initialization time.
|
|
529
|
+
|
|
530
|
+
## Resources
|
|
531
|
+
|
|
532
|
+
- [Reactive Demo](examples/reactiveDemo.html) - Interactive comparison of manual vs reactive mode
|
|
533
|
+
- [Performance Demo](examples/forOfBindingComplex.html) - Reactive mode with performance monitoring
|
|
534
|
+
- [Implementation Details](REACTIVE_RENDERING_PROPOSAL.md) - Technical deep dive
|
|
535
|
+
|
|
536
|
+
## Summary
|
|
537
|
+
|
|
538
|
+
Reactive mode brings modern, automatic rendering to DataBind.js:
|
|
539
|
+
|
|
540
|
+
- ✨ **Automatic** - No manual render() calls
|
|
541
|
+
- 🚀 **Fast** - Same performance as manual mode
|
|
542
|
+
- 🔄 **Deep** - Works with nested objects and arrays
|
|
543
|
+
- 📦 **Batched** - Multiple changes = 1 render
|
|
544
|
+
- 🔙 **Compatible** - Opt-in, existing code works
|
|
545
|
+
- 🌐 **Modern** - Proxy-based like Vue 3 and MobX
|
|
546
|
+
|
|
547
|
+
Get started today:
|
|
548
|
+
|
|
549
|
+
```javascript
|
|
550
|
+
dataBind.init(element, viewModel, {reactive: true});
|
|
551
|
+
```
|
|
552
|
+
|
|
553
|
+
Happy coding! 🎉
|