cx 25.5.0 → 25.5.2
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/dist/manifest.js +461 -461
- package/dist/ui.js +7 -16
- package/dist/widgets.js +16 -1
- package/package.json +1 -1
- package/src/data/AugmentedViewBase.js +77 -77
- package/src/data/ExposedRecordView.js +75 -75
- package/src/data/ExposedValueView.js +73 -73
- package/src/ui/Container.js +154 -154
- package/src/ui/DataProxy.js +31 -45
- package/src/ui/DetachedScope.js +98 -98
- package/src/ui/Instance.d.ts +72 -72
- package/src/ui/Instance.js +623 -623
- package/src/ui/IsolatedScope.js +30 -30
- package/src/ui/Repeater.js +8 -1
- package/src/ui/Rescope.js +35 -35
- package/src/ui/Restate.js +167 -167
- package/src/ui/Widget.js +184 -184
- package/src/ui/adapter/ArrayAdapter.js +152 -152
- package/src/ui/adapter/TreeAdapter.js +101 -101
- package/src/ui/layout/exploreChildren.d.ts +12 -12
- package/src/ui/layout/exploreChildren.js +27 -27
- package/src/widgets/List.js +9 -2
- package/src/widgets/form/Checkbox.js +203 -200
- package/src/widgets/grid/Grid.js +7 -0
- package/src/widgets/nav/Route.js +102 -102
package/src/ui/Instance.js
CHANGED
|
@@ -1,623 +1,623 @@
|
|
|
1
|
-
import { Controller } from "./Controller";
|
|
2
|
-
import { debug, renderFlag, processDataFlag, destroyFlag } from "../util/Debug";
|
|
3
|
-
import { GlobalCacheIdentifier } from "../util/GlobalCacheIdentifier";
|
|
4
|
-
import { throttle } from "../util/throttle";
|
|
5
|
-
import { validatedDebounce } from "../util/validatedDebounce";
|
|
6
|
-
import { batchUpdates } from "./batchUpdates";
|
|
7
|
-
import { isString } from "../util/isString";
|
|
8
|
-
import { isFunction } from "../util/isFunction";
|
|
9
|
-
import { isDefined } from "../util/isDefined";
|
|
10
|
-
import { isArray } from "../util/isArray";
|
|
11
|
-
import { isObject } from "../util/isObject";
|
|
12
|
-
import { isNonEmptyArray } from "../util/isNonEmptyArray";
|
|
13
|
-
import { isUndefined } from "../util/isUndefined";
|
|
14
|
-
import { isAccessorChain } from "../data/createAccessorModelProxy";
|
|
15
|
-
|
|
16
|
-
let instanceId = 1000;
|
|
17
|
-
|
|
18
|
-
export class Instance {
|
|
19
|
-
constructor(widget, key, parent, parentStore) {
|
|
20
|
-
this.widget = widget;
|
|
21
|
-
this.key = key;
|
|
22
|
-
this.id = String(++instanceId);
|
|
23
|
-
this.cached = {};
|
|
24
|
-
this.parent = parent;
|
|
25
|
-
this.parentStore = parentStore ?? parent?.store;
|
|
26
|
-
|
|
27
|
-
if (this.parentStore == null) throw new Error("Cannot create instance without a parent store.");
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
setParentStore(parentStore) {
|
|
31
|
-
this.parentStore = parentStore;
|
|
32
|
-
this.widget.applyParentStore(this);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
init(context) {
|
|
36
|
-
// widget is initialized when the first instance is initialized
|
|
37
|
-
if (!this.widget.initialized) {
|
|
38
|
-
this.widget.init(context);
|
|
39
|
-
|
|
40
|
-
// init default values
|
|
41
|
-
this.widget.selector.init(this.parentStore);
|
|
42
|
-
|
|
43
|
-
this.widget.initialized = true;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
if (!this.dataSelector) {
|
|
47
|
-
this.dataSelector = this.widget.selector.createStoreSelector();
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// init instance might change the store, so this must go before the controller initialization
|
|
51
|
-
this.widget.initInstance(context, this);
|
|
52
|
-
|
|
53
|
-
// initInstance can set the store, otherwise use parent store
|
|
54
|
-
if (!this.store) this.store = this.parentStore;
|
|
55
|
-
if (this.widget.onInit) this.widget.onInit(context, this);
|
|
56
|
-
|
|
57
|
-
this.widget.initState(context, this);
|
|
58
|
-
|
|
59
|
-
if (this.widget.controller)
|
|
60
|
-
this.controller = Controller.create(this.widget.controller, {
|
|
61
|
-
widget: this.widget,
|
|
62
|
-
instance: this,
|
|
63
|
-
store: this.store,
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
if (
|
|
67
|
-
this.widget.exploreCleanup ||
|
|
68
|
-
this.widget.outerLayout ||
|
|
69
|
-
this.widget.isContent ||
|
|
70
|
-
this.widget.controller ||
|
|
71
|
-
this.widget.prepareCleanup
|
|
72
|
-
)
|
|
73
|
-
this.needsExploreCleanup = true;
|
|
74
|
-
if (this.widget.prepare || this.widget.controller) this.needsPrepare = true;
|
|
75
|
-
if (this.widget.cleanup || this.widget.controller) this.needsCleanup = true;
|
|
76
|
-
this.initialized = true;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
checkVisible(context) {
|
|
80
|
-
if (!this.initialized) this.init(context);
|
|
81
|
-
|
|
82
|
-
let wasVisible = this.visible;
|
|
83
|
-
this.rawData = this.dataSelector(this.store);
|
|
84
|
-
this.visible = this.widget.checkVisible(context, this, this.rawData);
|
|
85
|
-
if (this.visible && !this.detached) this.parent.instanceCache.addChild(this);
|
|
86
|
-
this.explored = false;
|
|
87
|
-
this.prepared = false;
|
|
88
|
-
|
|
89
|
-
if (!this.visible && wasVisible) this.destroy();
|
|
90
|
-
|
|
91
|
-
return this.visible;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
scheduleExploreIfVisible(context) {
|
|
95
|
-
if (this.checkVisible(context)) {
|
|
96
|
-
context.exploreStack.push(this);
|
|
97
|
-
|
|
98
|
-
if (this.needsExploreCleanup) context.exploreStack.push(this);
|
|
99
|
-
|
|
100
|
-
return true;
|
|
101
|
-
}
|
|
102
|
-
return false;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
cache(key, value) {
|
|
106
|
-
let oldValue = this.cached[key];
|
|
107
|
-
if (oldValue === value) return false;
|
|
108
|
-
|
|
109
|
-
if (!this.cacheList) this.cacheList = {};
|
|
110
|
-
this.cacheList[key] = value;
|
|
111
|
-
return true;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
markShouldUpdate(context) {
|
|
115
|
-
let ins = this;
|
|
116
|
-
let renderList = this.renderList;
|
|
117
|
-
renderList.markReverseIndex();
|
|
118
|
-
|
|
119
|
-
//notify all parents that child state changed to bust up caching
|
|
120
|
-
while (ins && !ins.shouldUpdate && ins.explored) {
|
|
121
|
-
if (ins.renderList !== renderList) {
|
|
122
|
-
renderList.reverse();
|
|
123
|
-
renderList = ins.renderList;
|
|
124
|
-
renderList.markReverseIndex();
|
|
125
|
-
}
|
|
126
|
-
ins.shouldUpdate = true;
|
|
127
|
-
renderList.data.push(ins);
|
|
128
|
-
ins = ins.widget.isContent
|
|
129
|
-
? ins.contentPlaceholder
|
|
130
|
-
: ins.parent.outerLayout === ins
|
|
131
|
-
? ins.parent.parent
|
|
132
|
-
: ins.parent;
|
|
133
|
-
}
|
|
134
|
-
renderList.reverse();
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
explore(context) {
|
|
138
|
-
if (!this.visible) throw new Error("Explore invisible!");
|
|
139
|
-
|
|
140
|
-
if (this.explored) {
|
|
141
|
-
if (this.widget.prepareCleanup) context.prepareList.push(this);
|
|
142
|
-
|
|
143
|
-
if (this.widget.exploreCleanup) this.widget.exploreCleanup(context, this);
|
|
144
|
-
|
|
145
|
-
if (this.parent.outerLayout === this) context.popNamedValue("content", "body");
|
|
146
|
-
|
|
147
|
-
if (this.widget.controller) context.pop("controller");
|
|
148
|
-
|
|
149
|
-
return;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
this.explored = true;
|
|
153
|
-
|
|
154
|
-
if (this.needsPrepare) context.prepareList.push(this);
|
|
155
|
-
else this.prepared = true;
|
|
156
|
-
|
|
157
|
-
if (this.needsCleanup) context.cleanupList.push(this);
|
|
158
|
-
|
|
159
|
-
if (this.instanceCache) this.instanceCache.mark();
|
|
160
|
-
|
|
161
|
-
//controller may reconfigure the widget and need to go before shouldUpdate calculation
|
|
162
|
-
this.parentOptions = context.parentOptions;
|
|
163
|
-
|
|
164
|
-
if (!this.controller) {
|
|
165
|
-
if (context.controller) this.controller = context.controller;
|
|
166
|
-
else if (this.parent.controller) this.controller = this.parent.controller;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
this.destroyTracked = false;
|
|
170
|
-
|
|
171
|
-
if (this.controller) {
|
|
172
|
-
if (this.widget.controller) {
|
|
173
|
-
if (!this.controller.initialized) {
|
|
174
|
-
this.controller.init(context);
|
|
175
|
-
this.controller.initialized = true;
|
|
176
|
-
}
|
|
177
|
-
context.push("controller", this.controller);
|
|
178
|
-
this.controller.explore(context);
|
|
179
|
-
if (this.controller.onDestroy && this.controller.widget == this.widget) this.trackDestroy();
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
if (this.widget.onDestroy || isNonEmptyArray(this.destroySubscriptions)) this.trackDestroy();
|
|
184
|
-
|
|
185
|
-
this.renderList = this.assignedRenderList || this.parent.renderList || context.getRootRenderList();
|
|
186
|
-
|
|
187
|
-
let shouldUpdate =
|
|
188
|
-
this.rawData !== this.cached.rawData ||
|
|
189
|
-
this.state !== this.cached.state ||
|
|
190
|
-
this.widget.version !== this.cached.widgetVersion ||
|
|
191
|
-
this.cached.globalCacheIdentifier !== GlobalCacheIdentifier.get();
|
|
192
|
-
|
|
193
|
-
if (shouldUpdate) {
|
|
194
|
-
this.data = { ...this.rawData };
|
|
195
|
-
this.widget.prepareData(context, this);
|
|
196
|
-
debug(processDataFlag, this.widget);
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
//onExplore might set the outer layout
|
|
200
|
-
if (this.widget.onExplore) this.widget.onExplore(context, this);
|
|
201
|
-
|
|
202
|
-
if (this.parent.outerLayout === this) {
|
|
203
|
-
this.renderList = this.renderList.insertRight();
|
|
204
|
-
context.pushNamedValue("content", "body", this.parent);
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
if (this.widget.outerLayout) {
|
|
208
|
-
this.outerLayout = this.getChild(context, this.widget.outerLayout, null, this.store);
|
|
209
|
-
this.outerLayout.scheduleExploreIfVisible(context);
|
|
210
|
-
this.renderList = this.renderList.insertLeft();
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
if (this.widget.isContent) {
|
|
214
|
-
this.contentPlaceholder = context.contentPlaceholder && context.contentPlaceholder[this.widget.putInto];
|
|
215
|
-
if (this.contentPlaceholder) context.contentPlaceholder[this.widget.putInto](this);
|
|
216
|
-
else {
|
|
217
|
-
this.renderList = this.renderList.insertLeft();
|
|
218
|
-
context.pushNamedValue("content", this.widget.putInto, this);
|
|
219
|
-
if (!context.contentList) context.contentList = {};
|
|
220
|
-
let list = context.contentList[this.widget.putInto];
|
|
221
|
-
if (!list) list = context.contentList[this.widget.putInto] = [];
|
|
222
|
-
list.push(this);
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
this.shouldUpdate = false;
|
|
227
|
-
if (shouldUpdate || this.childStateDirty || !this.widget.memoize) this.markShouldUpdate(context);
|
|
228
|
-
|
|
229
|
-
context.exploreStack.hop();
|
|
230
|
-
|
|
231
|
-
if (this.widget.helpers) {
|
|
232
|
-
this.helpers = {};
|
|
233
|
-
for (let cmp in this.widget.helpers) {
|
|
234
|
-
let helper = this.widget.helpers[cmp];
|
|
235
|
-
if (helper) {
|
|
236
|
-
let ins = this.getChild(context, helper);
|
|
237
|
-
if (ins.scheduleExploreIfVisible(context)) this.helpers[cmp] = ins;
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
this.widget.explore(context, this, this.data);
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
prepare(context) {
|
|
246
|
-
if (!this.visible) throw new Error("Prepare invisible!");
|
|
247
|
-
|
|
248
|
-
if (this.prepared) {
|
|
249
|
-
if (this.widget.prepareCleanup) this.widget.prepareCleanup(context, this);
|
|
250
|
-
return;
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
this.prepared = true;
|
|
254
|
-
if (this.widget.prepare) this.widget.prepare(context, this);
|
|
255
|
-
|
|
256
|
-
if (this.widget.controller && this.controller.prepare) this.controller.prepare(context);
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
render(context) {
|
|
260
|
-
if (!this.visible) throw new Error("Render invisible!");
|
|
261
|
-
|
|
262
|
-
if (this.shouldUpdate) {
|
|
263
|
-
debug(renderFlag, this.widget, this.key);
|
|
264
|
-
let vdom = renderResultFix(this.widget.render(context, this, this.key));
|
|
265
|
-
if (this.widget.isContent || this.outerLayout) this.contentVDOM = vdom;
|
|
266
|
-
else this.vdom = vdom;
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
if (this.cacheList) for (let key in this.cacheList) this.cached[key] = this.cacheList[key];
|
|
270
|
-
|
|
271
|
-
this.cacheList = null;
|
|
272
|
-
|
|
273
|
-
this.cached.rawData = this.rawData;
|
|
274
|
-
this.cached.data = this.data;
|
|
275
|
-
this.cached.state = this.state;
|
|
276
|
-
this.cached.widgetVersion = this.widget.version;
|
|
277
|
-
this.cached.globalCacheIdentifier = GlobalCacheIdentifier.get();
|
|
278
|
-
this.childStateDirty = false;
|
|
279
|
-
|
|
280
|
-
if (this.instanceCache) this.instanceCache.sweep();
|
|
281
|
-
|
|
282
|
-
if (this.parent.outerLayout === this) {
|
|
283
|
-
//if outer layouts are chained we need to find the originating element (last element with OL set)
|
|
284
|
-
let parent = this.parent;
|
|
285
|
-
while (parent.parent.outerLayout == parent) parent = parent.parent;
|
|
286
|
-
parent.vdom = this.vdom;
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
return this.vdom;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
cleanup(context) {
|
|
293
|
-
if (this.widget.controller && this.controller.cleanup) this.controller.cleanup(context);
|
|
294
|
-
|
|
295
|
-
if (this.widget.cleanup) this.widget.cleanup(context, this);
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
trackDestroy() {
|
|
299
|
-
if (!this.destroyTracked) {
|
|
300
|
-
this.destroyTracked = true;
|
|
301
|
-
if (this.parent && !this.detached) this.parent.trackDestroyableChild(this);
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
trackDestroyableChild(child) {
|
|
306
|
-
this.instanceCache.trackDestroy(child);
|
|
307
|
-
this.trackDestroy();
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
subscribeOnDestroy(callback) {
|
|
311
|
-
if (!this.destroySubscriptions) this.destroySubscriptions = [];
|
|
312
|
-
this.destroySubscriptions.push(callback);
|
|
313
|
-
this.trackDestroy();
|
|
314
|
-
return () => {
|
|
315
|
-
this.destroySubscriptions && this.destroySubscriptions.filter((cb) => cb != callback);
|
|
316
|
-
};
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
destroy() {
|
|
320
|
-
if (this.instanceCache) {
|
|
321
|
-
this.instanceCache.destroy();
|
|
322
|
-
this.instanceCache = null;
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
if (this.destroySubscriptions) {
|
|
326
|
-
this.destroySubscriptions.forEach((cb) => cb());
|
|
327
|
-
this.destroySubscriptions = null;
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
if (this.destroyTracked) {
|
|
331
|
-
debug(destroyFlag, this);
|
|
332
|
-
|
|
333
|
-
if (this.widget.onDestroy) this.widget.onDestroy(this);
|
|
334
|
-
|
|
335
|
-
if (
|
|
336
|
-
this.widget.controller &&
|
|
337
|
-
this.controller &&
|
|
338
|
-
this.controller.onDestroy &&
|
|
339
|
-
this.controller.widget == this.widget
|
|
340
|
-
)
|
|
341
|
-
this.controller.onDestroy();
|
|
342
|
-
|
|
343
|
-
this.destroyTracked = false;
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
setState(state) {
|
|
348
|
-
let skip = !!this.state;
|
|
349
|
-
if (this.state)
|
|
350
|
-
for (let k in state) {
|
|
351
|
-
if (this.state[k] !== state[k]) {
|
|
352
|
-
skip = false;
|
|
353
|
-
break;
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
if (skip) return;
|
|
358
|
-
|
|
359
|
-
this.state = Object.assign({}, this.state, state);
|
|
360
|
-
let parent = this.parent;
|
|
361
|
-
//notify all parents that child state change to bust up caching
|
|
362
|
-
while (parent) {
|
|
363
|
-
parent.childStateDirty = true;
|
|
364
|
-
parent = parent.parent;
|
|
365
|
-
}
|
|
366
|
-
batchUpdates(() => {
|
|
367
|
-
this.store.notify();
|
|
368
|
-
});
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
set(prop, value, options = {}) {
|
|
372
|
-
//skip re-rendering (used for reading state from uncontrolled components)
|
|
373
|
-
if (options.internal && this.rawData) {
|
|
374
|
-
this.rawData[prop] = value;
|
|
375
|
-
this.data[prop] = value;
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
let setter = this.setters && this.setters[prop];
|
|
379
|
-
if (setter) {
|
|
380
|
-
if (options.immediate && isFunction(setter.reset)) setter.reset(value);
|
|
381
|
-
else setter(value);
|
|
382
|
-
return true;
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
let p = this.widget[prop];
|
|
386
|
-
if (p && typeof p == "object") {
|
|
387
|
-
if (p.debounce) {
|
|
388
|
-
this.definePropertySetter(
|
|
389
|
-
prop,
|
|
390
|
-
validatedDebounce(
|
|
391
|
-
(value) => this.doSet(prop, value),
|
|
392
|
-
() => this.dataSelector(this.store)[prop],
|
|
393
|
-
p.debounce,
|
|
394
|
-
),
|
|
395
|
-
);
|
|
396
|
-
this.set(prop, value, options);
|
|
397
|
-
return true;
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
if (p.throttle) {
|
|
401
|
-
this.definePropertySetter(
|
|
402
|
-
prop,
|
|
403
|
-
throttle((value) => this.doSet(prop, value), p.throttle),
|
|
404
|
-
);
|
|
405
|
-
this.set(prop, value, options);
|
|
406
|
-
return true;
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
return this.doSet(prop, value);
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
definePropertySetter(prop, setter) {
|
|
414
|
-
if (!this.setters) this.setters = {};
|
|
415
|
-
this.setters[prop] = setter;
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
doSet(prop, value) {
|
|
419
|
-
let changed = false;
|
|
420
|
-
batchUpdates(() => {
|
|
421
|
-
let p = this.widget[prop];
|
|
422
|
-
if (isObject(p)) {
|
|
423
|
-
if (p.set) {
|
|
424
|
-
if (isFunction(p.set)) {
|
|
425
|
-
p.set(value, this);
|
|
426
|
-
changed = true;
|
|
427
|
-
} else if (isString(p.set)) {
|
|
428
|
-
this.controller[p.set](value, this);
|
|
429
|
-
changed = true;
|
|
430
|
-
}
|
|
431
|
-
} else if (p.action) {
|
|
432
|
-
let action = p.action(value, this);
|
|
433
|
-
this.store.dispatch(action);
|
|
434
|
-
changed = true;
|
|
435
|
-
} else if (isString(p.bind) || isAccessorChain(p.bind)) {
|
|
436
|
-
changed = this.store.set(p.bind, value);
|
|
437
|
-
}
|
|
438
|
-
} else if (isAccessorChain(p)) changed = this.store.set(p.toString(), value);
|
|
439
|
-
});
|
|
440
|
-
return changed;
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
nestedDataSet(key, value, dataConfig, useParentStore) {
|
|
444
|
-
let config = dataConfig[key];
|
|
445
|
-
if (!config)
|
|
446
|
-
throw new Error(`Unknown nested data key ${key}. Known keys are ${Object.keys(dataConfig).join(", ")}.`);
|
|
447
|
-
|
|
448
|
-
if (isAccessorChain(config)) config = { bind: config.toString() };
|
|
449
|
-
|
|
450
|
-
if (config.bind) {
|
|
451
|
-
let store = this.store;
|
|
452
|
-
//in case of Rescope or DataProxy, bindings point to the data in the parent store
|
|
453
|
-
if (useParentStore && store.store) store = store.store;
|
|
454
|
-
return isUndefined(value) ? store.deleteItem(config.bind) : store.setItem(config.bind, value);
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
if (!config.set)
|
|
458
|
-
throw new Error(
|
|
459
|
-
`Cannot change nested data value for ${key} as it's read-only. Either define it as a binding or define a set function.`,
|
|
460
|
-
);
|
|
461
|
-
if (isString(config.set)) this.getControllerMethod(config.set)(value, this);
|
|
462
|
-
else if (isFunction(config.set)) config.set(value, this);
|
|
463
|
-
else
|
|
464
|
-
throw new Error(
|
|
465
|
-
`Cannot change nested data value for ${key} the defined setter is neither a function nor a controller method.`,
|
|
466
|
-
);
|
|
467
|
-
|
|
468
|
-
return true;
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
replaceState(state) {
|
|
472
|
-
this.cached.state = this.state;
|
|
473
|
-
this.state = state;
|
|
474
|
-
this.store.notify();
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
getInstanceCache() {
|
|
478
|
-
if (!this.instanceCache)
|
|
479
|
-
this.instanceCache = new InstanceCache(this, this.widget.isPureContainer ? this.key : null);
|
|
480
|
-
return this.instanceCache;
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
clearChildrenCache() {
|
|
484
|
-
if (this.instanceCache) this.instanceCache.destroy();
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
getChild(context, widget, key, store) {
|
|
488
|
-
return this.getInstanceCache().getChild(widget, store ?? this.store, key);
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
getDetachedChild(widget, key, store) {
|
|
492
|
-
let child = new Instance(widget, key, this, store ?? this.store);
|
|
493
|
-
child.detached = true;
|
|
494
|
-
return child;
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
prepareRenderCleanupChild(widget, store, keyPrefix, options) {
|
|
498
|
-
return widget.prepareRenderCleanup(store ?? this.store, options, keyPrefix, this);
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
getJsxEventProps() {
|
|
502
|
-
let { widget } = this;
|
|
503
|
-
|
|
504
|
-
if (!isArray(widget.jsxAttributes)) return null;
|
|
505
|
-
|
|
506
|
-
let props = {};
|
|
507
|
-
widget.jsxAttributes.forEach((attr) => {
|
|
508
|
-
if (attr.indexOf("on") == 0 && attr.length > 2) props[attr] = (e) => this.invoke(attr, e, this);
|
|
509
|
-
});
|
|
510
|
-
return props;
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
getCallback(methodName) {
|
|
514
|
-
let scope = this.widget;
|
|
515
|
-
let callback = scope[methodName];
|
|
516
|
-
|
|
517
|
-
if (typeof callback === "string") return this.getControllerMethod(callback);
|
|
518
|
-
|
|
519
|
-
if (typeof callback !== "function")
|
|
520
|
-
throw new Error(`Cannot invoke callback method ${methodName} as assigned value is not a function.`);
|
|
521
|
-
|
|
522
|
-
return callback.bind(scope);
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
getControllerMethod(methodName) {
|
|
526
|
-
if (!this.controller)
|
|
527
|
-
throw new Error(
|
|
528
|
-
`Cannot invoke controller method "${methodName}" as controller is not assigned to the widget.`,
|
|
529
|
-
);
|
|
530
|
-
|
|
531
|
-
let at = this;
|
|
532
|
-
while (at != null && at.controller && !at.controller[methodName]) at = at.parent;
|
|
533
|
-
|
|
534
|
-
if (!at || !at.controller || !at.controller[methodName])
|
|
535
|
-
throw new Error(
|
|
536
|
-
`Cannot invoke controller method "${methodName}". The method cannot be found in any of the assigned controllers.`,
|
|
537
|
-
);
|
|
538
|
-
|
|
539
|
-
return at.controller[methodName].bind(at.controller);
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
invoke(methodName, ...args) {
|
|
543
|
-
return this.getCallback(methodName).apply(null, args);
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
invokeControllerMethod(methodName, ...args) {
|
|
547
|
-
return this.getControllerMethod(methodName).apply(null, args);
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
function renderResultFix(res) {
|
|
552
|
-
return res != null && isDefined(res.content) ? res : { content: res };
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
export class InstanceCache {
|
|
556
|
-
constructor(parent, keyPrefix) {
|
|
557
|
-
this.children = {};
|
|
558
|
-
this.parent = parent;
|
|
559
|
-
this.marked = {};
|
|
560
|
-
this.monitored = null;
|
|
561
|
-
this.keyPrefix = keyPrefix != null ? keyPrefix + "-" : "";
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
getChild(widget, parentStore, key) {
|
|
565
|
-
let k = this.keyPrefix + (key != null ? key : widget.vdomKey || widget.widgetId);
|
|
566
|
-
let instance = this.children[k];
|
|
567
|
-
|
|
568
|
-
if (
|
|
569
|
-
!instance ||
|
|
570
|
-
instance.widget !== widget ||
|
|
571
|
-
(!instance.visible && (instance.widget.controller || instance.widget.onInit))
|
|
572
|
-
) {
|
|
573
|
-
instance = new Instance(widget, k, this.parent, parentStore);
|
|
574
|
-
this.children[k] = instance;
|
|
575
|
-
} else if (instance.parentStore !== parentStore) {
|
|
576
|
-
instance.setParentStore(parentStore);
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
return instance;
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
addChild(instance) {
|
|
583
|
-
this.marked[instance.key] = instance;
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
mark() {
|
|
587
|
-
this.marked = {};
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
trackDestroy(instance) {
|
|
591
|
-
if (!this.monitored) this.monitored = {};
|
|
592
|
-
this.monitored[instance.key] = instance;
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
destroy() {
|
|
596
|
-
this.children = {};
|
|
597
|
-
this.marked = {};
|
|
598
|
-
|
|
599
|
-
if (!this.monitored) return;
|
|
600
|
-
|
|
601
|
-
for (let key in this.monitored) {
|
|
602
|
-
this.monitored[key].destroy();
|
|
603
|
-
}
|
|
604
|
-
|
|
605
|
-
this.monitored = null;
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
sweep() {
|
|
609
|
-
this.children = this.marked;
|
|
610
|
-
if (!this.monitored) return;
|
|
611
|
-
let activeCount = 0;
|
|
612
|
-
for (let key in this.monitored) {
|
|
613
|
-
let monitoredChild = this.monitored[key];
|
|
614
|
-
let child = this.children[key];
|
|
615
|
-
if (child !== monitoredChild || !monitoredChild.visible) {
|
|
616
|
-
monitoredChild.destroy();
|
|
617
|
-
delete this.monitored[key];
|
|
618
|
-
if (child === monitoredChild) delete this.children[key];
|
|
619
|
-
} else activeCount++;
|
|
620
|
-
}
|
|
621
|
-
if (activeCount === 0) this.monitored = null;
|
|
622
|
-
}
|
|
623
|
-
}
|
|
1
|
+
import { Controller } from "./Controller";
|
|
2
|
+
import { debug, renderFlag, processDataFlag, destroyFlag } from "../util/Debug";
|
|
3
|
+
import { GlobalCacheIdentifier } from "../util/GlobalCacheIdentifier";
|
|
4
|
+
import { throttle } from "../util/throttle";
|
|
5
|
+
import { validatedDebounce } from "../util/validatedDebounce";
|
|
6
|
+
import { batchUpdates } from "./batchUpdates";
|
|
7
|
+
import { isString } from "../util/isString";
|
|
8
|
+
import { isFunction } from "../util/isFunction";
|
|
9
|
+
import { isDefined } from "../util/isDefined";
|
|
10
|
+
import { isArray } from "../util/isArray";
|
|
11
|
+
import { isObject } from "../util/isObject";
|
|
12
|
+
import { isNonEmptyArray } from "../util/isNonEmptyArray";
|
|
13
|
+
import { isUndefined } from "../util/isUndefined";
|
|
14
|
+
import { isAccessorChain } from "../data/createAccessorModelProxy";
|
|
15
|
+
|
|
16
|
+
let instanceId = 1000;
|
|
17
|
+
|
|
18
|
+
export class Instance {
|
|
19
|
+
constructor(widget, key, parent, parentStore) {
|
|
20
|
+
this.widget = widget;
|
|
21
|
+
this.key = key;
|
|
22
|
+
this.id = String(++instanceId);
|
|
23
|
+
this.cached = {};
|
|
24
|
+
this.parent = parent;
|
|
25
|
+
this.parentStore = parentStore ?? parent?.store;
|
|
26
|
+
|
|
27
|
+
if (this.parentStore == null) throw new Error("Cannot create instance without a parent store.");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
setParentStore(parentStore) {
|
|
31
|
+
this.parentStore = parentStore;
|
|
32
|
+
this.widget.applyParentStore(this);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
init(context) {
|
|
36
|
+
// widget is initialized when the first instance is initialized
|
|
37
|
+
if (!this.widget.initialized) {
|
|
38
|
+
this.widget.init(context);
|
|
39
|
+
|
|
40
|
+
// init default values
|
|
41
|
+
this.widget.selector.init(this.parentStore);
|
|
42
|
+
|
|
43
|
+
this.widget.initialized = true;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (!this.dataSelector) {
|
|
47
|
+
this.dataSelector = this.widget.selector.createStoreSelector();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// init instance might change the store, so this must go before the controller initialization
|
|
51
|
+
this.widget.initInstance(context, this);
|
|
52
|
+
|
|
53
|
+
// initInstance can set the store, otherwise use parent store
|
|
54
|
+
if (!this.store) this.store = this.parentStore;
|
|
55
|
+
if (this.widget.onInit) this.widget.onInit(context, this);
|
|
56
|
+
|
|
57
|
+
this.widget.initState(context, this);
|
|
58
|
+
|
|
59
|
+
if (this.widget.controller)
|
|
60
|
+
this.controller = Controller.create(this.widget.controller, {
|
|
61
|
+
widget: this.widget,
|
|
62
|
+
instance: this,
|
|
63
|
+
store: this.store,
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
if (
|
|
67
|
+
this.widget.exploreCleanup ||
|
|
68
|
+
this.widget.outerLayout ||
|
|
69
|
+
this.widget.isContent ||
|
|
70
|
+
this.widget.controller ||
|
|
71
|
+
this.widget.prepareCleanup
|
|
72
|
+
)
|
|
73
|
+
this.needsExploreCleanup = true;
|
|
74
|
+
if (this.widget.prepare || this.widget.controller) this.needsPrepare = true;
|
|
75
|
+
if (this.widget.cleanup || this.widget.controller) this.needsCleanup = true;
|
|
76
|
+
this.initialized = true;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
checkVisible(context) {
|
|
80
|
+
if (!this.initialized) this.init(context);
|
|
81
|
+
|
|
82
|
+
let wasVisible = this.visible;
|
|
83
|
+
this.rawData = this.dataSelector(this.store);
|
|
84
|
+
this.visible = this.widget.checkVisible(context, this, this.rawData);
|
|
85
|
+
if (this.visible && !this.detached) this.parent.instanceCache.addChild(this);
|
|
86
|
+
this.explored = false;
|
|
87
|
+
this.prepared = false;
|
|
88
|
+
|
|
89
|
+
if (!this.visible && wasVisible) this.destroy();
|
|
90
|
+
|
|
91
|
+
return this.visible;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
scheduleExploreIfVisible(context) {
|
|
95
|
+
if (this.checkVisible(context)) {
|
|
96
|
+
context.exploreStack.push(this);
|
|
97
|
+
|
|
98
|
+
if (this.needsExploreCleanup) context.exploreStack.push(this);
|
|
99
|
+
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
cache(key, value) {
|
|
106
|
+
let oldValue = this.cached[key];
|
|
107
|
+
if (oldValue === value) return false;
|
|
108
|
+
|
|
109
|
+
if (!this.cacheList) this.cacheList = {};
|
|
110
|
+
this.cacheList[key] = value;
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
markShouldUpdate(context) {
|
|
115
|
+
let ins = this;
|
|
116
|
+
let renderList = this.renderList;
|
|
117
|
+
renderList.markReverseIndex();
|
|
118
|
+
|
|
119
|
+
//notify all parents that child state changed to bust up caching
|
|
120
|
+
while (ins && !ins.shouldUpdate && ins.explored) {
|
|
121
|
+
if (ins.renderList !== renderList) {
|
|
122
|
+
renderList.reverse();
|
|
123
|
+
renderList = ins.renderList;
|
|
124
|
+
renderList.markReverseIndex();
|
|
125
|
+
}
|
|
126
|
+
ins.shouldUpdate = true;
|
|
127
|
+
renderList.data.push(ins);
|
|
128
|
+
ins = ins.widget.isContent
|
|
129
|
+
? ins.contentPlaceholder
|
|
130
|
+
: ins.parent.outerLayout === ins
|
|
131
|
+
? ins.parent.parent
|
|
132
|
+
: ins.parent;
|
|
133
|
+
}
|
|
134
|
+
renderList.reverse();
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
explore(context) {
|
|
138
|
+
if (!this.visible) throw new Error("Explore invisible!");
|
|
139
|
+
|
|
140
|
+
if (this.explored) {
|
|
141
|
+
if (this.widget.prepareCleanup) context.prepareList.push(this);
|
|
142
|
+
|
|
143
|
+
if (this.widget.exploreCleanup) this.widget.exploreCleanup(context, this);
|
|
144
|
+
|
|
145
|
+
if (this.parent.outerLayout === this) context.popNamedValue("content", "body");
|
|
146
|
+
|
|
147
|
+
if (this.widget.controller) context.pop("controller");
|
|
148
|
+
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
this.explored = true;
|
|
153
|
+
|
|
154
|
+
if (this.needsPrepare) context.prepareList.push(this);
|
|
155
|
+
else this.prepared = true;
|
|
156
|
+
|
|
157
|
+
if (this.needsCleanup) context.cleanupList.push(this);
|
|
158
|
+
|
|
159
|
+
if (this.instanceCache) this.instanceCache.mark();
|
|
160
|
+
|
|
161
|
+
//controller may reconfigure the widget and need to go before shouldUpdate calculation
|
|
162
|
+
this.parentOptions = context.parentOptions;
|
|
163
|
+
|
|
164
|
+
if (!this.controller) {
|
|
165
|
+
if (context.controller) this.controller = context.controller;
|
|
166
|
+
else if (this.parent.controller) this.controller = this.parent.controller;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
this.destroyTracked = false;
|
|
170
|
+
|
|
171
|
+
if (this.controller) {
|
|
172
|
+
if (this.widget.controller) {
|
|
173
|
+
if (!this.controller.initialized) {
|
|
174
|
+
this.controller.init(context);
|
|
175
|
+
this.controller.initialized = true;
|
|
176
|
+
}
|
|
177
|
+
context.push("controller", this.controller);
|
|
178
|
+
this.controller.explore(context);
|
|
179
|
+
if (this.controller.onDestroy && this.controller.widget == this.widget) this.trackDestroy();
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (this.widget.onDestroy || isNonEmptyArray(this.destroySubscriptions)) this.trackDestroy();
|
|
184
|
+
|
|
185
|
+
this.renderList = this.assignedRenderList || this.parent.renderList || context.getRootRenderList();
|
|
186
|
+
|
|
187
|
+
let shouldUpdate =
|
|
188
|
+
this.rawData !== this.cached.rawData ||
|
|
189
|
+
this.state !== this.cached.state ||
|
|
190
|
+
this.widget.version !== this.cached.widgetVersion ||
|
|
191
|
+
this.cached.globalCacheIdentifier !== GlobalCacheIdentifier.get();
|
|
192
|
+
|
|
193
|
+
if (shouldUpdate) {
|
|
194
|
+
this.data = { ...this.rawData };
|
|
195
|
+
this.widget.prepareData(context, this);
|
|
196
|
+
debug(processDataFlag, this.widget);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
//onExplore might set the outer layout
|
|
200
|
+
if (this.widget.onExplore) this.widget.onExplore(context, this);
|
|
201
|
+
|
|
202
|
+
if (this.parent.outerLayout === this) {
|
|
203
|
+
this.renderList = this.renderList.insertRight();
|
|
204
|
+
context.pushNamedValue("content", "body", this.parent);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (this.widget.outerLayout) {
|
|
208
|
+
this.outerLayout = this.getChild(context, this.widget.outerLayout, null, this.store);
|
|
209
|
+
this.outerLayout.scheduleExploreIfVisible(context);
|
|
210
|
+
this.renderList = this.renderList.insertLeft();
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (this.widget.isContent) {
|
|
214
|
+
this.contentPlaceholder = context.contentPlaceholder && context.contentPlaceholder[this.widget.putInto];
|
|
215
|
+
if (this.contentPlaceholder) context.contentPlaceholder[this.widget.putInto](this);
|
|
216
|
+
else {
|
|
217
|
+
this.renderList = this.renderList.insertLeft();
|
|
218
|
+
context.pushNamedValue("content", this.widget.putInto, this);
|
|
219
|
+
if (!context.contentList) context.contentList = {};
|
|
220
|
+
let list = context.contentList[this.widget.putInto];
|
|
221
|
+
if (!list) list = context.contentList[this.widget.putInto] = [];
|
|
222
|
+
list.push(this);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
this.shouldUpdate = false;
|
|
227
|
+
if (shouldUpdate || this.childStateDirty || !this.widget.memoize) this.markShouldUpdate(context);
|
|
228
|
+
|
|
229
|
+
context.exploreStack.hop();
|
|
230
|
+
|
|
231
|
+
if (this.widget.helpers) {
|
|
232
|
+
this.helpers = {};
|
|
233
|
+
for (let cmp in this.widget.helpers) {
|
|
234
|
+
let helper = this.widget.helpers[cmp];
|
|
235
|
+
if (helper) {
|
|
236
|
+
let ins = this.getChild(context, helper);
|
|
237
|
+
if (ins.scheduleExploreIfVisible(context)) this.helpers[cmp] = ins;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
this.widget.explore(context, this, this.data);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
prepare(context) {
|
|
246
|
+
if (!this.visible) throw new Error("Prepare invisible!");
|
|
247
|
+
|
|
248
|
+
if (this.prepared) {
|
|
249
|
+
if (this.widget.prepareCleanup) this.widget.prepareCleanup(context, this);
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
this.prepared = true;
|
|
254
|
+
if (this.widget.prepare) this.widget.prepare(context, this);
|
|
255
|
+
|
|
256
|
+
if (this.widget.controller && this.controller.prepare) this.controller.prepare(context);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
render(context) {
|
|
260
|
+
if (!this.visible) throw new Error("Render invisible!");
|
|
261
|
+
|
|
262
|
+
if (this.shouldUpdate) {
|
|
263
|
+
debug(renderFlag, this.widget, this.key);
|
|
264
|
+
let vdom = renderResultFix(this.widget.render(context, this, this.key));
|
|
265
|
+
if (this.widget.isContent || this.outerLayout) this.contentVDOM = vdom;
|
|
266
|
+
else this.vdom = vdom;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (this.cacheList) for (let key in this.cacheList) this.cached[key] = this.cacheList[key];
|
|
270
|
+
|
|
271
|
+
this.cacheList = null;
|
|
272
|
+
|
|
273
|
+
this.cached.rawData = this.rawData;
|
|
274
|
+
this.cached.data = this.data;
|
|
275
|
+
this.cached.state = this.state;
|
|
276
|
+
this.cached.widgetVersion = this.widget.version;
|
|
277
|
+
this.cached.globalCacheIdentifier = GlobalCacheIdentifier.get();
|
|
278
|
+
this.childStateDirty = false;
|
|
279
|
+
|
|
280
|
+
if (this.instanceCache) this.instanceCache.sweep();
|
|
281
|
+
|
|
282
|
+
if (this.parent.outerLayout === this) {
|
|
283
|
+
//if outer layouts are chained we need to find the originating element (last element with OL set)
|
|
284
|
+
let parent = this.parent;
|
|
285
|
+
while (parent.parent.outerLayout == parent) parent = parent.parent;
|
|
286
|
+
parent.vdom = this.vdom;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return this.vdom;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
cleanup(context) {
|
|
293
|
+
if (this.widget.controller && this.controller.cleanup) this.controller.cleanup(context);
|
|
294
|
+
|
|
295
|
+
if (this.widget.cleanup) this.widget.cleanup(context, this);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
trackDestroy() {
|
|
299
|
+
if (!this.destroyTracked) {
|
|
300
|
+
this.destroyTracked = true;
|
|
301
|
+
if (this.parent && !this.detached) this.parent.trackDestroyableChild(this);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
trackDestroyableChild(child) {
|
|
306
|
+
this.instanceCache.trackDestroy(child);
|
|
307
|
+
this.trackDestroy();
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
subscribeOnDestroy(callback) {
|
|
311
|
+
if (!this.destroySubscriptions) this.destroySubscriptions = [];
|
|
312
|
+
this.destroySubscriptions.push(callback);
|
|
313
|
+
this.trackDestroy();
|
|
314
|
+
return () => {
|
|
315
|
+
this.destroySubscriptions && this.destroySubscriptions.filter((cb) => cb != callback);
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
destroy() {
|
|
320
|
+
if (this.instanceCache) {
|
|
321
|
+
this.instanceCache.destroy();
|
|
322
|
+
this.instanceCache = null;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
if (this.destroySubscriptions) {
|
|
326
|
+
this.destroySubscriptions.forEach((cb) => cb());
|
|
327
|
+
this.destroySubscriptions = null;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
if (this.destroyTracked) {
|
|
331
|
+
debug(destroyFlag, this);
|
|
332
|
+
|
|
333
|
+
if (this.widget.onDestroy) this.widget.onDestroy(this);
|
|
334
|
+
|
|
335
|
+
if (
|
|
336
|
+
this.widget.controller &&
|
|
337
|
+
this.controller &&
|
|
338
|
+
this.controller.onDestroy &&
|
|
339
|
+
this.controller.widget == this.widget
|
|
340
|
+
)
|
|
341
|
+
this.controller.onDestroy();
|
|
342
|
+
|
|
343
|
+
this.destroyTracked = false;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
setState(state) {
|
|
348
|
+
let skip = !!this.state;
|
|
349
|
+
if (this.state)
|
|
350
|
+
for (let k in state) {
|
|
351
|
+
if (this.state[k] !== state[k]) {
|
|
352
|
+
skip = false;
|
|
353
|
+
break;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
if (skip) return;
|
|
358
|
+
|
|
359
|
+
this.state = Object.assign({}, this.state, state);
|
|
360
|
+
let parent = this.parent;
|
|
361
|
+
//notify all parents that child state change to bust up caching
|
|
362
|
+
while (parent) {
|
|
363
|
+
parent.childStateDirty = true;
|
|
364
|
+
parent = parent.parent;
|
|
365
|
+
}
|
|
366
|
+
batchUpdates(() => {
|
|
367
|
+
this.store.notify();
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
set(prop, value, options = {}) {
|
|
372
|
+
//skip re-rendering (used for reading state from uncontrolled components)
|
|
373
|
+
if (options.internal && this.rawData) {
|
|
374
|
+
this.rawData[prop] = value;
|
|
375
|
+
this.data[prop] = value;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
let setter = this.setters && this.setters[prop];
|
|
379
|
+
if (setter) {
|
|
380
|
+
if (options.immediate && isFunction(setter.reset)) setter.reset(value);
|
|
381
|
+
else setter(value);
|
|
382
|
+
return true;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
let p = this.widget[prop];
|
|
386
|
+
if (p && typeof p == "object") {
|
|
387
|
+
if (p.debounce) {
|
|
388
|
+
this.definePropertySetter(
|
|
389
|
+
prop,
|
|
390
|
+
validatedDebounce(
|
|
391
|
+
(value) => this.doSet(prop, value),
|
|
392
|
+
() => this.dataSelector(this.store)[prop],
|
|
393
|
+
p.debounce,
|
|
394
|
+
),
|
|
395
|
+
);
|
|
396
|
+
this.set(prop, value, options);
|
|
397
|
+
return true;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
if (p.throttle) {
|
|
401
|
+
this.definePropertySetter(
|
|
402
|
+
prop,
|
|
403
|
+
throttle((value) => this.doSet(prop, value), p.throttle),
|
|
404
|
+
);
|
|
405
|
+
this.set(prop, value, options);
|
|
406
|
+
return true;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
return this.doSet(prop, value);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
definePropertySetter(prop, setter) {
|
|
414
|
+
if (!this.setters) this.setters = {};
|
|
415
|
+
this.setters[prop] = setter;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
doSet(prop, value) {
|
|
419
|
+
let changed = false;
|
|
420
|
+
batchUpdates(() => {
|
|
421
|
+
let p = this.widget[prop];
|
|
422
|
+
if (isObject(p)) {
|
|
423
|
+
if (p.set) {
|
|
424
|
+
if (isFunction(p.set)) {
|
|
425
|
+
p.set(value, this);
|
|
426
|
+
changed = true;
|
|
427
|
+
} else if (isString(p.set)) {
|
|
428
|
+
this.controller[p.set](value, this);
|
|
429
|
+
changed = true;
|
|
430
|
+
}
|
|
431
|
+
} else if (p.action) {
|
|
432
|
+
let action = p.action(value, this);
|
|
433
|
+
this.store.dispatch(action);
|
|
434
|
+
changed = true;
|
|
435
|
+
} else if (isString(p.bind) || isAccessorChain(p.bind)) {
|
|
436
|
+
changed = this.store.set(p.bind, value);
|
|
437
|
+
}
|
|
438
|
+
} else if (isAccessorChain(p)) changed = this.store.set(p.toString(), value);
|
|
439
|
+
});
|
|
440
|
+
return changed;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
nestedDataSet(key, value, dataConfig, useParentStore) {
|
|
444
|
+
let config = dataConfig[key];
|
|
445
|
+
if (!config)
|
|
446
|
+
throw new Error(`Unknown nested data key ${key}. Known keys are ${Object.keys(dataConfig).join(", ")}.`);
|
|
447
|
+
|
|
448
|
+
if (isAccessorChain(config)) config = { bind: config.toString() };
|
|
449
|
+
|
|
450
|
+
if (config.bind) {
|
|
451
|
+
let store = this.store;
|
|
452
|
+
//in case of Rescope or DataProxy, bindings point to the data in the parent store
|
|
453
|
+
if (useParentStore && store.store) store = store.store;
|
|
454
|
+
return isUndefined(value) ? store.deleteItem(config.bind) : store.setItem(config.bind, value);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
if (!config.set)
|
|
458
|
+
throw new Error(
|
|
459
|
+
`Cannot change nested data value for ${key} as it's read-only. Either define it as a binding or define a set function.`,
|
|
460
|
+
);
|
|
461
|
+
if (isString(config.set)) this.getControllerMethod(config.set)(value, this);
|
|
462
|
+
else if (isFunction(config.set)) config.set(value, this);
|
|
463
|
+
else
|
|
464
|
+
throw new Error(
|
|
465
|
+
`Cannot change nested data value for ${key} the defined setter is neither a function nor a controller method.`,
|
|
466
|
+
);
|
|
467
|
+
|
|
468
|
+
return true;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
replaceState(state) {
|
|
472
|
+
this.cached.state = this.state;
|
|
473
|
+
this.state = state;
|
|
474
|
+
this.store.notify();
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
getInstanceCache() {
|
|
478
|
+
if (!this.instanceCache)
|
|
479
|
+
this.instanceCache = new InstanceCache(this, this.widget.isPureContainer ? this.key : null);
|
|
480
|
+
return this.instanceCache;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
clearChildrenCache() {
|
|
484
|
+
if (this.instanceCache) this.instanceCache.destroy();
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
getChild(context, widget, key, store) {
|
|
488
|
+
return this.getInstanceCache().getChild(widget, store ?? this.store, key);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
getDetachedChild(widget, key, store) {
|
|
492
|
+
let child = new Instance(widget, key, this, store ?? this.store);
|
|
493
|
+
child.detached = true;
|
|
494
|
+
return child;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
prepareRenderCleanupChild(widget, store, keyPrefix, options) {
|
|
498
|
+
return widget.prepareRenderCleanup(store ?? this.store, options, keyPrefix, this);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
getJsxEventProps() {
|
|
502
|
+
let { widget } = this;
|
|
503
|
+
|
|
504
|
+
if (!isArray(widget.jsxAttributes)) return null;
|
|
505
|
+
|
|
506
|
+
let props = {};
|
|
507
|
+
widget.jsxAttributes.forEach((attr) => {
|
|
508
|
+
if (attr.indexOf("on") == 0 && attr.length > 2) props[attr] = (e) => this.invoke(attr, e, this);
|
|
509
|
+
});
|
|
510
|
+
return props;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
getCallback(methodName) {
|
|
514
|
+
let scope = this.widget;
|
|
515
|
+
let callback = scope[methodName];
|
|
516
|
+
|
|
517
|
+
if (typeof callback === "string") return this.getControllerMethod(callback);
|
|
518
|
+
|
|
519
|
+
if (typeof callback !== "function")
|
|
520
|
+
throw new Error(`Cannot invoke callback method ${methodName} as assigned value is not a function.`);
|
|
521
|
+
|
|
522
|
+
return callback.bind(scope);
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
getControllerMethod(methodName) {
|
|
526
|
+
if (!this.controller)
|
|
527
|
+
throw new Error(
|
|
528
|
+
`Cannot invoke controller method "${methodName}" as controller is not assigned to the widget.`,
|
|
529
|
+
);
|
|
530
|
+
|
|
531
|
+
let at = this;
|
|
532
|
+
while (at != null && at.controller && !at.controller[methodName]) at = at.parent;
|
|
533
|
+
|
|
534
|
+
if (!at || !at.controller || !at.controller[methodName])
|
|
535
|
+
throw new Error(
|
|
536
|
+
`Cannot invoke controller method "${methodName}". The method cannot be found in any of the assigned controllers.`,
|
|
537
|
+
);
|
|
538
|
+
|
|
539
|
+
return at.controller[methodName].bind(at.controller);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
invoke(methodName, ...args) {
|
|
543
|
+
return this.getCallback(methodName).apply(null, args);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
invokeControllerMethod(methodName, ...args) {
|
|
547
|
+
return this.getControllerMethod(methodName).apply(null, args);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
function renderResultFix(res) {
|
|
552
|
+
return res != null && isDefined(res.content) ? res : { content: res };
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
export class InstanceCache {
|
|
556
|
+
constructor(parent, keyPrefix) {
|
|
557
|
+
this.children = {};
|
|
558
|
+
this.parent = parent;
|
|
559
|
+
this.marked = {};
|
|
560
|
+
this.monitored = null;
|
|
561
|
+
this.keyPrefix = keyPrefix != null ? keyPrefix + "-" : "";
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
getChild(widget, parentStore, key) {
|
|
565
|
+
let k = this.keyPrefix + (key != null ? key : widget.vdomKey || widget.widgetId);
|
|
566
|
+
let instance = this.children[k];
|
|
567
|
+
|
|
568
|
+
if (
|
|
569
|
+
!instance ||
|
|
570
|
+
instance.widget !== widget ||
|
|
571
|
+
(!instance.visible && (instance.widget.controller || instance.widget.onInit))
|
|
572
|
+
) {
|
|
573
|
+
instance = new Instance(widget, k, this.parent, parentStore);
|
|
574
|
+
this.children[k] = instance;
|
|
575
|
+
} else if (instance.parentStore !== parentStore) {
|
|
576
|
+
instance.setParentStore(parentStore);
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
return instance;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
addChild(instance) {
|
|
583
|
+
this.marked[instance.key] = instance;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
mark() {
|
|
587
|
+
this.marked = {};
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
trackDestroy(instance) {
|
|
591
|
+
if (!this.monitored) this.monitored = {};
|
|
592
|
+
this.monitored[instance.key] = instance;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
destroy() {
|
|
596
|
+
this.children = {};
|
|
597
|
+
this.marked = {};
|
|
598
|
+
|
|
599
|
+
if (!this.monitored) return;
|
|
600
|
+
|
|
601
|
+
for (let key in this.monitored) {
|
|
602
|
+
this.monitored[key].destroy();
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
this.monitored = null;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
sweep() {
|
|
609
|
+
this.children = this.marked;
|
|
610
|
+
if (!this.monitored) return;
|
|
611
|
+
let activeCount = 0;
|
|
612
|
+
for (let key in this.monitored) {
|
|
613
|
+
let monitoredChild = this.monitored[key];
|
|
614
|
+
let child = this.children[key];
|
|
615
|
+
if (child !== monitoredChild || !monitoredChild.visible) {
|
|
616
|
+
monitoredChild.destroy();
|
|
617
|
+
delete this.monitored[key];
|
|
618
|
+
if (child === monitoredChild) delete this.children[key];
|
|
619
|
+
} else activeCount++;
|
|
620
|
+
}
|
|
621
|
+
if (activeCount === 0) this.monitored = null;
|
|
622
|
+
}
|
|
623
|
+
}
|