onelaraveljs 1.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 (67) hide show
  1. package/README.md +87 -0
  2. package/docs/integration_analysis.md +116 -0
  3. package/docs/onejs_analysis.md +108 -0
  4. package/docs/optimization_implementation_group2.md +458 -0
  5. package/docs/optimization_plan.md +130 -0
  6. package/index.js +16 -0
  7. package/package.json +13 -0
  8. package/src/app.js +61 -0
  9. package/src/core/API.js +72 -0
  10. package/src/core/ChildrenRegistry.js +410 -0
  11. package/src/core/DOMBatcher.js +207 -0
  12. package/src/core/ErrorBoundary.js +226 -0
  13. package/src/core/EventDelegator.js +416 -0
  14. package/src/core/Helper.js +817 -0
  15. package/src/core/LoopContext.js +97 -0
  16. package/src/core/OneDOM.js +246 -0
  17. package/src/core/OneMarkup.js +444 -0
  18. package/src/core/Router.js +996 -0
  19. package/src/core/SEOConfig.js +321 -0
  20. package/src/core/SectionEngine.js +75 -0
  21. package/src/core/TemplateEngine.js +83 -0
  22. package/src/core/View.js +273 -0
  23. package/src/core/ViewConfig.js +229 -0
  24. package/src/core/ViewController.js +1410 -0
  25. package/src/core/ViewControllerOptimized.js +164 -0
  26. package/src/core/ViewIdentifier.js +361 -0
  27. package/src/core/ViewLoader.js +272 -0
  28. package/src/core/ViewManager.js +1962 -0
  29. package/src/core/ViewState.js +761 -0
  30. package/src/core/ViewSystem.js +301 -0
  31. package/src/core/ViewTemplate.js +4 -0
  32. package/src/core/helpers/BindingHelper.js +239 -0
  33. package/src/core/helpers/ConfigHelper.js +37 -0
  34. package/src/core/helpers/EventHelper.js +172 -0
  35. package/src/core/helpers/LifecycleHelper.js +17 -0
  36. package/src/core/helpers/ReactiveHelper.js +169 -0
  37. package/src/core/helpers/RenderHelper.js +15 -0
  38. package/src/core/helpers/ResourceHelper.js +89 -0
  39. package/src/core/helpers/TemplateHelper.js +11 -0
  40. package/src/core/managers/BindingManager.js +671 -0
  41. package/src/core/managers/ConfigurationManager.js +136 -0
  42. package/src/core/managers/EventManager.js +309 -0
  43. package/src/core/managers/LifecycleManager.js +356 -0
  44. package/src/core/managers/ReactiveManager.js +334 -0
  45. package/src/core/managers/RenderEngine.js +292 -0
  46. package/src/core/managers/ResourceManager.js +441 -0
  47. package/src/core/managers/ViewHierarchyManager.js +258 -0
  48. package/src/core/managers/ViewTemplateManager.js +127 -0
  49. package/src/core/reactive/ReactiveComponent.js +592 -0
  50. package/src/core/services/EventService.js +418 -0
  51. package/src/core/services/HttpService.js +106 -0
  52. package/src/core/services/LoggerService.js +57 -0
  53. package/src/core/services/StateService.js +512 -0
  54. package/src/core/services/StorageService.js +856 -0
  55. package/src/core/services/StoreService.js +258 -0
  56. package/src/core/services/TemplateDetectorService.js +361 -0
  57. package/src/core/services/Test.js +18 -0
  58. package/src/helpers/devWarnings.js +205 -0
  59. package/src/helpers/performance.js +226 -0
  60. package/src/helpers/utils.js +287 -0
  61. package/src/init.js +343 -0
  62. package/src/plugins/auto-plugin.js +34 -0
  63. package/src/services/Test.js +18 -0
  64. package/src/types/index.js +193 -0
  65. package/src/utils/date-helper.js +51 -0
  66. package/src/utils/helpers.js +39 -0
  67. package/src/utils/validation.js +32 -0
