@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.
Files changed (274) 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 +2772 -2519
  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/form.html +20 -4
  147. package/examples/globalConfig.html +131 -0
  148. package/examples/js/afterRenderDemo.js +190 -0
  149. package/examples/js/appTodo.js +46 -46
  150. package/examples/js/attrBindingDemo.js +2 -2
  151. package/examples/js/dbMonApp.js +24 -26
  152. package/examples/js/dbMonAppReact.jsx +79 -0
  153. package/examples/js/dbMonAppReactive.js +28 -0
  154. package/examples/js/fiberDemo.js +4 -4
  155. package/examples/js/filtersDemo.js +8 -8
  156. package/examples/js/forOfDemo.js +7 -9
  157. package/examples/js/forOfDemoComplex.js +44 -17
  158. package/examples/js/form.js +44 -12
  159. package/examples/js/globalConfig.js +117 -0
  160. package/examples/js/ifBindingDemo.js +16 -16
  161. package/examples/js/reactiveDemo.js +119 -0
  162. package/examples/js/switchBindingDemo.js +8 -8
  163. package/examples/react-dbmonster/dist/bundle.js +43 -0
  164. package/examples/react-dbmonster/package-lock.json +537 -0
  165. package/examples/react-dbmonster/package.json +16 -0
  166. package/examples/react-dbmonster/src/index.jsx +80 -0
  167. package/examples/reactiveDemo.html +127 -0
  168. package/examples/refreshRateTest.html +75 -75
  169. package/index.html +841 -0
  170. package/package.json +31 -34
  171. package/rollup.config.js +79 -36
  172. package/src/{_escape.js → _escape.ts} +19 -17
  173. package/src/applyBinding.ts +179 -0
  174. package/src/{attrBinding.js → attrBinding.ts} +14 -13
  175. package/src/binder.ts +289 -0
  176. package/src/changeBinding.ts +93 -0
  177. package/src/{commentWrapper.js → commentWrapper.ts} +33 -30
  178. package/src/config.ts +107 -0
  179. package/src/createBindingOption.ts +91 -0
  180. package/src/createEventBinding.ts +88 -0
  181. package/src/{cssBinding.js → cssBinding.ts} +13 -11
  182. package/src/{domWalker.js → domWalker.ts} +44 -30
  183. package/src/{forOfBinding.js → forOfBinding.ts} +4 -3
  184. package/src/hoverBinding.ts +84 -0
  185. package/src/{ifBinding.js → ifBinding.ts} +14 -12
  186. package/src/index.ts +53 -0
  187. package/src/{modelBinding.js → modelBinding.ts} +11 -9
  188. package/src/postProcess.ts +22 -0
  189. package/src/{pubSub.js → pubSub.ts} +24 -15
  190. package/src/reactiveProxy.ts +285 -0
  191. package/src/{renderForOfBinding.js → renderForOfBinding.ts} +55 -33
  192. package/src/{renderIfBinding.js → renderIfBinding.ts} +45 -20
  193. package/src/renderIteration.ts +53 -0
  194. package/src/renderTemplate.ts +165 -0
  195. package/src/renderTemplatesBinding.ts +73 -0
  196. package/src/{showBinding.js → showBinding.ts} +4 -3
  197. package/src/{switchBinding.js → switchBinding.ts} +18 -15
  198. package/src/{textBinding.js → textBinding.ts} +5 -4
  199. package/src/types.ts +124 -0
  200. package/src/util.ts +810 -0
  201. package/test/css/reporter.css +9 -9
  202. package/test/fixtures/dataBindBootstrap.html +2 -2
  203. package/test/fixtures/formBindings.html +9 -1
  204. package/test/globals.d.ts +19 -0
  205. package/test/helpers/testHelper.js +46 -11
  206. package/test/mocks/featureAdsResult.json +65 -65
  207. package/test/mocks/searchResult.json +57 -57
  208. package/test/specs/{attrBinding.spec.js → attrBinding.spec.ts} +103 -106
  209. package/test/specs/{binder.spec.js → binder.spec.ts} +29 -27
  210. package/test/specs/blurBinding.spec.ts +60 -0
  211. package/test/specs/chainableUse.spec.ts +125 -0
  212. package/test/specs/clickBinding.spec.ts +194 -0
  213. package/test/specs/{cssBinding.spec.js → cssBinding.spec.ts} +72 -79
  214. package/test/specs/{dataBindBootstrap.spec.js → dataBindBootstrap.spec.ts} +332 -313
  215. package/test/specs/{filter.spec.js → filter.spec.ts} +75 -76
  216. package/test/specs/{forOfBinding.spec.js → forOfBinding.spec.ts} +208 -219
  217. package/test/specs/formBinding.spec.ts +272 -0
  218. package/test/specs/ifBinding.spec.ts +165 -0
  219. package/test/specs/{nestedComponent.spec.js → nestedComponent.spec.ts} +88 -88
  220. package/test/specs/reactiveProxy.spec.ts +465 -0
  221. package/test/specs/{showBinding.spec.js → showBinding.spec.ts} +148 -149
  222. package/test/specs/{switchBinding.spec.js → switchBinding.spec.ts} +172 -173
  223. package/test/specs/templateBinding.spec.ts +273 -0
  224. package/test/specs/{textBinding.spec.js → textBinding.spec.ts} +47 -48
  225. package/test/tsconfig.json +31 -0
  226. package/test-output.txt +200 -0
  227. package/test-reactive.html +224 -0
  228. package/tsconfig.json +28 -0
  229. package/vendors/lodash.custom.js +4577 -4577
  230. package/vendors/lodash.custom.min.js +45 -45
  231. package/vitest.config.js +27 -0
  232. package/.eslintrc.js +0 -1
  233. package/.grunt/grunt-contrib-jasmine/boot.js +0 -161
  234. package/.grunt/grunt-contrib-jasmine/dist/js/dataBind.js +0 -9
  235. package/.grunt/grunt-contrib-jasmine/grunt-template-jasmine-istanbul/reporter.js +0 -23
  236. package/.grunt/grunt-contrib-jasmine/jasmine-html.js +0 -853
  237. package/.grunt/grunt-contrib-jasmine/jasmine.css +0 -271
  238. package/.grunt/grunt-contrib-jasmine/jasmine.js +0 -9761
  239. package/.grunt/grunt-contrib-jasmine/jasmine_favicon.png +0 -0
  240. package/.grunt/grunt-contrib-jasmine/json2.js +0 -489
  241. package/.grunt/grunt-contrib-jasmine/reporter.js +0 -107
  242. package/coverage/coverage.json +0 -1
  243. package/coverage/lcov/lcov-report/base.css +0 -213
  244. package/coverage/lcov/lcov-report/index.html +0 -93
  245. package/coverage/lcov/lcov-report/js/dataBind.js.html +0 -6596
  246. package/coverage/lcov/lcov-report/js/index.html +0 -93
  247. package/coverage/lcov/lcov-report/prettify.css +0 -1
  248. package/coverage/lcov/lcov-report/prettify.js +0 -1
  249. package/coverage/lcov/lcov-report/sort-arrow-sprite.png +0 -0
  250. package/coverage/lcov/lcov-report/sorter.js +0 -158
  251. package/coverage/lcov/lcov.info +0 -1991
  252. package/eslintrc.json +0 -40
  253. package/examples/bootstrap/js/bootstrap.min.js +0 -6
  254. package/examples/bootstrap/js/popper.min.js +0 -5
  255. package/examples/bootstrap/js/searchSuggestion.js +0 -58
  256. package/examples/bootstrap/js/typeahead.jquery.js +0 -1538
  257. package/gruntfile.js +0 -92
  258. package/gulpfile.js +0 -32
  259. package/src/binder.js +0 -422
  260. package/src/changeBinding.js +0 -57
  261. package/src/config.js +0 -65
  262. package/src/createBindingOption.js +0 -66
  263. package/src/createEventBinding.js +0 -46
  264. package/src/eventSystem.js +0 -46
  265. package/src/hoverBinding.js +0 -57
  266. package/src/index.js +0 -26
  267. package/src/renderTemplate.js +0 -128
  268. package/src/util.js +0 -648
  269. package/test/specs/blurBinding.spec.js +0 -57
  270. package/test/specs/formBinding.spec.js +0 -292
  271. package/test/specs/ifBinding.spec.js +0 -169
  272. package/test/specs/templateBinding.spec.js +0 -117
  273. package/vendors/jasmine-jquery.js +0 -841
  274. package/vendors/jquery-3.2.1.min.js +0 -4
