wunderbaum 0.0.2 → 0.0.3
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/wunderbaum.css +1 -1
- package/dist/wunderbaum.d.ts +233 -85
- package/dist/wunderbaum.esm.js +512 -346
- package/dist/wunderbaum.esm.min.js +20 -20
- package/dist/wunderbaum.esm.min.js.map +1 -1
- package/dist/wunderbaum.umd.js +512 -346
- package/dist/wunderbaum.umd.min.js +26 -26
- package/dist/wunderbaum.umd.min.js.map +1 -1
- package/package.json +2 -2
- package/src/common.ts +31 -0
- package/src/util.ts +1 -0
- package/src/wb_ext_edit.ts +10 -1
- package/src/wb_ext_keynav.ts +8 -4
- package/src/wb_node.ts +124 -54
- package/src/wunderbaum.scss +8 -4
- package/src/wunderbaum.ts +433 -318
package/dist/wunderbaum.esm.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/*!
|
|
2
2
|
* Wunderbaum - util
|
|
3
3
|
* Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
|
|
4
|
-
* v0.0.
|
|
4
|
+
* v0.0.3, Mon, 18 Apr 2022 11:52:44 GMT (https://github.com/mar10/wunderbaum)
|
|
5
5
|
*/
|
|
6
|
+
/** @module util */
|
|
6
7
|
/** Readable names for `MouseEvent.button` */
|
|
7
8
|
const MOUSE_BUTTONS = {
|
|
8
9
|
0: "",
|
|
@@ -613,7 +614,7 @@ var util = /*#__PURE__*/Object.freeze({
|
|
|
613
614
|
/*!
|
|
614
615
|
* Wunderbaum - common
|
|
615
616
|
* Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
|
|
616
|
-
* v0.0.
|
|
617
|
+
* v0.0.3, Mon, 18 Apr 2022 11:52:44 GMT (https://github.com/mar10/wunderbaum)
|
|
617
618
|
*/
|
|
618
619
|
const DEFAULT_DEBUGLEVEL = 4; // Replaced by rollup script
|
|
619
620
|
const ROW_HEIGHT = 22;
|
|
@@ -701,6 +702,8 @@ const KEY_TO_ACTION_DICT = {
|
|
|
701
702
|
Home: "firstCol",
|
|
702
703
|
"Control+End": "last",
|
|
703
704
|
"Control+Home": "first",
|
|
705
|
+
"Meta+ArrowDown": "last",
|
|
706
|
+
"Meta+ArrowUp": "first",
|
|
704
707
|
"*": "expandAll",
|
|
705
708
|
Multiply: "expandAll",
|
|
706
709
|
PageDown: "pageDown",
|
|
@@ -727,7 +730,7 @@ function makeNodeTitleStartMatcher(s) {
|
|
|
727
730
|
/*!
|
|
728
731
|
* Wunderbaum - wb_extension_base
|
|
729
732
|
* Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
|
|
730
|
-
* v0.0.
|
|
733
|
+
* v0.0.3, Mon, 18 Apr 2022 11:52:44 GMT (https://github.com/mar10/wunderbaum)
|
|
731
734
|
*/
|
|
732
735
|
class WunderbaumExtension {
|
|
733
736
|
constructor(tree, id, defaults) {
|
|
@@ -1082,7 +1085,7 @@ function throttle(func, wait = 0, options = {}) {
|
|
|
1082
1085
|
/*!
|
|
1083
1086
|
* Wunderbaum - ext-filter
|
|
1084
1087
|
* Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
|
|
1085
|
-
* v0.0.
|
|
1088
|
+
* v0.0.3, Mon, 18 Apr 2022 11:52:44 GMT (https://github.com/mar10/wunderbaum)
|
|
1086
1089
|
*/
|
|
1087
1090
|
const START_MARKER = "\uFFF7";
|
|
1088
1091
|
const END_MARKER = "\uFFF8";
|
|
@@ -1376,7 +1379,7 @@ function _markFuzzyMatchedChars(text, matches, escapeTitles = true) {
|
|
|
1376
1379
|
/*!
|
|
1377
1380
|
* Wunderbaum - ext-keynav
|
|
1378
1381
|
* Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
|
|
1379
|
-
* v0.0.
|
|
1382
|
+
* v0.0.3, Mon, 18 Apr 2022 11:52:44 GMT (https://github.com/mar10/wunderbaum)
|
|
1380
1383
|
*/
|
|
1381
1384
|
class KeynavExtension extends WunderbaumExtension {
|
|
1382
1385
|
constructor(tree) {
|
|
@@ -1443,7 +1446,7 @@ class KeynavExtension extends WunderbaumExtension {
|
|
|
1443
1446
|
eventName = "Add"; // expand
|
|
1444
1447
|
}
|
|
1445
1448
|
else if (navModeOption === NavigationModeOption.startRow) {
|
|
1446
|
-
tree.
|
|
1449
|
+
tree.setNavigationMode(NavigationMode.cellNav);
|
|
1447
1450
|
return;
|
|
1448
1451
|
}
|
|
1449
1452
|
break;
|
|
@@ -1482,6 +1485,8 @@ class KeynavExtension extends WunderbaumExtension {
|
|
|
1482
1485
|
case "Home":
|
|
1483
1486
|
case "Control+End":
|
|
1484
1487
|
case "Control+Home":
|
|
1488
|
+
case "Meta+ArrowDown":
|
|
1489
|
+
case "Meta+ArrowUp":
|
|
1485
1490
|
case "PageDown":
|
|
1486
1491
|
case "PageUp":
|
|
1487
1492
|
node.navigate(eventName, { activate: activate, event: event });
|
|
@@ -1513,11 +1518,11 @@ class KeynavExtension extends WunderbaumExtension {
|
|
|
1513
1518
|
break;
|
|
1514
1519
|
case "Escape":
|
|
1515
1520
|
if (tree.navMode === NavigationMode.cellEdit) {
|
|
1516
|
-
tree.
|
|
1521
|
+
tree.setNavigationMode(NavigationMode.cellNav);
|
|
1517
1522
|
handled = true;
|
|
1518
1523
|
}
|
|
1519
1524
|
else if (tree.navMode === NavigationMode.cellNav) {
|
|
1520
|
-
tree.
|
|
1525
|
+
tree.setNavigationMode(NavigationMode.row);
|
|
1521
1526
|
handled = true;
|
|
1522
1527
|
}
|
|
1523
1528
|
break;
|
|
@@ -1527,7 +1532,7 @@ class KeynavExtension extends WunderbaumExtension {
|
|
|
1527
1532
|
handled = true;
|
|
1528
1533
|
}
|
|
1529
1534
|
else if (navModeOption !== NavigationModeOption.cell) {
|
|
1530
|
-
tree.
|
|
1535
|
+
tree.setNavigationMode(NavigationMode.row);
|
|
1531
1536
|
handled = true;
|
|
1532
1537
|
}
|
|
1533
1538
|
break;
|
|
@@ -1544,6 +1549,8 @@ class KeynavExtension extends WunderbaumExtension {
|
|
|
1544
1549
|
case "Home":
|
|
1545
1550
|
case "Control+End":
|
|
1546
1551
|
case "Control+Home":
|
|
1552
|
+
case "Meta+ArrowDown":
|
|
1553
|
+
case "Meta+ArrowUp":
|
|
1547
1554
|
case "PageDown":
|
|
1548
1555
|
case "PageUp":
|
|
1549
1556
|
node.navigate(eventName, { activate: activate, event: event });
|
|
@@ -1562,7 +1569,7 @@ class KeynavExtension extends WunderbaumExtension {
|
|
|
1562
1569
|
/*!
|
|
1563
1570
|
* Wunderbaum - ext-logger
|
|
1564
1571
|
* Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
|
|
1565
|
-
* v0.0.
|
|
1572
|
+
* v0.0.3, Mon, 18 Apr 2022 11:52:44 GMT (https://github.com/mar10/wunderbaum)
|
|
1566
1573
|
*/
|
|
1567
1574
|
class LoggerExtension extends WunderbaumExtension {
|
|
1568
1575
|
constructor(tree) {
|
|
@@ -1602,7 +1609,7 @@ class LoggerExtension extends WunderbaumExtension {
|
|
|
1602
1609
|
/*!
|
|
1603
1610
|
* Wunderbaum - ext-dnd
|
|
1604
1611
|
* Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
|
|
1605
|
-
* v0.0.
|
|
1612
|
+
* v0.0.3, Mon, 18 Apr 2022 11:52:44 GMT (https://github.com/mar10/wunderbaum)
|
|
1606
1613
|
*/
|
|
1607
1614
|
const nodeMimeType = "application/x-wunderbaum-node";
|
|
1608
1615
|
class DndExtension extends WunderbaumExtension {
|
|
@@ -1870,7 +1877,7 @@ class DndExtension extends WunderbaumExtension {
|
|
|
1870
1877
|
/*!
|
|
1871
1878
|
* Wunderbaum - drag_observer
|
|
1872
1879
|
* Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
|
|
1873
|
-
* v0.0.
|
|
1880
|
+
* v0.0.3, Mon, 18 Apr 2022 11:52:44 GMT (https://github.com/mar10/wunderbaum)
|
|
1874
1881
|
*/
|
|
1875
1882
|
/**
|
|
1876
1883
|
* Convert mouse- and touch events to 'dragstart', 'drag', and 'dragstop'.
|
|
@@ -2004,7 +2011,7 @@ class DragObserver {
|
|
|
2004
2011
|
/*!
|
|
2005
2012
|
* Wunderbaum - ext-grid
|
|
2006
2013
|
* Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
|
|
2007
|
-
* v0.0.
|
|
2014
|
+
* v0.0.3, Mon, 18 Apr 2022 11:52:44 GMT (https://github.com/mar10/wunderbaum)
|
|
2008
2015
|
*/
|
|
2009
2016
|
class GridExtension extends WunderbaumExtension {
|
|
2010
2017
|
constructor(tree) {
|
|
@@ -2041,7 +2048,7 @@ class GridExtension extends WunderbaumExtension {
|
|
|
2041
2048
|
/*!
|
|
2042
2049
|
* Wunderbaum - deferred
|
|
2043
2050
|
* Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
|
|
2044
|
-
* v0.0.
|
|
2051
|
+
* v0.0.3, Mon, 18 Apr 2022 11:52:44 GMT (https://github.com/mar10/wunderbaum)
|
|
2045
2052
|
*/
|
|
2046
2053
|
/**
|
|
2047
2054
|
* Deferred is a ES6 Promise, that exposes the resolve() and reject()` method.
|
|
@@ -2084,7 +2091,7 @@ class Deferred {
|
|
|
2084
2091
|
/*!
|
|
2085
2092
|
* Wunderbaum - wunderbaum_node
|
|
2086
2093
|
* Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
|
|
2087
|
-
* v0.0.
|
|
2094
|
+
* v0.0.3, Mon, 18 Apr 2022 11:52:44 GMT (https://github.com/mar10/wunderbaum)
|
|
2088
2095
|
*/
|
|
2089
2096
|
/** Top-level properties that can be passed with `data`. */
|
|
2090
2097
|
const NODE_PROPS = new Set([
|
|
@@ -2121,15 +2128,31 @@ const NODE_ATTRS = new Set([
|
|
|
2121
2128
|
"unselectableIgnore",
|
|
2122
2129
|
"unselectableStatus",
|
|
2123
2130
|
]);
|
|
2131
|
+
/**
|
|
2132
|
+
* A single tree node.
|
|
2133
|
+
*
|
|
2134
|
+
* **NOTE:** <br>
|
|
2135
|
+
* Generally you should not modify properties directly, since this may break
|
|
2136
|
+
* the internal bookkeeping.
|
|
2137
|
+
*/
|
|
2124
2138
|
class WunderbaumNode {
|
|
2125
2139
|
constructor(tree, parent, data) {
|
|
2126
2140
|
var _a, _b;
|
|
2141
|
+
/** Reference key. Unlike {@link key}, a `refKey` may occur multiple
|
|
2142
|
+
* times within a tree (in this case we have 'clone nodes').
|
|
2143
|
+
* @see Use {@link setKey} to modify.
|
|
2144
|
+
*/
|
|
2127
2145
|
this.refKey = undefined;
|
|
2128
2146
|
this.children = null;
|
|
2129
2147
|
this.lazy = false;
|
|
2148
|
+
/** Expansion state.
|
|
2149
|
+
* @see {@link isExpandable}, {@link isExpanded}, {@link setExpanded}. */
|
|
2130
2150
|
this.expanded = false;
|
|
2151
|
+
/** Selection state.
|
|
2152
|
+
* @see {@link isSelected}, {@link setSelected}. */
|
|
2131
2153
|
this.selected = false;
|
|
2132
|
-
/** Additional classes added to `div.wb-row`.
|
|
2154
|
+
/** Additional classes added to `div.wb-row`.
|
|
2155
|
+
* @see {@link addClass}, {@link removeClass}, {@link toggleClass}. */
|
|
2133
2156
|
this.extraClasses = new Set();
|
|
2134
2157
|
/** Custom data that was passed to the constructor */
|
|
2135
2158
|
this.data = {};
|
|
@@ -2295,7 +2318,8 @@ class WunderbaumNode {
|
|
|
2295
2318
|
}
|
|
2296
2319
|
/**
|
|
2297
2320
|
* Apply a modification (or navigation) operation.
|
|
2298
|
-
*
|
|
2321
|
+
*
|
|
2322
|
+
* @see {@link Wunderbaum.applyCommand}
|
|
2299
2323
|
*/
|
|
2300
2324
|
applyCommand(cmd, opts) {
|
|
2301
2325
|
return this.tree.applyCommand(cmd, this, opts);
|
|
@@ -2388,8 +2412,7 @@ class WunderbaumNode {
|
|
|
2388
2412
|
}
|
|
2389
2413
|
/** Find a node relative to self.
|
|
2390
2414
|
*
|
|
2391
|
-
* @
|
|
2392
|
-
* or a keyword ('down', 'first', 'last', 'left', 'parent', 'right', 'up').
|
|
2415
|
+
* @see {@link Wunderbaum.findRelatedNode|tree.findRelatedNode()}
|
|
2393
2416
|
*/
|
|
2394
2417
|
findRelatedNode(where, includeHidden = false) {
|
|
2395
2418
|
return this.tree.findRelatedNode(this, where, includeHidden);
|
|
@@ -2690,7 +2713,7 @@ class WunderbaumNode {
|
|
|
2690
2713
|
assert(!this.parent);
|
|
2691
2714
|
tree.columns = data.columns;
|
|
2692
2715
|
delete data.columns;
|
|
2693
|
-
tree.
|
|
2716
|
+
tree.updateColumns({ calculateCols: false });
|
|
2694
2717
|
}
|
|
2695
2718
|
this._loadSourceObject(data);
|
|
2696
2719
|
}
|
|
@@ -2726,19 +2749,20 @@ class WunderbaumNode {
|
|
|
2726
2749
|
this.setStatus(NodeStatusType.ok);
|
|
2727
2750
|
return;
|
|
2728
2751
|
}
|
|
2729
|
-
assert(isArray(source) || (source && source.url), "The lazyLoad event must return a node list, `{url: ...}
|
|
2752
|
+
assert(isArray(source) || (source && source.url), "The lazyLoad event must return a node list, `{url: ...}`, or false.");
|
|
2730
2753
|
await this.load(source); // also calls setStatus('ok')
|
|
2731
2754
|
if (wasExpanded) {
|
|
2732
2755
|
this.expanded = true;
|
|
2733
2756
|
this.tree.setModified(ChangeType.structure);
|
|
2734
2757
|
}
|
|
2735
2758
|
else {
|
|
2736
|
-
this.
|
|
2759
|
+
this.setModified(); // Fix expander icon to 'loaded'
|
|
2737
2760
|
}
|
|
2738
2761
|
}
|
|
2739
2762
|
catch (e) {
|
|
2763
|
+
this.logError("Error during loadLazy()", e);
|
|
2764
|
+
this._callEvent("error", { error: e });
|
|
2740
2765
|
this.setStatus(NodeStatusType.error, "" + e);
|
|
2741
|
-
// } finally {
|
|
2742
2766
|
}
|
|
2743
2767
|
return;
|
|
2744
2768
|
}
|
|
@@ -2906,31 +2930,33 @@ class WunderbaumNode {
|
|
|
2906
2930
|
// Allow to pass 'ArrowLeft' instead of 'left'
|
|
2907
2931
|
where = KEY_TO_ACTION_DICT[where] || where;
|
|
2908
2932
|
// Otherwise activate or focus the related node
|
|
2909
|
-
|
|
2910
|
-
if (node) {
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
node.makeVisible({ scrollIntoView: false });
|
|
2914
|
-
}
|
|
2915
|
-
catch (e) { } // #272
|
|
2916
|
-
node.setFocus();
|
|
2917
|
-
if ((options === null || options === void 0 ? void 0 : options.activate) === false) {
|
|
2918
|
-
return Promise.resolve(this);
|
|
2919
|
-
}
|
|
2920
|
-
return node.setActive(true, { event: options === null || options === void 0 ? void 0 : options.event });
|
|
2933
|
+
const node = this.findRelatedNode(where);
|
|
2934
|
+
if (!node) {
|
|
2935
|
+
this.logWarn(`Could not find related node '${where}'.`);
|
|
2936
|
+
return Promise.resolve(this);
|
|
2921
2937
|
}
|
|
2922
|
-
|
|
2923
|
-
|
|
2938
|
+
// setFocus/setActive will scroll later (if autoScroll is specified)
|
|
2939
|
+
try {
|
|
2940
|
+
node.makeVisible({ scrollIntoView: false });
|
|
2941
|
+
}
|
|
2942
|
+
catch (e) { } // #272
|
|
2943
|
+
node.setFocus();
|
|
2944
|
+
if ((options === null || options === void 0 ? void 0 : options.activate) === false) {
|
|
2945
|
+
return Promise.resolve(this);
|
|
2946
|
+
}
|
|
2947
|
+
return node.setActive(true, { event: options === null || options === void 0 ? void 0 : options.event });
|
|
2924
2948
|
}
|
|
2925
2949
|
/** Delete this node and all descendants. */
|
|
2926
2950
|
remove() {
|
|
2927
2951
|
const tree = this.tree;
|
|
2928
2952
|
const pos = this.parent.children.indexOf(this);
|
|
2953
|
+
this.triggerModify("remove");
|
|
2929
2954
|
this.parent.children.splice(pos, 1);
|
|
2930
2955
|
this.visit((n) => {
|
|
2931
2956
|
n.removeMarkup();
|
|
2932
2957
|
tree._unregisterNode(n);
|
|
2933
2958
|
}, true);
|
|
2959
|
+
tree.setModified(ChangeType.structure);
|
|
2934
2960
|
}
|
|
2935
2961
|
/** Remove all descendants of this node. */
|
|
2936
2962
|
removeChildren() {
|
|
@@ -3055,6 +3081,7 @@ class WunderbaumNode {
|
|
|
3055
3081
|
const activeColIdx = tree.navMode === NavigationMode.row ? null : tree.activeColIdx;
|
|
3056
3082
|
// let colElems: HTMLElement[];
|
|
3057
3083
|
const isNew = !rowDiv;
|
|
3084
|
+
assert(!isNew || (opts && opts.after), "opts.after expected, unless updating");
|
|
3058
3085
|
assert(!this.isRootNode());
|
|
3059
3086
|
//
|
|
3060
3087
|
let rowClasses = ["wb-row"];
|
|
@@ -3106,7 +3133,7 @@ class WunderbaumNode {
|
|
|
3106
3133
|
nodeElem.appendChild(elem);
|
|
3107
3134
|
ofsTitlePx += ICON_WIDTH;
|
|
3108
3135
|
}
|
|
3109
|
-
if (treeOptions.minExpandLevel
|
|
3136
|
+
if (!treeOptions.minExpandLevel || level > treeOptions.minExpandLevel) {
|
|
3110
3137
|
expanderSpan = document.createElement("i");
|
|
3111
3138
|
nodeElem.appendChild(expanderSpan);
|
|
3112
3139
|
ofsTitlePx += ICON_WIDTH;
|
|
@@ -3235,9 +3262,19 @@ class WunderbaumNode {
|
|
|
3235
3262
|
});
|
|
3236
3263
|
}
|
|
3237
3264
|
// Attach to DOM as late as possible
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3265
|
+
if (isNew) {
|
|
3266
|
+
const after = opts ? opts.after : "last";
|
|
3267
|
+
switch (after) {
|
|
3268
|
+
case "first":
|
|
3269
|
+
tree.nodeListElement.prepend(rowDiv);
|
|
3270
|
+
break;
|
|
3271
|
+
case "last":
|
|
3272
|
+
tree.nodeListElement.appendChild(rowDiv);
|
|
3273
|
+
break;
|
|
3274
|
+
default:
|
|
3275
|
+
opts.after.after(rowDiv);
|
|
3276
|
+
}
|
|
3277
|
+
}
|
|
3241
3278
|
}
|
|
3242
3279
|
/**
|
|
3243
3280
|
* Remove all children, collapse, and set the lazy-flag, so that the lazyLoad
|
|
@@ -3311,14 +3348,15 @@ class WunderbaumNode {
|
|
|
3311
3348
|
*
|
|
3312
3349
|
* Evaluation sequence:
|
|
3313
3350
|
*
|
|
3314
|
-
* If `tree.options.<name>` is a callback that returns something, use that.
|
|
3315
|
-
* Else if `node.<name>` is defined, use that.
|
|
3316
|
-
* Else if `tree.types[<node.type>]` is a value, use that.
|
|
3317
|
-
* Else if `tree.options.<name>` is a value, use that.
|
|
3318
|
-
* Else use `defaultValue`.
|
|
3351
|
+
* - If `tree.options.<name>` is a callback that returns something, use that.
|
|
3352
|
+
* - Else if `node.<name>` is defined, use that.
|
|
3353
|
+
* - Else if `tree.types[<node.type>]` is a value, use that.
|
|
3354
|
+
* - Else if `tree.options.<name>` is a value, use that.
|
|
3355
|
+
* - Else use `defaultValue`.
|
|
3319
3356
|
*
|
|
3320
3357
|
* @param name name of the option property (on node and tree)
|
|
3321
3358
|
* @param defaultValue return this if nothing else matched
|
|
3359
|
+
* {@link Wunderbaum.getOption|Wunderbaum.getOption()}
|
|
3322
3360
|
*/
|
|
3323
3361
|
getOption(name, defaultValue) {
|
|
3324
3362
|
let tree = this.tree;
|
|
@@ -3353,15 +3391,21 @@ class WunderbaumNode {
|
|
|
3353
3391
|
// Use value from value options dict, fallback do default
|
|
3354
3392
|
return value !== null && value !== void 0 ? value : defaultValue;
|
|
3355
3393
|
}
|
|
3394
|
+
/** Make sure that this node is visible in the viewport.
|
|
3395
|
+
* @see {@link Wunderbaum.scrollTo|Wunderbaum.scrollTo()}
|
|
3396
|
+
*/
|
|
3356
3397
|
async scrollIntoView(options) {
|
|
3357
3398
|
return this.tree.scrollTo(this);
|
|
3358
3399
|
}
|
|
3400
|
+
/**
|
|
3401
|
+
* Activate this node, deactivate previous, send events, activate column and scroll int viewport.
|
|
3402
|
+
*/
|
|
3359
3403
|
async setActive(flag = true, options) {
|
|
3360
3404
|
const tree = this.tree;
|
|
3361
3405
|
const prev = tree.activeNode;
|
|
3362
3406
|
const retrigger = options === null || options === void 0 ? void 0 : options.retrigger;
|
|
3363
|
-
const
|
|
3364
|
-
if (!
|
|
3407
|
+
const noEvents = options === null || options === void 0 ? void 0 : options.noEvents;
|
|
3408
|
+
if (!noEvents) {
|
|
3365
3409
|
let orgEvent = options === null || options === void 0 ? void 0 : options.event;
|
|
3366
3410
|
if (flag) {
|
|
3367
3411
|
if (prev !== this || retrigger) {
|
|
@@ -3399,19 +3443,18 @@ class WunderbaumNode {
|
|
|
3399
3443
|
// requestAnimationFrame(() => {
|
|
3400
3444
|
// this.scrollIntoView();
|
|
3401
3445
|
// })
|
|
3402
|
-
this.scrollIntoView();
|
|
3403
|
-
}
|
|
3404
|
-
setModified(change = ChangeType.status) {
|
|
3405
|
-
assert(change === ChangeType.status);
|
|
3406
|
-
this.tree.setModified(ChangeType.row, this);
|
|
3446
|
+
return this.scrollIntoView();
|
|
3407
3447
|
}
|
|
3448
|
+
/**
|
|
3449
|
+
* Expand or collapse this node.
|
|
3450
|
+
*/
|
|
3408
3451
|
async setExpanded(flag = true, options) {
|
|
3409
3452
|
// alert("" + this.getLevel() + ", "+ this.getOption("minExpandLevel");
|
|
3410
3453
|
if (!flag &&
|
|
3411
3454
|
this.isExpanded() &&
|
|
3412
3455
|
this.getLevel() < this.getOption("minExpandLevel") &&
|
|
3413
3456
|
!getOption(options, "force")) {
|
|
3414
|
-
this.logDebug("Ignored collapse request.");
|
|
3457
|
+
this.logDebug("Ignored collapse request below expandLevel.");
|
|
3415
3458
|
return;
|
|
3416
3459
|
}
|
|
3417
3460
|
if (flag && this.lazy && this.children == null) {
|
|
@@ -3420,16 +3463,31 @@ class WunderbaumNode {
|
|
|
3420
3463
|
this.expanded = flag;
|
|
3421
3464
|
this.tree.setModified(ChangeType.structure);
|
|
3422
3465
|
}
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3466
|
+
/**
|
|
3467
|
+
* Set keyboard focus here.
|
|
3468
|
+
* @see {@link setActive}
|
|
3469
|
+
*/
|
|
3427
3470
|
setFocus(flag = true, options) {
|
|
3428
3471
|
const prev = this.tree.focusNode;
|
|
3429
3472
|
this.tree.focusNode = this;
|
|
3430
3473
|
prev === null || prev === void 0 ? void 0 : prev.setModified();
|
|
3431
3474
|
this.setModified();
|
|
3432
3475
|
}
|
|
3476
|
+
/** Set a new icon path or class. */
|
|
3477
|
+
setIcon() {
|
|
3478
|
+
throw new Error("Not yet implemented");
|
|
3479
|
+
// this.setModified();
|
|
3480
|
+
}
|
|
3481
|
+
/** Change node's {@link key} and/or {@link refKey}. */
|
|
3482
|
+
setKey(key, refKey) {
|
|
3483
|
+
throw new Error("Not yet implemented");
|
|
3484
|
+
}
|
|
3485
|
+
/** Schedule a render, typically called to update after a status or data change. */
|
|
3486
|
+
setModified(change = ChangeType.status) {
|
|
3487
|
+
assert(change === ChangeType.status);
|
|
3488
|
+
this.tree.setModified(ChangeType.row, this);
|
|
3489
|
+
}
|
|
3490
|
+
/** Modify the check/uncheck state. */
|
|
3433
3491
|
setSelected(flag = true, options) {
|
|
3434
3492
|
const prev = this.selected;
|
|
3435
3493
|
if (!!flag !== prev) {
|
|
@@ -3438,10 +3496,9 @@ class WunderbaumNode {
|
|
|
3438
3496
|
this.selected = !!flag;
|
|
3439
3497
|
this.setModified();
|
|
3440
3498
|
}
|
|
3441
|
-
/**
|
|
3442
|
-
*/
|
|
3499
|
+
/** Display node status (ok, loading, error, noData) using styles and a dummy child node. */
|
|
3443
3500
|
setStatus(status, message, details) {
|
|
3444
|
-
|
|
3501
|
+
const tree = this.tree;
|
|
3445
3502
|
let statusNode = null;
|
|
3446
3503
|
const _clearStatusNode = () => {
|
|
3447
3504
|
// Remove dedicated dummy node, if any
|
|
@@ -3515,6 +3572,7 @@ class WunderbaumNode {
|
|
|
3515
3572
|
tree.setModified(ChangeType.structure);
|
|
3516
3573
|
return statusNode;
|
|
3517
3574
|
}
|
|
3575
|
+
/** Rename this node. */
|
|
3518
3576
|
setTitle(title) {
|
|
3519
3577
|
this.title = title;
|
|
3520
3578
|
this.setModified();
|
|
@@ -3538,10 +3596,16 @@ class WunderbaumNode {
|
|
|
3538
3596
|
* @param {object} [extra]
|
|
3539
3597
|
*/
|
|
3540
3598
|
triggerModify(operation, extra) {
|
|
3599
|
+
if (!this.parent) {
|
|
3600
|
+
return;
|
|
3601
|
+
}
|
|
3541
3602
|
this.parent.triggerModifyChild(operation, this, extra);
|
|
3542
3603
|
}
|
|
3543
|
-
/**
|
|
3544
|
-
*
|
|
3604
|
+
/**
|
|
3605
|
+
* Call fn(node) for all child nodes in hierarchical order (depth-first).
|
|
3606
|
+
*
|
|
3607
|
+
* Stop iteration, if fn() returns false. Skip current branch, if fn()
|
|
3608
|
+
* returns "skip".<br>
|
|
3545
3609
|
* Return false if iteration was stopped.
|
|
3546
3610
|
*
|
|
3547
3611
|
* @param {function} callback the callback function.
|
|
@@ -3585,7 +3649,8 @@ class WunderbaumNode {
|
|
|
3585
3649
|
}
|
|
3586
3650
|
return true;
|
|
3587
3651
|
}
|
|
3588
|
-
/**
|
|
3652
|
+
/**
|
|
3653
|
+
* Call fn(node) for all sibling nodes.<br>
|
|
3589
3654
|
* Stop iteration, if fn() returns false.<br>
|
|
3590
3655
|
* Return false if iteration was stopped.
|
|
3591
3656
|
*
|
|
@@ -3616,7 +3681,7 @@ WunderbaumNode.sequence = 0;
|
|
|
3616
3681
|
/*!
|
|
3617
3682
|
* Wunderbaum - ext-edit
|
|
3618
3683
|
* Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
|
|
3619
|
-
* v0.0.
|
|
3684
|
+
* v0.0.3, Mon, 18 Apr 2022 11:52:44 GMT (https://github.com/mar10/wunderbaum)
|
|
3620
3685
|
*/
|
|
3621
3686
|
// const START_MARKER = "\uFFF7";
|
|
3622
3687
|
class EditExtension extends WunderbaumExtension {
|
|
@@ -3734,7 +3799,7 @@ class EditExtension extends WunderbaumExtension {
|
|
|
3734
3799
|
break;
|
|
3735
3800
|
case "F2":
|
|
3736
3801
|
if (trigger.indexOf("F2") >= 0) {
|
|
3737
|
-
// tree.
|
|
3802
|
+
// tree.setNavigationMode(NavigationMode.cellEdit);
|
|
3738
3803
|
this.startEditTitle();
|
|
3739
3804
|
return false;
|
|
3740
3805
|
}
|
|
@@ -3775,6 +3840,7 @@ class EditExtension extends WunderbaumExtension {
|
|
|
3775
3840
|
if (validity) {
|
|
3776
3841
|
// Permanently apply input validations (CSS and tooltip)
|
|
3777
3842
|
inputElem.addEventListener("keydown", (e) => {
|
|
3843
|
+
inputElem.setCustomValidity("");
|
|
3778
3844
|
if (!inputElem.reportValidity()) ;
|
|
3779
3845
|
});
|
|
3780
3846
|
}
|
|
@@ -3815,6 +3881,11 @@ class EditExtension extends WunderbaumExtension {
|
|
|
3815
3881
|
}
|
|
3816
3882
|
node.logDebug(`stopEditTitle(${apply})`, opts, focusElem, newValue);
|
|
3817
3883
|
if (apply && newValue !== null && newValue !== node.title) {
|
|
3884
|
+
const errMsg = focusElem.validationMessage;
|
|
3885
|
+
if (errMsg) {
|
|
3886
|
+
// input element's native validation failed
|
|
3887
|
+
throw new Error(`Input validation failed for "${newValue}": ${errMsg}.`);
|
|
3888
|
+
}
|
|
3818
3889
|
const colElem = node.getColElem(0);
|
|
3819
3890
|
this._applyChange("edit.apply", node, colElem, {
|
|
3820
3891
|
oldValue: node.title,
|
|
@@ -3901,8 +3972,8 @@ class EditExtension extends WunderbaumExtension {
|
|
|
3901
3972
|
* Copyright (c) 2021-2022, Martin Wendt (https://wwWendt.de).
|
|
3902
3973
|
* Released under the MIT license.
|
|
3903
3974
|
*
|
|
3904
|
-
* @version v0.0.
|
|
3905
|
-
* @date
|
|
3975
|
+
* @version v0.0.3
|
|
3976
|
+
* @date Mon, 18 Apr 2022 11:52:44 GMT
|
|
3906
3977
|
*/
|
|
3907
3978
|
// const class_prefix = "wb-";
|
|
3908
3979
|
// const node_props: string[] = ["title", "key", "refKey"];
|
|
@@ -3918,37 +3989,43 @@ class Wunderbaum {
|
|
|
3918
3989
|
this.extensions = {};
|
|
3919
3990
|
this.keyMap = new Map();
|
|
3920
3991
|
this.refKeyMap = new Map();
|
|
3921
|
-
|
|
3922
|
-
|
|
3923
|
-
|
|
3992
|
+
// protected viewNodes = new Set<WunderbaumNode>();
|
|
3993
|
+
this.treeRowCount = 0;
|
|
3994
|
+
this._disableUpdateCount = 0;
|
|
3924
3995
|
// protected eventHandlers : Array<function> = [];
|
|
3996
|
+
/** Currently active node if any. */
|
|
3925
3997
|
this.activeNode = null;
|
|
3998
|
+
/** Current node hat has keyboard focus if any. */
|
|
3926
3999
|
this.focusNode = null;
|
|
3927
|
-
this._disableUpdate = 0;
|
|
3928
|
-
this._disableUpdateCount = 0;
|
|
3929
4000
|
/** Shared properties, referenced by `node.type`. */
|
|
3930
4001
|
this.types = {};
|
|
3931
4002
|
/** List of column definitions. */
|
|
3932
4003
|
this.columns = [];
|
|
3933
4004
|
this._columnsById = {};
|
|
3934
4005
|
// Modification Status
|
|
3935
|
-
|
|
3936
|
-
|
|
3937
|
-
|
|
3938
|
-
this.
|
|
4006
|
+
// protected changedSince = 0;
|
|
4007
|
+
// protected changes = new Set<ChangeType>();
|
|
4008
|
+
// protected changedNodes = new Set<WunderbaumNode>();
|
|
4009
|
+
this.changeRedrawRequestPending = false;
|
|
4010
|
+
/** Expose some useful methods of the util.ts module as `tree._util`. */
|
|
4011
|
+
this._util = util;
|
|
3939
4012
|
// --- FILTER ---
|
|
3940
4013
|
this.filterMode = null;
|
|
3941
4014
|
// --- KEYNAV ---
|
|
4015
|
+
/** @internal Use `setColumn()`/`getActiveColElem()`*/
|
|
3942
4016
|
this.activeColIdx = 0;
|
|
4017
|
+
/** @internal */
|
|
3943
4018
|
this.navMode = NavigationMode.row;
|
|
4019
|
+
/** @internal */
|
|
3944
4020
|
this.lastQuicksearchTime = 0;
|
|
4021
|
+
/** @internal */
|
|
3945
4022
|
this.lastQuicksearchTerm = "";
|
|
3946
4023
|
// --- EDIT ---
|
|
3947
4024
|
this.lastClickTime = 0;
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
|
|
3951
|
-
this.log = this.logDebug;
|
|
4025
|
+
/** Alias for {@link Wunderbaum.logDebug}.
|
|
4026
|
+
* @alias Wunderbaum.logDebug
|
|
4027
|
+
*/
|
|
4028
|
+
this.log = this.logDebug;
|
|
3952
4029
|
let opts = (this.options = extend({
|
|
3953
4030
|
id: null,
|
|
3954
4031
|
source: null,
|
|
@@ -4182,37 +4259,18 @@ class Wunderbaum {
|
|
|
4182
4259
|
forceClose: true,
|
|
4183
4260
|
});
|
|
4184
4261
|
}
|
|
4185
|
-
// if (flag && !this.activeNode ) {
|
|
4186
|
-
// setTimeout(() => {
|
|
4187
|
-
// if (!this.activeNode) {
|
|
4188
|
-
// const firstNode = this.getFirstChild();
|
|
4189
|
-
// if (firstNode && !firstNode?.isStatusNode()) {
|
|
4190
|
-
// firstNode.logInfo("Activate on focus", e);
|
|
4191
|
-
// firstNode.setActive(true, { event: e });
|
|
4192
|
-
// }
|
|
4193
|
-
// }
|
|
4194
|
-
// }, 10);
|
|
4195
|
-
// }
|
|
4196
4262
|
});
|
|
4197
4263
|
}
|
|
4198
|
-
/**
|
|
4199
|
-
|
|
4200
|
-
// const coldivs = "<span class='wb-col'></span>".repeat(this.columns.length);
|
|
4201
|
-
// this.element.innerHTML = `
|
|
4202
|
-
// <div class='wb-header'>
|
|
4203
|
-
// <div class='wb-row'>
|
|
4204
|
-
// ${coldivs}
|
|
4205
|
-
// </div>
|
|
4206
|
-
// </div>`;
|
|
4207
|
-
// }
|
|
4208
|
-
/** Return a Wunderbaum instance, from element, index, or event.
|
|
4264
|
+
/**
|
|
4265
|
+
* Return a Wunderbaum instance, from element, id, index, or event.
|
|
4209
4266
|
*
|
|
4210
|
-
*
|
|
4211
|
-
* getTree();
|
|
4212
|
-
* getTree(1);
|
|
4213
|
-
* getTree(event);
|
|
4214
|
-
* getTree("foo");
|
|
4267
|
+
* ```js
|
|
4268
|
+
* getTree(); // Get first Wunderbaum instance on page
|
|
4269
|
+
* getTree(1); // Get second Wunderbaum instance on page
|
|
4270
|
+
* getTree(event); // Get tree for this mouse- or keyboard event
|
|
4271
|
+
* getTree("foo"); // Get tree for this `tree.options.id`
|
|
4215
4272
|
* getTree("#tree"); // Get tree for this matching element
|
|
4273
|
+
* ```
|
|
4216
4274
|
*/
|
|
4217
4275
|
static getTree(el) {
|
|
4218
4276
|
if (el instanceof Wunderbaum) {
|
|
@@ -4253,9 +4311,8 @@ class Wunderbaum {
|
|
|
4253
4311
|
}
|
|
4254
4312
|
return null;
|
|
4255
4313
|
}
|
|
4256
|
-
/**
|
|
4257
|
-
*
|
|
4258
|
-
* @param el
|
|
4314
|
+
/**
|
|
4315
|
+
* Return a WunderbaumNode instance from element or event.
|
|
4259
4316
|
*/
|
|
4260
4317
|
static getNode(el) {
|
|
4261
4318
|
if (!el) {
|
|
@@ -4277,7 +4334,7 @@ class Wunderbaum {
|
|
|
4277
4334
|
}
|
|
4278
4335
|
return null;
|
|
4279
4336
|
}
|
|
4280
|
-
/** */
|
|
4337
|
+
/** @internal */
|
|
4281
4338
|
_registerExtension(extension) {
|
|
4282
4339
|
this.extensionList.push(extension);
|
|
4283
4340
|
this.extensions[extension.id] = extension;
|
|
@@ -4319,7 +4376,7 @@ class Wunderbaum {
|
|
|
4319
4376
|
node.tree = null;
|
|
4320
4377
|
node.parent = null;
|
|
4321
4378
|
// node.title = "DISPOSED: " + node.title
|
|
4322
|
-
this.viewNodes.delete(node);
|
|
4379
|
+
// this.viewNodes.delete(node);
|
|
4323
4380
|
node.removeMarkup();
|
|
4324
4381
|
}
|
|
4325
4382
|
/** Call all hook methods of all registered extensions.*/
|
|
@@ -4337,7 +4394,9 @@ class Wunderbaum {
|
|
|
4337
4394
|
}
|
|
4338
4395
|
return res;
|
|
4339
4396
|
}
|
|
4340
|
-
/**
|
|
4397
|
+
/**
|
|
4398
|
+
* Call tree method or extension method if defined.
|
|
4399
|
+
*
|
|
4341
4400
|
* Example:
|
|
4342
4401
|
* ```js
|
|
4343
4402
|
* tree._callMethod("edit.startEdit", "arg1", "arg2")
|
|
@@ -4354,7 +4413,9 @@ class Wunderbaum {
|
|
|
4354
4413
|
this.logError(`Calling undefined method '${name}()'.`);
|
|
4355
4414
|
}
|
|
4356
4415
|
}
|
|
4357
|
-
/**
|
|
4416
|
+
/**
|
|
4417
|
+
* Call event handler if defined in tree or tree.EXTENSION options.
|
|
4418
|
+
*
|
|
4358
4419
|
* Example:
|
|
4359
4420
|
* ```js
|
|
4360
4421
|
* tree._callEvent("edit.beforeEdit", {foo: 42})
|
|
@@ -4370,27 +4431,33 @@ class Wunderbaum {
|
|
|
4370
4431
|
// this.logError(`Triggering undefined event '${name}'.`)
|
|
4371
4432
|
}
|
|
4372
4433
|
}
|
|
4373
|
-
/** Return the
|
|
4374
|
-
|
|
4375
|
-
let topIdx, node;
|
|
4376
|
-
if (complete) {
|
|
4377
|
-
topIdx = Math.ceil(this.scrollContainer.scrollTop / ROW_HEIGHT);
|
|
4378
|
-
}
|
|
4379
|
-
else {
|
|
4380
|
-
topIdx = Math.floor(this.scrollContainer.scrollTop / ROW_HEIGHT);
|
|
4381
|
-
}
|
|
4434
|
+
/** Return the node for given row index. */
|
|
4435
|
+
_getNodeByRowIdx(idx) {
|
|
4382
4436
|
// TODO: start searching from active node (reverse)
|
|
4437
|
+
let node = null;
|
|
4383
4438
|
this.visitRows((n) => {
|
|
4384
|
-
if (n._rowIdx ===
|
|
4439
|
+
if (n._rowIdx === idx) {
|
|
4385
4440
|
node = n;
|
|
4386
4441
|
return false;
|
|
4387
4442
|
}
|
|
4388
4443
|
});
|
|
4389
4444
|
return node;
|
|
4390
4445
|
}
|
|
4391
|
-
/** Return the
|
|
4446
|
+
/** Return the topmost visible node in the viewport. */
|
|
4447
|
+
_firstNodeInView(complete = true) {
|
|
4448
|
+
let topIdx;
|
|
4449
|
+
const gracePy = 1; // ignore subpixel scrolling
|
|
4450
|
+
if (complete) {
|
|
4451
|
+
topIdx = Math.ceil((this.scrollContainer.scrollTop - gracePy) / ROW_HEIGHT);
|
|
4452
|
+
}
|
|
4453
|
+
else {
|
|
4454
|
+
topIdx = Math.floor(this.scrollContainer.scrollTop / ROW_HEIGHT);
|
|
4455
|
+
}
|
|
4456
|
+
return this._getNodeByRowIdx(topIdx);
|
|
4457
|
+
}
|
|
4458
|
+
/** Return the lowest visible node in the viewport. */
|
|
4392
4459
|
_lastNodeInView(complete = true) {
|
|
4393
|
-
let bottomIdx
|
|
4460
|
+
let bottomIdx;
|
|
4394
4461
|
if (complete) {
|
|
4395
4462
|
bottomIdx =
|
|
4396
4463
|
Math.floor((this.scrollContainer.scrollTop + this.scrollContainer.clientHeight) /
|
|
@@ -4401,16 +4468,10 @@ class Wunderbaum {
|
|
|
4401
4468
|
Math.ceil((this.scrollContainer.scrollTop + this.scrollContainer.clientHeight) /
|
|
4402
4469
|
ROW_HEIGHT) - 1;
|
|
4403
4470
|
}
|
|
4404
|
-
|
|
4405
|
-
this.
|
|
4406
|
-
if (n._rowIdx === bottomIdx) {
|
|
4407
|
-
node = n;
|
|
4408
|
-
return false;
|
|
4409
|
-
}
|
|
4410
|
-
});
|
|
4411
|
-
return node;
|
|
4471
|
+
bottomIdx = Math.min(bottomIdx, this.count(true) - 1);
|
|
4472
|
+
return this._getNodeByRowIdx(bottomIdx);
|
|
4412
4473
|
}
|
|
4413
|
-
/** Return preceeding visible node in the viewport */
|
|
4474
|
+
/** Return preceeding visible node in the viewport. */
|
|
4414
4475
|
_getPrevNodeInView(node, ofs = 1) {
|
|
4415
4476
|
this.visitRows((n) => {
|
|
4416
4477
|
node = n;
|
|
@@ -4420,7 +4481,7 @@ class Wunderbaum {
|
|
|
4420
4481
|
}, { reverse: true, start: node || this.getActiveNode() });
|
|
4421
4482
|
return node;
|
|
4422
4483
|
}
|
|
4423
|
-
/** Return following visible node in the viewport */
|
|
4484
|
+
/** Return following visible node in the viewport. */
|
|
4424
4485
|
_getNextNodeInView(node, ofs = 1) {
|
|
4425
4486
|
this.visitRows((n) => {
|
|
4426
4487
|
node = n;
|
|
@@ -4430,10 +4491,15 @@ class Wunderbaum {
|
|
|
4430
4491
|
}, { reverse: false, start: node || this.getActiveNode() });
|
|
4431
4492
|
return node;
|
|
4432
4493
|
}
|
|
4494
|
+
/**
|
|
4495
|
+
* Append (or insert) a list of toplevel nodes.
|
|
4496
|
+
*
|
|
4497
|
+
* @see {@link WunderbaumNode.addChildren}
|
|
4498
|
+
*/
|
|
4433
4499
|
addChildren(nodeData, options) {
|
|
4434
4500
|
return this.root.addChildren(nodeData, options);
|
|
4435
4501
|
}
|
|
4436
|
-
|
|
4502
|
+
/**
|
|
4437
4503
|
* Apply a modification or navigation operation.
|
|
4438
4504
|
*
|
|
4439
4505
|
* Most of these commands simply map to a node or tree method.
|
|
@@ -4558,16 +4624,17 @@ class Wunderbaum {
|
|
|
4558
4624
|
this.root.children = null;
|
|
4559
4625
|
this.keyMap.clear();
|
|
4560
4626
|
this.refKeyMap.clear();
|
|
4561
|
-
this.viewNodes.clear();
|
|
4627
|
+
// this.viewNodes.clear();
|
|
4628
|
+
this.treeRowCount = 0;
|
|
4562
4629
|
this.activeNode = null;
|
|
4563
4630
|
this.focusNode = null;
|
|
4564
4631
|
// this.types = {};
|
|
4565
4632
|
// this. columns =[];
|
|
4566
4633
|
// this._columnsById = {};
|
|
4567
4634
|
// Modification Status
|
|
4568
|
-
this.changedSince = 0;
|
|
4569
|
-
this.changes.clear();
|
|
4570
|
-
this.changedNodes.clear();
|
|
4635
|
+
// this.changedSince = 0;
|
|
4636
|
+
// this.changes.clear();
|
|
4637
|
+
// this.changedNodes.clear();
|
|
4571
4638
|
// // --- FILTER ---
|
|
4572
4639
|
// public filterMode: FilterModeType = null;
|
|
4573
4640
|
// // --- KEYNAV ---
|
|
@@ -4595,10 +4662,11 @@ class Wunderbaum {
|
|
|
4595
4662
|
/**
|
|
4596
4663
|
* Return `tree.option.NAME` (also resolving if this is a callback).
|
|
4597
4664
|
*
|
|
4598
|
-
* See also
|
|
4599
|
-
* `tree.types[node.type].NAME`.
|
|
4665
|
+
* See also {@link WunderbaumNode.getOption|WunderbaumNode.getOption()}
|
|
4666
|
+
* to consider `node.NAME` setting and `tree.types[node.type].NAME`.
|
|
4600
4667
|
*
|
|
4601
|
-
* @param name option name (use dot notation to access extension option, e.g.
|
|
4668
|
+
* @param name option name (use dot notation to access extension option, e.g.
|
|
4669
|
+
* `filter.mode`)
|
|
4602
4670
|
*/
|
|
4603
4671
|
getOption(name, defaultValue) {
|
|
4604
4672
|
let ext;
|
|
@@ -4642,18 +4710,14 @@ class Wunderbaum {
|
|
|
4642
4710
|
}
|
|
4643
4711
|
/** Run code, but defer `updateViewport()` until done. */
|
|
4644
4712
|
runWithoutUpdate(func, hint = null) {
|
|
4645
|
-
// const prev = this._disableUpdate;
|
|
4646
|
-
// const start = Date.now();
|
|
4647
|
-
// this._disableUpdate = Date.now();
|
|
4648
4713
|
try {
|
|
4649
4714
|
this.enableUpdate(false);
|
|
4650
|
-
|
|
4715
|
+
const res = func();
|
|
4716
|
+
assert(!(res instanceof Promise));
|
|
4717
|
+
return res;
|
|
4651
4718
|
}
|
|
4652
4719
|
finally {
|
|
4653
4720
|
this.enableUpdate(true);
|
|
4654
|
-
// if (!prev && this._disableUpdate === start) {
|
|
4655
|
-
// this._disableUpdate = 0;
|
|
4656
|
-
// }
|
|
4657
4721
|
}
|
|
4658
4722
|
}
|
|
4659
4723
|
/** Recursively expand all expandable nodes (triggers lazy load id needed). */
|
|
@@ -4671,11 +4735,12 @@ class Wunderbaum {
|
|
|
4671
4735
|
/** Return the number of nodes in the data model.*/
|
|
4672
4736
|
count(visible = false) {
|
|
4673
4737
|
if (visible) {
|
|
4674
|
-
return this.
|
|
4738
|
+
return this.treeRowCount;
|
|
4739
|
+
// return this.viewNodes.size;
|
|
4675
4740
|
}
|
|
4676
4741
|
return this.keyMap.size;
|
|
4677
4742
|
}
|
|
4678
|
-
|
|
4743
|
+
/** @internal sanity check. */
|
|
4679
4744
|
_check() {
|
|
4680
4745
|
let i = 0;
|
|
4681
4746
|
this.visit((n) => {
|
|
@@ -4686,25 +4751,30 @@ class Wunderbaum {
|
|
|
4686
4751
|
}
|
|
4687
4752
|
// util.assert(this.keyMap.size === i);
|
|
4688
4753
|
}
|
|
4689
|
-
/**
|
|
4754
|
+
/**
|
|
4755
|
+
* Find all nodes that matches condition.
|
|
4690
4756
|
*
|
|
4691
4757
|
* @param match title string to search for, or a
|
|
4692
4758
|
* callback function that returns `true` if a node is matched.
|
|
4693
|
-
*
|
|
4759
|
+
*
|
|
4760
|
+
* @see {@link WunderbaumNode.findAll}
|
|
4694
4761
|
*/
|
|
4695
4762
|
findAll(match) {
|
|
4696
4763
|
return this.root.findAll(match);
|
|
4697
4764
|
}
|
|
4698
|
-
/**
|
|
4765
|
+
/**
|
|
4766
|
+
* Find first node that matches condition.
|
|
4699
4767
|
*
|
|
4700
4768
|
* @param match title string to search for, or a
|
|
4701
4769
|
* callback function that returns `true` if a node is matched.
|
|
4702
|
-
* @see
|
|
4770
|
+
* @see {@link WunderbaumNode.findFirst}
|
|
4771
|
+
*
|
|
4703
4772
|
*/
|
|
4704
4773
|
findFirst(match) {
|
|
4705
4774
|
return this.root.findFirst(match);
|
|
4706
4775
|
}
|
|
4707
|
-
/**
|
|
4776
|
+
/**
|
|
4777
|
+
* Find the next visible node that starts with `match`, starting at `startNode`
|
|
4708
4778
|
* and wrap-around at the end.
|
|
4709
4779
|
*/
|
|
4710
4780
|
findNextNode(match, startNode) {
|
|
@@ -4734,7 +4804,8 @@ class Wunderbaum {
|
|
|
4734
4804
|
}
|
|
4735
4805
|
return res;
|
|
4736
4806
|
}
|
|
4737
|
-
/**
|
|
4807
|
+
/**
|
|
4808
|
+
* Find a node relative to another node.
|
|
4738
4809
|
*
|
|
4739
4810
|
* @param node
|
|
4740
4811
|
* @param where 'down', 'first', 'last', 'left', 'parent', 'right', or 'up'.
|
|
@@ -4744,7 +4815,7 @@ class Wunderbaum {
|
|
|
4744
4815
|
*/
|
|
4745
4816
|
findRelatedNode(node, where, includeHidden = false) {
|
|
4746
4817
|
let res = null;
|
|
4747
|
-
|
|
4818
|
+
const pageSize = Math.floor(this.scrollContainer.clientHeight / ROW_HEIGHT);
|
|
4748
4819
|
switch (where) {
|
|
4749
4820
|
case "parent":
|
|
4750
4821
|
if (node.parent && node.parent.parent) {
|
|
@@ -4800,9 +4871,9 @@ class Wunderbaum {
|
|
|
4800
4871
|
res = this._getNextNodeInView(node);
|
|
4801
4872
|
break;
|
|
4802
4873
|
case "pageDown":
|
|
4803
|
-
|
|
4804
|
-
// this.logDebug(where
|
|
4805
|
-
if (
|
|
4874
|
+
const bottomNode = this._lastNodeInView();
|
|
4875
|
+
// this.logDebug(`${where}(${node}) -> ${bottomNode}`);
|
|
4876
|
+
if (node._rowIdx < bottomNode._rowIdx) {
|
|
4806
4877
|
res = bottomNode;
|
|
4807
4878
|
}
|
|
4808
4879
|
else {
|
|
@@ -4810,12 +4881,13 @@ class Wunderbaum {
|
|
|
4810
4881
|
}
|
|
4811
4882
|
break;
|
|
4812
4883
|
case "pageUp":
|
|
4813
|
-
if (
|
|
4814
|
-
res =
|
|
4884
|
+
if (node._rowIdx === 0) {
|
|
4885
|
+
res = node;
|
|
4815
4886
|
}
|
|
4816
4887
|
else {
|
|
4817
|
-
|
|
4818
|
-
|
|
4888
|
+
const topNode = this._firstNodeInView();
|
|
4889
|
+
// this.logDebug(`${where}(${node}) -> ${topNode}`);
|
|
4890
|
+
if (node._rowIdx > topNode._rowIdx) {
|
|
4819
4891
|
res = topNode;
|
|
4820
4892
|
}
|
|
4821
4893
|
else {
|
|
@@ -4829,7 +4901,7 @@ class Wunderbaum {
|
|
|
4829
4901
|
return res;
|
|
4830
4902
|
}
|
|
4831
4903
|
/**
|
|
4832
|
-
* Return the active cell of the currently active node or null.
|
|
4904
|
+
* Return the active cell (`span.wb-col`) of the currently active node or null.
|
|
4833
4905
|
*/
|
|
4834
4906
|
getActiveColElem() {
|
|
4835
4907
|
if (this.activeNode && this.activeColIdx >= 0) {
|
|
@@ -4895,7 +4967,7 @@ class Wunderbaum {
|
|
|
4895
4967
|
}
|
|
4896
4968
|
else {
|
|
4897
4969
|
// Somewhere near the title
|
|
4898
|
-
if (event.type !== "mousemove") {
|
|
4970
|
+
if (event.type !== "mousemove" && !(event instanceof KeyboardEvent)) {
|
|
4899
4971
|
console.warn("getEventInfo(): not found", event, res);
|
|
4900
4972
|
}
|
|
4901
4973
|
return res;
|
|
@@ -4927,7 +4999,8 @@ class Wunderbaum {
|
|
|
4927
4999
|
isEditing() {
|
|
4928
5000
|
return this._callMethod("edit.isEditingTitle");
|
|
4929
5001
|
}
|
|
4930
|
-
/**
|
|
5002
|
+
/**
|
|
5003
|
+
* Return true if any node is currently beeing loaded, i.e. a Ajax request is pending.
|
|
4931
5004
|
*/
|
|
4932
5005
|
isLoading() {
|
|
4933
5006
|
var res = false;
|
|
@@ -4954,7 +5027,7 @@ class Wunderbaum {
|
|
|
4954
5027
|
console.error.apply(console, args);
|
|
4955
5028
|
}
|
|
4956
5029
|
}
|
|
4957
|
-
|
|
5030
|
+
/** Log to console if opts.debugLevel >= 3 */
|
|
4958
5031
|
logInfo(...args) {
|
|
4959
5032
|
if (this.options.debugLevel >= 3) {
|
|
4960
5033
|
Array.prototype.unshift.call(args, this.toString());
|
|
@@ -4981,75 +5054,6 @@ class Wunderbaum {
|
|
|
4981
5054
|
console.warn.apply(console, args);
|
|
4982
5055
|
}
|
|
4983
5056
|
}
|
|
4984
|
-
/** */
|
|
4985
|
-
render(opts) {
|
|
4986
|
-
const label = this.logTime("render");
|
|
4987
|
-
let idx = 0;
|
|
4988
|
-
let top = 0;
|
|
4989
|
-
const height = ROW_HEIGHT;
|
|
4990
|
-
let modified = false;
|
|
4991
|
-
let start = opts === null || opts === void 0 ? void 0 : opts.startIdx;
|
|
4992
|
-
let end = opts === null || opts === void 0 ? void 0 : opts.endIdx;
|
|
4993
|
-
const obsoleteViewNodes = this.viewNodes;
|
|
4994
|
-
const newNodesOnly = !!getOption(opts, "newNodesOnly");
|
|
4995
|
-
this.viewNodes = new Set();
|
|
4996
|
-
let viewNodes = this.viewNodes;
|
|
4997
|
-
// this.debug("render", opts);
|
|
4998
|
-
assert(start != null && end != null);
|
|
4999
|
-
// Make sure start is always even, so the alternating row colors don't
|
|
5000
|
-
// change when scrolling:
|
|
5001
|
-
if (start % 2) {
|
|
5002
|
-
start--;
|
|
5003
|
-
}
|
|
5004
|
-
this.visitRows(function (node) {
|
|
5005
|
-
const prevIdx = node._rowIdx;
|
|
5006
|
-
viewNodes.add(node);
|
|
5007
|
-
obsoleteViewNodes.delete(node);
|
|
5008
|
-
if (prevIdx !== idx) {
|
|
5009
|
-
node._rowIdx = idx;
|
|
5010
|
-
modified = true;
|
|
5011
|
-
}
|
|
5012
|
-
if (idx < start || idx > end) {
|
|
5013
|
-
node._callEvent("discard");
|
|
5014
|
-
node.removeMarkup();
|
|
5015
|
-
}
|
|
5016
|
-
else if (!node._rowElem || !newNodesOnly) {
|
|
5017
|
-
node.render({ top: top });
|
|
5018
|
-
// }else{
|
|
5019
|
-
// node.log("ignrored render")
|
|
5020
|
-
}
|
|
5021
|
-
idx++;
|
|
5022
|
-
top += height;
|
|
5023
|
-
});
|
|
5024
|
-
for (const prevNode of obsoleteViewNodes) {
|
|
5025
|
-
prevNode._callEvent("discard");
|
|
5026
|
-
prevNode.removeMarkup();
|
|
5027
|
-
}
|
|
5028
|
-
// Resize tree container
|
|
5029
|
-
this.nodeListElement.style.height = "" + top + "px";
|
|
5030
|
-
// this.log("render()", this.nodeListElement.style.height);
|
|
5031
|
-
this.logTimeEnd(label);
|
|
5032
|
-
return modified;
|
|
5033
|
-
}
|
|
5034
|
-
/**Recalc and apply header columns from `this.columns`. */
|
|
5035
|
-
renderHeader() {
|
|
5036
|
-
if (!this.headerElement) {
|
|
5037
|
-
return;
|
|
5038
|
-
}
|
|
5039
|
-
const headerRow = this.headerElement.querySelector(".wb-row");
|
|
5040
|
-
assert(headerRow);
|
|
5041
|
-
headerRow.innerHTML = "<span class='wb-col'></span>".repeat(this.columns.length);
|
|
5042
|
-
for (let i = 0; i < this.columns.length; i++) {
|
|
5043
|
-
const col = this.columns[i];
|
|
5044
|
-
const colElem = headerRow.children[i];
|
|
5045
|
-
colElem.style.left = col._ofsPx + "px";
|
|
5046
|
-
colElem.style.width = col._widthPx + "px";
|
|
5047
|
-
// colElem.textContent = col.title || col.id;
|
|
5048
|
-
const title = escapeHtml(col.title || col.id);
|
|
5049
|
-
colElem.innerHTML = `<span class="wb-col-title">${title}</span> <span class="wb-col-resizer"></span>`;
|
|
5050
|
-
// colElem.innerHTML = `${title} <span class="wb-col-resizer"></span>`;
|
|
5051
|
-
}
|
|
5052
|
-
}
|
|
5053
5057
|
/**
|
|
5054
5058
|
* Make sure that this node is scrolled into the viewport.
|
|
5055
5059
|
*
|
|
@@ -5083,24 +5087,12 @@ class Wunderbaum {
|
|
|
5083
5087
|
this.setModified(ChangeType.vscroll);
|
|
5084
5088
|
}
|
|
5085
5089
|
}
|
|
5086
|
-
/**
|
|
5087
|
-
|
|
5088
|
-
|
|
5089
|
-
|
|
5090
|
-
|
|
5091
|
-
|
|
5092
|
-
}
|
|
5093
|
-
const prevMode = this.navMode;
|
|
5094
|
-
const cellMode = mode !== NavigationMode.row;
|
|
5095
|
-
this.navMode = mode;
|
|
5096
|
-
if (cellMode && prevMode === NavigationMode.row) {
|
|
5097
|
-
this.setColumn(0);
|
|
5098
|
-
}
|
|
5099
|
-
this.element.classList.toggle("wb-cell-mode", cellMode);
|
|
5100
|
-
this.element.classList.toggle("wb-cell-edit-mode", mode === NavigationMode.cellEdit);
|
|
5101
|
-
this.setModified(ChangeType.row, this.activeNode);
|
|
5102
|
-
}
|
|
5103
|
-
/** */
|
|
5090
|
+
/**
|
|
5091
|
+
* Set column #colIdx to 'active'.
|
|
5092
|
+
*
|
|
5093
|
+
* This higlights the column header and -cells by adding the `wb-active` class.
|
|
5094
|
+
* Available in cell-nav and cell-edit mode, not in row-mode.
|
|
5095
|
+
*/
|
|
5104
5096
|
setColumn(colIdx) {
|
|
5105
5097
|
assert(this.navMode !== NavigationMode.row);
|
|
5106
5098
|
assert(0 <= colIdx && colIdx < this.columns.length);
|
|
@@ -5125,7 +5117,7 @@ class Wunderbaum {
|
|
|
5125
5117
|
}
|
|
5126
5118
|
}
|
|
5127
5119
|
}
|
|
5128
|
-
/** */
|
|
5120
|
+
/** Set or remove keybaord focus to the tree container. */
|
|
5129
5121
|
setFocus(flag = true) {
|
|
5130
5122
|
if (flag) {
|
|
5131
5123
|
this.element.focus();
|
|
@@ -5134,20 +5126,24 @@ class Wunderbaum {
|
|
|
5134
5126
|
this.element.blur();
|
|
5135
5127
|
}
|
|
5136
5128
|
}
|
|
5137
|
-
/* */
|
|
5138
5129
|
setModified(change, node, options) {
|
|
5130
|
+
if (this._disableUpdateCount) {
|
|
5131
|
+
// Assuming that we redraw all when enableUpdate() is re-enabled.
|
|
5132
|
+
// this.log(
|
|
5133
|
+
// `IGNORED setModified(${change}) node=${node} (disable level ${this._disableUpdateCount})`
|
|
5134
|
+
// );
|
|
5135
|
+
return;
|
|
5136
|
+
}
|
|
5137
|
+
// this.log(`setModified(${change}) node=${node}`);
|
|
5139
5138
|
if (!(node instanceof WunderbaumNode)) {
|
|
5140
5139
|
options = node;
|
|
5141
5140
|
}
|
|
5142
|
-
if (this._disableUpdate) {
|
|
5143
|
-
return;
|
|
5144
|
-
}
|
|
5145
5141
|
const immediate = !!getOption(options, "immediate");
|
|
5146
5142
|
switch (change) {
|
|
5147
5143
|
case ChangeType.any:
|
|
5148
5144
|
case ChangeType.structure:
|
|
5149
5145
|
case ChangeType.header:
|
|
5150
|
-
this.
|
|
5146
|
+
this.changeRedrawRequestPending = true;
|
|
5151
5147
|
this.updateViewport(immediate);
|
|
5152
5148
|
break;
|
|
5153
5149
|
case ChangeType.vscroll:
|
|
@@ -5164,84 +5160,111 @@ class Wunderbaum {
|
|
|
5164
5160
|
default:
|
|
5165
5161
|
error(`Invalid change type ${change}`);
|
|
5166
5162
|
}
|
|
5167
|
-
// if (!this.changedSince) {
|
|
5168
|
-
// this.changedSince = Date.now();
|
|
5169
|
-
// }
|
|
5170
|
-
// this.changes.add(change);
|
|
5171
|
-
// if (change === ChangeType.structure) {
|
|
5172
|
-
// this.changedNodes.clear();
|
|
5173
|
-
// } else if (node && !this.changes.has(ChangeType.structure)) {
|
|
5174
|
-
// if (this.changedNodes.size < MAX_CHANGED_NODES) {
|
|
5175
|
-
// this.changedNodes.add(node);
|
|
5176
|
-
// } else {
|
|
5177
|
-
// this.changes.add(ChangeType.structure);
|
|
5178
|
-
// this.changedNodes.clear();
|
|
5179
|
-
// }
|
|
5180
|
-
// }
|
|
5181
|
-
// this.log("setModified(" + change + ")", node);
|
|
5182
5163
|
}
|
|
5164
|
+
/** Set the tree's navigation mode. */
|
|
5165
|
+
setNavigationMode(mode) {
|
|
5166
|
+
// util.assert(this.cellNavMode);
|
|
5167
|
+
// util.assert(0 <= colIdx && colIdx < this.columns.length);
|
|
5168
|
+
if (mode === this.navMode) {
|
|
5169
|
+
return;
|
|
5170
|
+
}
|
|
5171
|
+
const prevMode = this.navMode;
|
|
5172
|
+
const cellMode = mode !== NavigationMode.row;
|
|
5173
|
+
this.navMode = mode;
|
|
5174
|
+
if (cellMode && prevMode === NavigationMode.row) {
|
|
5175
|
+
this.setColumn(0);
|
|
5176
|
+
}
|
|
5177
|
+
this.element.classList.toggle("wb-cell-mode", cellMode);
|
|
5178
|
+
this.element.classList.toggle("wb-cell-edit-mode", mode === NavigationMode.cellEdit);
|
|
5179
|
+
this.setModified(ChangeType.row, this.activeNode);
|
|
5180
|
+
}
|
|
5181
|
+
/** Display tree status (ok, loading, error, noData) using styles and a dummy root node. */
|
|
5183
5182
|
setStatus(status, message, details) {
|
|
5184
5183
|
return this.root.setStatus(status, message, details);
|
|
5185
5184
|
}
|
|
5186
5185
|
/** Update column headers and width. */
|
|
5187
5186
|
updateColumns(opts) {
|
|
5188
|
-
|
|
5189
|
-
|
|
5190
|
-
|
|
5187
|
+
opts = Object.assign({ calculateCols: true, updateRows: true }, opts);
|
|
5188
|
+
const minWidth = 4;
|
|
5189
|
+
const vpWidth = this.element.clientWidth;
|
|
5191
5190
|
let totalWeight = 0;
|
|
5192
5191
|
let fixedWidth = 0;
|
|
5193
|
-
|
|
5194
|
-
|
|
5195
|
-
|
|
5196
|
-
this._columnsById
|
|
5197
|
-
let
|
|
5198
|
-
|
|
5199
|
-
|
|
5200
|
-
|
|
5201
|
-
|
|
5202
|
-
|
|
5203
|
-
|
|
5204
|
-
|
|
5205
|
-
|
|
5206
|
-
|
|
5207
|
-
|
|
5208
|
-
|
|
5209
|
-
|
|
5210
|
-
|
|
5211
|
-
col._widthPx
|
|
5192
|
+
let modified = false;
|
|
5193
|
+
if (opts.calculateCols) {
|
|
5194
|
+
// Gather width requests
|
|
5195
|
+
this._columnsById = {};
|
|
5196
|
+
for (let col of this.columns) {
|
|
5197
|
+
this._columnsById[col.id] = col;
|
|
5198
|
+
let cw = col.width;
|
|
5199
|
+
if (!cw || cw === "*") {
|
|
5200
|
+
col._weight = 1.0;
|
|
5201
|
+
totalWeight += 1.0;
|
|
5202
|
+
}
|
|
5203
|
+
else if (typeof cw === "number") {
|
|
5204
|
+
col._weight = cw;
|
|
5205
|
+
totalWeight += cw;
|
|
5206
|
+
}
|
|
5207
|
+
else if (typeof cw === "string" && cw.endsWith("px")) {
|
|
5208
|
+
col._weight = 0;
|
|
5209
|
+
let px = parseFloat(cw.slice(0, -2));
|
|
5210
|
+
if (col._widthPx != px) {
|
|
5211
|
+
modified = true;
|
|
5212
|
+
col._widthPx = px;
|
|
5213
|
+
}
|
|
5214
|
+
fixedWidth += px;
|
|
5215
|
+
}
|
|
5216
|
+
else {
|
|
5217
|
+
error("Invalid column width: " + cw);
|
|
5212
5218
|
}
|
|
5213
|
-
fixedWidth += px;
|
|
5214
5219
|
}
|
|
5215
|
-
|
|
5216
|
-
|
|
5217
|
-
|
|
5218
|
-
|
|
5219
|
-
|
|
5220
|
-
|
|
5221
|
-
|
|
5222
|
-
|
|
5223
|
-
|
|
5224
|
-
|
|
5225
|
-
if (col._widthPx != px) {
|
|
5226
|
-
modified = true;
|
|
5227
|
-
col._widthPx = px;
|
|
5220
|
+
// Share remaining space between non-fixed columns
|
|
5221
|
+
const restPx = Math.max(0, vpWidth - fixedWidth);
|
|
5222
|
+
let ofsPx = 0;
|
|
5223
|
+
for (let col of this.columns) {
|
|
5224
|
+
if (col._weight) {
|
|
5225
|
+
const px = Math.max(minWidth, (restPx * col._weight) / totalWeight);
|
|
5226
|
+
if (col._widthPx != px) {
|
|
5227
|
+
modified = true;
|
|
5228
|
+
col._widthPx = px;
|
|
5229
|
+
}
|
|
5228
5230
|
}
|
|
5231
|
+
col._ofsPx = ofsPx;
|
|
5232
|
+
ofsPx += col._widthPx;
|
|
5229
5233
|
}
|
|
5230
|
-
col._ofsPx = ofsPx;
|
|
5231
|
-
ofsPx += col._widthPx;
|
|
5232
5234
|
}
|
|
5233
5235
|
// Every column has now a calculated `_ofsPx` and `_widthPx`
|
|
5234
5236
|
// this.logInfo("UC", this.columns, vpWidth, this.element.clientWidth, this.element);
|
|
5235
5237
|
// console.trace();
|
|
5236
5238
|
// util.error("BREAK");
|
|
5237
5239
|
if (modified) {
|
|
5238
|
-
this.
|
|
5239
|
-
if (opts.
|
|
5240
|
-
this.
|
|
5240
|
+
this._renderHeaderMarkup();
|
|
5241
|
+
if (opts.updateRows) {
|
|
5242
|
+
this._updateRows();
|
|
5241
5243
|
}
|
|
5242
5244
|
}
|
|
5243
5245
|
}
|
|
5244
|
-
/**
|
|
5246
|
+
/** Create/update header markup from `this.columns` definition.
|
|
5247
|
+
* @internal
|
|
5248
|
+
*/
|
|
5249
|
+
_renderHeaderMarkup() {
|
|
5250
|
+
if (!this.headerElement) {
|
|
5251
|
+
return;
|
|
5252
|
+
}
|
|
5253
|
+
const headerRow = this.headerElement.querySelector(".wb-row");
|
|
5254
|
+
assert(headerRow);
|
|
5255
|
+
headerRow.innerHTML = "<span class='wb-col'></span>".repeat(this.columns.length);
|
|
5256
|
+
for (let i = 0; i < this.columns.length; i++) {
|
|
5257
|
+
const col = this.columns[i];
|
|
5258
|
+
const colElem = headerRow.children[i];
|
|
5259
|
+
colElem.style.left = col._ofsPx + "px";
|
|
5260
|
+
colElem.style.width = col._widthPx + "px";
|
|
5261
|
+
// colElem.textContent = col.title || col.id;
|
|
5262
|
+
const title = escapeHtml(col.title || col.id);
|
|
5263
|
+
colElem.innerHTML = `<span class="wb-col-title">${title}</span> <span class="wb-col-resizer"></span>`;
|
|
5264
|
+
// colElem.innerHTML = `${title} <span class="wb-col-resizer"></span>`;
|
|
5265
|
+
}
|
|
5266
|
+
}
|
|
5267
|
+
/** Render header and all rows that are visible in the viewport (async, throttled). */
|
|
5245
5268
|
updateViewport(immediate = false) {
|
|
5246
5269
|
// Call the `throttle` wrapper for `this._updateViewport()` which will
|
|
5247
5270
|
// execute immediately on the leading edge of a sequence:
|
|
@@ -5250,42 +5273,163 @@ class Wunderbaum {
|
|
|
5250
5273
|
this._updateViewportThrottled.flush();
|
|
5251
5274
|
}
|
|
5252
5275
|
}
|
|
5276
|
+
/**
|
|
5277
|
+
* This is the actual update method, which is wrapped inside a throttle method.
|
|
5278
|
+
* This protected method should not be called directly but via
|
|
5279
|
+
* `tree.updateViewport()` or `tree.setModified()`.
|
|
5280
|
+
* It calls `updateColumns()` and `_updateRows()`.
|
|
5281
|
+
* @internal
|
|
5282
|
+
*/
|
|
5253
5283
|
_updateViewport() {
|
|
5254
|
-
if (this.
|
|
5284
|
+
if (this._disableUpdateCount) {
|
|
5285
|
+
this.log(`IGNORED _updateViewport() disable level: ${this._disableUpdateCount}`);
|
|
5255
5286
|
return;
|
|
5256
5287
|
}
|
|
5257
|
-
const newNodesOnly = !this.
|
|
5258
|
-
this.
|
|
5288
|
+
const newNodesOnly = !this.changeRedrawRequestPending;
|
|
5289
|
+
this.changeRedrawRequestPending = false;
|
|
5259
5290
|
let height = this.scrollContainer.clientHeight;
|
|
5260
|
-
// We cannot get the height for
|
|
5291
|
+
// We cannot get the height for absolute positioned parent, so look at first col
|
|
5261
5292
|
// let headerHeight = this.headerElement.clientHeight
|
|
5262
5293
|
// let headerHeight = this.headerElement.children[0].children[0].clientHeight;
|
|
5263
5294
|
const headerHeight = this.options.headerHeightPx;
|
|
5264
5295
|
const wantHeight = this.element.clientHeight - headerHeight;
|
|
5265
|
-
const ofs = this.scrollContainer.scrollTop;
|
|
5266
5296
|
if (Math.abs(height - wantHeight) > 1.0) {
|
|
5267
5297
|
// this.log("resize", height, wantHeight);
|
|
5268
5298
|
this.scrollContainer.style.height = wantHeight + "px";
|
|
5269
5299
|
height = wantHeight;
|
|
5270
5300
|
}
|
|
5271
|
-
this.updateColumns({
|
|
5272
|
-
this.
|
|
5273
|
-
startIdx: Math.max(0, ofs / ROW_HEIGHT - RENDER_MAX_PREFETCH),
|
|
5274
|
-
endIdx: Math.max(0, (ofs + height) / ROW_HEIGHT + RENDER_MAX_PREFETCH),
|
|
5275
|
-
newNodesOnly: newNodesOnly,
|
|
5276
|
-
});
|
|
5301
|
+
this.updateColumns({ updateRows: false });
|
|
5302
|
+
this._updateRows({ newNodesOnly: newNodesOnly });
|
|
5277
5303
|
this._callEvent("update");
|
|
5278
5304
|
}
|
|
5279
|
-
/**
|
|
5305
|
+
/**
|
|
5306
|
+
* Assert that TR order matches the natural node order
|
|
5307
|
+
* @internal
|
|
5308
|
+
*/
|
|
5309
|
+
_validateRows() {
|
|
5310
|
+
let trs = this.nodeListElement.childNodes;
|
|
5311
|
+
let i = 0;
|
|
5312
|
+
let prev = -1;
|
|
5313
|
+
let ok = true;
|
|
5314
|
+
trs.forEach((element) => {
|
|
5315
|
+
const tr = element;
|
|
5316
|
+
const top = Number.parseInt(tr.style.top);
|
|
5317
|
+
const n = tr._wb_node;
|
|
5318
|
+
// if (i < 4) {
|
|
5319
|
+
// console.info(
|
|
5320
|
+
// `TR#${i}, rowIdx=${n._rowIdx} , top=${top}px: '${n.title}'`
|
|
5321
|
+
// );
|
|
5322
|
+
// }
|
|
5323
|
+
if (top <= prev) {
|
|
5324
|
+
console.warn(`TR order mismatch at index ${i}: top=${top}px, node=${n}`);
|
|
5325
|
+
// throw new Error("fault");
|
|
5326
|
+
ok = false;
|
|
5327
|
+
}
|
|
5328
|
+
prev = top;
|
|
5329
|
+
i++;
|
|
5330
|
+
});
|
|
5331
|
+
return ok;
|
|
5332
|
+
}
|
|
5333
|
+
/*
|
|
5334
|
+
* - Traverse all *visible* of the whole tree, i.e. skip collapsed nodes.
|
|
5335
|
+
* - Store count of rows to `tree.treeRowCount`.
|
|
5336
|
+
* - Renumber `node._rowIdx` for all visible nodes.
|
|
5337
|
+
* - Calculate the index range that must be rendered to fill the viewport
|
|
5338
|
+
* (including upper and lower prefetch)
|
|
5339
|
+
* -
|
|
5340
|
+
*/
|
|
5341
|
+
_updateRows(opts) {
|
|
5342
|
+
const label = this.logTime("_updateRows");
|
|
5343
|
+
opts = Object.assign({ newNodesOnly: false }, opts);
|
|
5344
|
+
const newNodesOnly = !!opts.newNodesOnly;
|
|
5345
|
+
const row_height = ROW_HEIGHT;
|
|
5346
|
+
const vp_height = this.scrollContainer.clientHeight;
|
|
5347
|
+
const prefetch = RENDER_MAX_PREFETCH;
|
|
5348
|
+
const ofs = this.scrollContainer.scrollTop;
|
|
5349
|
+
let startIdx = Math.max(0, ofs / row_height - prefetch);
|
|
5350
|
+
startIdx = Math.floor(startIdx);
|
|
5351
|
+
// Make sure start is always even, so the alternating row colors don't
|
|
5352
|
+
// change when scrolling:
|
|
5353
|
+
if (startIdx % 2) {
|
|
5354
|
+
startIdx--;
|
|
5355
|
+
}
|
|
5356
|
+
let endIdx = Math.max(0, (ofs + vp_height) / row_height + prefetch);
|
|
5357
|
+
endIdx = Math.ceil(endIdx);
|
|
5358
|
+
// const obsoleteViewNodes = this.viewNodes;
|
|
5359
|
+
// this.viewNodes = new Set();
|
|
5360
|
+
// const viewNodes = this.viewNodes;
|
|
5361
|
+
// this.debug("render", opts);
|
|
5362
|
+
const obsoleteNodes = new Set();
|
|
5363
|
+
this.nodeListElement.childNodes.forEach((elem) => {
|
|
5364
|
+
const tr = elem;
|
|
5365
|
+
obsoleteNodes.add(tr._wb_node);
|
|
5366
|
+
});
|
|
5367
|
+
let idx = 0;
|
|
5368
|
+
let top = 0;
|
|
5369
|
+
let modified = false;
|
|
5370
|
+
let prevElem = "first";
|
|
5371
|
+
this.visitRows(function (node) {
|
|
5372
|
+
// console.log("visit", node)
|
|
5373
|
+
const rowDiv = node._rowElem;
|
|
5374
|
+
// Renumber all expanded nodes
|
|
5375
|
+
if (node._rowIdx !== idx) {
|
|
5376
|
+
node._rowIdx = idx;
|
|
5377
|
+
modified = true;
|
|
5378
|
+
}
|
|
5379
|
+
if (idx < startIdx || idx > endIdx) {
|
|
5380
|
+
// row is outside viewport bounds
|
|
5381
|
+
if (rowDiv) {
|
|
5382
|
+
prevElem = rowDiv;
|
|
5383
|
+
}
|
|
5384
|
+
}
|
|
5385
|
+
else if (rowDiv && newNodesOnly) {
|
|
5386
|
+
obsoleteNodes.delete(node);
|
|
5387
|
+
// no need to update existing node markup
|
|
5388
|
+
rowDiv.style.top = idx * ROW_HEIGHT + "px";
|
|
5389
|
+
prevElem = rowDiv;
|
|
5390
|
+
}
|
|
5391
|
+
else {
|
|
5392
|
+
obsoleteNodes.delete(node);
|
|
5393
|
+
// Create new markup
|
|
5394
|
+
node.render({ top: top, after: prevElem });
|
|
5395
|
+
// console.log("render", top, prevElem, "=>", node._rowElem);
|
|
5396
|
+
prevElem = node._rowElem;
|
|
5397
|
+
}
|
|
5398
|
+
idx++;
|
|
5399
|
+
top += row_height;
|
|
5400
|
+
});
|
|
5401
|
+
this.treeRowCount = idx;
|
|
5402
|
+
for (const n of obsoleteNodes) {
|
|
5403
|
+
n._callEvent("discard");
|
|
5404
|
+
n.removeMarkup();
|
|
5405
|
+
}
|
|
5406
|
+
// Resize tree container
|
|
5407
|
+
this.nodeListElement.style.height = `${top}px`;
|
|
5408
|
+
// this.log(
|
|
5409
|
+
// `render(scrollOfs:${ofs}, ${startIdx}..${endIdx})`,
|
|
5410
|
+
// this.nodeListElement.style.height
|
|
5411
|
+
// );
|
|
5412
|
+
this.logTimeEnd(label);
|
|
5413
|
+
this._validateRows();
|
|
5414
|
+
return modified;
|
|
5415
|
+
}
|
|
5416
|
+
/**
|
|
5417
|
+
* Call callback(node) for all nodes in hierarchical order (depth-first).
|
|
5280
5418
|
*
|
|
5281
5419
|
* @param {function} callback the callback function.
|
|
5282
|
-
* Return false to stop iteration, return "skip" to skip this node and
|
|
5420
|
+
* Return false to stop iteration, return "skip" to skip this node and
|
|
5421
|
+
* children only.
|
|
5283
5422
|
* @returns {boolean} false, if the iterator was stopped.
|
|
5284
5423
|
*/
|
|
5285
5424
|
visit(callback) {
|
|
5286
5425
|
return this.root.visit(callback, false);
|
|
5287
5426
|
}
|
|
5288
|
-
/**
|
|
5427
|
+
/**
|
|
5428
|
+
* Call fn(node) for all nodes in vertical order, top down (or bottom up).
|
|
5429
|
+
*
|
|
5430
|
+
* Note that this considers expansion state, i.e. children of collapsed nodes
|
|
5431
|
+
* are skipped.
|
|
5432
|
+
*
|
|
5289
5433
|
* Stop iteration, if fn() returns false.<br>
|
|
5290
5434
|
* Return false if iteration was stopped.
|
|
5291
5435
|
*
|
|
@@ -5365,7 +5509,8 @@ class Wunderbaum {
|
|
|
5365
5509
|
}
|
|
5366
5510
|
return true;
|
|
5367
5511
|
}
|
|
5368
|
-
/**
|
|
5512
|
+
/**
|
|
5513
|
+
* Call fn(node) for all nodes in vertical order, bottom up.
|
|
5369
5514
|
* @internal
|
|
5370
5515
|
*/
|
|
5371
5516
|
_visitRowsUp(callback, opts) {
|
|
@@ -5409,19 +5554,36 @@ class Wunderbaum {
|
|
|
5409
5554
|
}
|
|
5410
5555
|
return true;
|
|
5411
5556
|
}
|
|
5412
|
-
/**
|
|
5557
|
+
/**
|
|
5558
|
+
* Reload the tree with a new source.
|
|
5559
|
+
*
|
|
5560
|
+
* Previous data is cleared.
|
|
5561
|
+
* Pass `options.columns` to define a header (may also be part of `source.columns`).
|
|
5562
|
+
*/
|
|
5413
5563
|
load(source, options = {}) {
|
|
5414
5564
|
this.clear();
|
|
5415
5565
|
const columns = options.columns || source.columns;
|
|
5416
5566
|
if (columns) {
|
|
5417
5567
|
this.columns = options.columns;
|
|
5418
|
-
this.
|
|
5419
|
-
|
|
5568
|
+
// this._renderHeaderMarkup();
|
|
5569
|
+
this.updateColumns({ calculateCols: false });
|
|
5420
5570
|
}
|
|
5421
5571
|
return this.root.load(source);
|
|
5422
5572
|
}
|
|
5423
5573
|
/**
|
|
5574
|
+
* Disable render requests during operations that would trigger many updates.
|
|
5424
5575
|
*
|
|
5576
|
+
* ```js
|
|
5577
|
+
* try {
|
|
5578
|
+
* tree.enableUpdate(false);
|
|
5579
|
+
* // ... (long running operation that would trigger many updates)
|
|
5580
|
+
* foo();
|
|
5581
|
+
* // ... NOTE: make sure that async operations have finished
|
|
5582
|
+
* await foo();
|
|
5583
|
+
* } finally {
|
|
5584
|
+
* tree.enableUpdate(true);
|
|
5585
|
+
* }
|
|
5586
|
+
* ```
|
|
5425
5587
|
*/
|
|
5426
5588
|
enableUpdate(flag) {
|
|
5427
5589
|
/*
|
|
@@ -5429,20 +5591,22 @@ class Wunderbaum {
|
|
|
5429
5591
|
1 >-------------------------------------<
|
|
5430
5592
|
2 >--------------------<
|
|
5431
5593
|
3 >--------------------------<
|
|
5432
|
-
|
|
5433
|
-
5
|
|
5434
|
-
|
|
5435
5594
|
*/
|
|
5436
|
-
// this.logDebug( `enableUpdate(${flag}): count=${this._disableUpdateCount}...` );
|
|
5437
5595
|
if (flag) {
|
|
5438
|
-
assert(this._disableUpdateCount > 0);
|
|
5596
|
+
assert(this._disableUpdateCount > 0, "enableUpdate(true) was called too often");
|
|
5439
5597
|
this._disableUpdateCount--;
|
|
5598
|
+
// this.logDebug(
|
|
5599
|
+
// `enableUpdate(${flag}): count -> ${this._disableUpdateCount}...`
|
|
5600
|
+
// );
|
|
5440
5601
|
if (this._disableUpdateCount === 0) {
|
|
5441
5602
|
this.updateViewport();
|
|
5442
5603
|
}
|
|
5443
5604
|
}
|
|
5444
5605
|
else {
|
|
5445
5606
|
this._disableUpdateCount++;
|
|
5607
|
+
// this.logDebug(
|
|
5608
|
+
// `enableUpdate(${flag}): count -> ${this._disableUpdateCount}...`
|
|
5609
|
+
// );
|
|
5446
5610
|
// this._disableUpdate = Date.now();
|
|
5447
5611
|
}
|
|
5448
5612
|
// return !flag; // return previous value
|
|
@@ -5475,8 +5639,10 @@ class Wunderbaum {
|
|
|
5475
5639
|
return this.extensions.filter.updateFilter();
|
|
5476
5640
|
}
|
|
5477
5641
|
}
|
|
5478
|
-
Wunderbaum.version = "v0.0.2"; // Set to semver by 'grunt release'
|
|
5479
5642
|
Wunderbaum.sequence = 0;
|
|
5643
|
+
/** Wunderbaum release version number "MAJOR.MINOR.PATCH". */
|
|
5644
|
+
Wunderbaum.version = "v0.0.3"; // Set to semver by 'grunt release'
|
|
5645
|
+
/** Expose some useful methods of the util.ts module as `Wunderbaum.util`. */
|
|
5480
5646
|
Wunderbaum.util = util;
|
|
5481
5647
|
|
|
5482
5648
|
export { Wunderbaum };
|