@gogocat/data-bind 1.12.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.
Files changed (271) hide show
  1. package/.editorconfig +14 -14
  2. package/.vscode/launch.json +12 -12
  3. package/CONFIGURATION.md +294 -0
  4. package/REACTIVE_MODE.md +553 -0
  5. package/README.md +266 -829
  6. package/babel.config.json +30 -0
  7. package/dist/js/_escape.d.ts +14 -0
  8. package/dist/js/_escape.d.ts.map +1 -0
  9. package/dist/js/applyBinding.d.ts +11 -0
  10. package/dist/js/applyBinding.d.ts.map +1 -0
  11. package/dist/js/attrBinding.d.ts +12 -0
  12. package/dist/js/attrBinding.d.ts.map +1 -0
  13. package/dist/js/binder.d.ts +67 -0
  14. package/dist/js/binder.d.ts.map +1 -0
  15. package/dist/js/changeBinding.d.ts +19 -0
  16. package/dist/js/changeBinding.d.ts.map +1 -0
  17. package/dist/js/commentWrapper.d.ts +39 -0
  18. package/dist/js/commentWrapper.d.ts.map +1 -0
  19. package/dist/js/config.d.ts +55 -0
  20. package/dist/js/config.d.ts.map +1 -0
  21. package/dist/js/createBindingOption.d.ts +32 -0
  22. package/dist/js/createBindingOption.d.ts.map +1 -0
  23. package/dist/js/createEventBinding.d.ts +10 -0
  24. package/dist/js/createEventBinding.d.ts.map +1 -0
  25. package/dist/js/cssBinding.d.ts +15 -0
  26. package/dist/js/cssBinding.d.ts.map +1 -0
  27. package/dist/js/dataBind.js +2756 -2530
  28. package/dist/js/dataBind.min.js +8 -1
  29. package/dist/js/dataBind.min.js.map +1 -1
  30. package/dist/js/domWalker.d.ts +9 -0
  31. package/dist/js/domWalker.d.ts.map +1 -0
  32. package/dist/js/forOfBinding.d.ts +12 -0
  33. package/dist/js/forOfBinding.d.ts.map +1 -0
  34. package/dist/js/hoverBinding.d.ts +13 -0
  35. package/dist/js/hoverBinding.d.ts.map +1 -0
  36. package/dist/js/ifBinding.d.ts +12 -0
  37. package/dist/js/ifBinding.d.ts.map +1 -0
  38. package/dist/js/index.d.ts +10 -0
  39. package/dist/js/index.d.ts.map +1 -0
  40. package/dist/js/modelBinding.d.ts +12 -0
  41. package/dist/js/modelBinding.d.ts.map +1 -0
  42. package/dist/js/postProcess.d.ts +3 -0
  43. package/dist/js/postProcess.d.ts.map +1 -0
  44. package/dist/js/pubSub.d.ts +11 -0
  45. package/dist/js/pubSub.d.ts.map +1 -0
  46. package/dist/js/reactiveProxy.d.ts +28 -0
  47. package/dist/js/reactiveProxy.d.ts.map +1 -0
  48. package/dist/js/renderForOfBinding.d.ts +8 -0
  49. package/dist/js/renderForOfBinding.d.ts.map +1 -0
  50. package/dist/js/renderIfBinding.d.ts +22 -0
  51. package/dist/js/renderIfBinding.d.ts.map +1 -0
  52. package/dist/js/renderIteration.d.ts +16 -0
  53. package/dist/js/renderIteration.d.ts.map +1 -0
  54. package/dist/js/renderTemplate.d.ts +14 -0
  55. package/dist/js/renderTemplate.d.ts.map +1 -0
  56. package/dist/js/renderTemplatesBinding.d.ts +19 -0
  57. package/dist/js/renderTemplatesBinding.d.ts.map +1 -0
  58. package/dist/js/showBinding.d.ts +13 -0
  59. package/dist/js/showBinding.d.ts.map +1 -0
  60. package/dist/js/switchBinding.d.ts +13 -0
  61. package/dist/js/switchBinding.d.ts.map +1 -0
  62. package/dist/js/textBinding.d.ts +13 -0
  63. package/dist/js/textBinding.d.ts.map +1 -0
  64. package/dist/js/types/_escape.d.ts +14 -0
  65. package/dist/js/types/_escape.d.ts.map +1 -0
  66. package/dist/js/types/applyBinding.d.ts +11 -0
  67. package/dist/js/types/applyBinding.d.ts.map +1 -0
  68. package/dist/js/types/attrBinding.d.ts +12 -0
  69. package/dist/js/types/attrBinding.d.ts.map +1 -0
  70. package/dist/js/types/binder.d.ts +67 -0
  71. package/dist/js/types/binder.d.ts.map +1 -0
  72. package/dist/js/types/changeBinding.d.ts +19 -0
  73. package/dist/js/types/changeBinding.d.ts.map +1 -0
  74. package/dist/js/types/commentWrapper.d.ts +39 -0
  75. package/dist/js/types/commentWrapper.d.ts.map +1 -0
  76. package/dist/js/types/config.d.ts +55 -0
  77. package/dist/js/types/config.d.ts.map +1 -0
  78. package/dist/js/types/createBindingOption.d.ts +32 -0
  79. package/dist/js/types/createBindingOption.d.ts.map +1 -0
  80. package/dist/js/types/createEventBinding.d.ts +10 -0
  81. package/dist/js/types/createEventBinding.d.ts.map +1 -0
  82. package/dist/js/types/cssBinding.d.ts +15 -0
  83. package/dist/js/types/cssBinding.d.ts.map +1 -0
  84. package/dist/js/types/domWalker.d.ts +9 -0
  85. package/dist/js/types/domWalker.d.ts.map +1 -0
  86. package/dist/js/types/forOfBinding.d.ts +12 -0
  87. package/dist/js/types/forOfBinding.d.ts.map +1 -0
  88. package/dist/js/types/hoverBinding.d.ts +13 -0
  89. package/dist/js/types/hoverBinding.d.ts.map +1 -0
  90. package/dist/js/types/ifBinding.d.ts +12 -0
  91. package/dist/js/types/ifBinding.d.ts.map +1 -0
  92. package/dist/js/types/index.d.ts +10 -0
  93. package/dist/js/types/index.d.ts.map +1 -0
  94. package/dist/js/types/modelBinding.d.ts +12 -0
  95. package/dist/js/types/modelBinding.d.ts.map +1 -0
  96. package/dist/js/types/postProcess.d.ts +3 -0
  97. package/dist/js/types/postProcess.d.ts.map +1 -0
  98. package/dist/js/types/pubSub.d.ts +11 -0
  99. package/dist/js/types/pubSub.d.ts.map +1 -0
  100. package/dist/js/types/reactiveProxy.d.ts +28 -0
  101. package/dist/js/types/reactiveProxy.d.ts.map +1 -0
  102. package/dist/js/types/renderForOfBinding.d.ts +8 -0
  103. package/dist/js/types/renderForOfBinding.d.ts.map +1 -0
  104. package/dist/js/types/renderIfBinding.d.ts +22 -0
  105. package/dist/js/types/renderIfBinding.d.ts.map +1 -0
  106. package/dist/js/types/renderIteration.d.ts +16 -0
  107. package/dist/js/types/renderIteration.d.ts.map +1 -0
  108. package/dist/js/types/renderTemplate.d.ts +14 -0
  109. package/dist/js/types/renderTemplate.d.ts.map +1 -0
  110. package/dist/js/types/renderTemplatesBinding.d.ts +19 -0
  111. package/dist/js/types/renderTemplatesBinding.d.ts.map +1 -0
  112. package/dist/js/types/showBinding.d.ts +13 -0
  113. package/dist/js/types/showBinding.d.ts.map +1 -0
  114. package/dist/js/types/switchBinding.d.ts +13 -0
  115. package/dist/js/types/switchBinding.d.ts.map +1 -0
  116. package/dist/js/types/textBinding.d.ts +13 -0
  117. package/dist/js/types/textBinding.d.ts.map +1 -0
  118. package/dist/js/types/types.d.ts +111 -0
  119. package/dist/js/types/types.d.ts.map +1 -0
  120. package/dist/js/types/util.d.ts +119 -0
  121. package/dist/js/types/util.d.ts.map +1 -0
  122. package/dist/js/types.d.ts +111 -0
  123. package/dist/js/types.d.ts.map +1 -0
  124. package/dist/js/util.d.ts +119 -0
  125. package/dist/js/util.d.ts.map +1 -0
  126. package/eslint.config.js +124 -0
  127. package/examples/DBMONSTER_COMPARISON.md +123 -0
  128. package/examples/afterRenderDemo.html +119 -0
  129. package/examples/bootstrap/css/animate.css +1579 -1579
  130. package/examples/bootstrap/css/bootstrap.min.css +6 -6
  131. package/examples/bootstrap/css/homeservices.css +378 -390
  132. package/examples/bootstrap/css/open-iconic.css +511 -511
  133. package/examples/bootstrap/fonts/open-iconic.svg +543 -543
  134. package/examples/bootstrap/js/compMessageDialog.js +20 -19
  135. package/examples/bootstrap/js/compSearchBar.js +12 -19
  136. package/examples/bootstrap/js/compSearchResults.js +50 -46
  137. package/examples/bootstrap/js/featureAdsResult.json +65 -65
  138. package/examples/bootstrap/js/searchResult.json +57 -57
  139. package/examples/bootstrap.html +343 -332
  140. package/examples/css/baseTodo.css +141 -141
  141. package/examples/css/dbMonsterStyles.css +27 -27
  142. package/examples/css/indexTodo.css +374 -374
  143. package/examples/dbmonsterForOfReactive.html +40 -0
  144. package/examples/dbmonsterReact.html +19 -0
  145. package/examples/forOfBindingSimpleDebug.html +45 -0
  146. package/examples/globalConfig.html +131 -0
  147. package/examples/js/afterRenderDemo.js +190 -0
  148. package/examples/js/appTodo.js +46 -46
  149. package/examples/js/attrBindingDemo.js +2 -2
  150. package/examples/js/dbMonApp.js +24 -26
  151. package/examples/js/dbMonAppReact.jsx +79 -0
  152. package/examples/js/dbMonAppReactive.js +28 -0
  153. package/examples/js/fiberDemo.js +4 -4
  154. package/examples/js/filtersDemo.js +8 -8
  155. package/examples/js/forOfDemo.js +7 -9
  156. package/examples/js/forOfDemoComplex.js +44 -17
  157. package/examples/js/form.js +14 -14
  158. package/examples/js/globalConfig.js +117 -0
  159. package/examples/js/ifBindingDemo.js +16 -16
  160. package/examples/js/reactiveDemo.js +119 -0
  161. package/examples/js/switchBindingDemo.js +8 -8
  162. package/examples/react-dbmonster/dist/bundle.js +43 -0
  163. package/examples/react-dbmonster/package-lock.json +537 -0
  164. package/examples/react-dbmonster/package.json +16 -0
  165. package/examples/react-dbmonster/src/index.jsx +80 -0
  166. package/examples/reactiveDemo.html +127 -0
  167. package/examples/refreshRateTest.html +75 -75
  168. package/index.html +841 -0
  169. package/package.json +31 -34
  170. package/rollup.config.js +79 -36
  171. package/src/{_escape.js → _escape.ts} +19 -17
  172. package/src/{applyBinding.js → applyBinding.ts} +27 -18
  173. package/src/{attrBinding.js → attrBinding.ts} +14 -13
  174. package/src/{binder.js → binder.ts} +289 -181
  175. package/src/changeBinding.ts +93 -0
  176. package/src/{commentWrapper.js → commentWrapper.ts} +33 -30
  177. package/src/config.ts +107 -0
  178. package/src/{createBindingOption.js → createBindingOption.ts} +39 -15
  179. package/src/createEventBinding.ts +88 -0
  180. package/src/{cssBinding.js → cssBinding.ts} +13 -11
  181. package/src/{domWalker.js → domWalker.ts} +44 -30
  182. package/src/{forOfBinding.js → forOfBinding.ts} +4 -3
  183. package/src/hoverBinding.ts +84 -0
  184. package/src/{ifBinding.js → ifBinding.ts} +14 -12
  185. package/src/index.ts +53 -0
  186. package/src/{modelBinding.js → modelBinding.ts} +11 -9
  187. package/src/{postProcess.js → postProcess.ts} +6 -4
  188. package/src/{pubSub.js → pubSub.ts} +24 -21
  189. package/src/reactiveProxy.ts +285 -0
  190. package/src/{renderForOfBinding.js → renderForOfBinding.ts} +54 -32
  191. package/src/{renderIfBinding.js → renderIfBinding.ts} +41 -19
  192. package/src/{renderIteration.js → renderIteration.ts} +24 -8
  193. package/src/renderTemplate.ts +165 -0
  194. package/src/renderTemplatesBinding.ts +73 -0
  195. package/src/{showBinding.js → showBinding.ts} +4 -3
  196. package/src/{switchBinding.js → switchBinding.ts} +18 -15
  197. package/src/{textBinding.js → textBinding.ts} +5 -4
  198. package/src/types.ts +124 -0
  199. package/src/util.ts +810 -0
  200. package/test/css/reporter.css +9 -9
  201. package/test/globals.d.ts +19 -0
  202. package/test/helpers/testHelper.js +46 -11
  203. package/test/mocks/featureAdsResult.json +65 -65
  204. package/test/mocks/searchResult.json +57 -57
  205. package/test/specs/{attrBinding.spec.js → attrBinding.spec.ts} +103 -106
  206. package/test/specs/{binder.spec.js → binder.spec.ts} +29 -27
  207. package/test/specs/blurBinding.spec.ts +60 -0
  208. package/test/specs/chainableUse.spec.ts +125 -0
  209. package/test/specs/clickBinding.spec.ts +194 -0
  210. package/test/specs/{cssBinding.spec.js → cssBinding.spec.ts} +72 -79
  211. package/test/specs/{dataBindBootstrap.spec.js → dataBindBootstrap.spec.ts} +332 -313
  212. package/test/specs/{filter.spec.js → filter.spec.ts} +75 -76
  213. package/test/specs/{forOfBinding.spec.js → forOfBinding.spec.ts} +208 -219
  214. package/test/specs/formBinding.spec.ts +272 -0
  215. package/test/specs/ifBinding.spec.ts +165 -0
  216. package/test/specs/{nestedComponent.spec.js → nestedComponent.spec.ts} +88 -88
  217. package/test/specs/reactiveProxy.spec.ts +465 -0
  218. package/test/specs/{showBinding.spec.js → showBinding.spec.ts} +148 -149
  219. package/test/specs/{switchBinding.spec.js → switchBinding.spec.ts} +172 -173
  220. package/test/specs/templateBinding.spec.ts +273 -0
  221. package/test/specs/{textBinding.spec.js → textBinding.spec.ts} +47 -48
  222. package/test/tsconfig.json +31 -0
  223. package/test-output.txt +200 -0
  224. package/test-reactive.html +224 -0
  225. package/tsconfig.json +28 -0
  226. package/vendors/lodash.custom.js +4577 -4577
  227. package/vendors/lodash.custom.min.js +45 -45
  228. package/vitest.config.js +27 -0
  229. package/.eslintrc.js +0 -1
  230. package/.grunt/grunt-contrib-jasmine/boot.js +0 -161
  231. package/.grunt/grunt-contrib-jasmine/dist/js/dataBind.js +0 -9
  232. package/.grunt/grunt-contrib-jasmine/grunt-template-jasmine-istanbul/reporter.js +0 -23
  233. package/.grunt/grunt-contrib-jasmine/jasmine-html.js +0 -853
  234. package/.grunt/grunt-contrib-jasmine/jasmine.css +0 -271
  235. package/.grunt/grunt-contrib-jasmine/jasmine.js +0 -9761
  236. package/.grunt/grunt-contrib-jasmine/jasmine_favicon.png +0 -0
  237. package/.grunt/grunt-contrib-jasmine/json2.js +0 -489
  238. package/.grunt/grunt-contrib-jasmine/reporter.js +0 -107
  239. package/coverage/coverage.json +0 -1
  240. package/coverage/lcov/lcov-report/base.css +0 -213
  241. package/coverage/lcov/lcov-report/index.html +0 -93
  242. package/coverage/lcov/lcov-report/js/dataBind.js.html +0 -6596
  243. package/coverage/lcov/lcov-report/js/index.html +0 -93
  244. package/coverage/lcov/lcov-report/prettify.css +0 -1
  245. package/coverage/lcov/lcov-report/prettify.js +0 -1
  246. package/coverage/lcov/lcov-report/sort-arrow-sprite.png +0 -0
  247. package/coverage/lcov/lcov-report/sorter.js +0 -158
  248. package/coverage/lcov/lcov.info +0 -1991
  249. package/eslintrc.json +0 -40
  250. package/examples/bootstrap/js/bootstrap.min.js +0 -6
  251. package/examples/bootstrap/js/popper.min.js +0 -5
  252. package/examples/bootstrap/js/searchSuggestion.js +0 -58
  253. package/examples/bootstrap/js/typeahead.jquery.js +0 -1538
  254. package/gruntfile.js +0 -92
  255. package/gulpfile.js +0 -32
  256. package/src/applyBindingExport.js +0 -5
  257. package/src/changeBinding.js +0 -63
  258. package/src/config.js +0 -66
  259. package/src/createEventBinding.js +0 -46
  260. package/src/eventSystem.js +0 -46
  261. package/src/hoverBinding.js +0 -57
  262. package/src/index.js +0 -26
  263. package/src/renderTemplate.js +0 -128
  264. package/src/renderTemplatesBinding.js +0 -44
  265. package/src/util.js +0 -648
  266. package/test/specs/blurBinding.spec.js +0 -57
  267. package/test/specs/formBinding.spec.js +0 -316
  268. package/test/specs/ifBinding.spec.js +0 -169
  269. package/test/specs/templateBinding.spec.js +0 -117
  270. package/vendors/jasmine-jquery.js +0 -841
  271. package/vendors/jquery-3.2.1.min.js +0 -4
