sdc_client 0.57.11 → 0.57.14

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.
@@ -1,4 +1,9 @@
1
- import {controllerFactory, runControlFlowFunctions, tagList} from "./sdc_controller.js";
1
+ import {
2
+ controllerFactory,
3
+ prepareRefreshProcess,
4
+ runControlFlowFunctions,
5
+ updateEventAndTriggerOnRefresh
6
+ } from "./sdc_controller.js";
2
7
  import {getUrlParam} from "./sdc_params.js";
3
8
  import {app} from "./sdc_main.js";
4
9
  import {trigger} from "./sdc_events.js";
@@ -14,7 +19,7 @@ export const CONTROLLER_CLASS = '_sdc_controller_';
14
19
 
15
20
 
16
21
  export function cleanCache() {
17
- htmlFiles = {};
22
+ htmlFiles = {};
18
23
  }
19
24
 
20
25
  /**
@@ -29,29 +34,29 @@ export function cleanCache() {
29
34
  * @return {Array} - a array of objects with all register tags found
30
35
  */
31
36
  function findSdcTgs($container, tagNameList, parentController) {
32
- if (!$container) {
33
- return [];
37
+ if (!$container) {
38
+ return [];
39
+ }
40
+ let $children = $container.children();
41
+ let emptyList = [];
42
+ $children.each(function (_, element) {
43
+ let $element = $(element);
44
+ let tagName = $element.prop('tagName').toLowerCase().split('_');
45
+ if ($.inArray(tagName[0], tagNameList) >= 0) {
46
+ emptyList.push({
47
+ tag: tagName[0],
48
+ super: tagName.splice(1) || [],
49
+ dom: $element
50
+ });
51
+
52
+ } else if (tagName[0].startsWith('this.')) {
53
+ $element.addClass(`_bind_to_update_handler sdc_uuid_${parentController._uuid}`)
54
+ } else {
55
+ emptyList = emptyList.concat(findSdcTgs($element, tagNameList, parentController))
34
56
  }
35
- let $children = $container.children();
36
- let emptyList = [];
37
- $children.each(function (_, element) {
38
- let $element = $(element);
39
- let tagName = $element.prop('tagName').toLowerCase().split('_');
40
- if ($.inArray(tagName[0], tagNameList) >= 0) {
41
- emptyList.push({
42
- tag: tagName[0],
43
- super: tagName.splice(1) || [],
44
- dom: $element
45
- });
46
-
47
- } else if (tagName[0].startsWith('this.')) {
48
- $element.addClass(`_bind_to_update_handler sdc_uuid_${parentController._uuid}`)
49
- } else {
50
- emptyList = emptyList.concat(findSdcTgs($element, tagNameList, parentController))
51
- }
52
- });
57
+ });
53
58
 
54
- return emptyList;
59
+ return emptyList;
55
60
  }
56
61
 
57
62
  /**
@@ -64,15 +69,15 @@ function findSdcTgs($container, tagNameList, parentController) {
64
69
  * @returns {string} - the correct URL with prefix.
65
70
  */
66
71
  function replacePlaceholderController(controller, url, urlValues) {
67
- for (let key_idx in controller._urlParams) {
68
- if (controller._urlParams.hasOwnProperty(key_idx)) {
69
- let key = controller._urlParams[key_idx];
70
- let re = RegExp("%\\(" + key + "\\)\\w", "gm");
71
- url = url.replace(re, "" + urlValues.shift());
72
- }
72
+ for (let key_idx in controller._urlParams) {
73
+ if (controller._urlParams.hasOwnProperty(key_idx)) {
74
+ let key = controller._urlParams[key_idx];
75
+ let re = RegExp("%\\(" + key + "\\)\\w", "gm");
76
+ url = url.replace(re, "" + urlValues.shift());
73
77
  }
78
+ }
74
79
 
75
- return url;
80
+ return url;
76
81
  }
77
82
 
78
83
  /**
@@ -87,30 +92,30 @@ function replacePlaceholderController(controller, url, urlValues) {
87
92
  * @returns {Promise<Boolean>} - waits for the file to be loaded.
88
93
  */
89
94
  function loadHTMLFile(path, args, tag, hardReload) {
90
- if (!path) {
91
- return Promise.resolve(false);
92
- } else if (htmlFiles[tag]) {
93
- return Promise.resolve(htmlFiles[tag])
95
+ if (!path) {
96
+ return Promise.resolve(false);
97
+ } else if (htmlFiles[tag]) {
98
+ return Promise.resolve(htmlFiles[tag])
99
+ }
100
+
101
+ args.VERSION = app.VERSION;
102
+ args._method = 'content';
103
+
104
+ return $.get(path, args).then(function (data) {
105
+ if (!hardReload) {
106
+ htmlFiles[tag] = data;
94
107
  }
95
108
 
96
- args.VERSION = app.VERSION;
97
- args._method = 'content';
98
-
99
- return $.get(path, args).then(function (data) {
100
- if (!hardReload) {
101
- htmlFiles[tag] = data;
102
- }
103
-
104
- return data;
105
- }).catch(function (err) {
106
- if (err.status === 301) {
107
- const data = err.responseJSON;
108
- trigger('_RedirectOnView', data['url-link']);
109
- }
110
- trigger('navLoaded', {'controller_name': () => err.status});
109
+ return data;
110
+ }).catch(function (err) {
111
+ if (err.status === 301) {
112
+ const data = err.responseJSON;
113
+ trigger('_RedirectOnView', data['url-link']);
114
+ }
115
+ trigger('navLoaded', {'controller_name': () => err.status});
111
116
 
112
- throw `<sdc-error data-code="${err.status}">${err.responseText}</sdc-error>`;
113
- });
117
+ throw `<sdc-error data-code="${err.status}">${err.responseText}</sdc-error>`;
118
+ });
114
119
  }
115
120
 
116
121
  /**
@@ -119,10 +124,11 @@ function loadHTMLFile(path, args, tag, hardReload) {
119
124
  *
120
125
  * @param {jquery} $container - given container
121
126
  * @param {AbstractSDC} parentController - parent contoller surrounded the container
127
+ * @param {Object} process - Process object containing the refresh process
122
128
  */
123
- function replaceAllTagElementsInContainer($container, parentController) {
124
- parentController = parentController || $container.data(DATA_CONTROLLER_KEY);
125
- return replaceTagElementsInContainer(app.tagNames, $container, parentController);
129
+ function replaceAllTagElementsInContainer($container, parentController, process = null) {
130
+ parentController = parentController || $container.data(DATA_CONTROLLER_KEY);
131
+ return replaceTagElementsInContainer(app.tagNames, $container, parentController, process);
126
132
  }
127
133
 
128
134
  /**
@@ -133,25 +139,25 @@ function replaceAllTagElementsInContainer($container, parentController) {
133
139
  * @returns {string} - the correct URL with prefix.
134
140
  */
135
141
  function parseContentUrl(controller) {
136
- let url = controller.contentUrl;
137
- if (controller && controller._urlParams.length === 0) {
138
- let re = /%\(([^)]+)\)\w/gm;
139
- let matches;
140
- controller._urlParams = [];
141
- while ((matches = re.exec(url))) {
142
- controller._urlParams.push(matches[1]);
143
- controller.contentReload = true;
144
- }
142
+ let url = controller.contentUrl;
143
+ if (controller && controller._urlParams.length === 0) {
144
+ let re = /%\(([^)]+)\)\w/gm;
145
+ let matches;
146
+ controller._urlParams = [];
147
+ while ((matches = re.exec(url))) {
148
+ controller._urlParams.push(matches[1]);
149
+ controller.contentReload = true;
145
150
  }
151
+ }
146
152
 
147
- let params = getUrlParam(controller, controller.$container);
148
- if (controller._urlParams.length) {
149
- url = replacePlaceholderController(controller, url, params);
150
- }
153
+ let params = getUrlParam(controller, controller.$container);
154
+ if (controller._urlParams.length) {
155
+ url = replacePlaceholderController(controller, url, params);
156
+ }
151
157
 
152
- controller.parsedContentUrl = url;
158
+ controller.parsedContentUrl = url;
153
159
 
154
- return {url: url, args: params[params.length - 1]};
160
+ return {url: url, args: params[params.length - 1]};
155
161
  }
156
162
 
157
163
 
@@ -161,10 +167,10 @@ function parseContentUrl(controller) {
161
167
  * @return {AbstractSDC}
162
168
  */
163
169
  export function getController($elem) {
164
- if ($elem.hasClass(CONTROLLER_CLASS)) {
165
- return $elem.data(`${DATA_CONTROLLER_KEY}`);
166
- }
167
- return $elem.closest(`.${CONTROLLER_CLASS}`).data(`${DATA_CONTROLLER_KEY}`);
170
+ if ($elem.hasClass(CONTROLLER_CLASS)) {
171
+ return $elem.data(`${DATA_CONTROLLER_KEY}`);
172
+ }
173
+ return $elem.closest(`.${CONTROLLER_CLASS}`).data(`${DATA_CONTROLLER_KEY}`);
168
174
  }
169
175
 
170
176
  /**
@@ -179,26 +185,26 @@ export function getController($elem) {
179
185
  * @returns {Promise<jQuery>} - the promise waits to the files are loaded. it returns the jQuery object.
180
186
  */
181
187
  export function loadFilesFromController(controller) {
182
- let getElements = {args: {}};
183
- if (controller.contentUrl) {
184
- getElements = parseContentUrl(controller, controller.contentUrl);
185
- controller.contentUrl = getElements.url;
188
+ let getElements = {args: {}};
189
+ if (controller.contentUrl) {
190
+ getElements = parseContentUrl(controller, controller.contentUrl);
191
+ controller.contentUrl = getElements.url;
192
+ }
193
+
194
+ return Promise.all([
195
+ loadHTMLFile(controller.contentUrl, getElements.args, controller._tagName, controller.contentReload)
196
+ ]).then(function (results) {
197
+ let htmlFile = results[0];
198
+ if (htmlFile) {
199
+ try {
200
+ return $(htmlFile);
201
+ } catch {
202
+ return $('<div></div>').append(htmlFile);
203
+ }
186
204
  }
187
205
 
188
- return Promise.all([
189
- loadHTMLFile(controller.contentUrl, getElements.args, controller._tagName, controller.contentReload)
190
- ]).then(function (results) {
191
- let htmlFile = results[0];
192
- if (htmlFile) {
193
- try {
194
- return $(htmlFile);
195
- } catch {
196
- return $('<div></div>').append(htmlFile);
197
- }
198
- }
199
-
200
- return null;
201
- });
206
+ return null;
207
+ });
202
208
  }
203
209
 
204
210
  /**
@@ -212,15 +218,15 @@ export function loadFilesFromController(controller) {
212
218
  * @returns {Promise<jQuery>} - the promise waits to the files are loaded. it returns the jQuery object.
213
219
  */
214
220
  export function reloadHTMLController(controller) {
215
- if (controller.contentUrl) {
216
- let getElements = parseContentUrl(controller, controller.contentUrl);
217
- controller.contentUrl = getElements.url;
218
- return loadHTMLFile(controller.contentUrl, getElements.args, controller._tagName, controller.contentReload);
219
- }
220
-
221
- return new Promise(resolve => {
222
- resolve($());
223
- });
221
+ if (controller.contentUrl) {
222
+ let getElements = parseContentUrl(controller, controller.contentUrl);
223
+ controller.contentUrl = getElements.url;
224
+ return loadHTMLFile(controller.contentUrl, getElements.args, controller._tagName, controller.contentReload);
225
+ }
226
+
227
+ return new Promise(resolve => {
228
+ resolve($());
229
+ });
224
230
  }
225
231
 
226
232
  /**
@@ -229,18 +235,19 @@ export function reloadHTMLController(controller) {
229
235
  * @param {string} tagName
230
236
  * @param {Array<string>} superTagNameList
231
237
  * @param {AbstractSDC} parentController
232
- * @returns {boolean}
238
+ * @param {Object} process - Process object containing the refresh process
239
+ * @returns {Promise}
233
240
  */
234
- function runReplaceTagElementsInContainer($element, tagName, superTagNameList, parentController) {
235
- let controller = $element.data(DATA_CONTROLLER_KEY);
236
- if (controller) {
237
- return replaceAllTagElementsInContainer($element, controller);
238
- }
239
-
240
- controller = controllerFactory(parentController, $element, tagName, superTagNameList);
241
- $element.data(DATA_CONTROLLER_KEY, controller);
242
- $element.addClass(CONTROLLER_CLASS);
243
- return runControlFlowFunctions(controller, $element);
241
+ function runReplaceTagElementsInContainer($element, tagName, superTagNameList, parentController, process) {
242
+ let controller = $element.data(DATA_CONTROLLER_KEY);
243
+ if (controller) {
244
+ return replaceAllTagElementsInContainer($element, controller, process);
245
+ }
246
+
247
+ controller = controllerFactory(parentController, $element, tagName, superTagNameList);
248
+ $element.data(DATA_CONTROLLER_KEY, controller);
249
+ $element.addClass(CONTROLLER_CLASS);
250
+ return runControlFlowFunctions(controller, process);
244
251
  }
245
252
 
246
253
 
@@ -250,19 +257,20 @@ function runReplaceTagElementsInContainer($element, tagName, superTagNameList, p
250
257
  *
251
258
  * @param {AbstractSDC} controller - js controller instance
252
259
  * @param {jquery} $html - jQuery loaded content
260
+ * @param {Object} process - Process object containing the refresh process
253
261
  * @return {Promise}
254
262
  */
255
- export function runControllerFillContent(controller, $html) {
256
- if ($html && $html.length > 0) {
257
- controller.$container.empty();
258
- controller.$container.attr(controller._tagName, '');
259
- for (let mixinKey in controller._mixins) {
260
- controller.$container.attr(controller._mixins[mixinKey]._tagName, '');
261
- }
262
- controller.$container.append($html);
263
+ export function runControllerFillContent(controller, $html, process = null) {
264
+ if ($html && $html.length > 0) {
265
+ controller.$container.empty();
266
+ controller.$container.attr(controller._tagName, '');
267
+ for (let mixinKey in controller._mixins) {
268
+ controller.$container.attr(controller._mixins[mixinKey]._tagName, '');
263
269
  }
270
+ controller.$container.append($html);
271
+ }
264
272
 
265
- return replaceAllTagElementsInContainer(controller.$container, controller);
273
+ return replaceAllTagElementsInContainer(controller.$container, controller, process);
266
274
  }
267
275
 
268
276
 
@@ -275,123 +283,291 @@ export function runControllerFillContent(controller, $html) {
275
283
  * @param {Array<string>} tagList - list of all registered tags
276
284
  * @param {jquery} $container - jQuery container to find the tags
277
285
  * @param {AbstractSDC} parentController - controller in surrounding
286
+ * @param {Object} process - Process object containing the refresh process
278
287
  */
279
- export function replaceTagElementsInContainer(tagList, $container, parentController) {
280
- return new Promise((resolve) => {
288
+ export function replaceTagElementsInContainer(tagList, $container, parentController, process) {
289
+ return new Promise((resolve) => {
281
290
 
282
- let tagDescriptionElements = findSdcTgs($container, tagList, parentController);
283
- let tagCount = tagDescriptionElements.length;
291
+ let tagDescriptionElements = findSdcTgs($container, tagList, parentController);
292
+ let tagCount = tagDescriptionElements.length;
284
293
 
294
+ if (tagCount === 0) {
295
+ return resolve();
296
+ }
297
+
298
+ for (let elementIndex = 0; elementIndex < tagDescriptionElements.length; elementIndex++) {
299
+ runReplaceTagElementsInContainer(tagDescriptionElements[elementIndex].dom,
300
+ tagDescriptionElements[elementIndex].tag,
301
+ tagDescriptionElements[elementIndex].super,
302
+ parentController, process).then(() => {
303
+ tagCount--;
285
304
  if (tagCount === 0) {
286
- return resolve();
305
+ return resolve();
287
306
  }
307
+ });
308
+ }
309
+ });
310
+ }
288
311
 
289
- for (let elementIndex = 0; elementIndex < tagDescriptionElements.length; elementIndex++) {
290
- runReplaceTagElementsInContainer(tagDescriptionElements[elementIndex].dom,
291
- tagDescriptionElements[elementIndex].tag,
292
- tagDescriptionElements[elementIndex].super,
293
- parentController).then(() => {
294
- tagCount--;
295
- if (tagCount === 0) {
296
- return resolve();
297
- }
298
- });
299
- }
312
+ export function reloadMethodHTML(controller, $container, process) {
313
+ return _reloadMethodHTML(controller, $container ?? controller.$container, process)
314
+ }
315
+
316
+ function _reloadMethodHTML(controller, $dom, process) {
317
+ const plist = [];
318
+
319
+ $dom.find(`._bind_to_update_handler.sdc_uuid_${controller._uuid}`).each(function () {
320
+ const $this = $(this);
321
+ let result = undefined;
322
+ if ($this.hasClass(`_with_handler`)) {
323
+ result = $this.data('handler');
324
+ } else {
325
+ let controller_handler = this.tagName.toLowerCase().replace(/^this./, '');
326
+ if (controller[controller_handler]) {
327
+ result = controller[controller_handler];
328
+ }
329
+ }
330
+
331
+
332
+ if (typeof result === 'function') {
333
+ result = result.bind(controller)($this.data());
334
+ }
335
+ if (result !== undefined) {
336
+ plist.push(Promise.resolve(result).then((x) => {
337
+ let $newContent = $(`<div></div>`);
338
+ $newContent.append(x);
339
+ $newContent = $this.clone().empty().append($newContent);
340
+ return app.reconcile(controller, $newContent, $this, process);
341
+ }));
342
+ }
343
+
344
+ });
345
+
346
+ return Promise.all(plist);
347
+ }
348
+
349
+
350
+ function getNodeKey(node) {
351
+ if (node[0].nodeType === 3) {
352
+ return `TEXT__${node[0].nodeValue}`;
353
+ }
354
+ const res = [node[0].tagName];
355
+ if (node[0].nodeName === 'INPUT') {
356
+ [['name', ''], ['type', 'text'], ['id', '']].forEach(([key, defaultValue]) => {
357
+ const attr = node.attr(key) ?? defaultValue;
358
+ if (attr) {
359
+ res.push(attr);
360
+ }
300
361
  });
362
+ }
363
+ return res.join('__');
301
364
  }
302
365
 
303
- export function reloadMethodHTML(controller, $container) {
304
- return _reloadMethodHTML(controller, $container ?? controller.$container)
366
+ function reconcileTree($element, id = [], parent = null) {
367
+ id.push(getNodeKey($element));
368
+ const obj = {
369
+ $element,
370
+ id: id.join('::'),
371
+ depth: id.length,
372
+ idx: 0,
373
+ getRealParent: () => parent,
374
+ getIdx: function () {
375
+ this.idx = (this.getRealParent()?.getIdx() ?? -1) + $element.index() + 1;
376
+ return this.idx;
377
+ },
378
+ op: null,
379
+ parent
380
+ };
381
+ obj.getIdx.bind(obj);
382
+ return [obj].concat($element.contents().toArray().map((x) => reconcileTree($(x), id.slice(), obj)).flat());
383
+
305
384
  }
306
385
 
307
- function _reloadMethodHTML(controller, $dom) {
308
- const plist = [];
309
386
 
310
- $dom.find(`._bind_to_update_handler.sdc_uuid_${controller._uuid}`).each(function () {
311
- const $this = $(this);
312
- let result = undefined;
313
- if ($this.hasClass(`_with_handler`)) {
314
- result = $this.data('handler');
387
+ export function reconcile($virtualNode, $realNode) {
388
+ const $old = reconcileTree($realNode);
389
+ const $new = reconcileTree($virtualNode);
390
+ $old.map((x, i) => x.idx = i);
391
+ $new.map((x, i) => x.idx = i);
392
+ const depth = Math.max(...$new.concat($old).map(x => x.depth));
393
+ const op_steps = lcbDiff($old, $new, depth);
394
+ let toRemove = [];
395
+ window.MAIN = $realNode;
396
+ window.OPS = op_steps;
397
+
398
+ op_steps.forEach((op_step, i) => {
399
+ const {op, $element, idx} = op_step;
400
+
401
+ if (op.type === 'keep_counterpart') {
402
+ let cIdx = op.counterpart.getIdx();
403
+ if (cIdx !== idx) {
404
+ const elemBefore = op_step.getBefore();
405
+ if (!elemBefore) {
406
+ op_step.getRealParent().$element.prepend(op.counterpart.$element);
315
407
  } else {
316
- let controller_handler = this.tagName.toLowerCase().replace(/^this./, '');
317
- if (controller[controller_handler]) {
318
- result = controller[controller_handler];
319
- }
408
+ op.counterpart.$element.insertAfter(elemBefore.$element);
320
409
  }
410
+ }
411
+
412
+ syncAttributes(op.counterpart.$element, $element);
413
+ if ($element.hasClass(CONTROLLER_CLASS)) {
414
+ $element.data(DATA_CONTROLLER_KEY).$container = op.counterpart.$element;
415
+ $element.data(DATA_CONTROLLER_KEY, null);
416
+ }
417
+
418
+ toRemove.push($element);
419
+ } else if (op.type === 'delete') {
420
+ $element.safeRemove();
421
+ } else if (op.type === 'insert') {
422
+ const {after, target} = op_step.op;
423
+ if (after) {
424
+ $element.insertAfter(after.$element);
425
+ } else if (target) {
426
+ target.$element.prepend($element);
427
+ }
321
428
 
429
+ }
430
+ });
322
431
 
323
- if (typeof result === 'function') {
324
- result = result.bind(controller)($this.data());
325
- }
326
- if (result !== undefined) {
327
- plist.push(Promise.resolve(result).then((x) => {
328
- let $newContent = $(`<div></div>`);
329
- $newContent.append(x);
330
- $newContent = $this.clone().empty().append($newContent);
331
- return controller.reconcile($newContent, $this);
332
- }));
333
- }
432
+ toRemove.forEach(($element) => $element.safeRemove());
433
+ }
334
434
 
335
- });
435
+ function syncAttributes($real, $virtual) {
436
+ const realAttrs = $real[0].attributes ?? [];
437
+ const virtualAttrs = $virtual[0].attributes ?? [];
438
+ // Remove missing attrs
439
+ [...realAttrs].forEach(attr => {
440
+ if (!$virtual.is(`[${attr.name}]`)) {
441
+ $real.removeAttr(attr.name);
442
+ }
443
+ });
444
+
445
+ // Add or update
446
+ [...virtualAttrs].forEach(attr => {
447
+ if (!attr.name.startsWith(`data`) && $real.attr(attr.name) !== attr.value) {
448
+ $real.attr(attr.name, attr.value);
449
+ }
450
+ });
336
451
 
337
- return Promise.all(plist);
452
+ $real.removeData();
453
+ Object.entries($virtual.data()).forEach(([key, value]) => {
454
+ $real.data(key, value);
455
+ });
338
456
  }
339
457
 
458
+ /**
459
+ * LCB (Longest Common Branch) finds matching branches and reserves them!
460
+ *
461
+ * @param oldNodes
462
+ * @param newNodes
463
+ * @param depth
464
+ * @returns {*|*[]}
465
+ */
466
+ function lcbDiff(oldNodes, newNodes, depth) {
467
+ newNodes.filter(x => x.depth === depth && !x.op).forEach((newNode) => {
468
+ const oldNode = oldNodes.find((tempOldNode) => {
469
+ return !tempOldNode.op && tempOldNode.id === newNode.id;
470
+ });
471
+
472
+ if (oldNode) {
473
+ const keepTreeBranch = (oldNode, newNode) => {
474
+ oldNode.op = {type: 'keep', idx: newNode.idx};
475
+ newNode.op = {type: 'keep_counterpart', counterpart: oldNode};
476
+ oldNode = oldNode.parent;
477
+ newNode = newNode.parent;
478
+ if (!oldNode || oldNode.op || newNode?.op) {
479
+ return;
480
+ }
481
+ keepTreeBranch(oldNode, newNode);
340
482
 
341
- export function reconcile($virtualNode, $realNode, controller) {
342
- // Step 1: Check node types (e.g., div vs span)
343
- if ($virtualNode[0].nodeType !== $realNode[0].nodeType ||
344
- $virtualNode.prop("nodeName") !== $realNode.prop("nodeName")) {
345
- $realNode.safeReplace($virtualNode);
346
- return;
483
+ }
484
+ keepTreeBranch(oldNode, newNode);
485
+ }
486
+ });
487
+ if (depth > 1) {
488
+ return lcbDiff(oldNodes, newNodes, depth - 1);
489
+ }
490
+
491
+ oldNodes.forEach((x, i) => {
492
+ if (!x.op) {
493
+ const idx = (oldNodes[i - 1]?.op.idx ?? -1) + 1;
494
+ x.op = {type: 'delete', idx}
347
495
  }
496
+ });
348
497
 
349
- if ($virtualNode[0].nodeType === 3) {
350
- if ($virtualNode[0].nodeValue !== $realNode[0].nodeValue) {
351
- $realNode[0].nodeValue = $virtualNode[0].nodeValue;
352
- }
353
- return;
498
+ function getRealParent(element) {
499
+ if (!element.parent) {
500
+ return null;
501
+ }
502
+ return element.parent.op.type === 'keep_counterpart' ? element.parent.op.counterpart : element.parent;
503
+ }
504
+
505
+ function getBefore(element, idx) {
506
+ const startDepth = element.depth;
507
+ while (idx >= 0 && element.depth >= startDepth) {
508
+ idx -= 1;
509
+ element = newNodes[idx];
510
+ if (element.depth === startDepth) {
511
+ return element.op.type === 'keep_counterpart' ? element.op.counterpart : element;
512
+ }
354
513
  }
355
514
 
356
- // Step 2: Compare attributes
357
- syncAttributes($realNode, $virtualNode);
515
+ return null
358
516
 
359
- // Step 3: Recurse children
360
- const virtualChildren = $virtualNode.contents();
361
- const realChildren = $realNode.contents();
517
+ }
362
518
 
363
- const len = Math.max(virtualChildren.length, realChildren.length);
364
- for (let i = 0; i < len; i++) {
365
- if (i >= realChildren.length) {
366
- $realNode.append($(virtualChildren[i]));
367
- } else if (i >= virtualChildren.length) {
368
- $(realChildren[i]).safeRemove();
369
- } else {
370
- reconcile($(virtualChildren[i]), $(realChildren[i]), controller);
371
- }
519
+ newNodes.forEach((x, i) => {
520
+ x.getBefore = () => getBefore(x, i);
521
+ x.getRealParent = () => getRealParent(x);
522
+
523
+ if (!x.op) {
524
+ const target = x.getRealParent();
525
+ const type = target?.op.type === 'insert' ? 'insert_ignore' : 'insert';
526
+ x.op = {type, target, after: x.getBefore()}
372
527
  }
528
+ });
529
+
530
+ const tagged = [
531
+ ...oldNodes,
532
+ ...newNodes,
533
+ ];
534
+
535
+
536
+ return tagged.sort((a, b) => {
537
+ const aVal = a.op?.idx ?? a.idx;
538
+ const bVal = b.op?.idx ?? b.idx;
539
+
540
+ return aVal - bVal;
541
+ });
373
542
  }
374
543
 
375
- function syncAttributes($real, $virtual) {
376
- const realAttrs = $real[0].attributes ?? [];
377
- const virtualAttrs = $virtual[0].attributes ?? [];
378
- // Remove missing attrs
379
- [...realAttrs].forEach(attr => {
380
- if (!$virtual.is(`[${attr.name}]`)) {
381
- $real.removeAttr(attr.name);
382
- }
383
- });
544
+ /**
545
+ *
546
+ * @param {jquery} $dom
547
+ * @param {AbstractSDC} leafController
548
+ * @param {Object} process - Process object containing the refresh process
549
+ * @return {Promise<void>}
550
+ */
384
551
 
385
- // Add or update
386
- [...virtualAttrs].forEach(attr => {
387
- if (!attr.name.startsWith(`data`) && $real.attr(attr.name) !== attr.value) {
388
- $real.attr(attr.name, attr.value);
389
- }
390
- });
552
+ export function refresh($dom, leafController, process = null) {
553
+ if (!leafController) {
554
+ leafController = getController($dom);
555
+ }
391
556
 
392
- Object.entries($virtual.data()).forEach(([key, value]) => {
393
- if (key !== DATA_CONTROLLER_KEY) {
394
- $real.data(key, value);
395
- }
557
+ if (!leafController) {
558
+ return Promise.resolve();
559
+ }
560
+
561
+ const {refreshProcess, isRunningProcess} = prepareRefreshProcess(process, leafController);
562
+
563
+ $dom ??= leafController.$container;
564
+
565
+ return replaceTagElementsInContainer(app.tagNames, $dom, leafController, process).then(() => {
566
+ reloadMethodHTML(leafController, $dom, refreshProcess).then(() => {
567
+ if (!isRunningProcess) {
568
+ updateEventAndTriggerOnRefresh(refreshProcess);
569
+ }
396
570
  });
571
+
572
+ });
397
573
  }