@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/src/binder.ts
ADDED
|
@@ -0,0 +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
|
+
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;
|
|
@@ -1,15 +1,16 @@
|
|
|
1
|
-
|
|
1
|
+
|
|
2
2
|
import * as config from './config';
|
|
3
3
|
import * as util from './util';
|
|
4
|
+
import type {BindingCache} from './types';
|
|
4
5
|
|
|
5
|
-
const createClonedElementCache = (bindingData) => {
|
|
6
|
+
const createClonedElementCache = (bindingData: BindingCache): BindingCache => {
|
|
6
7
|
const clonedElement = bindingData.el.cloneNode(true);
|
|
7
8
|
bindingData.fragment = document.createDocumentFragment();
|
|
8
9
|
bindingData.fragment.appendChild(clonedElement);
|
|
9
10
|
return bindingData;
|
|
10
11
|
};
|
|
11
12
|
|
|
12
|
-
const setCommentPrefix = (bindingData) => {
|
|
13
|
+
const setCommentPrefix = (bindingData: BindingCache): BindingCache => {
|
|
13
14
|
if (!bindingData || !bindingData.type) {
|
|
14
15
|
return bindingData;
|
|
15
16
|
}
|
|
@@ -17,18 +18,18 @@ const setCommentPrefix = (bindingData) => {
|
|
|
17
18
|
const dataKeyMarker = bindingData.dataKey ? bindingData.dataKey.replace(util.REGEX.WHITE_SPACES, '_') : '';
|
|
18
19
|
|
|
19
20
|
switch (bindingData.type) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
21
|
+
case config.bindingAttrs.forOf:
|
|
22
|
+
commentPrefix = config.commentPrefix.forOf;
|
|
23
|
+
break;
|
|
24
|
+
case config.bindingAttrs.if:
|
|
25
|
+
commentPrefix = config.commentPrefix.if;
|
|
26
|
+
break;
|
|
27
|
+
case config.bindingAttrs.case:
|
|
28
|
+
commentPrefix = config.commentPrefix.case;
|
|
29
|
+
break;
|
|
30
|
+
case config.bindingAttrs.default:
|
|
31
|
+
commentPrefix = config.commentPrefix.default;
|
|
32
|
+
break;
|
|
32
33
|
}
|
|
33
34
|
bindingData.commentPrefix = commentPrefix + dataKeyMarker;
|
|
34
35
|
return bindingData;
|
|
@@ -44,18 +45,18 @@ const setCommentPrefix = (bindingData) => {
|
|
|
44
45
|
* if not found deleteContents will has no operation
|
|
45
46
|
* @return {undefined}
|
|
46
47
|
*/
|
|
47
|
-
const setDocRangeEndAfter = (node, bindingData) => {
|
|
48
|
+
const setDocRangeEndAfter = (node: Node | null, bindingData: BindingCache): void => {
|
|
48
49
|
if (!bindingData.commentPrefix) {
|
|
49
50
|
setCommentPrefix(bindingData);
|
|
50
51
|
}
|
|
51
|
-
const startTextContent = bindingData.commentPrefix;
|
|
52
|
+
const startTextContent = bindingData.commentPrefix as string;
|
|
52
53
|
const endTextContent = startTextContent + config.commentSuffix;
|
|
53
54
|
node = node.nextSibling;
|
|
54
55
|
|
|
55
56
|
// check last wrap comment node
|
|
56
57
|
if (node) {
|
|
57
58
|
if (node.nodeType === 8 && node.textContent === endTextContent) {
|
|
58
|
-
return bindingData.docRange.setEndBefore(node);
|
|
59
|
+
return (bindingData.docRange as Range).setEndBefore(node);
|
|
59
60
|
}
|
|
60
61
|
setDocRangeEndAfter(node, bindingData);
|
|
61
62
|
}
|
|
@@ -69,12 +70,12 @@ const setDocRangeEndAfter = (node, bindingData) => {
|
|
|
69
70
|
* @description
|
|
70
71
|
* wrap frament with comment node
|
|
71
72
|
*/
|
|
72
|
-
const wrapCommentAround = (bindingData, node) => {
|
|
73
|
+
const wrapCommentAround = (bindingData: BindingCache, node: Node | DocumentFragment): Node | DocumentFragment => {
|
|
73
74
|
let prefix = '';
|
|
74
75
|
if (!bindingData.commentPrefix) {
|
|
75
76
|
setCommentPrefix(bindingData);
|
|
76
77
|
}
|
|
77
|
-
prefix = bindingData.commentPrefix;
|
|
78
|
+
prefix = bindingData.commentPrefix as string;
|
|
78
79
|
const commentBegin = document.createComment(prefix);
|
|
79
80
|
const commentEnd = document.createComment(prefix + config.commentSuffix);
|
|
80
81
|
// document fragment - logic for ForOf binding
|
|
@@ -100,25 +101,26 @@ const wrapCommentAround = (bindingData, node) => {
|
|
|
100
101
|
* @return {undefined}
|
|
101
102
|
* @description remove elments by range
|
|
102
103
|
*/
|
|
103
|
-
const removeElemnetsByCommentWrap = (bindingData) => {
|
|
104
|
+
const removeElemnetsByCommentWrap = (bindingData: BindingCache): void => {
|
|
104
105
|
if (!bindingData.docRange) {
|
|
105
106
|
bindingData.docRange = document.createRange();
|
|
106
107
|
}
|
|
108
|
+
const docRange = bindingData.docRange as Range;
|
|
107
109
|
try {
|
|
108
110
|
if (bindingData.previousNonTemplateElement) {
|
|
109
111
|
// update docRange start and end match the wrapped comment node
|
|
110
|
-
|
|
112
|
+
docRange.setStartBefore(bindingData.previousNonTemplateElement.nextSibling as Node);
|
|
111
113
|
setDocRangeEndAfter(bindingData.previousNonTemplateElement.nextSibling, bindingData);
|
|
112
114
|
} else {
|
|
113
115
|
// insert before next non template element
|
|
114
|
-
|
|
115
|
-
setDocRangeEndAfter(bindingData.parentElement.firstChild, bindingData);
|
|
116
|
+
docRange.setStartBefore((bindingData.parentElement as HTMLElement).firstChild as Node);
|
|
117
|
+
setDocRangeEndAfter((bindingData.parentElement as HTMLElement).firstChild, bindingData);
|
|
116
118
|
}
|
|
117
|
-
} catch (err) {
|
|
118
|
-
console.log('error removeElemnetsByCommentWrap: ', err.message);
|
|
119
|
+
} catch (err: unknown) {
|
|
120
|
+
console.log('error removeElemnetsByCommentWrap: ', err instanceof Error ? err.message : String(err));
|
|
119
121
|
}
|
|
120
122
|
|
|
121
|
-
|
|
123
|
+
docRange.deleteContents();
|
|
122
124
|
};
|
|
123
125
|
|
|
124
126
|
/**
|
|
@@ -126,15 +128,16 @@ const removeElemnetsByCommentWrap = (bindingData) => {
|
|
|
126
128
|
* @param {object} bindingData
|
|
127
129
|
* @return {object} null
|
|
128
130
|
*/
|
|
129
|
-
const removeDomTemplateElement = (bindingData) => {
|
|
131
|
+
const removeDomTemplateElement = (bindingData: BindingCache): void => {
|
|
130
132
|
// first render - forElement is live DOM element so has parentNode
|
|
131
133
|
if (bindingData.el.parentNode) {
|
|
132
|
-
|
|
134
|
+
bindingData.el.parentNode.removeChild(bindingData.el);
|
|
135
|
+
return;
|
|
133
136
|
}
|
|
134
137
|
removeElemnetsByCommentWrap(bindingData);
|
|
135
138
|
};
|
|
136
139
|
|
|
137
|
-
const insertRenderedElements = (bindingData, fragment) => {
|
|
140
|
+
const insertRenderedElements = (bindingData: BindingCache, fragment: DocumentFragment): void => {
|
|
138
141
|
// insert rendered fragment after the previousNonTemplateElement
|
|
139
142
|
if (bindingData.previousNonTemplateElement) {
|
|
140
143
|
util.insertAfter(bindingData.parentElement, fragment, bindingData.previousNonTemplateElement);
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
export interface BindingAttrs {
|
|
2
|
+
comp: string;
|
|
3
|
+
tmp: string;
|
|
4
|
+
text: string;
|
|
5
|
+
click: string;
|
|
6
|
+
dblclick: string;
|
|
7
|
+
blur: string;
|
|
8
|
+
focus: string;
|
|
9
|
+
hover: string;
|
|
10
|
+
input: string;
|
|
11
|
+
change: string;
|
|
12
|
+
submit: string;
|
|
13
|
+
model: string;
|
|
14
|
+
show: string;
|
|
15
|
+
css: string;
|
|
16
|
+
attr: string;
|
|
17
|
+
forOf: string;
|
|
18
|
+
if: string;
|
|
19
|
+
switch: string;
|
|
20
|
+
case: string;
|
|
21
|
+
default: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const bindingAttrs: BindingAttrs = {
|
|
25
|
+
comp: 'data-bind-comp',
|
|
26
|
+
tmp: 'data-bind-tmp',
|
|
27
|
+
text: 'data-bind-text',
|
|
28
|
+
click: 'data-bind-click',
|
|
29
|
+
dblclick: 'data-bind-dblclick',
|
|
30
|
+
blur: 'data-bind-blur',
|
|
31
|
+
focus: 'data-bind-focus',
|
|
32
|
+
hover: 'data-bind-hover',
|
|
33
|
+
input: 'data-bind-input',
|
|
34
|
+
change: 'data-bind-change',
|
|
35
|
+
submit: 'data-bind-submit',
|
|
36
|
+
model: 'data-bind-model',
|
|
37
|
+
show: 'data-bind-show',
|
|
38
|
+
css: 'data-bind-css',
|
|
39
|
+
attr: 'data-bind-attr',
|
|
40
|
+
forOf: 'data-bind-for',
|
|
41
|
+
if: 'data-bind-if',
|
|
42
|
+
switch: 'data-bind-switch',
|
|
43
|
+
case: 'data-bind-case',
|
|
44
|
+
default: 'data-bind-default',
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export const serverRenderedAttr = 'data-server-rendered';
|
|
48
|
+
export const dataIndexAttr = 'data-index';
|
|
49
|
+
|
|
50
|
+
export interface CommentPrefix {
|
|
51
|
+
forOf: string;
|
|
52
|
+
if: string;
|
|
53
|
+
case: string;
|
|
54
|
+
default: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export const commentPrefix: CommentPrefix = {
|
|
58
|
+
forOf: 'data-forOf_',
|
|
59
|
+
if: 'data-if_',
|
|
60
|
+
case: 'data-case_',
|
|
61
|
+
default: 'data-default_',
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export const commentSuffix = '_end';
|
|
65
|
+
|
|
66
|
+
export interface BindingDataReference {
|
|
67
|
+
rootDataKey: string;
|
|
68
|
+
currentData: string;
|
|
69
|
+
currentIndex: string;
|
|
70
|
+
mouseEnterHandlerName: string;
|
|
71
|
+
mouseLeaveHandlerName: string;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export const bindingDataReference: BindingDataReference = {
|
|
75
|
+
rootDataKey: '$root',
|
|
76
|
+
currentData: '$data',
|
|
77
|
+
currentIndex: '$index',
|
|
78
|
+
mouseEnterHandlerName: 'in',
|
|
79
|
+
mouseLeaveHandlerName: 'out',
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export interface BindingUpdateConditions {
|
|
83
|
+
serverRendered: string;
|
|
84
|
+
init: string;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export const bindingUpdateConditions: BindingUpdateConditions = {
|
|
88
|
+
serverRendered: 'SERVER-RENDERED',
|
|
89
|
+
init: 'INIT',
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
// maximum string length before running regex
|
|
93
|
+
export const maxDatakeyLength = 250;
|
|
94
|
+
|
|
95
|
+
export interface Constants {
|
|
96
|
+
filters: {
|
|
97
|
+
ONCE: string;
|
|
98
|
+
};
|
|
99
|
+
PARENT_REF: string;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export const constants: Constants = {
|
|
103
|
+
filters: {
|
|
104
|
+
ONCE: 'once',
|
|
105
|
+
},
|
|
106
|
+
PARENT_REF: '_parent',
|
|
107
|
+
};
|