@@ -1,181 +1,289 @@
1
- import * as config from './config';
2
- import {debounceRaf} from './util';
3
- import createBindingCache from './domWalker';
4
- import createBindingOption from './createBindingOption';
5
- import applyBinding from './applyBinding';
6
- import renderTemplatesBinding from './renderTemplatesBinding';
7
- import postProcess from './postProcess';
8
- import * as pubSub from './pubSub';
9
-
10
- let compIdIndex = 0;
11
-
12
- class Binder {
13
- constructor($rootElement, viewModel, bindingAttrs) {
14
- if (!$rootElement || $rootElement.nodeType !== 1 || viewModel === null || typeof viewModel !== 'object') {
15
- throw new TypeError('$rootElement or viewModel is invalid');
16
- }
17
-
18
- this.initRendered = false;
19
-
20
- this.compId = compIdIndex += 1;
21
-
22
- this.$rootElement = $rootElement;
23
-
24
- this.viewModel = viewModel;
25
-
26
- this.bindingAttrs = bindingAttrs;
27
-
28
- this.render = debounceRaf(this.render, this);
29
-
30
- this.isServerRendered = this.$rootElement.getAttribute(config.serverRenderedAttr) !== null;
31
-
32
- // inject instance into viewModel
33
- this.viewModel.APP = this;
34
-
35
- // add $root pointer to viewModel so binding can be refer as $root.something
36
- this.viewModel.$root = this.viewModel;
37
-
38
- // 1st step
39
- // parsView walk the DOM and create binding cache that holds each element's binding details
40
- // this binding cache is like AST for render and update
41
- this.parseView();
42
-
43
- // for jquery user set viewModel referece to $rootElement for easy debug
44
- // otherwise use Expando to attach viewModel to $rootElement
45
- this.$rootElement[config.bindingDataReference.rootDataKey] = this.viewModel;
46
-
47
- return this;
48
- }
49
-
50
- /**
51
- * parseView
52
- * @description
53
- * @return {this}
54
- * traver from $rootElement to find each data-bind-* element
55
- * then apply data binding
56
- */
57
- parseView() {
58
- this.elementCache = createBindingCache({
59
- rootNode: this.$rootElement,
60
- bindingAttrs: this.bindingAttrs,
61
- });
62
-
63
- // updateElementCache if server rendered on init
64
- if (this.isServerRendered && !this.initRendered) {
65
- this.updateElementCache({
66
- templateCache: true,
67
- });
68
- }
69
- return this;
70
- }
71
-
72
- /**
73
- * updateElementCache
74
- * @param {object} opt
75
- * @description call createBindingCache to parse view and generate bindingCache
76
- */
77
- updateElementCache(opt = {}) {
78
- const elementCache = opt.elementCache || this.elementCache;
79
-
80
- if (opt.allCache) {
81
- // walk dom from root element to regenerate elementCache
82
- this.elementCache = createBindingCache({
83
- rootNode: this.$rootElement,
84
- bindingAttrs: this.bindingAttrs,
85
- });
86
- }
87
- // walk from first rendered template node to create/update child bindingCache
88
- if (opt.allCache || opt.templateCache) {
89
- if (elementCache[this.bindingAttrs.tmp] && elementCache[this.bindingAttrs.tmp].length) {
90
- elementCache[this.bindingAttrs.tmp].forEach((cache) => {
91
- // set skipCheck as skipForOfParseFn whenever an node has
92
- // both template and forOf bindings
93
- // then the template bindingCache should be an empty object
94
- let skipForOfParseFn = null;
95
- if (cache.el.hasAttribute(this.bindingAttrs.forOf)) {
96
- skipForOfParseFn = () => {
97
- return true;
98
- };
99
- }
100
- cache.bindingCache = createBindingCache({
101
- rootNode: cache.el,
102
- bindingAttrs: this.bindingAttrs,
103
- skipCheck: skipForOfParseFn,
104
- isRenderedTemplate: opt.isRenderedTemplates,
105
- });
106
- });
107
- }
108
- }
109
- }
110
-
111
- render(opt = {}) {
112
- let updateOption = {};
113
-
114
- if (!this.initRendered) {
115
- // only update eventsBinding if server rendered
116
- if (this.isServerRendered) {
117
- this.$rootElement.removeAttribute(config.serverRenderedAttr);
118
- updateOption = createBindingOption(config.bindingUpdateConditions.serverRendered, opt);
119
- } else {
120
- updateOption = createBindingOption(config.bindingUpdateConditions.init, opt);
121
- }
122
- } else {
123
- // when called again only update visualBinding options
124
- updateOption = createBindingOption('', opt);
125
- }
126
-
127
- // create postProcessQueue before start rendering
128
- this.postProcessQueue = [];
129
-
130
- const renderBindingOption = {
131
- ctx: this,
132
- elementCache: this.elementCache,
133
- updateOption: updateOption,
134
- bindingAttrs: this.bindingAttrs,
135
- viewModel: this.viewModel,
136
- };
137
-
138
- // always render template binding first
139
- // render and apply binding to template(s)
140
- // this is an share function therefore passing 'this' context
141
- renderTemplatesBinding(renderBindingOption);
142
-
143
- // apply bindings to rest of the DOM
144
- applyBinding(renderBindingOption);
145
-
146
- // trigger postProcess
147
- postProcess(this.postProcessQueue);
148
- // clear postProcessQueue
149
- this.postProcessQueue.length = 0;
150
- delete this.postProcessQueue;
151
-
152
- this.initRendered = true;
153
- }
154
-
155
- subscribe(eventName = '', fn) {
156
- pubSub.subscribeEvent(this, eventName, fn);
157
- return this;
158
- }
159
-
160
- subscribeOnce(eventName = '', fn) {
161
- pubSub.subscribeEventOnce(this, eventName, fn);
162
- return this;
163
- }
164
-
165
- unsubscribe(eventName = '') {
166
- pubSub.unsubscribeEvent(this.compId, eventName);
167
- return this;
168
- }
169
-
170
- unsubscribeAll() {
171
- pubSub.unsubscribeAllEvent(this.compId);
172
- return this;
173
- }
174
-
175
- publish(eventName = '', ...args) {
176
- pubSub.publishEvent(eventName, ...args);
177
- return this;
178
- }
179
- }
180
-
181
- export default Binder;
1
+ import * as config from './config';
2
+ import {debounceRaf} from './util';
3
+ import createBindingCache from './domWalker';
4
+ import createBindingOption from './createBindingOption';
5
+ import applyBinding from './applyBinding';
6
+ import renderTemplatesBinding from './renderTemplatesBinding';
7
+ import postProcess from './postProcess';
8
+ import * as pubSub from './pubSub';
9
+ import {createReactiveProxy, isProxySupported} from './reactiveProxy';
10
+ import type {ViewModel, ElementCache, UpdateOption, BindingAttrs, BinderOptions} from './types';
11
+
12
+ let compIdIndex = 0;
13
+
14
+ class Binder {
15
+ [key: string]: unknown;
16
+ public initRendered: boolean;
17
+ public compId: number;
18
+ public $rootElement: HTMLElement;
19
+ public viewModel: ViewModel;
20
+ public bindingAttrs: BindingAttrs;
21
+ public isServerRendered: boolean;
22
+ public elementCache: ElementCache;
23
+ public postProcessQueue: Array<() => void>;
24
+ public render: (opt?: UpdateOption) => void;
25
+ public isReactive: boolean;
26
+ public originalViewModel: ViewModel;
27
+ private afterRenderCallbacks: Array<() => void>;
28
+
29
+ constructor($rootElement: HTMLElement, viewModel: ViewModel, bindingAttrs: BindingAttrs, options: BinderOptions = {}) {
30
+ if (!$rootElement || $rootElement.nodeType !== 1 || viewModel === null || typeof viewModel !== 'object') {
31
+ throw new TypeError('$rootElement or viewModel is invalid');
32
+ }
33
+
34
+ this.initRendered = false;
35
+
36
+ this.compId = compIdIndex += 1;
37
+
38
+ this.$rootElement = $rootElement;
39
+
40
+ this.bindingAttrs = bindingAttrs;
41
+
42
+ this.isServerRendered = this.$rootElement.getAttribute(config.serverRenderedAttr) !== null;
43
+
44
+ // Initialize afterRender callbacks array
45
+ this.afterRenderCallbacks = [];
46
+
47
+ // Initialize render method with debounced version
48
+ this.render = debounceRaf(this._render.bind(this), this) as (opt?: UpdateOption) => void;
49
+
50
+ // Store original viewModel reference
51
+ this.originalViewModel = viewModel;
52
+
53
+ // Reactive mode is controlled by options (defaults merged in index.ts)
54
+ // options.reactive is guaranteed to be defined due to merge in index.ts
55
+ this.isReactive = !!options.reactive;
56
+
57
+ // If reactive mode is enabled, wrap viewModel in proxy
58
+ if (this.isReactive) {
59
+ if (!isProxySupported()) {
60
+ console.warn('Reactive mode requires Proxy support. Falling back to manual mode.');
61
+ this.isReactive = false;
62
+ this.viewModel = viewModel;
63
+ } else {
64
+ this.viewModel = createReactiveProxy(viewModel, {
65
+ onChange: () => this.render(),
66
+ deep: true,
67
+ trackChanges: options.trackChanges,
68
+ });
69
+ }
70
+ } else {
71
+ this.viewModel = viewModel;
72
+ }
73
+
74
+ // inject instance into viewModel
75
+ this.viewModel.APP = this;
76
+
77
+ // add $root pointer to viewModel so binding can be refer as $root.something
78
+ this.viewModel.$root = this.viewModel;
79
+
80
+ // 1st step
81
+ // parsView walk the DOM and create binding cache that holds each element's binding details
82
+ // this binding cache is like AST for render and update
83
+ this.parseView();
84
+
85
+ // for jquery user set viewModel referece to $rootElement for easy debug
86
+ // otherwise use Expando to attach viewModel to $rootElement
87
+ this.$rootElement[config.bindingDataReference.rootDataKey] = this.viewModel;
88
+
89
+ return this;
90
+ }
91
+
92
+ /**
93
+ * parseView
94
+ * @description
95
+ * @return {this}
96
+ * traver from $rootElement to find each data-bind-* element
97
+ * then apply data binding
98
+ */
99
+ public parseView(): this {
100
+ this.elementCache = createBindingCache({
101
+ rootNode: this.$rootElement,
102
+ bindingAttrs: this.bindingAttrs,
103
+ });
104
+
105
+ // updateElementCache if server rendered on init
106
+ if (this.isServerRendered && !this.initRendered) {
107
+ this.updateElementCache({
108
+ templateCache: true,
109
+ });
110
+ }
111
+ return this;
112
+ }
113
+
114
+ /**
115
+ * updateElementCache
116
+ * @param {object} opt
117
+ * @description call createBindingCache to parse view and generate bindingCache
118
+ */
119
+ public updateElementCache(
120
+ opt: {
121
+ allCache?: boolean;
122
+ templateCache?: boolean;
123
+ elementCache?: ElementCache;
124
+ isRenderedTemplates?: boolean;
125
+ } = {},
126
+ ): void {
127
+ const elementCache = opt.elementCache || this.elementCache;
128
+
129
+ if (opt.allCache) {
130
+ // walk dom from root element to regenerate elementCache
131
+ this.elementCache = createBindingCache({
132
+ rootNode: this.$rootElement,
133
+ bindingAttrs: this.bindingAttrs,
134
+ });
135
+ }
136
+ // walk from first rendered template node to create/update child bindingCache
137
+ if (opt.allCache || opt.templateCache) {
138
+ if (elementCache[this.bindingAttrs.tmp] && elementCache[this.bindingAttrs.tmp].length) {
139
+ // Use for loop to handle templates added during rendering
140
+ for (let i = 0; i < elementCache[this.bindingAttrs.tmp].length; i++) {
141
+ const cache = elementCache[this.bindingAttrs.tmp][i];
142
+ // set skipCheck as skipForOfParseFn whenever an node has
143
+ // both template and forOf bindings
144
+ // then the template bindingCache should be an empty object
145
+ let skipForOfParseFn: (() => boolean) | null = null;
146
+ if (cache.el.hasAttribute(this.bindingAttrs.forOf)) {
147
+ skipForOfParseFn = (): boolean => {
148
+ return true;
149
+ };
150
+ }
151
+ cache.bindingCache = createBindingCache({
152
+ rootNode: cache.el,
153
+ bindingAttrs: this.bindingAttrs,
154
+ skipCheck: skipForOfParseFn,
155
+ isRenderedTemplate: opt.isRenderedTemplates,
156
+ });
157
+ }
158
+ }
159
+ }
160
+ }
161
+
162
+ private _render(opt: UpdateOption = {}): void {
163
+ let updateOption: UpdateOption = {};
164
+
165
+ if (!this.initRendered) {
166
+ // only update eventsBinding if server rendered
167
+ if (this.isServerRendered) {
168
+ this.$rootElement.removeAttribute(config.serverRenderedAttr);
169
+ updateOption = createBindingOption(config.bindingUpdateConditions.serverRendered, opt);
170
+ } else {
171
+ updateOption = createBindingOption(config.bindingUpdateConditions.init, opt);
172
+ }
173
+ } else {
174
+ // when called again only update visualBinding options
175
+ updateOption = createBindingOption('', opt);
176
+ }
177
+
178
+ // create postProcessQueue before start rendering
179
+ this.postProcessQueue = [];
180
+
181
+ const renderBindingOption = {
182
+ ctx: this,
183
+ elementCache: this.elementCache,
184
+ updateOption,
185
+ bindingAttrs: this.bindingAttrs,
186
+ viewModel: this.viewModel,
187
+ };
188
+
189
+ // always render template binding first
190
+ // render and apply binding to template(s)
191
+ // this is an share function therefore passing 'this' context
192
+ renderTemplatesBinding(renderBindingOption);
193
+
194
+ // apply bindings to rest of the DOM
195
+ applyBinding(renderBindingOption);
196
+
197
+ // trigger postProcess
198
+ postProcess(this.postProcessQueue);
199
+ // clear postProcessQueue
200
+ this.postProcessQueue.length = 0;
201
+ delete this.postProcessQueue;
202
+
203
+ this.initRendered = true;
204
+
205
+ // Call afterRender callbacks after rendering is fully complete
206
+ this._callAfterRenderCallbacks();
207
+ }
208
+
209
+ /**
210
+ * Call all registered afterRender callbacks
211
+ * Called automatically after each render completes
212
+ */
213
+ private _callAfterRenderCallbacks(): void {
214
+ if (this.afterRenderCallbacks.length > 0) {
215
+ // Clone array to avoid issues if callbacks modify the array
216
+ const callbacks = this.afterRenderCallbacks.slice();
217
+ for (let i = 0, len = callbacks.length; i < len; i += 1) {
218
+ try {
219
+ callbacks[i]();
220
+ } catch (err) {
221
+ console.error('Error in afterRender callback:', err);
222
+ }
223
+ }
224
+ }
225
+ }
226
+
227
+ /**
228
+ * Register a callback to be called after each render completes
229
+ * Useful for reactive mode where renders happen automatically
230
+ * @param callback Function to call after render completes
231
+ * @returns this for chaining
232
+ */
233
+ public afterRender(callback: () => void): this {
234
+ if (typeof callback !== 'function') {
235
+ throw new TypeError('afterRender callback must be a function');
236
+ }
237
+ this.afterRenderCallbacks.push(callback);
238
+ return this;
239
+ }
240
+
241
+ /**
242
+ * Remove a specific afterRender callback
243
+ * @param callback The callback function to remove
244
+ * @returns this for chaining
245
+ */
246
+ public removeAfterRender(callback: () => void): this {
247
+ const index = this.afterRenderCallbacks.indexOf(callback);
248
+ if (index !== -1) {
249
+ this.afterRenderCallbacks.splice(index, 1);
250
+ }
251
+ return this;
252
+ }
253
+
254
+ /**
255
+ * Clear all afterRender callbacks
256
+ * @returns this for chaining
257
+ */
258
+ public clearAfterRender(): this {
259
+ this.afterRenderCallbacks.length = 0;
260
+ return this;
261
+ }
262
+
263
+ public subscribe(eventName: string = '', fn: (...args: unknown[]) => void): this {
264
+ pubSub.subscribeEvent(this, eventName, fn);
265
+ return this;
266
+ }
267
+
268
+ public subscribeOnce(eventName: string = '', fn: (...args: unknown[]) => void): this {
269
+ pubSub.subscribeEventOnce(this, eventName, fn);
270
+ return this;
271
+ }
272
+
273
+ public unsubscribe(eventName: string = ''): this {
274
+ pubSub.unsubscribeEvent(this.compId, eventName);
275
+ return this;
276
+ }
277
+
278
+ public unsubscribeAll(): this {
279
+ pubSub.unsubscribeAllEvent(this.compId);
280
+ return this;
281
+ }
282
+
283
+ public publish(eventName: string = '', ...args: unknown[]): this {
284
+ pubSub.publishEvent(eventName, ...args);
285
+ return this;
286
+ }
287
+ }
288
+
289
+ export default Binder;
@@ -0,0 +1,93 @@
1
+
2
+ import {
3
+ getViewModelValue,
4
+ setViewModelValue,
5
+ resolveViewModelContext,
6
+ resolveParamList,
7
+ } from './util';
8
+ import _escape from './_escape';
9
+ import type {BindingCache, ViewModel, BindingAttrs} from './types';
10
+
11
+ /**
12
+ * Create change handler
13
+ */
14
+ const createChangeHandler = (
15
+ viewModel: ViewModel,
16
+ modelDataKey: string | null,
17
+ paramList: unknown[],
18
+ handlerFn: Function,
19
+ viewModelContext: ViewModel,
20
+ ): EventListener => {
21
+ let oldValue: unknown = '';
22
+ let newValue: unknown = '';
23
+
24
+ return function changeHandler(this: HTMLInputElement, e: Event) {
25
+ const $this = this;
26
+ const isCheckbox = $this.type === 'checkbox';
27
+ newValue = isCheckbox ? $this.checked : _escape($this.value);
28
+ // set data to viewModel
29
+ if (modelDataKey) {
30
+ oldValue = getViewModelValue(viewModel, modelDataKey);
31
+ setViewModelValue(viewModel, modelDataKey, newValue);
32
+ }
33
+ const args = [e, e.currentTarget, newValue, oldValue, ...paramList];
34
+ handlerFn.apply(viewModelContext, args);
35
+ oldValue = newValue;
36
+ };
37
+ };
38
+
39
+ interface ChangeBindingParams {
40
+ cache: BindingCache;
41
+ viewModel: ViewModel;
42
+ bindingAttrs: BindingAttrs;
43
+ forceRender: boolean;
44
+ type?: string;
45
+ }
46
+
47
+ /**
48
+ * changeBinding
49
+ * @description input element on change event binding. DOM -> viewModel update
50
+ * @param {object} cache
51
+ * @param {object} viewModel
52
+ * @param {object} bindingAttrs
53
+ * @param {boolean} forceRender
54
+ */
55
+ const changeBinding = ({
56
+ cache,
57
+ viewModel,
58
+ bindingAttrs,
59
+ forceRender,
60
+ type = 'change',
61
+ }: ChangeBindingParams): void => {
62
+ const handlerName = cache.dataKey;
63
+ let paramList = cache.parameters;
64
+ const modelDataKey = cache.el.getAttribute(bindingAttrs.model);
65
+ let viewModelContext: ViewModel;
66
+ const APP = viewModel.APP || viewModel.$root?.APP;
67
+ const rootElement = APP?.$rootElement as HTMLElement | undefined;
68
+
69
+ if (!handlerName || (!forceRender && rootElement && !rootElement.contains(cache.el))) {
70
+ return;
71
+ }
72
+
73
+ const handlerFn = getViewModelValue(viewModel, handlerName);
74
+
75
+ if (typeof handlerFn === 'function') {
76
+ viewModelContext = resolveViewModelContext(viewModel, handlerName);
77
+ paramList = paramList ? resolveParamList(viewModel, paramList) : [];
78
+
79
+ const changeHandler = createChangeHandler(
80
+ viewModel,
81
+ modelDataKey,
82
+ paramList,
83
+ handlerFn,
84
+ viewModelContext,
85
+ );
86
+
87
+ // assign on change event
88
+ cache.el.removeEventListener(type, changeHandler, false);
89
+ cache.el.addEventListener(type, changeHandler, false);
90
+ }
91
+ };
92
+
93
+ export default changeBinding;