@@ -0,0 +1,1410 @@
1
+ /**
2
+ * View Engine class for managing view instances
3
+ * @param {Object} config - View configuration
4
+ *
5
+ * Refactored 2025-12-29: Now uses manager pattern for better maintainability
6
+ */
7
+ import { __defineGetters, __defineMethods, __defineProperties, __defineProps, deleteProp, escapeString, hasData, uniqId } from '../helpers/utils.js';
8
+ import { ViewState } from './ViewState.js';
9
+ import logger from './services/LoggerService.js';
10
+ import { ATTR, FORBIDDEN_KEYS } from './ViewConfig.js';
11
+ import { LoopContext } from './LoopContext.js';
12
+ import { ResourceManager } from './managers/ResourceManager.js';
13
+ import { EventManager } from './managers/EventManager.js';
14
+ import { RenderEngine } from './managers/RenderEngine.js';
15
+ import { LifecycleManager } from './managers/LifecycleManager.js';
16
+ import { ReactiveManager } from './managers/ReactiveManager.js';
17
+ import { BindingManager } from './managers/BindingManager.js';
18
+ import { ViewTemplateManager } from './managers/ViewTemplateManager.js';
19
+ import { ConfigurationManager } from './managers/ConfigurationManager.js';
20
+ import { ViewHierarchyManager } from './managers/ViewHierarchyManager.js';
21
+ import { ChildrenRegistry } from './ChildrenRegistry.js';
22
+ import { OneMarkupModel } from './OneMarkup.js';
23
+
24
+ export class ViewController {
25
+ /**
26
+ *
27
+ * @param {string} path
28
+ * @param {View} view
29
+ */
30
+ constructor(path, view, app) {
31
+ /**
32
+ * @type {View}
33
+ */
34
+ this.view = view;
35
+ this.states = new ViewState(this);
36
+ // Private properties using naming convention for browser compatibility
37
+ /**
38
+ * @type {Application}
39
+ */
40
+ this.App = app;
41
+ /**
42
+ * @type {boolean}
43
+ */
44
+ this.isSuperView = false;
45
+ /**
46
+ * @type {function}
47
+ */
48
+ this._emptyFn = () => { };
49
+ /**
50
+ * @type {string}
51
+ */
52
+ this.id = null;
53
+ /**
54
+ * @type {function}
55
+ */
56
+ this.init = this._emptyFn;
57
+ /**
58
+ * @type {ViewController}
59
+ */
60
+ this.parent = null;
61
+ /**
62
+ * @type {Array<ViewController>}
63
+ */
64
+ this.children = [];
65
+ /**
66
+ * @type {ViewController}
67
+ */
68
+ this.superView = null;
69
+ /**
70
+ * @type {string}
71
+ */
72
+ this.superViewPath = null;
73
+ /**
74
+ * @type {string}
75
+ */
76
+ this.superViewId = null;
77
+ /**
78
+ * @type {boolean}
79
+ */
80
+ this.hasSuperView = false;
81
+ /**
82
+ * @type {string}
83
+ */
84
+ this.originalViewPath = null;
85
+ /**
86
+ * @type {string}
87
+ */
88
+ this.originalViewId = null;
89
+ /**
90
+ * @type {ViewController}
91
+ */
92
+ this.originalView = null;
93
+ /**
94
+ * @type {boolean}
95
+ */
96
+ this.hasAwaitData = false;
97
+ /**
98
+ * @type {boolean}
99
+ */
100
+ this.hasFetchData = false;
101
+ /**
102
+ * @type {Object}
103
+ */
104
+ this.fetch = {};
105
+ /**
106
+ * @type {boolean}
107
+ */
108
+ this.usesVars = false;
109
+ /**
110
+ * @type {boolean}
111
+ */
112
+ this.hasSections = false;
113
+ /**
114
+ * @type {Array<string>}
115
+ */
116
+ this.renderLongSections = [];
117
+ /**
118
+ * @type {Object}
119
+ */
120
+ this.sections = {};
121
+ /**
122
+ * @type {function}
123
+ */
124
+ this.addCSS = this._emptyFn;
125
+ this.removeCSS = this._emptyFn;
126
+ /**
127
+ * @type {Array<string>}
128
+ */
129
+ this.resources = [];
130
+ /**
131
+ * @type {Array}
132
+ */
133
+ this.scripts = [];
134
+ /**
135
+ * @type {Array}
136
+ */
137
+ this.styles = [];
138
+ /**
139
+ * @type {Set<string>}
140
+ */
141
+ this.insertedResourceKeys = new Set();
142
+ this.userDefined = {};
143
+ /**
144
+ * @type {Object}
145
+ */
146
+ this.events = {};
147
+ this.eventIndex = 0;
148
+ /**
149
+ * @type {Object}
150
+ */
151
+ this.data = {};
152
+ this.urlPath = '';
153
+ /**
154
+ * @type {boolean}
155
+ */
156
+ this.isInitlized = false;
157
+ /**
158
+ * @type {TemplateEngine}
159
+ */
160
+ this.templateEngine = null;
161
+
162
+ // Initialize
163
+ this.path = path;
164
+ this.viewType = 'view';
165
+ this.config = {};
166
+
167
+ this.subscribeStates = true;
168
+
169
+ this.isRendering = false;
170
+ this.isRendered = false;
171
+
172
+ this.isMounted = false;
173
+ this.isDestroyed = false;
174
+ this.isScanning = false;
175
+ this.isScanned = false;
176
+
177
+ this.isFirstClientRendering = true;
178
+
179
+ this.isReady = false;
180
+
181
+ this.isMarkupScanned = false;
182
+ /**
183
+ * @type {OneMarkupModel}
184
+ */
185
+ this.markup = null;
186
+
187
+ /**
188
+ * @type {HTMLElement}
189
+ */
190
+ this.rootElement = null;
191
+
192
+ /**
193
+ * @type {Array<HTMLElement>}
194
+ */
195
+ this.refElements = [];
196
+
197
+ this.eventListenerStatus = false;
198
+
199
+ this.eventListeners = [];
200
+
201
+ this.isVirtualRendering = false;
202
+ this.renderedContent = null;
203
+
204
+ this.__scope = {};
205
+ this.isRefreshing = false;
206
+
207
+ this.changeStateQueueCount = 0;
208
+ this.changedStateKeys = new Set();
209
+ this._stateChangePending = false;
210
+
211
+ this.loopContext = null;
212
+
213
+ this.isCommitedConstructorData = false;
214
+
215
+ this.isReadyToStateChangeListen = false;
216
+
217
+ this.childrenNeedToRefreshID = null;
218
+
219
+ // Memoization cache for expensive operations
220
+ this._memoCache = {
221
+ isHtmlString: new Map(),
222
+ escapedStrings: new Map()
223
+ };
224
+
225
+ // Initialize managers for better code organization
226
+ /**
227
+ * @type {ResourceManager}
228
+ */
229
+ this._resourceManager = new ResourceManager(this);
230
+ /**
231
+ * @type {EventManager}
232
+ */
233
+ this._eventManager = new EventManager(this);
234
+ /**
235
+ * @type {RenderEngine}
236
+ */
237
+ this._renderEngine = new RenderEngine(this);
238
+ /**
239
+ * @type {LifecycleManager}
240
+ */
241
+ this._lifecycleManager = new LifecycleManager(this);
242
+ /**
243
+ * @type {ReactiveManager}
244
+ */
245
+ this._reactiveManager = new ReactiveManager(this);
246
+ /**
247
+ * @type {BindingManager}
248
+ */
249
+ this._bindingManager = new BindingManager(this);
250
+ /**
251
+ * @type {ViewTemplateManager}
252
+ */
253
+ this._templateManager = new ViewTemplateManager(this);
254
+ /**
255
+ * @type {ConfigurationManager}
256
+ */
257
+ this._configManager = new ConfigurationManager(this);
258
+ /**
259
+ * @type {ViewHierarchyManager}
260
+ */
261
+ this._hierarchyManager = new ViewHierarchyManager(this);
262
+ /**
263
+ * @type {ChildrenRegistry}
264
+ */
265
+ this._childrenRegistry = new ChildrenRegistry(this);
266
+
267
+ this.renuewnChildrenIDs = [];
268
+
269
+ // Performance tracking
270
+ this._perfMarks = new Map();
271
+
272
+ }
273
+
274
+ /**
275
+ * Mark performance checkpoint
276
+ * @param {string} label - Performance marker label
277
+ */
278
+ _perfMark(label) {
279
+ if (this.App?.env?.debug) {
280
+ this._perfMarks.set(label, performance.now());
281
+ }
282
+ }
283
+
284
+ /**
285
+ * Measure performance between two marks
286
+ * @param {string} startLabel - Start marker
287
+ * @param {string} endLabel - End marker
288
+ * @returns {number} Duration in ms
289
+ */
290
+ _perfMeasure(startLabel, endLabel) {
291
+ if (!this.App?.env?.debug) {
292
+ return 0;
293
+ }
294
+ const start = this._perfMarks.get(startLabel);
295
+ const end = this._perfMarks.get(endLabel);
296
+ if (start && end) {
297
+ const duration = end - start;
298
+ logger.info(`[Perf] ${this.path} ${startLabel}->${endLabel}: ${duration.toFixed(2)}ms`);
299
+ return duration;
300
+ }
301
+ return 0;
302
+ }
303
+
304
+ /**
305
+ * Destroy view controller and cleanup all resources
306
+ * Prevents memory leaks by properly cleaning up all references
307
+ */
308
+ destroy() {
309
+ if (this.isDestroyed) {
310
+ return;
311
+ }
312
+
313
+ this._perfMark('destroy-start');
314
+
315
+ // Call lifecycle beforeDestroy
316
+ this._lifecycleManager?.beforeDestroy();
317
+
318
+ // Destroy all managers
319
+ this._reactiveManager?.destroy();
320
+ this._eventManager?.destroy();
321
+ this._resourceManager?.removeResources();
322
+ this._bindingManager?.destroy();
323
+
324
+ // Destroy state manager
325
+ this.states?.__?.destroy();
326
+
327
+ // Clear all references
328
+ this.children = [];
329
+ this.parent = null;
330
+ this.superView = null;
331
+ this.originalView = null;
332
+ this.eventListeners = [];
333
+ this.refElements = [];
334
+ this.loopContext = null;
335
+
336
+ // Clear data
337
+ this.data = {};
338
+ this.userDefined = {};
339
+ this.events = {};
340
+ this.sections = {};
341
+ this.__scope = {};
342
+
343
+ // Mark as destroyed
344
+ this.isDestroyed = true;
345
+
346
+ // Call lifecycle destroyed
347
+ this._lifecycleManager?.destroyed();
348
+
349
+ this._perfMark('destroy-end');
350
+ this._perfMeasure('destroy-start', 'destroy-end');
351
+
352
+ // Clear perf marks
353
+ this._perfMarks.clear();
354
+
355
+ logger.info(`[ViewController] Destroyed: ${this.path}`);
356
+ }
357
+
358
+
359
+ /**
360
+ * Setup the view engine with configuration
361
+ * @param {string} path - View path
362
+ * @param {Object} config - View configuration
363
+ */
364
+ setup(path, config) {
365
+ if (this.isInitlized) {
366
+ return this;
367
+ }
368
+
369
+ this._perfMark('setup-start');
370
+
371
+ // Set config and path
372
+ this.path = path;
373
+ this.config = config || {};
374
+
375
+ // Call _initialize to do the actual setup
376
+ this.initialize();
377
+
378
+ this._perfMark('setup-end');
379
+ this._perfMeasure('setup-start', 'setup-end');
380
+
381
+ return this;
382
+ }
383
+
384
+
385
+ /**
386
+ * Initialize the view engine with configuration
387
+ * @private
388
+ */
389
+ initialize() {
390
+ if (this.isInitlized) {
391
+ if (typeof this.view.initialize === 'function') {
392
+ return this.view.initialize.apply(this.view, arguments);
393
+ }
394
+ return;
395
+ }
396
+
397
+ this.isInitlized = true;
398
+ const config = this.config;
399
+
400
+ // Set basic properties (giữ nguyên tên từ code gốc)
401
+ this.id = config.viewId || uniqId();
402
+ deleteProp(config, 'viewId');
403
+ this.init = config.init || this._emptyFn;
404
+ deleteProp(config, 'init');
405
+ this.addCSS = config.addCSS || this._emptyFn;
406
+ deleteProp(config, 'addCSS');
407
+ this.removeCSS = config.removeCSS || this._emptyFn;
408
+ deleteProp(config, 'removeCSS');
409
+ this.superViewPath = config.superViewPath || config.superView;
410
+ deleteProp(config, 'superViewPath');
411
+ deleteProp(config, 'superView');
412
+ this.hasSuperView = config.hasSuperView;
413
+ deleteProp(config, 'hasSuperView');
414
+ this.hasAwaitData = config.hasAwaitData;
415
+ deleteProp(config, 'hasAwaitData');
416
+ this.hasFetchData = config.hasFetchData;
417
+ deleteProp(config, 'hasFetchData');
418
+ this.fetch = config.fetch;
419
+ deleteProp(config, 'fetch');
420
+ this.usesVars = config.usesVars;
421
+ deleteProp(config, 'usesVars');
422
+ this.hasSections = config.hasSections;
423
+ deleteProp(config, 'hasSections');
424
+ this.hasSectionPreload = config.hasSectionPreload;
425
+ deleteProp(config, 'hasSectionPreload');
426
+ this.renderLongSections = config.renderLongSections || [];
427
+ deleteProp(config, 'renderLongSections');
428
+ this.hasPrerender = config.hasPrerender;
429
+ deleteProp(config, 'hasPrerender');
430
+ this.sections = config.sections;
431
+ deleteProp(config, 'sections');
432
+ this._templateManager.initializeWrapperConfig(config.wrapperConfig);
433
+ deleteProp(config, 'wrapperConfig');
434
+ this.resources = config.resources || [];
435
+ deleteProp(config, 'resources');
436
+ this.scripts = config.scripts || [];
437
+ deleteProp(config, 'scripts');
438
+ this.styles = config.styles || [];
439
+ deleteProp(config, 'styles');
440
+ this.events = {};
441
+
442
+ this.subscribeStates = false;
443
+ this._lifecycleManager.beforeCreate();
444
+ // Process defined properties and methods (giữ nguyên logic từ code gốc)
445
+ this.processDefinedProperties(config);
446
+
447
+ if (config && config.data && typeof config.data === 'object' && config.data.__SSR_VIEW_ID__) {
448
+ config.data.__SSR_VIEW_ID__ = null;
449
+ deleteProp(config.data, '__SSR_VIEW_ID__');
450
+ }
451
+
452
+ // Merge data
453
+ this.data = { ...(this.data || {}), ...(config.data || {}) };
454
+
455
+ this._eventManager.updateController(this);
456
+ this._resourceManager.updateController(this);
457
+
458
+ this.commitConstructorData();
459
+
460
+ // Call lifecycle hooks
461
+ this._lifecycleManager.created(); // ✅ Insert styles here
462
+
463
+ this._lifecycleManager.beforeInit();
464
+ this._lifecycleManager.init();
465
+ this._lifecycleManager.afterInit();
466
+
467
+ }
468
+
469
+
470
+
471
+ /**
472
+ * Insert styles into DOM
473
+ * Styles should be inserted before render (in created hook)
474
+ */
475
+ /**
476
+ * Insert styles into DOM with reference counting
477
+ * Delegated to ResourceManager for better code organization
478
+ */
479
+ insertStyles() {
480
+ return this._resourceManager.insertStyles();
481
+ }
482
+
483
+ /**
484
+ * Insert scripts into DOM
485
+ * Scripts should be inserted after DOM ready (in mounted hook)
486
+ */
487
+ /**
488
+ * Insert scripts into DOM
489
+ * Scripts should be inserted after DOM ready (in mounted hook)
490
+ * Delegated to ResourceManager for better code organization
491
+ */
492
+ insertScripts() {
493
+ return this._resourceManager.insertScripts();
494
+ }
495
+
496
+ /**
497
+ * Remove styles from registry (but not from DOM if other views use them)
498
+ */
499
+ /**
500
+ * Remove styles from registry (but not from DOM if other views use them)
501
+ * Delegated to ResourceManager for better code organization
502
+ */
503
+ removeStyles() {
504
+ return this._resourceManager.removeStyles();
505
+ }
506
+
507
+
508
+ /**
509
+ * Remove all styles from DOM that belong to this view path
510
+ * Used as fallback when registry-based removal fails
511
+ * @param {string} viewPath - Optional view path, defaults to this.path
512
+ */
513
+ /**
514
+ * Remove all styles from DOM that belong to this view path
515
+ * Used as fallback when registry-based removal fails
516
+ * @param {string} viewPath - Optional view path, defaults to this.path
517
+ * Delegated to ResourceManager for better code organization
518
+ */
519
+ removeStylesByViewPath(viewPath = null) {
520
+ return this._resourceManager.removeStylesByViewPath(viewPath);
521
+ }
522
+
523
+
524
+ /**
525
+ * Remove scripts from registry (but not from DOM if other views use them)
526
+ */
527
+ /**
528
+ * Remove scripts from registry (but not from DOM if other views use them)
529
+ * Delegated to ResourceManager for better code organization
530
+ */
531
+ removeScripts() {
532
+ return this._resourceManager.removeScripts();
533
+ }
534
+
535
+
536
+ /**
537
+ * Process defined properties and methods from config
538
+ * @private
539
+ */
540
+ /**
541
+ * Process defined properties and methods from config
542
+ * Delegated to ConfigurationManager
543
+ */
544
+ processDefinedProperties(config) {
545
+ return this._configManager.processDefinedProperties(config);
546
+ }
547
+
548
+
549
+
550
+ /**
551
+ * Set scope for the view
552
+ * Delegated to ConfigurationManager
553
+ */
554
+ setScope(scope) {
555
+ return this._configManager.setScope(scope);
556
+ }
557
+
558
+
559
+ /**
560
+ * Commit constructor data
561
+ * Delegated to ConfigurationManager
562
+ */
563
+ commitConstructorData() {
564
+ return this._configManager.commitConstructorData();
565
+ }
566
+
567
+ /**
568
+ * Add event configuration for view engine
569
+ * @param {string} eventType - Type of event
570
+ * @param {Array} handlers - Array of handler objects
571
+ * @returns {string} Event attribute string
572
+ * @example
573
+ * <AppViewEngine>.addEventConfig('click', [{handler: 'handleClick', params: [1, 2, 3]}]);
574
+ */
575
+ addEventConfig(eventType, handlers) {
576
+ if (typeof eventType !== 'string' || eventType === '') {
577
+ return;
578
+ }
579
+ if (typeof handlers !== 'object' || handlers === null) {
580
+ return;
581
+ }
582
+ return this.addEventStack(eventType, handlers);
583
+ }
584
+
585
+
586
+
587
+ /**
588
+ * Render the view with data
589
+ * @param {Object} _data - Additional data to merge
590
+ * @returns {string|Object} Rendered content
591
+ */
592
+ /**
593
+ * Render the view
594
+ * Delegated to RenderEngine for better code organization
595
+ */
596
+ render() {
597
+ return this._renderEngine.render();
598
+ }
599
+
600
+ /**
601
+ * Virtual render the view with data (Scan version)
602
+ * @param {Object} _data - Additional data to merge
603
+ * @returns {string|Object} Virtual rendered content
604
+ */
605
+ virtualRender() {
606
+ return this._renderEngine.virtualRender();
607
+ }
608
+
609
+
610
+
611
+
612
+ /**
613
+ * Prerender the view with data
614
+ * @param {Object} _data - Additional data to merge
615
+ * @returns {string|Object} Prerendered content
616
+ */
617
+ prerender(_data = {}) {
618
+ return this._renderEngine.prerender(_data);
619
+ }
620
+
621
+ /**
622
+ * Virtual prerender the view with data (Scan version)
623
+ * @param {Object} _data - Additional data to merge
624
+ * @returns {string|Object} Virtual prerendered content
625
+ */
626
+ virtualPrerender(_data = {}) {
627
+ return this._renderEngine.virtualPrerender(_data);
628
+ }
629
+
630
+
631
+
632
+
633
+ /**
634
+ * Replace view content with new HTML
635
+ * @param {string} htmlString - New HTML content
636
+ * @returns {boolean} True if replaced successfully
637
+ */
638
+ replaceView(htmlString) {
639
+ if (this.isHtmlString(htmlString)) {
640
+ const container = document.createElement('div');
641
+ container.innerHTML = htmlString;
642
+ const frag = container.content;
643
+ const oldElements = document.querySelectorAll(`[x-ref-view="${this.id}"]`);
644
+ const elemtntCount = oldElements.length;
645
+ for (let i = elemtntCount - 1; i > 0; i--) {
646
+ const oldElement = oldElements[i];
647
+ const newElement = frag.childNodes[i];
648
+ oldElement.parentNode.replaceChild(newElement, oldElement);
649
+ }
650
+ const oldElement = oldElements[0];
651
+ oldElement.parentNode.replaceChild(frag, oldElement);
652
+ return true;
653
+ }
654
+ return false;
655
+ }
656
+
657
+ refresh(data = null) {
658
+ return this._renderEngine.refresh(data);
659
+ }
660
+
661
+
662
+ onChildrenRefresh(childName, childIndex) {
663
+ return this._lifecycleManager.onChildrenRefresh(childName, childIndex);
664
+ }
665
+ /**
666
+ * Lifecycle: Called when super view is mounted (giữ nguyên tên từ code gốc)
667
+ */
668
+ onSuperViewMounted() {
669
+ this._lifecycleManager.mounted();
670
+ }
671
+
672
+ /**
673
+ * Lifecycle: Called when super view is unmounted (giữ nguyên tên từ code gốc)
674
+ */
675
+ onSuperViewUnmounted() {
676
+
677
+ }
678
+
679
+ /**
680
+ * Lifecycle: Called when parent view is mounted (giữ nguyên tên từ code gốc)
681
+ */
682
+ onParentMounted() {
683
+ this._lifecycleManager.mounted();
684
+ }
685
+ /**
686
+ * Lifecycle: Called when parent view is unmounted (giữ nguyên tên từ code gốc)
687
+ */
688
+ onParentUnmounted() {
689
+ // Placeholder for when parent view is unmounted
690
+ }
691
+
692
+
693
+
694
+ /**
695
+ * Hàm parse string HTML thành DOM, thêm thuộc tính x-ref-view cho các phần tử con level 0, trả về string HTML mới.
696
+ * @param {string} htmlString - Chuỗi HTML đầu vào
697
+ * @param {string|number} id - Giá trị cho thuộc tính x-ref-view
698
+ * @returns {string} Chuỗi HTML đã thêm thuộc tính x-ref-view cho các phần tử level 0
699
+ */
700
+ addXRefViewToRootElements(htmlString) {
701
+ // Tạo một container ảo để parse HTML
702
+ const container = document.createElement('div');
703
+ container.innerHTML = htmlString;
704
+
705
+ // Lặp qua các phần tử con trực tiếp (level 0)
706
+ Array.from(container.children).forEach(child => {
707
+ child.setAttribute('x-ref-view', this.id);
708
+ });
709
+
710
+ // Trả về HTML đã được thêm thuộc tính
711
+ return container.innerHTML;
712
+ }
713
+
714
+ /**
715
+ * Check if string is HTML
716
+ * @param {string} str - String to check
717
+ * @returns {boolean} True if HTML string
718
+ * @private
719
+ */
720
+ isHtmlString(str) {
721
+ return /<[a-z][\s\S]*>/i.test(str);
722
+ }
723
+
724
+
725
+
726
+ renderPlaceholder() {
727
+ return `<div class="${ATTR.className('placeholder')}" ${ATTR.KEYS.VIEW_ID}="${this.id}"></div>`;
728
+ }
729
+
730
+ showError(error) {
731
+ if (this.isSuperView) {
732
+ return `<div class="${ATTR.className('ERROR_VIEW')}">${error}</div>`;
733
+ }
734
+ else if (this.hasSuperView) {
735
+ if (this.renderLongSections.length > 0) {
736
+ return this.renderLongSections.map(section => {
737
+ return this.App.View.section(section, `<div class="${ATTR.className('section-error')}" ${ATTR.KEYS.VIEW_SECTION_REF}="${this.id}">${error}</div>`, 'html');
738
+ }).join('');
739
+ }
740
+ else {
741
+ return `<div class="${ATTR.className('ERROR_VIEW')}" ${ATTR.KEYS.VIEW_ID}="${this.id}">${error}</div>`;
742
+ }
743
+ }
744
+ else {
745
+ if (this.renderLongSections.length > 0) {
746
+ return this.renderLongSections.map(section => {
747
+ return this.App.View.section(section, `<div class="${ATTR.className('section-error')}" ${ATTR.KEYS.VIEW_SECTION_REF}="${this.id}">${error}</div>`, 'html');
748
+ }).join('');
749
+ }
750
+ return `<div class="${ATTR.className('ERROR_VIEW')}" ${ATTR.KEYS.VIEW_ID}="${this.id}">${error}</div>`;
751
+ }
752
+ }
753
+
754
+ showErrorScan(error) {
755
+ return null;
756
+ }
757
+
758
+
759
+ /**
760
+ * Reset original view
761
+ * Delegated to ViewHierarchyManager
762
+ */
763
+ resetOriginalView() {
764
+ return this._hierarchyManager.resetOriginalView();
765
+ }
766
+
767
+ /**
768
+ * Eject original view
769
+ * Delegated to ViewHierarchyManager
770
+ */
771
+ ejectOriginalView() {
772
+ return this._hierarchyManager.ejectOriginalView();
773
+ }
774
+
775
+ /**
776
+ * Remove this view and all children
777
+ * Delegated to ViewHierarchyManager
778
+ */
779
+ remove() {
780
+ return this._hierarchyManager.remove();
781
+ }
782
+
783
+ /**
784
+ * Insert resources into DOM
785
+ */
786
+ insertResources() {
787
+ this.resources.forEach(resource => {
788
+ if (document.querySelector(`[data-resource-uuid="${resource.uuid}"]`)) {
789
+ return;
790
+ }
791
+ const element = document.createElement(resource.tag);
792
+ Object.entries(resource.attrs).forEach(([key, value]) => {
793
+ element.setAttribute(key, value);
794
+ });
795
+ element.setAttribute('data-resource-uuid', resource.uuid);
796
+ if (resource.tag === 'script') {
797
+ document.body.appendChild(element);
798
+ }
799
+ else if (resource.tag === 'link') {
800
+ document.head.appendChild(element);
801
+ }
802
+ else if (resource.tag === 'style') {
803
+ document.head.appendChild(element);
804
+ }
805
+ document.head.appendChild(element);
806
+ });
807
+ }
808
+
809
+ /**
810
+ * Remove resources from DOM
811
+ */
812
+ removeResources() {
813
+ this.resources.forEach(resource => {
814
+ const element = document.querySelector(`[data-resource-uuid="${resource.uuid}"]`);
815
+ if (element) {
816
+ element.remove();
817
+ }
818
+ });
819
+ }
820
+
821
+
822
+ __showError(error) {
823
+ logger.error(`ViewEngine Error [${this.path}]: ${error}`);
824
+ return this.showError(error);
825
+ }
826
+
827
+ __section(name, content, type) {
828
+ return this._templateManager.section(name, content, type);
829
+ }
830
+
831
+ __yield(name, defaultValue = '') {
832
+ return this._templateManager.yieldSection(name, defaultValue);
833
+ }
834
+
835
+ __yieldContent(name, defaultValue = '') {
836
+ return this._templateManager.yieldContent(name, defaultValue);
837
+ }
838
+
839
+
840
+
841
+ __execute(...args) {
842
+ return this.App.View.execute(...args);
843
+ }
844
+
845
+ __subscribe(...args) {
846
+ // return this.subscribe(...args);
847
+ }
848
+
849
+
850
+ __text(...args) {
851
+ return this.App.View.text(...args);
852
+ }
853
+
854
+
855
+ __follow(stateKeys = [], renderBlock = () => '') {
856
+ // @follow directive has been removed - use __watch instead
857
+ console.warn('@follow directive is deprecated. Use __watch() instead.');
858
+ return this.__watch(uniqId(), stateKeys, renderBlock);
859
+ }
860
+
861
+ __attr(attrs = {}) {
862
+ if (typeof attrs !== 'object' || attrs === null) {
863
+ return '';
864
+ }
865
+ let result = this._reactiveManager.renderBindingAttribute(attrs);
866
+ return result;
867
+ }
868
+
869
+ __watch(watchID, watchKeys = [], callback = () => { }) {
870
+ return this._reactiveManager.renderWatchComponent(watchID, watchKeys, callback);
871
+ }
872
+
873
+ __output(subscribeKeys = [], renderBlock = () => '') {
874
+ if (this.isVirtualRendering) {
875
+ return this._reactiveManager.renderOutputComponentScan(subscribeKeys, renderBlock);
876
+ }
877
+ return this._reactiveManager.renderOutputComponent(subscribeKeys, renderBlock);
878
+ }
879
+
880
+ __outputEscaped(subscribeKeys = [], renderBlock = () => '') {
881
+ return this._reactiveManager.renderOutputEscapedComponent(subscribeKeys, renderBlock);
882
+ }
883
+
884
+ /**
885
+ * Unified reactive component method
886
+ * @param {string} reactiveID - Component ID
887
+ * @param {Array<string>} stateKeys - State keys to watch
888
+ * @param {Function} renderBlock - Render function
889
+ * @param {Object} options - Component options
890
+ * @returns {string} Rendered component
891
+ */
892
+ __reactive(reactiveID, stateKeys = [], renderBlock = () => '', options = {}) {
893
+ const {
894
+ type = 'output',
895
+ escapeHTML = false
896
+ } = options;
897
+
898
+ if (type === 'watch') {
899
+ return this._reactiveManager.renderWatchComponent(reactiveID, stateKeys, renderBlock);
900
+ }
901
+
902
+ if (escapeHTML) {
903
+ return this._reactiveManager.renderOutputEscapedComponent(stateKeys, renderBlock);
904
+ }
905
+
906
+ return this._reactiveManager.renderOutputComponent(stateKeys, renderBlock);
907
+ }
908
+
909
+ __checked(condition) {
910
+ return condition ? 'checked' : '';
911
+ }
912
+ __classBinding(bindings = []) {
913
+ if (!Array.isArray(bindings)) {
914
+ return '';
915
+ }
916
+ return this._reactiveManager.renderClassBinding(bindings);
917
+ }
918
+
919
+ __styleBinding(watchKeys = [], styles = []) {
920
+ // Generate reactive style binding
921
+ // styles = [['color', 'red'], ['font-size', '14px']]
922
+ // Returns: "color: red; font-size: 14px;"
923
+
924
+ const generateStyle = () => {
925
+ if (!Array.isArray(styles)) return '';
926
+
927
+ return styles
928
+ .map(([prop, value]) => {
929
+ if (prop && value !== null && value !== undefined) {
930
+ return `${prop}: ${value}`;
931
+ }
932
+ return '';
933
+ })
934
+ .filter(s => s)
935
+ .join('; ');
936
+ };
937
+
938
+ if (watchKeys && watchKeys.length > 0) {
939
+ // Reactive - watch state changes
940
+ return this._reactiveManager.renderWatchComponent(watchKeys, generateStyle);
941
+ } else {
942
+ // Static - generate once
943
+ return generateStyle();
944
+ }
945
+ }
946
+
947
+ __showBinding(watchKeys = [], condition) {
948
+ // Generate reactive visibility binding
949
+ // Similar to v-show in Vue - toggles display property
950
+ // Returns: "display: none;" or "" based on condition
951
+
952
+ const generateDisplay = () => {
953
+ return condition ? '' : 'display: none;';
954
+ };
955
+
956
+ if (watchKeys && watchKeys.length > 0) {
957
+ // Reactive - watch state changes
958
+ return this._reactiveManager.renderWatchComponent(watchKeys, generateDisplay);
959
+ } else {
960
+ // Static - generate once
961
+ return generateDisplay();
962
+ }
963
+ }
964
+
965
+
966
+
967
+ __block(name, attributes = {}, content) {
968
+ return this._templateManager.addBlock(name, attributes, content);
969
+ }
970
+
971
+ __useBlock(name, defaultValue = '') {
972
+ return this._templateManager.useBlock(name, defaultValue);
973
+ }
974
+
975
+ __mountBlock(name, defaultValue = '') {
976
+ return this._templateManager.mountBlock(name, defaultValue);
977
+ }
978
+
979
+ __subscribeBlock(name, defaultValue = '') {
980
+ return this._templateManager.subscribeBlock(name, defaultValue);
981
+ }
982
+
983
+ __include(path, data = {}) {
984
+ if (this.isVirtualRendering) {
985
+ const childParams = this._templateManager.childrenConfig[this._templateManager.childrenIndex];
986
+ if (!(childParams && childParams.name === path)) {
987
+ return null;
988
+ }
989
+ this._templateManager.childrenIndex++;
990
+ const childConfig = this.App.View.ssrViewManager.getInstance(childParams.name, childParams.id);
991
+ if (!childConfig) {
992
+ return null;
993
+ }
994
+ const childData = { ...data, ...childConfig.data, __SSR_VIEW_ID__: childParams.id };
995
+ const child = this.$include(childParams.name, childData);
996
+ if (!child) {
997
+ return null;
998
+ }
999
+ child.__.__scan(childConfig);
1000
+ return this.App.View.renderView(child, null, true);
1001
+ }
1002
+
1003
+ if (this.isRefreshing) {
1004
+ let childCtrl = this.children.find(c => c.__scope.name === path && c.__scope.index === this._templateManager.childrenIndex);
1005
+ if (childCtrl) {
1006
+ childCtrl.isFirstClientRendering = false;
1007
+ childCtrl._templateManager.wrapperConfig.enable = true;
1008
+ this._templateManager.childrenIndex++;
1009
+ const result = this.childrenNeedToRefreshID === childCtrl.id ? childCtrl.rrend() : null;
1010
+
1011
+ return childCtrl.render();
1012
+ }
1013
+ }
1014
+ return this.$include(path, data);
1015
+
1016
+ }
1017
+
1018
+ __includeif(path, data = {}) {
1019
+ if (!this.App.View.exists(path)) {
1020
+ return null;
1021
+ }
1022
+ return this.__include(path, data);
1023
+ }
1024
+
1025
+ __includewhen(condition, path, data = {}) {
1026
+ if (!condition) {
1027
+ return null;
1028
+ }
1029
+ return this.__include(path, data);
1030
+ }
1031
+
1032
+
1033
+ __extends(name, data = {}) {
1034
+ if (this.isVirtualRendering) {
1035
+ if (!this.App.View.exists(name)) {
1036
+ return null;
1037
+ }
1038
+ let superViewConfig = null;
1039
+ let superViewOfChildren = this._templateManager.childrenConfig.find((child, index) => child.name === name && index == this._templateManager.childrenConfig.length - 1);
1040
+ if (superViewOfChildren) {
1041
+ superViewConfig = this.App.View.ssrViewManager.getInstance(superViewOfChildren.name, superViewOfChildren.id);
1042
+ } else {
1043
+ superViewConfig = this.App.View.ssrViewManager.scan(name);
1044
+ }
1045
+ if (!superViewConfig) {
1046
+ return null;
1047
+ }
1048
+ const superViewData = { ...data, ...superViewConfig.data, __SSR_VIEW_ID__: superViewConfig.viewId };
1049
+ const superView = this.$extends(name, superViewData);
1050
+ if (!superView) {
1051
+ return null;
1052
+ }
1053
+ superView.__.__scan(superViewConfig);
1054
+ return superView;
1055
+ }
1056
+ return this.$extends(name, data);
1057
+ }
1058
+
1059
+
1060
+ __setLoopContext(length) {
1061
+ let parent = this.loopContext;
1062
+ this.loopContext = new LoopContext(parent);
1063
+ this.loopContext.setCount(length);
1064
+ return this.loopContext;
1065
+ }
1066
+
1067
+ __resetLoopContext() {
1068
+ if (this.loopContext && this.loopContext.parent) {
1069
+ let parent = this.loopContext.parent;
1070
+ this.loopContext.parent = null;
1071
+ Object.freeze(this.loopContext);
1072
+ this.loopContext = parent;
1073
+ } else if (this.loopContext) {
1074
+ Object.freeze(this.loopContext);
1075
+ this.loopContext = null;
1076
+ } else {
1077
+ this.loopContext = null;
1078
+ }
1079
+ return this.loopContext;
1080
+ }
1081
+
1082
+ /**
1083
+ * Lặp qua danh sách hoặc đối tượng và gọi callback cho mỗi phần tử hoặc key
1084
+ * @param {Array|Object} list danh sách cần lặp hoặc đối tượng cần lặp
1085
+ * @param {function(item: any, defaultKeyName: string, index: number, loopContext: LoopContext): string} callback hàm callback để xử lý mỗi phần tử hoặc key của đối tượng
1086
+ * @returns {string} kết quả của việc lặp
1087
+ * @example
1088
+ * <AppViewEngine>.foreach([1, 2, 3], (item, defaultKeyName, index, loopContext) => {
1089
+ * return `<div>${defaultKeyName}: ${item}</div>`;
1090
+ * });
1091
+ * // returns '<div>1</div><div>2</div><div>3</div>'
1092
+ * <AppViewEngine>.foreach({a: 1, b: 2, c: 3}, (value, key, index, loopContext) => {
1093
+ * return `<div>${key}: ${value}</div>`;
1094
+ * });
1095
+ */
1096
+ __foreach(list, callback) {
1097
+ if (!list || (typeof list !== 'object')) {
1098
+ return '';
1099
+ }
1100
+ let result = '';
1101
+ if (Array.isArray(list)) {
1102
+ let loopContext = this.__setLoopContext(list);
1103
+ loopContext.setType('increment');
1104
+ list.forEach((item, index) => {
1105
+ loopContext.setCurrentTimes(index);
1106
+ result += callback(item, index, index, loopContext);
1107
+ });
1108
+ this.__resetLoopContext();
1109
+ } else {
1110
+ let count = Object.keys(list).length;
1111
+ let loopContext = this.__setLoopContext(count);
1112
+ loopContext.setType('increment');
1113
+ let index = 0;
1114
+ Object.entries(list).forEach(([key, value]) => {
1115
+ loopContext.setCurrentTimes(index);
1116
+ result += callback(value, key, index, loopContext);
1117
+ index = index + 1;
1118
+ });
1119
+ this.__resetLoopContext();
1120
+ }
1121
+
1122
+ return result;
1123
+ }
1124
+
1125
+ __for(loopType = 'increment', start = 0, end = 0, execute = (loop) => '') {
1126
+ const LoopContext = this.__setLoopContext(end);
1127
+ LoopContext.setType(loopType);
1128
+ const result = typeof execute === 'function' ? execute(LoopContext) : '';
1129
+ this.__resetLoopContext();
1130
+ return result;
1131
+ }
1132
+
1133
+
1134
+ /**
1135
+ * Scan and hydrate server-rendered view
1136
+ * This method:
1137
+ * 1. Finds DOM elements for this view
1138
+ * 2. Attaches event handlers from server data
1139
+ * 3. Sets up state subscriptions
1140
+ * 4. Stores children and following block references
1141
+ *
1142
+ * @param {Object} config - Server-side view configuration
1143
+ * @param {string} config.viewId - View instance ID
1144
+ * @param {Object} config.data - View data from server
1145
+ * @param {Object} config.events - Event handlers to attach
1146
+ * @param {Array} config.following - Following blocks to setup
1147
+ * @param {Array} config.children - Child views to scan
1148
+ * @param {Object} config.parent - Parent view reference
1149
+ */
1150
+ /**
1151
+ * Scan view configuration and setup reactive components
1152
+ * Delegated to RenderEngine for better code organization
1153
+ */
1154
+ __scan(config) {
1155
+ return this._renderEngine.scan(config);
1156
+ }
1157
+
1158
+ /**
1159
+ * Find and store DOM elements for this view
1160
+ * @private
1161
+ * @param {string} viewId - View instance ID
1162
+ */
1163
+ __scanDOMElements(viewId) {
1164
+ return this._renderEngine.scanDOMElements(viewId);
1165
+ }
1166
+
1167
+ /**
1168
+ * Store children view references for hydration
1169
+ * @private
1170
+ * @param {Array} children - Child view configurations
1171
+ */
1172
+ __storeChildrenReferences(children) {
1173
+ this._templateManager.storeChildrenReferences(children);
1174
+ }
1175
+
1176
+
1177
+
1178
+ $extends(path, data = {}) {
1179
+ return this._hierarchyManager.createSuperView(path, data);
1180
+ }
1181
+
1182
+ $include(path, data = {}) {
1183
+ return this._hierarchyManager.createChildView(path, data);
1184
+ }
1185
+
1186
+
1187
+ addEventStack(eventType, handlers) {
1188
+ if (typeof handlers !== 'object' || handlers === null) {
1189
+ return;
1190
+ }
1191
+ let eventIndex = this.eventIndex++;
1192
+ let eventID = this.id + '-' + eventType + '-' + eventIndex;
1193
+ if (typeof eventID !== 'string' || eventID === '') {
1194
+ return;
1195
+ }
1196
+ if (typeof this.events[eventType] === 'undefined') {
1197
+ this.events[eventType] = {};
1198
+ }
1199
+ if (typeof this.events[eventType][eventID] === 'undefined') {
1200
+ this.events[eventType][eventID] = []
1201
+ }
1202
+ this.events[eventType][eventID].push(...handlers);
1203
+
1204
+ return ` data-${eventType}-id="${eventID}"`;
1205
+ }
1206
+
1207
+
1208
+ __addEventConfig(eventType, handlers) {
1209
+ return this.addEventConfig(eventType, handlers);
1210
+ }
1211
+
1212
+ /**
1213
+ * Set event configuration for view engine
1214
+ * @param {Object} events - Event configuration object
1215
+ */
1216
+ setEventConfig(events) {
1217
+ if (typeof events !== 'object' || events === null) {
1218
+ return;
1219
+ }
1220
+ Object.entries(events).forEach((eventType, eventObjectList) => {
1221
+ if (typeof eventObjectList !== 'object' || eventObjectList === null) {
1222
+ return;
1223
+ }
1224
+ Object.entries(eventObjectList).forEach((eventID, handlers) => {
1225
+ this.addEventStack(eventType, eventID, handlers);
1226
+ });
1227
+ });
1228
+ }
1229
+
1230
+
1231
+
1232
+ wrapattr() {
1233
+ return this._templateManager.wrapperAttribute();
1234
+ }
1235
+
1236
+ startWrapper(tag = null, attributes = {}) {
1237
+ return this._templateManager.startWrapper(tag, attributes);
1238
+ }
1239
+
1240
+ endWrapper() {
1241
+ return this._templateManager.endWrapper();
1242
+ }
1243
+
1244
+
1245
+
1246
+
1247
+ onStateDataChanges() {
1248
+ const data = this.stateChangeData;
1249
+ this.stateChangeData = null;
1250
+ }
1251
+
1252
+ /**
1253
+ * Set App instance
1254
+ * @param {Object} app - App instance
1255
+ * @returns {ViewController} This instance for chaining
1256
+ */
1257
+ setApp(app) {
1258
+ this.App = app;
1259
+ return this;
1260
+ }
1261
+
1262
+ setUrlPath(urlPath) {
1263
+ this.urlPath = urlPath;
1264
+ return this;
1265
+ }
1266
+ /**
1267
+ * Set super view
1268
+ * Delegated to ViewHierarchyManager
1269
+ */
1270
+ setSuperView(superView) {
1271
+ return this._hierarchyManager.setSuperView(superView);
1272
+ }
1273
+
1274
+ /**
1275
+ * Set parent view
1276
+ * Delegated to ViewHierarchyManager
1277
+ */
1278
+ setParent(parent) {
1279
+ return this._hierarchyManager.setParent(parent);
1280
+ }
1281
+
1282
+ /**
1283
+ * Set original view
1284
+ * Delegated to ViewHierarchyManager
1285
+ */
1286
+ setOriginalView(originalView) {
1287
+ return this._hierarchyManager.setOriginalView(originalView);
1288
+ }
1289
+
1290
+ /**
1291
+ * Add child view
1292
+ * Delegated to ViewHierarchyManager
1293
+ */
1294
+ addChild(child, data = {}) {
1295
+ return this._hierarchyManager.addChild(child, data);
1296
+ }
1297
+
1298
+ /**
1299
+ * Remove child view
1300
+ * Delegated to ViewHierarchyManager
1301
+ */
1302
+ removeChild(child) {
1303
+ return this._hierarchyManager.removeChild(child);
1304
+ }
1305
+
1306
+ /**
1307
+ * Update data
1308
+ * Delegated to ConfigurationManager
1309
+ */
1310
+ updateData(__data = {}) {
1311
+ return this._configManager.updateData(__data);
1312
+ }
1313
+
1314
+ /**
1315
+ * Update variable data
1316
+ * Delegated to ConfigurationManager
1317
+ */
1318
+ updateVariableData(data = {}) {
1319
+ return this._configManager.updateVariableData(data);
1320
+ }
1321
+
1322
+ /**
1323
+ * Update variable item
1324
+ * Delegated to ConfigurationManager
1325
+ */
1326
+ updateVariableItem(key, value) {
1327
+ return this._configManager.updateVariableItem(key, value);
1328
+ }
1329
+
1330
+ /**
1331
+ * Set isSuperView flag
1332
+ * @param {boolean} isSuperView - Super view flag
1333
+ * @returns {AppViewEngine} This instance for chaining
1334
+ */
1335
+ setIsSuperView(isSuperView) {
1336
+ this.isSuperView = isSuperView;
1337
+ return this;
1338
+ }
1339
+
1340
+ /** Events */
1341
+
1342
+
1343
+ reset() {
1344
+ this._templateManager.resetChildrenIndex();
1345
+ this._reactiveManager.resetScanIndex();
1346
+ }
1347
+
1348
+ clear() {
1349
+ // Use registry for proper cleanup if available
1350
+ if (this._childrenRegistry) {
1351
+ this._childrenRegistry.clear();
1352
+ } else {
1353
+ // Fallback to manual cleanup
1354
+ this.children.forEach(childCtrl => {
1355
+ if (childCtrl && childCtrl instanceof ViewController) {
1356
+ childCtrl.destroy();
1357
+ }
1358
+ });
1359
+ this.children = [];
1360
+ }
1361
+
1362
+ this._reactiveManager.clearForRefresh();
1363
+ this._bindingManager.reset();
1364
+ this.refElements = {};
1365
+ }
1366
+
1367
+ query(selector) {
1368
+ for (const el of this.refElements) {
1369
+ // 1. chính element
1370
+ if (el.matches?.(selector)) {
1371
+ return el;
1372
+ }
1373
+
1374
+ // 2. con bên trong
1375
+ const found = el.querySelector?.(selector);
1376
+ if (found) {
1377
+ return found;
1378
+ }
1379
+ }
1380
+ return null;
1381
+ }
1382
+
1383
+ queryAll(selector) {
1384
+ const results = [];
1385
+
1386
+ for (const el of this.refElements) {
1387
+ // chính element
1388
+ if (el.matches?.(selector)) {
1389
+ results.push(el);
1390
+ }
1391
+
1392
+ // con bên trong
1393
+ const found = el.querySelectorAll?.(selector);
1394
+ if (found?.length) {
1395
+ results.push(...found);
1396
+ }
1397
+ }
1398
+
1399
+ return results;
1400
+ }
1401
+ // accessors
1402
+
1403
+ get type() {
1404
+ return this.viewType;
1405
+ }
1406
+ set type(value) {
1407
+ this.viewType = value;
1408
+ }
1409
+
1410
+ }