@@ -1,8 +1,15 @@
1
1
  import {isEmptyObject} from './util';
2
- import {renderIteration} from './binder';
2
+ import renderIteration from './renderIteration';
3
3
  import createBindingCache from './domWalker';
4
4
  import {commentSuffix} from './config';
5
5
  import {removeElemnetsByCommentWrap, insertRenderedElements} from './commentWrapper';
6
+ import type {BindingCache, ViewModel, BindingAttrs, ElementCache} from './types';
7
+
8
+ interface RenderIfBindingParams {
9
+ bindingData: BindingCache;
10
+ viewModel: ViewModel;
11
+ bindingAttrs: BindingAttrs;
12
+ }
6
13
 
7
14
  /**
8
15
  * isTargetDomRemoved
@@ -10,13 +17,13 @@ import {removeElemnetsByCommentWrap, insertRenderedElements} from './commentWrap
10
17
  * @param {object} bindingData
11
18
  * @return {boolean}
12
19
  */
13
- const isTargetDomRemoved = (bindingData) => {
20
+ const isTargetDomRemoved = (bindingData: BindingCache): boolean => {
14
21
  let ret = false;
15
22
  if (bindingData && bindingData.previousNonTemplateElement) {
16
23
  const commentStartTextContent = bindingData.previousNonTemplateElement.textContent;
17
24
  const endCommentTag = bindingData.previousNonTemplateElement.nextSibling;
18
25
 
19
- if (endCommentTag.nodeType === 8) {
26
+ if (endCommentTag && endCommentTag.nodeType === 8) {
20
27
  if (endCommentTag.textContent === commentStartTextContent + commentSuffix) {
21
28
  ret = true;
22
29
  }
@@ -25,27 +32,51 @@ const isTargetDomRemoved = (bindingData) => {
25
32
  return ret;
26
33
  };
27
34
 
28
- const renderIfBinding = ({bindingData, viewModel, bindingAttrs}) => {
35
+ /**
36
+ * removeIfBinding
37
+ * @description remove if binding DOM and clean up cache
38
+ * @param {object} bindingData
39
+ */
40
+ const removeIfBinding = (bindingData: BindingCache): void => {
41
+ removeElemnetsByCommentWrap(bindingData);
42
+ // remove cache.IterationBindingCache to prevent memory leak
43
+ if (bindingData.hasIterationBindingCache) {
44
+ delete bindingData.iterationBindingCache;
45
+ delete bindingData.hasIterationBindingCache;
46
+ }
47
+ };
48
+
49
+ /**
50
+ * renderIfBinding
51
+ * @description render if binding DOM
52
+ * @param {object} bindingData
53
+ * @param {object} viewModel
54
+ * @param {object} bindingAttrs
55
+ */
56
+ const renderIfBinding = ({bindingData, viewModel, bindingAttrs}: RenderIfBindingParams): void => {
29
57
  if (!bindingData.fragment) {
30
58
  return;
31
59
  }
32
60
 
33
61
  const isDomRemoved = isTargetDomRemoved(bindingData);
34
- let rootElement = bindingData.el;
62
+ let rootElement: Node = bindingData.el;
35
63
 
36
64
  // remove current old DOM.
37
65
  // TODO: try preserve DOM
38
66
  if (!isDomRemoved && !bindingData.isOnce) {
39
67
  removeIfBinding(bindingData);
40
68
  // use fragment for create iterationBindingCache
41
- rootElement = bindingData.fragment.firstChild.cloneNode(true);
69
+ const firstChild = bindingData.fragment.firstChild;
70
+ if (firstChild) {
71
+ rootElement = firstChild.cloneNode(true);
72
+ }
42
73
  }
43
74
 
44
75
  // walk clonedElement to create iterationBindingCache once
45
76
  if (!bindingData.iterationBindingCache || !bindingData.hasIterationBindingCache) {
46
77
  bindingData.iterationBindingCache = createBindingCache({
47
- rootNode: rootElement,
48
- bindingAttrs: bindingAttrs,
78
+ rootNode: rootElement as HTMLElement,
79
+ bindingAttrs,
49
80
  });
50
81
  }
51
82
 
@@ -54,25 +85,19 @@ const renderIfBinding = ({bindingData, viewModel, bindingAttrs}) => {
54
85
  if (!isEmptyObject(bindingData.iterationBindingCache)) {
55
86
  bindingData.hasIterationBindingCache = true;
56
87
  renderIteration({
57
- elementCache: bindingData.iterationBindingCache,
88
+ elementCache: bindingData.iterationBindingCache as ElementCache,
58
89
  iterationVm: viewModel,
59
- bindingAttrs: bindingAttrs,
90
+ bindingAttrs,
60
91
  isRegenerate: true,
61
92
  });
62
93
  }
63
94
 
64
95
  // insert to new rendered DOM
65
96
  // TODO: check unnecessary insertion when DOM is preserved
66
- insertRenderedElements(bindingData, rootElement);
97
+ insertRenderedElements(bindingData, rootElement as DocumentFragment);
67
98
  };
68
99
 
69
- const removeIfBinding = (bindingData) => {
70
- removeElemnetsByCommentWrap(bindingData);
71
- // remove cache.IterationBindingCache to prevent memory leak
72
- if (bindingData.hasIterationBindingCache) {
73
- delete bindingData.iterationBindingCache;
74
- delete bindingData.hasIterationBindingCache;
75
- }
100
+ export {
101
+ renderIfBinding,
102
+ removeIfBinding,
76
103
  };
77
-
78
- export {renderIfBinding, removeIfBinding};
@@ -0,0 +1,53 @@
1
+ import {bindingUpdateConditions} from './config';
2
+ import createBindingOption from './createBindingOption';
3
+ import renderTemplatesBinding from './renderTemplatesBinding';
4
+ import * as applyBindingModule from './applyBinding';
5
+ import type {ElementCache, ViewModel, BindingAttrs} from './types';
6
+ import type Binder from './binder';
7
+
8
+ /**
9
+ * renderIteration
10
+ * @param {object} opt
11
+ * @description
12
+ * render element's binding by supplied elementCache
13
+ * This function is desidned for FoOf, If, switch bindings
14
+ */
15
+ const renderIteration = ({
16
+ elementCache,
17
+ iterationVm,
18
+ bindingAttrs,
19
+ isRegenerate,
20
+ }: {
21
+ elementCache: ElementCache;
22
+ iterationVm: ViewModel;
23
+ bindingAttrs: BindingAttrs;
24
+ isRegenerate: boolean;
25
+ }): void => {
26
+ const bindingUpdateOption = isRegenerate ? createBindingOption(bindingUpdateConditions.init) : createBindingOption();
27
+
28
+ // enforce render even element is not in DOM tree
29
+ bindingUpdateOption.forceRender = true;
30
+
31
+ // render and apply binding to template(s)
32
+ // this is an share function therefore passing current APP 'this' context
33
+ // viewModel is a dynamic generated iterationVm
34
+ renderTemplatesBinding({
35
+ ctx: (iterationVm.$root ? iterationVm.$root.APP : iterationVm.APP) as Binder,
36
+ elementCache,
37
+ updateOption: bindingUpdateOption,
38
+ bindingAttrs,
39
+ viewModel: iterationVm,
40
+ });
41
+
42
+ // Use namespace import to access the function at runtime,
43
+ // which breaks the circular dependency during module initialization
44
+ applyBindingModule.default({
45
+ ctx: (iterationVm.$root ? iterationVm.$root.APP : iterationVm.APP) as Binder,
46
+ elementCache,
47
+ updateOption: bindingUpdateOption,
48
+ bindingAttrs,
49
+ viewModel: iterationVm,
50
+ });
51
+ };
52
+
53
+ export default renderIteration;
@@ -0,0 +1,165 @@
1
+ import {dataIndexAttr} from './config';
2
+ import {
3
+ createHtmlFragment,
4
+ emptyElement,
5
+ getViewModelPropValue,
6
+ parseStringToJson,
7
+ updateDomWithMinimalChanges,
8
+ } from './util';
9
+ import type {BindingCache, ViewModel, BindingAttrs, ElementCache, PlainObject} from './types';
10
+
11
+ let $domFragment: DocumentFragment | null = null;
12
+ let $templateRoot: HTMLElement | null = null;
13
+ let $templateRootPrepend = false;
14
+ let $templateRootAppend = false;
15
+ let nestTemplatesCount = 0;
16
+
17
+ /**
18
+ * getTemplateString
19
+ * @description get Template tag innerHTML string
20
+ * @param {string} id
21
+ * @return {string} rendered html string
22
+ */
23
+ const getTemplateString = (id: string): string => {
24
+ const templateElement = document.getElementById(id);
25
+
26
+ return templateElement ? templateElement.innerHTML : '';
27
+ };
28
+
29
+ /**
30
+ * renderTemplate
31
+ * @description
32
+ * get template setting from DOM attribute then call compileTemplate
33
+ * to render and append to target DOM
34
+ * @param {object} cache
35
+ * @param {object} viewModel
36
+ * @param {object} bindingAttrs
37
+ * @param {object} elementCache
38
+ */
39
+ const renderTemplate = (cache: BindingCache, viewModel: ViewModel, bindingAttrs: BindingAttrs, elementCache: ElementCache): void => {
40
+ const settings = typeof cache.dataKey === 'string' ? parseStringToJson(cache.dataKey) : cache.dataKey as PlainObject;
41
+ let viewData: unknown = (settings as PlainObject).data;
42
+ const isAppend = (settings as PlainObject).append;
43
+ const isPrepend = (settings as PlainObject).prepend;
44
+ let $currentElement: DocumentFragment | HTMLElement;
45
+
46
+ cache.dataKey = settings as unknown as string;
47
+
48
+ viewData = (typeof viewData === 'undefined' || viewData === '$root') ?
49
+ viewModel :
50
+ getViewModelPropValue(viewModel, {
51
+ dataKey: (settings as PlainObject).data,
52
+ parameters: cache.parameters,
53
+ } as BindingCache);
54
+
55
+ if (!viewData) {
56
+ return;
57
+ }
58
+
59
+ const $element = cache.el;
60
+ const $indexAttr = $element.getAttribute(dataIndexAttr);
61
+ const $index = typeof viewModel.$index !== 'undefined' ? viewModel.$index : ($indexAttr ? parseInt($indexAttr, 10) : undefined);
62
+
63
+ if (typeof $index !== 'undefined' && viewData && typeof viewData === 'object') {
64
+ (viewData as ViewModel).$index = $index;
65
+ }
66
+
67
+ $domFragment = $domFragment || document.createDocumentFragment();
68
+
69
+ if (!$templateRoot) {
70
+ $templateRoot = $element;
71
+ // Store the prepend/append flags from the root template only
72
+ $templateRootPrepend = Boolean(isPrepend);
73
+ $templateRootAppend = Boolean(isAppend);
74
+ }
75
+
76
+ const htmlString = getTemplateString((settings as PlainObject).id as string);
77
+
78
+ const htmlFragment = createHtmlFragment(htmlString);
79
+
80
+ // Return early if htmlFragment is null (invalid template)
81
+ if (!htmlFragment) {
82
+ return;
83
+ }
84
+
85
+ // append rendered html
86
+ if (!$domFragment.childNodes.length) {
87
+ // domFragment should be empty in first run
88
+ $currentElement = $domFragment; // copy of $domFragment for later find nested template check
89
+ $domFragment.appendChild(htmlFragment);
90
+ } else {
91
+ // during recursive run keep append to current fragment
92
+ // For nested templates, use the original behavior (clear and append)
93
+ // because they may contain forOf bindings or other dynamic content
94
+ // that manages its own DOM structure
95
+ $currentElement = $element; // reset to current nested template element
96
+ if (!isAppend && !isPrepend) {
97
+ $currentElement = emptyElement($currentElement);
98
+ }
99
+ if (isPrepend) {
100
+ $currentElement.insertBefore(htmlFragment, $currentElement.firstChild);
101
+ } else {
102
+ $currentElement.appendChild(htmlFragment);
103
+ }
104
+ }
105
+
106
+ // check if there are nested template then recurisive render them
107
+ const $nestedTemplates = $currentElement.querySelectorAll(`[${ bindingAttrs.tmp }]`);
108
+
109
+ const nestedTemplatesLength = $nestedTemplates.length;
110
+
111
+ if (nestedTemplatesLength) {
112
+ nestTemplatesCount += nestedTemplatesLength;
113
+
114
+ for (let i=0; i < nestedTemplatesLength; i+=1) {
115
+ const thisTemplateCache = {
116
+ el: $nestedTemplates[i] as HTMLElement,
117
+ dataKey: $nestedTemplates[i].getAttribute(bindingAttrs.tmp),
118
+ } as BindingCache;
119
+ elementCache[bindingAttrs.tmp].push(thisTemplateCache);
120
+ // recursive template render
121
+ renderTemplate(thisTemplateCache, viewModel, bindingAttrs, elementCache);
122
+ nestTemplatesCount -= 1;
123
+ }
124
+ }
125
+
126
+ // no more nested tempalted to render, start to append $domFragment into $templateRoot
127
+ if (nestTemplatesCount === 0) {
128
+ // append to DOM once
129
+ // Use the prepend/append flags from the root template, not the current nested template
130
+ if (!$templateRootAppend && !$templateRootPrepend) {
131
+ // Check if this is a re-render by looking for a marker attribute
132
+ // This is more reliable than checking childNodes.length because templates
133
+ // may have placeholder content
134
+ const isRerender = $templateRoot.hasAttribute('data-template-rendered');
135
+
136
+ if (isRerender) {
137
+ // Re-render: Use minimal DOM updates to preserve unchanged elements
138
+ // This is faster and preserves DOM state (focus, scroll, animations)
139
+ updateDomWithMinimalChanges($templateRoot, $domFragment);
140
+ } else {
141
+ // Initial render: Clear any placeholder content and render fresh
142
+ $templateRoot = emptyElement($templateRoot);
143
+ $templateRoot.appendChild($domFragment);
144
+ // Mark this template as rendered for future re-renders
145
+ $templateRoot.setAttribute('data-template-rendered', 'true');
146
+ }
147
+ } else {
148
+ // For prepend/append modes, use the original behavior
149
+ if ($templateRootPrepend) {
150
+ $templateRoot.insertBefore($domFragment, $templateRoot.firstChild);
151
+ } else {
152
+ $templateRoot.appendChild($domFragment);
153
+ }
154
+ }
155
+ // clear cached fragment and flags
156
+ $domFragment = $templateRoot = null;
157
+ $templateRootPrepend = $templateRootAppend = false;
158
+ // trigger callback if provided
159
+ if (viewModel.afterTemplateRender && typeof viewModel.afterTemplateRender === 'function') {
160
+ (viewModel.afterTemplateRender as Function)(viewData);
161
+ }
162
+ }
163
+ };
164
+
165
+ export default renderTemplate;
@@ -0,0 +1,73 @@
1
+ import {bindingUpdateConditions} from './config';
2
+ import * as applyBindingModule from './applyBinding';
3
+ import createBindingOption from './createBindingOption';
4
+ import renderTemplate from './renderTemplate';
5
+ import type {ElementCache, ViewModel, BindingAttrs, BindingCache} from './types';
6
+ import type {BindingOption} from './createBindingOption';
7
+
8
+ interface BinderContext {
9
+ updateElementCache: (opt: {
10
+ allCache?: boolean;
11
+ templateCache?: boolean;
12
+ elementCache?: ElementCache;
13
+ isRenderedTemplates?: boolean;
14
+ }) => void;
15
+ }
16
+
17
+ const renderTemplatesBinding = ({
18
+ ctx,
19
+ elementCache,
20
+ updateOption,
21
+ bindingAttrs,
22
+ viewModel,
23
+ }: {
24
+ ctx: BinderContext;
25
+ elementCache: ElementCache;
26
+ updateOption: BindingOption;
27
+ bindingAttrs: BindingAttrs;
28
+ viewModel: ViewModel;
29
+ }): boolean => {
30
+ if (!elementCache || !bindingAttrs) {
31
+ return false;
32
+ }
33
+ // render and apply binding to template(s) and forOf DOM
34
+ if (elementCache[bindingAttrs.tmp] && elementCache[bindingAttrs.tmp].length) {
35
+ // when re-render call with {templateBinding: true}
36
+ // template and nested templates
37
+ if (updateOption.templateBinding) {
38
+ // overwrite updateOption with 'init' bindingUpdateConditions
39
+ updateOption = createBindingOption(bindingUpdateConditions.init);
40
+
41
+ // forEach is correct here - nested templates are added to array but rendered recursively
42
+ // We don't want the loop to re-render templates that were already rendered via recursion
43
+ elementCache[bindingAttrs.tmp].forEach(($element: unknown) => {
44
+ renderTemplate($element as BindingCache, viewModel, bindingAttrs, elementCache);
45
+ });
46
+ // update cache after all template(s) rendered
47
+ ctx.updateElementCache({
48
+ templateCache: true,
49
+ elementCache,
50
+ isRenderedTemplates: true,
51
+ });
52
+ }
53
+ // enforce render even element is not in DOM tree
54
+ updateOption.forceRender = true;
55
+
56
+ // apply bindings to rendered templates element
57
+ // Use namespace import to access the function at runtime,
58
+ // which breaks the circular dependency during module initialization
59
+ // Use for loop to handle templates added during rendering
60
+ for (let i = 0; i < elementCache[bindingAttrs.tmp].length; i++) {
61
+ applyBindingModule.default({
62
+ ctx,
63
+ elementCache: elementCache[bindingAttrs.tmp][i].bindingCache as ElementCache,
64
+ updateOption,
65
+ bindingAttrs,
66
+ viewModel,
67
+ });
68
+ }
69
+ }
70
+ return true;
71
+ };
72
+
73
+ export default renderTemplatesBinding;
@@ -1,4 +1,5 @@
1
1
  import {getViewModelPropValue} from './util';
2
+ import type {BindingCache, ViewModel, BindingAttrs} from './types';
2
3
 
3
4
  /**
4
5
  * showBinding
@@ -9,9 +10,9 @@ import {getViewModelPropValue} from './util';
9
10
  * @param {object} viewModel
10
11
  * @param {object} bindingAttrs
11
12
  */
12
- const showBinding = (cache, viewModel, bindingAttrs) => {
13
+ const showBinding = (cache: BindingCache, viewModel: ViewModel, _bindingAttrs: BindingAttrs, _forceRender?: boolean): void => {
13
14
  const dataKey = cache.dataKey;
14
- let currentInlineSytle = {};
15
+ let currentInlineSytle: CSSStyleDeclaration | Record<string, never> = {};
15
16
  let currentInlineDisplaySytle = '';
16
17
  let shouldShow = true;
17
18
 
@@ -42,7 +43,7 @@ const showBinding = (cache, viewModel, bindingAttrs) => {
42
43
  }
43
44
  }
44
45
 
45
- shouldShow = getViewModelPropValue(viewModel, cache);
46
+ shouldShow = getViewModelPropValue(viewModel, cache) as boolean;
46
47
 
47
48
  // treat undefined || null as false.
48
49
  // eg if property doesn't exsits in viewModel, it will treat as false to hide element
@@ -1,6 +1,8 @@
1
1
  import {getViewModelPropValue} from './util';
2
2
  import {createClonedElementCache, wrapCommentAround} from './commentWrapper';
3
3
  import {renderIfBinding, removeIfBinding} from './renderIfBinding';
4
+ import type {BindingCache, ViewModel, BindingAttrs, CaseData} from './types';
5
+
4
6
  /**
5
7
  * switch-Binding
6
8
  * @description
@@ -10,7 +12,7 @@ import {renderIfBinding, removeIfBinding} from './renderIfBinding';
10
12
  * @param {object} viewModel
11
13
  * @param {object} bindingAttrs
12
14
  */
13
- const switchBinding = (cache, viewModel, bindingAttrs) => {
15
+ const switchBinding = (cache: BindingCache, viewModel: ViewModel, bindingAttrs: BindingAttrs, _forceRender?: boolean): void => {
14
16
  const dataKey = cache.dataKey;
15
17
 
16
18
  if (!dataKey) {
@@ -35,11 +37,12 @@ const switchBinding = (cache, viewModel, bindingAttrs) => {
35
37
  }
36
38
  cache.cases = [];
37
39
  for (let i = 0, elementLength = childrenElements.length; i < elementLength; i += 1) {
38
- let caseData = null;
39
- if (childrenElements[i].hasAttribute(bindingAttrs.case)) {
40
- caseData = createCaseData(childrenElements[i], bindingAttrs.case);
41
- } else if (childrenElements[i].hasAttribute(bindingAttrs.default)) {
42
- caseData = createCaseData(childrenElements[i], bindingAttrs.default);
40
+ let caseData: CaseData | null = null;
41
+ const childElement = childrenElements[i] as HTMLElement;
42
+ if (childElement.hasAttribute(bindingAttrs.case)) {
43
+ caseData = createCaseData(childElement, bindingAttrs.case);
44
+ } else if (childElement.hasAttribute(bindingAttrs.default)) {
45
+ caseData = createCaseData(childElement, bindingAttrs.default);
43
46
  caseData.isDefault = true;
44
47
  }
45
48
  // create fragment by clone node
@@ -62,7 +65,7 @@ const switchBinding = (cache, viewModel, bindingAttrs) => {
62
65
  let hasMatch = false;
63
66
  // do switch operation - reuse if binding logic
64
67
  for (let j = 0, casesLength = cache.cases.length; j < casesLength; j += 1) {
65
- let newCaseValue;
68
+ let newCaseValue: unknown;
66
69
  if (cache.cases[j].dataKey) {
67
70
  // set back to dataKey if nothing found in viewModel
68
71
  newCaseValue = getViewModelPropValue(viewModel, cache.cases[j]) || cache.cases[j].dataKey;
@@ -73,8 +76,8 @@ const switchBinding = (cache, viewModel, bindingAttrs) => {
73
76
  // render element
74
77
  renderIfBinding({
75
78
  bindingData: cache.cases[j],
76
- viewModel: viewModel,
77
- bindingAttrs: bindingAttrs,
79
+ viewModel,
80
+ bindingAttrs,
78
81
  });
79
82
 
80
83
  // remove other elements
@@ -89,8 +92,8 @@ const switchBinding = (cache, viewModel, bindingAttrs) => {
89
92
  }
90
93
  };
91
94
 
92
- function removeUnmatchCases(cases, matchedIndex) {
93
- cases.forEach((caseData, index) => {
95
+ const removeUnmatchCases = (cases: CaseData[], matchedIndex?: number): void => {
96
+ cases.forEach((caseData: CaseData, index: number) => {
94
97
  if (index !== matchedIndex || typeof matchedIndex === 'undefined') {
95
98
  removeIfBinding(caseData);
96
99
  // remove cache.IterationBindingCache to prevent memory leak
@@ -100,15 +103,15 @@ function removeUnmatchCases(cases, matchedIndex) {
100
103
  }
101
104
  }
102
105
  });
103
- }
106
+ };
104
107
 
105
- function createCaseData(node, attrName) {
106
- const caseData = {
108
+ const createCaseData = (node: HTMLElement, attrName: string): CaseData => {
109
+ const caseData: CaseData = {
107
110
  el: node,
108
111
  dataKey: node.getAttribute(attrName),
109
112
  type: attrName,
110
113
  };
111
114
  return caseData;
112
- }
115
+ };
113
116
 
114
117
  export default switchBinding;
@@ -1,4 +1,5 @@
1
1
  import {getViewModelPropValue} from './util';
2
+ import type {BindingCache, ViewModel, BindingAttrs} from './types';
2
3
 
3
4
  /**
4
5
  * textBinding
@@ -9,12 +10,12 @@ import {getViewModelPropValue} from './util';
9
10
  * @param {object} bindingAttrs
10
11
  * @param {boolean} forceRender
11
12
  */
12
- const textBinding = (cache, viewModel, bindingAttrs, forceRender) => {
13
+ const textBinding = (cache: BindingCache, viewModel: ViewModel, bindingAttrs: BindingAttrs, forceRender: boolean): void => {
13
14
  const dataKey = cache.dataKey;
14
- const APP = viewModel.APP || viewModel.$root.APP;
15
+ const APP = viewModel.APP || viewModel.$root?.APP;
15
16
 
16
17
  // NOTE: this doesn't work for for-of, if and switch bindings because element was not in DOM
17
- if (!dataKey || (!forceRender && !APP.$rootElement.contains(cache.el))) {
18
+ if (!dataKey || (!forceRender && !(APP?.$rootElement as HTMLElement)?.contains(cache.el))) {
18
19
  return;
19
20
  }
20
21
 
@@ -23,7 +24,7 @@ const textBinding = (cache, viewModel, bindingAttrs, forceRender) => {
23
24
 
24
25
  if (typeof newValue !== 'undefined' && typeof newValue !== 'object' && newValue !== null) {
25
26
  if (newValue !== oldValue) {
26
- cache.el.textContent = newValue;
27
+ cache.el.textContent = String(newValue);
27
28
  }
28
29
  }
29
30
  };
package/src/types.ts ADDED
@@ -0,0 +1,124 @@
1
+ // Common types used across the application
2
+
3
+ // Generic unknown value type - safer than any
4
+ export type UnknownValue = unknown;
5
+
6
+ export interface PlainObject {
7
+ [key: string]: unknown;
8
+ }
9
+
10
+ export interface ViewModel {
11
+ [key: string]: unknown;
12
+ APP?: {
13
+ render?: (opt?: UpdateOption) => void | Promise<void>;
14
+ postProcessQueue?: Array<() => void>;
15
+ [key: string]: unknown;
16
+ };
17
+ $root?: ViewModel;
18
+ $data?: unknown;
19
+ $index?: number;
20
+ }
21
+
22
+ export interface ElementData {
23
+ viewModelPropValue?: unknown;
24
+ displayStyle?: string | null;
25
+ computedStyle?: string | null;
26
+ [key: string]: unknown;
27
+ }
28
+
29
+ export interface BindingCache {
30
+ el: HTMLElement;
31
+ dataKey?: string;
32
+ parameters?: unknown[];
33
+ filters?: string[];
34
+ isOnce?: boolean;
35
+ elementData?: ElementData;
36
+ bindingCache?: unknown;
37
+ type?: string;
38
+ fragment?: DocumentFragment;
39
+ hasIterationBindingCache?: boolean;
40
+ iterationBindingCache?: unknown;
41
+ parentElement?: HTMLElement | null;
42
+ previousNonTemplateElement?: Node | null;
43
+ nextNonTemplateElement?: Node | null;
44
+ iterator?: {
45
+ alias?: string;
46
+ dataKey?: string;
47
+ };
48
+ cases?: CaseData[];
49
+ [key: string]: unknown;
50
+ }
51
+
52
+ export interface CaseData {
53
+ el: HTMLElement;
54
+ dataKey?: string;
55
+ type: string;
56
+ isDefault?: boolean;
57
+ fragment?: DocumentFragment;
58
+ hasIterationBindingCache?: boolean;
59
+ iterationBindingCache?: unknown;
60
+ [key: string]: unknown;
61
+ }
62
+
63
+ export interface ElementCache {
64
+ [key: string]: BindingCache[];
65
+ }
66
+
67
+ export interface UpdateOption {
68
+ forceRender?: boolean;
69
+ attrBinding?: boolean;
70
+ cssBinding?: boolean;
71
+ textBinding?: boolean;
72
+ modelBinding?: boolean;
73
+ showBinding?: boolean;
74
+ ifBinding?: boolean;
75
+ switchBinding?: boolean;
76
+ forOfBinding?: boolean;
77
+ changeBinding?: boolean;
78
+ submitBinding?: boolean;
79
+ clickBinding?: boolean;
80
+ dblclickBinding?: boolean;
81
+ blurBinding?: boolean;
82
+ focusBinding?: boolean;
83
+ hoverBinding?: boolean;
84
+ inputBinding?: boolean;
85
+ [key: string]: unknown;
86
+ }
87
+
88
+ export interface DeferredObj<T = unknown> {
89
+ promise: Promise<T>;
90
+ resolve: (value?: T) => void;
91
+ reject: (reason?: unknown) => void;
92
+ }
93
+
94
+ export interface WrapMap {
95
+ [key: string]: [string, string, string];
96
+ }
97
+
98
+ export interface BindingAttrs {
99
+ [key: string]: string;
100
+ attr: string;
101
+ css: string;
102
+ text: string;
103
+ model: string;
104
+ show: string;
105
+ if: string;
106
+ switch: string;
107
+ case: string;
108
+ default: string;
109
+ forOf: string;
110
+ change: string;
111
+ submit: string;
112
+ click: string;
113
+ dblclick: string;
114
+ blur: string;
115
+ focus: string;
116
+ hover: string;
117
+ input: string;
118
+ tmp: string;
119
+ }
120
+
121
+ export interface BinderOptions {
122
+ reactive?: boolean;
123
+ trackChanges?: boolean;
124
+ }