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.umd.js
CHANGED
|
@@ -7,8 +7,9 @@
|
|
|
7
7
|
/*!
|
|
8
8
|
* Wunderbaum - util
|
|
9
9
|
* Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
|
|
10
|
-
* v0.0.
|
|
10
|
+
* v0.0.3, Mon, 18 Apr 2022 11:52:44 GMT (https://github.com/mar10/wunderbaum)
|
|
11
11
|
*/
|
|
12
|
+
/** @module util */
|
|
12
13
|
/** Readable names for `MouseEvent.button` */
|
|
13
14
|
const MOUSE_BUTTONS = {
|
|
14
15
|
0: "",
|
|
@@ -619,7 +620,7 @@
|
|
|
619
620
|
/*!
|
|
620
621
|
* Wunderbaum - common
|
|
621
622
|
* Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
|
|
622
|
-
* v0.0.
|
|
623
|
+
* v0.0.3, Mon, 18 Apr 2022 11:52:44 GMT (https://github.com/mar10/wunderbaum)
|
|
623
624
|
*/
|
|
624
625
|
const DEFAULT_DEBUGLEVEL = 4; // Replaced by rollup script
|
|
625
626
|
const ROW_HEIGHT = 22;
|
|
@@ -707,6 +708,8 @@
|
|
|
707
708
|
Home: "firstCol",
|
|
708
709
|
"Control+End": "last",
|
|
709
710
|
"Control+Home": "first",
|
|
711
|
+
"Meta+ArrowDown": "last",
|
|
712
|
+
"Meta+ArrowUp": "first",
|
|
710
713
|
"*": "expandAll",
|
|
711
714
|
Multiply: "expandAll",
|
|
712
715
|
PageDown: "pageDown",
|
|
@@ -733,7 +736,7 @@
|
|
|
733
736
|
/*!
|
|
734
737
|
* Wunderbaum - wb_extension_base
|
|
735
738
|
* Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
|
|
736
|
-
* v0.0.
|
|
739
|
+
* v0.0.3, Mon, 18 Apr 2022 11:52:44 GMT (https://github.com/mar10/wunderbaum)
|
|
737
740
|
*/
|
|
738
741
|
class WunderbaumExtension {
|
|
739
742
|
constructor(tree, id, defaults) {
|
|
@@ -1088,7 +1091,7 @@
|
|
|
1088
1091
|
/*!
|
|
1089
1092
|
* Wunderbaum - ext-filter
|
|
1090
1093
|
* Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
|
|
1091
|
-
* v0.0.
|
|
1094
|
+
* v0.0.3, Mon, 18 Apr 2022 11:52:44 GMT (https://github.com/mar10/wunderbaum)
|
|
1092
1095
|
*/
|
|
1093
1096
|
const START_MARKER = "\uFFF7";
|
|
1094
1097
|
const END_MARKER = "\uFFF8";
|
|
@@ -1382,7 +1385,7 @@
|
|
|
1382
1385
|
/*!
|
|
1383
1386
|
* Wunderbaum - ext-keynav
|
|
1384
1387
|
* Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
|
|
1385
|
-
* v0.0.
|
|
1388
|
+
* v0.0.3, Mon, 18 Apr 2022 11:52:44 GMT (https://github.com/mar10/wunderbaum)
|
|
1386
1389
|
*/
|
|
1387
1390
|
class KeynavExtension extends WunderbaumExtension {
|
|
1388
1391
|
constructor(tree) {
|
|
@@ -1449,7 +1452,7 @@
|
|
|
1449
1452
|
eventName = "Add"; // expand
|
|
1450
1453
|
}
|
|
1451
1454
|
else if (navModeOption === NavigationModeOption.startRow) {
|
|
1452
|
-
tree.
|
|
1455
|
+
tree.setNavigationMode(NavigationMode.cellNav);
|
|
1453
1456
|
return;
|
|
1454
1457
|
}
|
|
1455
1458
|
break;
|
|
@@ -1488,6 +1491,8 @@
|
|
|
1488
1491
|
case "Home":
|
|
1489
1492
|
case "Control+End":
|
|
1490
1493
|
case "Control+Home":
|
|
1494
|
+
case "Meta+ArrowDown":
|
|
1495
|
+
case "Meta+ArrowUp":
|
|
1491
1496
|
case "PageDown":
|
|
1492
1497
|
case "PageUp":
|
|
1493
1498
|
node.navigate(eventName, { activate: activate, event: event });
|
|
@@ -1519,11 +1524,11 @@
|
|
|
1519
1524
|
break;
|
|
1520
1525
|
case "Escape":
|
|
1521
1526
|
if (tree.navMode === NavigationMode.cellEdit) {
|
|
1522
|
-
tree.
|
|
1527
|
+
tree.setNavigationMode(NavigationMode.cellNav);
|
|
1523
1528
|
handled = true;
|
|
1524
1529
|
}
|
|
1525
1530
|
else if (tree.navMode === NavigationMode.cellNav) {
|
|
1526
|
-
tree.
|
|
1531
|
+
tree.setNavigationMode(NavigationMode.row);
|
|
1527
1532
|
handled = true;
|
|
1528
1533
|
}
|
|
1529
1534
|
break;
|
|
@@ -1533,7 +1538,7 @@
|
|
|
1533
1538
|
handled = true;
|
|
1534
1539
|
}
|
|
1535
1540
|
else if (navModeOption !== NavigationModeOption.cell) {
|
|
1536
|
-
tree.
|
|
1541
|
+
tree.setNavigationMode(NavigationMode.row);
|
|
1537
1542
|
handled = true;
|
|
1538
1543
|
}
|
|
1539
1544
|
break;
|
|
@@ -1550,6 +1555,8 @@
|
|
|
1550
1555
|
case "Home":
|
|
1551
1556
|
case "Control+End":
|
|
1552
1557
|
case "Control+Home":
|
|
1558
|
+
case "Meta+ArrowDown":
|
|
1559
|
+
case "Meta+ArrowUp":
|
|
1553
1560
|
case "PageDown":
|
|
1554
1561
|
case "PageUp":
|
|
1555
1562
|
node.navigate(eventName, { activate: activate, event: event });
|
|
@@ -1568,7 +1575,7 @@
|
|
|
1568
1575
|
/*!
|
|
1569
1576
|
* Wunderbaum - ext-logger
|
|
1570
1577
|
* Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
|
|
1571
|
-
* v0.0.
|
|
1578
|
+
* v0.0.3, Mon, 18 Apr 2022 11:52:44 GMT (https://github.com/mar10/wunderbaum)
|
|
1572
1579
|
*/
|
|
1573
1580
|
class LoggerExtension extends WunderbaumExtension {
|
|
1574
1581
|
constructor(tree) {
|
|
@@ -1608,7 +1615,7 @@
|
|
|
1608
1615
|
/*!
|
|
1609
1616
|
* Wunderbaum - ext-dnd
|
|
1610
1617
|
* Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
|
|
1611
|
-
* v0.0.
|
|
1618
|
+
* v0.0.3, Mon, 18 Apr 2022 11:52:44 GMT (https://github.com/mar10/wunderbaum)
|
|
1612
1619
|
*/
|
|
1613
1620
|
const nodeMimeType = "application/x-wunderbaum-node";
|
|
1614
1621
|
class DndExtension extends WunderbaumExtension {
|
|
@@ -1876,7 +1883,7 @@
|
|
|
1876
1883
|
/*!
|
|
1877
1884
|
* Wunderbaum - drag_observer
|
|
1878
1885
|
* Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
|
|
1879
|
-
* v0.0.
|
|
1886
|
+
* v0.0.3, Mon, 18 Apr 2022 11:52:44 GMT (https://github.com/mar10/wunderbaum)
|
|
1880
1887
|
*/
|
|
1881
1888
|
/**
|
|
1882
1889
|
* Convert mouse- and touch events to 'dragstart', 'drag', and 'dragstop'.
|
|
@@ -2010,7 +2017,7 @@
|
|
|
2010
2017
|
/*!
|
|
2011
2018
|
* Wunderbaum - ext-grid
|
|
2012
2019
|
* Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
|
|
2013
|
-
* v0.0.
|
|
2020
|
+
* v0.0.3, Mon, 18 Apr 2022 11:52:44 GMT (https://github.com/mar10/wunderbaum)
|
|
2014
2021
|
*/
|
|
2015
2022
|
class GridExtension extends WunderbaumExtension {
|
|
2016
2023
|
constructor(tree) {
|
|
@@ -2047,7 +2054,7 @@
|
|
|
2047
2054
|
/*!
|
|
2048
2055
|
* Wunderbaum - deferred
|
|
2049
2056
|
* Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
|
|
2050
|
-
* v0.0.
|
|
2057
|
+
* v0.0.3, Mon, 18 Apr 2022 11:52:44 GMT (https://github.com/mar10/wunderbaum)
|
|
2051
2058
|
*/
|
|
2052
2059
|
/**
|
|
2053
2060
|
* Deferred is a ES6 Promise, that exposes the resolve() and reject()` method.
|
|
@@ -2090,7 +2097,7 @@
|
|
|
2090
2097
|
/*!
|
|
2091
2098
|
* Wunderbaum - wunderbaum_node
|
|
2092
2099
|
* Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
|
|
2093
|
-
* v0.0.
|
|
2100
|
+
* v0.0.3, Mon, 18 Apr 2022 11:52:44 GMT (https://github.com/mar10/wunderbaum)
|
|
2094
2101
|
*/
|
|
2095
2102
|
/** Top-level properties that can be passed with `data`. */
|
|
2096
2103
|
const NODE_PROPS = new Set([
|
|
@@ -2127,15 +2134,31 @@
|
|
|
2127
2134
|
"unselectableIgnore",
|
|
2128
2135
|
"unselectableStatus",
|
|
2129
2136
|
]);
|
|
2137
|
+
/**
|
|
2138
|
+
* A single tree node.
|
|
2139
|
+
*
|
|
2140
|
+
* **NOTE:** <br>
|
|
2141
|
+
* Generally you should not modify properties directly, since this may break
|
|
2142
|
+
* the internal bookkeeping.
|
|
2143
|
+
*/
|
|
2130
2144
|
class WunderbaumNode {
|
|
2131
2145
|
constructor(tree, parent, data) {
|
|
2132
2146
|
var _a, _b;
|
|
2147
|
+
/** Reference key. Unlike {@link key}, a `refKey` may occur multiple
|
|
2148
|
+
* times within a tree (in this case we have 'clone nodes').
|
|
2149
|
+
* @see Use {@link setKey} to modify.
|
|
2150
|
+
*/
|
|
2133
2151
|
this.refKey = undefined;
|
|
2134
2152
|
this.children = null;
|
|
2135
2153
|
this.lazy = false;
|
|
2154
|
+
/** Expansion state.
|
|
2155
|
+
* @see {@link isExpandable}, {@link isExpanded}, {@link setExpanded}. */
|
|
2136
2156
|
this.expanded = false;
|
|
2157
|
+
/** Selection state.
|
|
2158
|
+
* @see {@link isSelected}, {@link setSelected}. */
|
|
2137
2159
|
this.selected = false;
|
|
2138
|
-
/** Additional classes added to `div.wb-row`.
|
|
2160
|
+
/** Additional classes added to `div.wb-row`.
|
|
2161
|
+
* @see {@link addClass}, {@link removeClass}, {@link toggleClass}. */
|
|
2139
2162
|
this.extraClasses = new Set();
|
|
2140
2163
|
/** Custom data that was passed to the constructor */
|
|
2141
2164
|
this.data = {};
|
|
@@ -2301,7 +2324,8 @@
|
|
|
2301
2324
|
}
|
|
2302
2325
|
/**
|
|
2303
2326
|
* Apply a modification (or navigation) operation.
|
|
2304
|
-
*
|
|
2327
|
+
*
|
|
2328
|
+
* @see {@link Wunderbaum.applyCommand}
|
|
2305
2329
|
*/
|
|
2306
2330
|
applyCommand(cmd, opts) {
|
|
2307
2331
|
return this.tree.applyCommand(cmd, this, opts);
|
|
@@ -2394,8 +2418,7 @@
|
|
|
2394
2418
|
}
|
|
2395
2419
|
/** Find a node relative to self.
|
|
2396
2420
|
*
|
|
2397
|
-
* @
|
|
2398
|
-
* or a keyword ('down', 'first', 'last', 'left', 'parent', 'right', 'up').
|
|
2421
|
+
* @see {@link Wunderbaum.findRelatedNode|tree.findRelatedNode()}
|
|
2399
2422
|
*/
|
|
2400
2423
|
findRelatedNode(where, includeHidden = false) {
|
|
2401
2424
|
return this.tree.findRelatedNode(this, where, includeHidden);
|
|
@@ -2696,7 +2719,7 @@
|
|
|
2696
2719
|
assert(!this.parent);
|
|
2697
2720
|
tree.columns = data.columns;
|
|
2698
2721
|
delete data.columns;
|
|
2699
|
-
tree.
|
|
2722
|
+
tree.updateColumns({ calculateCols: false });
|
|
2700
2723
|
}
|
|
2701
2724
|
this._loadSourceObject(data);
|
|
2702
2725
|
}
|
|
@@ -2732,19 +2755,20 @@
|
|
|
2732
2755
|
this.setStatus(NodeStatusType.ok);
|
|
2733
2756
|
return;
|
|
2734
2757
|
}
|
|
2735
|
-
assert(isArray(source) || (source && source.url), "The lazyLoad event must return a node list, `{url: ...}
|
|
2758
|
+
assert(isArray(source) || (source && source.url), "The lazyLoad event must return a node list, `{url: ...}`, or false.");
|
|
2736
2759
|
await this.load(source); // also calls setStatus('ok')
|
|
2737
2760
|
if (wasExpanded) {
|
|
2738
2761
|
this.expanded = true;
|
|
2739
2762
|
this.tree.setModified(ChangeType.structure);
|
|
2740
2763
|
}
|
|
2741
2764
|
else {
|
|
2742
|
-
this.
|
|
2765
|
+
this.setModified(); // Fix expander icon to 'loaded'
|
|
2743
2766
|
}
|
|
2744
2767
|
}
|
|
2745
2768
|
catch (e) {
|
|
2769
|
+
this.logError("Error during loadLazy()", e);
|
|
2770
|
+
this._callEvent("error", { error: e });
|
|
2746
2771
|
this.setStatus(NodeStatusType.error, "" + e);
|
|
2747
|
-
// } finally {
|
|
2748
2772
|
}
|
|
2749
2773
|
return;
|
|
2750
2774
|
}
|
|
@@ -2912,31 +2936,33 @@
|
|
|
2912
2936
|
// Allow to pass 'ArrowLeft' instead of 'left'
|
|
2913
2937
|
where = KEY_TO_ACTION_DICT[where] || where;
|
|
2914
2938
|
// Otherwise activate or focus the related node
|
|
2915
|
-
|
|
2916
|
-
if (node) {
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
node.makeVisible({ scrollIntoView: false });
|
|
2920
|
-
}
|
|
2921
|
-
catch (e) { } // #272
|
|
2922
|
-
node.setFocus();
|
|
2923
|
-
if ((options === null || options === void 0 ? void 0 : options.activate) === false) {
|
|
2924
|
-
return Promise.resolve(this);
|
|
2925
|
-
}
|
|
2926
|
-
return node.setActive(true, { event: options === null || options === void 0 ? void 0 : options.event });
|
|
2939
|
+
const node = this.findRelatedNode(where);
|
|
2940
|
+
if (!node) {
|
|
2941
|
+
this.logWarn(`Could not find related node '${where}'.`);
|
|
2942
|
+
return Promise.resolve(this);
|
|
2927
2943
|
}
|
|
2928
|
-
|
|
2929
|
-
|
|
2944
|
+
// setFocus/setActive will scroll later (if autoScroll is specified)
|
|
2945
|
+
try {
|
|
2946
|
+
node.makeVisible({ scrollIntoView: false });
|
|
2947
|
+
}
|
|
2948
|
+
catch (e) { } // #272
|
|
2949
|
+
node.setFocus();
|
|
2950
|
+
if ((options === null || options === void 0 ? void 0 : options.activate) === false) {
|
|
2951
|
+
return Promise.resolve(this);
|
|
2952
|
+
}
|
|
2953
|
+
return node.setActive(true, { event: options === null || options === void 0 ? void 0 : options.event });
|
|
2930
2954
|
}
|
|
2931
2955
|
/** Delete this node and all descendants. */
|
|
2932
2956
|
remove() {
|
|
2933
2957
|
const tree = this.tree;
|
|
2934
2958
|
const pos = this.parent.children.indexOf(this);
|
|
2959
|
+
this.triggerModify("remove");
|
|
2935
2960
|
this.parent.children.splice(pos, 1);
|
|
2936
2961
|
this.visit((n) => {
|
|
2937
2962
|
n.removeMarkup();
|
|
2938
2963
|
tree._unregisterNode(n);
|
|
2939
2964
|
}, true);
|
|
2965
|
+
tree.setModified(ChangeType.structure);
|
|
2940
2966
|
}
|
|
2941
2967
|
/** Remove all descendants of this node. */
|
|
2942
2968
|
removeChildren() {
|
|
@@ -3061,6 +3087,7 @@
|
|
|
3061
3087
|
const activeColIdx = tree.navMode === NavigationMode.row ? null : tree.activeColIdx;
|
|
3062
3088
|
// let colElems: HTMLElement[];
|
|
3063
3089
|
const isNew = !rowDiv;
|
|
3090
|
+
assert(!isNew || (opts && opts.after), "opts.after expected, unless updating");
|
|
3064
3091
|
assert(!this.isRootNode());
|
|
3065
3092
|
//
|
|
3066
3093
|
let rowClasses = ["wb-row"];
|
|
@@ -3112,7 +3139,7 @@
|
|
|
3112
3139
|
nodeElem.appendChild(elem);
|
|
3113
3140
|
ofsTitlePx += ICON_WIDTH;
|
|
3114
3141
|
}
|
|
3115
|
-
if (treeOptions.minExpandLevel
|
|
3142
|
+
if (!treeOptions.minExpandLevel || level > treeOptions.minExpandLevel) {
|
|
3116
3143
|
expanderSpan = document.createElement("i");
|
|
3117
3144
|
nodeElem.appendChild(expanderSpan);
|
|
3118
3145
|
ofsTitlePx += ICON_WIDTH;
|
|
@@ -3241,9 +3268,19 @@
|
|
|
3241
3268
|
});
|
|
3242
3269
|
}
|
|
3243
3270
|
// Attach to DOM as late as possible
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3271
|
+
if (isNew) {
|
|
3272
|
+
const after = opts ? opts.after : "last";
|
|
3273
|
+
switch (after) {
|
|
3274
|
+
case "first":
|
|
3275
|
+
tree.nodeListElement.prepend(rowDiv);
|
|
3276
|
+
break;
|
|
3277
|
+
case "last":
|
|
3278
|
+
tree.nodeListElement.appendChild(rowDiv);
|
|
3279
|
+
break;
|
|
3280
|
+
default:
|
|
3281
|
+
opts.after.after(rowDiv);
|
|
3282
|
+
}
|
|
3283
|
+
}
|
|
3247
3284
|
}
|
|
3248
3285
|
/**
|
|
3249
3286
|
* Remove all children, collapse, and set the lazy-flag, so that the lazyLoad
|
|
@@ -3317,14 +3354,15 @@
|
|
|
3317
3354
|
*
|
|
3318
3355
|
* Evaluation sequence:
|
|
3319
3356
|
*
|
|
3320
|
-
* If `tree.options.<name>` is a callback that returns something, use that.
|
|
3321
|
-
* Else if `node.<name>` is defined, use that.
|
|
3322
|
-
* Else if `tree.types[<node.type>]` is a value, use that.
|
|
3323
|
-
* Else if `tree.options.<name>` is a value, use that.
|
|
3324
|
-
* Else use `defaultValue`.
|
|
3357
|
+
* - If `tree.options.<name>` is a callback that returns something, use that.
|
|
3358
|
+
* - Else if `node.<name>` is defined, use that.
|
|
3359
|
+
* - Else if `tree.types[<node.type>]` is a value, use that.
|
|
3360
|
+
* - Else if `tree.options.<name>` is a value, use that.
|
|
3361
|
+
* - Else use `defaultValue`.
|
|
3325
3362
|
*
|
|
3326
3363
|
* @param name name of the option property (on node and tree)
|
|
3327
3364
|
* @param defaultValue return this if nothing else matched
|
|
3365
|
+
* {@link Wunderbaum.getOption|Wunderbaum.getOption()}
|
|
3328
3366
|
*/
|
|
3329
3367
|
getOption(name, defaultValue) {
|
|
3330
3368
|
let tree = this.tree;
|
|
@@ -3359,15 +3397,21 @@
|
|
|
3359
3397
|
// Use value from value options dict, fallback do default
|
|
3360
3398
|
return value !== null && value !== void 0 ? value : defaultValue;
|
|
3361
3399
|
}
|
|
3400
|
+
/** Make sure that this node is visible in the viewport.
|
|
3401
|
+
* @see {@link Wunderbaum.scrollTo|Wunderbaum.scrollTo()}
|
|
3402
|
+
*/
|
|
3362
3403
|
async scrollIntoView(options) {
|
|
3363
3404
|
return this.tree.scrollTo(this);
|
|
3364
3405
|
}
|
|
3406
|
+
/**
|
|
3407
|
+
* Activate this node, deactivate previous, send events, activate column and scroll int viewport.
|
|
3408
|
+
*/
|
|
3365
3409
|
async setActive(flag = true, options) {
|
|
3366
3410
|
const tree = this.tree;
|
|
3367
3411
|
const prev = tree.activeNode;
|
|
3368
3412
|
const retrigger = options === null || options === void 0 ? void 0 : options.retrigger;
|
|
3369
|
-
const
|
|
3370
|
-
if (!
|
|
3413
|
+
const noEvents = options === null || options === void 0 ? void 0 : options.noEvents;
|
|
3414
|
+
if (!noEvents) {
|
|
3371
3415
|
let orgEvent = options === null || options === void 0 ? void 0 : options.event;
|
|
3372
3416
|
if (flag) {
|
|
3373
3417
|
if (prev !== this || retrigger) {
|
|
@@ -3405,19 +3449,18 @@
|
|
|
3405
3449
|
// requestAnimationFrame(() => {
|
|
3406
3450
|
// this.scrollIntoView();
|
|
3407
3451
|
// })
|
|
3408
|
-
this.scrollIntoView();
|
|
3409
|
-
}
|
|
3410
|
-
setModified(change = ChangeType.status) {
|
|
3411
|
-
assert(change === ChangeType.status);
|
|
3412
|
-
this.tree.setModified(ChangeType.row, this);
|
|
3452
|
+
return this.scrollIntoView();
|
|
3413
3453
|
}
|
|
3454
|
+
/**
|
|
3455
|
+
* Expand or collapse this node.
|
|
3456
|
+
*/
|
|
3414
3457
|
async setExpanded(flag = true, options) {
|
|
3415
3458
|
// alert("" + this.getLevel() + ", "+ this.getOption("minExpandLevel");
|
|
3416
3459
|
if (!flag &&
|
|
3417
3460
|
this.isExpanded() &&
|
|
3418
3461
|
this.getLevel() < this.getOption("minExpandLevel") &&
|
|
3419
3462
|
!getOption(options, "force")) {
|
|
3420
|
-
this.logDebug("Ignored collapse request.");
|
|
3463
|
+
this.logDebug("Ignored collapse request below expandLevel.");
|
|
3421
3464
|
return;
|
|
3422
3465
|
}
|
|
3423
3466
|
if (flag && this.lazy && this.children == null) {
|
|
@@ -3426,16 +3469,31 @@
|
|
|
3426
3469
|
this.expanded = flag;
|
|
3427
3470
|
this.tree.setModified(ChangeType.structure);
|
|
3428
3471
|
}
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3472
|
+
/**
|
|
3473
|
+
* Set keyboard focus here.
|
|
3474
|
+
* @see {@link setActive}
|
|
3475
|
+
*/
|
|
3433
3476
|
setFocus(flag = true, options) {
|
|
3434
3477
|
const prev = this.tree.focusNode;
|
|
3435
3478
|
this.tree.focusNode = this;
|
|
3436
3479
|
prev === null || prev === void 0 ? void 0 : prev.setModified();
|
|
3437
3480
|
this.setModified();
|
|
3438
3481
|
}
|
|
3482
|
+
/** Set a new icon path or class. */
|
|
3483
|
+
setIcon() {
|
|
3484
|
+
throw new Error("Not yet implemented");
|
|
3485
|
+
// this.setModified();
|
|
3486
|
+
}
|
|
3487
|
+
/** Change node's {@link key} and/or {@link refKey}. */
|
|
3488
|
+
setKey(key, refKey) {
|
|
3489
|
+
throw new Error("Not yet implemented");
|
|
3490
|
+
}
|
|
3491
|
+
/** Schedule a render, typically called to update after a status or data change. */
|
|
3492
|
+
setModified(change = ChangeType.status) {
|
|
3493
|
+
assert(change === ChangeType.status);
|
|
3494
|
+
this.tree.setModified(ChangeType.row, this);
|
|
3495
|
+
}
|
|
3496
|
+
/** Modify the check/uncheck state. */
|
|
3439
3497
|
setSelected(flag = true, options) {
|
|
3440
3498
|
const prev = this.selected;
|
|
3441
3499
|
if (!!flag !== prev) {
|
|
@@ -3444,10 +3502,9 @@
|
|
|
3444
3502
|
this.selected = !!flag;
|
|
3445
3503
|
this.setModified();
|
|
3446
3504
|
}
|
|
3447
|
-
/**
|
|
3448
|
-
*/
|
|
3505
|
+
/** Display node status (ok, loading, error, noData) using styles and a dummy child node. */
|
|
3449
3506
|
setStatus(status, message, details) {
|
|
3450
|
-
|
|
3507
|
+
const tree = this.tree;
|
|
3451
3508
|
let statusNode = null;
|
|
3452
3509
|
const _clearStatusNode = () => {
|
|
3453
3510
|
// Remove dedicated dummy node, if any
|
|
@@ -3521,6 +3578,7 @@
|
|
|
3521
3578
|
tree.setModified(ChangeType.structure);
|
|
3522
3579
|
return statusNode;
|
|
3523
3580
|
}
|
|
3581
|
+
/** Rename this node. */
|
|
3524
3582
|
setTitle(title) {
|
|
3525
3583
|
this.title = title;
|
|
3526
3584
|
this.setModified();
|
|
@@ -3544,10 +3602,16 @@
|
|
|
3544
3602
|
* @param {object} [extra]
|
|
3545
3603
|
*/
|
|
3546
3604
|
triggerModify(operation, extra) {
|
|
3605
|
+
if (!this.parent) {
|
|
3606
|
+
return;
|
|
3607
|
+
}
|
|
3547
3608
|
this.parent.triggerModifyChild(operation, this, extra);
|
|
3548
3609
|
}
|
|
3549
|
-
/**
|
|
3550
|
-
*
|
|
3610
|
+
/**
|
|
3611
|
+
* Call fn(node) for all child nodes in hierarchical order (depth-first).
|
|
3612
|
+
*
|
|
3613
|
+
* Stop iteration, if fn() returns false. Skip current branch, if fn()
|
|
3614
|
+
* returns "skip".<br>
|
|
3551
3615
|
* Return false if iteration was stopped.
|
|
3552
3616
|
*
|
|
3553
3617
|
* @param {function} callback the callback function.
|
|
@@ -3591,7 +3655,8 @@
|
|
|
3591
3655
|
}
|
|
3592
3656
|
return true;
|
|
3593
3657
|
}
|
|
3594
|
-
/**
|
|
3658
|
+
/**
|
|
3659
|
+
* Call fn(node) for all sibling nodes.<br>
|
|
3595
3660
|
* Stop iteration, if fn() returns false.<br>
|
|
3596
3661
|
* Return false if iteration was stopped.
|
|
3597
3662
|
*
|
|
@@ -3622,7 +3687,7 @@
|
|
|
3622
3687
|
/*!
|
|
3623
3688
|
* Wunderbaum - ext-edit
|
|
3624
3689
|
* Copyright (c) 2021-2022, Martin Wendt. Released under the MIT license.
|
|
3625
|
-
* v0.0.
|
|
3690
|
+
* v0.0.3, Mon, 18 Apr 2022 11:52:44 GMT (https://github.com/mar10/wunderbaum)
|
|
3626
3691
|
*/
|
|
3627
3692
|
// const START_MARKER = "\uFFF7";
|
|
3628
3693
|
class EditExtension extends WunderbaumExtension {
|
|
@@ -3740,7 +3805,7 @@
|
|
|
3740
3805
|
break;
|
|
3741
3806
|
case "F2":
|
|
3742
3807
|
if (trigger.indexOf("F2") >= 0) {
|
|
3743
|
-
// tree.
|
|
3808
|
+
// tree.setNavigationMode(NavigationMode.cellEdit);
|
|
3744
3809
|
this.startEditTitle();
|
|
3745
3810
|
return false;
|
|
3746
3811
|
}
|
|
@@ -3781,6 +3846,7 @@
|
|
|
3781
3846
|
if (validity) {
|
|
3782
3847
|
// Permanently apply input validations (CSS and tooltip)
|
|
3783
3848
|
inputElem.addEventListener("keydown", (e) => {
|
|
3849
|
+
inputElem.setCustomValidity("");
|
|
3784
3850
|
if (!inputElem.reportValidity()) ;
|
|
3785
3851
|
});
|
|
3786
3852
|
}
|
|
@@ -3821,6 +3887,11 @@
|
|
|
3821
3887
|
}
|
|
3822
3888
|
node.logDebug(`stopEditTitle(${apply})`, opts, focusElem, newValue);
|
|
3823
3889
|
if (apply && newValue !== null && newValue !== node.title) {
|
|
3890
|
+
const errMsg = focusElem.validationMessage;
|
|
3891
|
+
if (errMsg) {
|
|
3892
|
+
// input element's native validation failed
|
|
3893
|
+
throw new Error(`Input validation failed for "${newValue}": ${errMsg}.`);
|
|
3894
|
+
}
|
|
3824
3895
|
const colElem = node.getColElem(0);
|
|
3825
3896
|
this._applyChange("edit.apply", node, colElem, {
|
|
3826
3897
|
oldValue: node.title,
|
|
@@ -3907,8 +3978,8 @@
|
|
|
3907
3978
|
* Copyright (c) 2021-2022, Martin Wendt (https://wwWendt.de).
|
|
3908
3979
|
* Released under the MIT license.
|
|
3909
3980
|
*
|
|
3910
|
-
* @version v0.0.
|
|
3911
|
-
* @date
|
|
3981
|
+
* @version v0.0.3
|
|
3982
|
+
* @date Mon, 18 Apr 2022 11:52:44 GMT
|
|
3912
3983
|
*/
|
|
3913
3984
|
// const class_prefix = "wb-";
|
|
3914
3985
|
// const node_props: string[] = ["title", "key", "refKey"];
|
|
@@ -3924,37 +3995,43 @@
|
|
|
3924
3995
|
this.extensions = {};
|
|
3925
3996
|
this.keyMap = new Map();
|
|
3926
3997
|
this.refKeyMap = new Map();
|
|
3927
|
-
|
|
3928
|
-
|
|
3929
|
-
|
|
3998
|
+
// protected viewNodes = new Set<WunderbaumNode>();
|
|
3999
|
+
this.treeRowCount = 0;
|
|
4000
|
+
this._disableUpdateCount = 0;
|
|
3930
4001
|
// protected eventHandlers : Array<function> = [];
|
|
4002
|
+
/** Currently active node if any. */
|
|
3931
4003
|
this.activeNode = null;
|
|
4004
|
+
/** Current node hat has keyboard focus if any. */
|
|
3932
4005
|
this.focusNode = null;
|
|
3933
|
-
this._disableUpdate = 0;
|
|
3934
|
-
this._disableUpdateCount = 0;
|
|
3935
4006
|
/** Shared properties, referenced by `node.type`. */
|
|
3936
4007
|
this.types = {};
|
|
3937
4008
|
/** List of column definitions. */
|
|
3938
4009
|
this.columns = [];
|
|
3939
4010
|
this._columnsById = {};
|
|
3940
4011
|
// Modification Status
|
|
3941
|
-
|
|
3942
|
-
|
|
3943
|
-
|
|
3944
|
-
this.
|
|
4012
|
+
// protected changedSince = 0;
|
|
4013
|
+
// protected changes = new Set<ChangeType>();
|
|
4014
|
+
// protected changedNodes = new Set<WunderbaumNode>();
|
|
4015
|
+
this.changeRedrawRequestPending = false;
|
|
4016
|
+
/** Expose some useful methods of the util.ts module as `tree._util`. */
|
|
4017
|
+
this._util = util;
|
|
3945
4018
|
// --- FILTER ---
|
|
3946
4019
|
this.filterMode = null;
|
|
3947
4020
|
// --- KEYNAV ---
|
|
4021
|
+
/** @internal Use `setColumn()`/`getActiveColElem()`*/
|
|
3948
4022
|
this.activeColIdx = 0;
|
|
4023
|
+
/** @internal */
|
|
3949
4024
|
this.navMode = NavigationMode.row;
|
|
4025
|
+
/** @internal */
|
|
3950
4026
|
this.lastQuicksearchTime = 0;
|
|
4027
|
+
/** @internal */
|
|
3951
4028
|
this.lastQuicksearchTerm = "";
|
|
3952
4029
|
// --- EDIT ---
|
|
3953
4030
|
this.lastClickTime = 0;
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
|
|
3957
|
-
this.log = this.logDebug;
|
|
4031
|
+
/** Alias for {@link Wunderbaum.logDebug}.
|
|
4032
|
+
* @alias Wunderbaum.logDebug
|
|
4033
|
+
*/
|
|
4034
|
+
this.log = this.logDebug;
|
|
3958
4035
|
let opts = (this.options = extend({
|
|
3959
4036
|
id: null,
|
|
3960
4037
|
source: null,
|
|
@@ -4188,37 +4265,18 @@
|
|
|
4188
4265
|
forceClose: true,
|
|
4189
4266
|
});
|
|
4190
4267
|
}
|
|
4191
|
-
// if (flag && !this.activeNode ) {
|
|
4192
|
-
// setTimeout(() => {
|
|
4193
|
-
// if (!this.activeNode) {
|
|
4194
|
-
// const firstNode = this.getFirstChild();
|
|
4195
|
-
// if (firstNode && !firstNode?.isStatusNode()) {
|
|
4196
|
-
// firstNode.logInfo("Activate on focus", e);
|
|
4197
|
-
// firstNode.setActive(true, { event: e });
|
|
4198
|
-
// }
|
|
4199
|
-
// }
|
|
4200
|
-
// }, 10);
|
|
4201
|
-
// }
|
|
4202
4268
|
});
|
|
4203
4269
|
}
|
|
4204
|
-
/**
|
|
4205
|
-
|
|
4206
|
-
// const coldivs = "<span class='wb-col'></span>".repeat(this.columns.length);
|
|
4207
|
-
// this.element.innerHTML = `
|
|
4208
|
-
// <div class='wb-header'>
|
|
4209
|
-
// <div class='wb-row'>
|
|
4210
|
-
// ${coldivs}
|
|
4211
|
-
// </div>
|
|
4212
|
-
// </div>`;
|
|
4213
|
-
// }
|
|
4214
|
-
/** Return a Wunderbaum instance, from element, index, or event.
|
|
4270
|
+
/**
|
|
4271
|
+
* Return a Wunderbaum instance, from element, id, index, or event.
|
|
4215
4272
|
*
|
|
4216
|
-
*
|
|
4217
|
-
* getTree();
|
|
4218
|
-
* getTree(1);
|
|
4219
|
-
* getTree(event);
|
|
4220
|
-
* getTree("foo");
|
|
4273
|
+
* ```js
|
|
4274
|
+
* getTree(); // Get first Wunderbaum instance on page
|
|
4275
|
+
* getTree(1); // Get second Wunderbaum instance on page
|
|
4276
|
+
* getTree(event); // Get tree for this mouse- or keyboard event
|
|
4277
|
+
* getTree("foo"); // Get tree for this `tree.options.id`
|
|
4221
4278
|
* getTree("#tree"); // Get tree for this matching element
|
|
4279
|
+
* ```
|
|
4222
4280
|
*/
|
|
4223
4281
|
static getTree(el) {
|
|
4224
4282
|
if (el instanceof Wunderbaum) {
|
|
@@ -4259,9 +4317,8 @@
|
|
|
4259
4317
|
}
|
|
4260
4318
|
return null;
|
|
4261
4319
|
}
|
|
4262
|
-
/**
|
|
4263
|
-
*
|
|
4264
|
-
* @param el
|
|
4320
|
+
/**
|
|
4321
|
+
* Return a WunderbaumNode instance from element or event.
|
|
4265
4322
|
*/
|
|
4266
4323
|
static getNode(el) {
|
|
4267
4324
|
if (!el) {
|
|
@@ -4283,7 +4340,7 @@
|
|
|
4283
4340
|
}
|
|
4284
4341
|
return null;
|
|
4285
4342
|
}
|
|
4286
|
-
/** */
|
|
4343
|
+
/** @internal */
|
|
4287
4344
|
_registerExtension(extension) {
|
|
4288
4345
|
this.extensionList.push(extension);
|
|
4289
4346
|
this.extensions[extension.id] = extension;
|
|
@@ -4325,7 +4382,7 @@
|
|
|
4325
4382
|
node.tree = null;
|
|
4326
4383
|
node.parent = null;
|
|
4327
4384
|
// node.title = "DISPOSED: " + node.title
|
|
4328
|
-
this.viewNodes.delete(node);
|
|
4385
|
+
// this.viewNodes.delete(node);
|
|
4329
4386
|
node.removeMarkup();
|
|
4330
4387
|
}
|
|
4331
4388
|
/** Call all hook methods of all registered extensions.*/
|
|
@@ -4343,7 +4400,9 @@
|
|
|
4343
4400
|
}
|
|
4344
4401
|
return res;
|
|
4345
4402
|
}
|
|
4346
|
-
/**
|
|
4403
|
+
/**
|
|
4404
|
+
* Call tree method or extension method if defined.
|
|
4405
|
+
*
|
|
4347
4406
|
* Example:
|
|
4348
4407
|
* ```js
|
|
4349
4408
|
* tree._callMethod("edit.startEdit", "arg1", "arg2")
|
|
@@ -4360,7 +4419,9 @@
|
|
|
4360
4419
|
this.logError(`Calling undefined method '${name}()'.`);
|
|
4361
4420
|
}
|
|
4362
4421
|
}
|
|
4363
|
-
/**
|
|
4422
|
+
/**
|
|
4423
|
+
* Call event handler if defined in tree or tree.EXTENSION options.
|
|
4424
|
+
*
|
|
4364
4425
|
* Example:
|
|
4365
4426
|
* ```js
|
|
4366
4427
|
* tree._callEvent("edit.beforeEdit", {foo: 42})
|
|
@@ -4376,27 +4437,33 @@
|
|
|
4376
4437
|
// this.logError(`Triggering undefined event '${name}'.`)
|
|
4377
4438
|
}
|
|
4378
4439
|
}
|
|
4379
|
-
/** Return the
|
|
4380
|
-
|
|
4381
|
-
let topIdx, node;
|
|
4382
|
-
if (complete) {
|
|
4383
|
-
topIdx = Math.ceil(this.scrollContainer.scrollTop / ROW_HEIGHT);
|
|
4384
|
-
}
|
|
4385
|
-
else {
|
|
4386
|
-
topIdx = Math.floor(this.scrollContainer.scrollTop / ROW_HEIGHT);
|
|
4387
|
-
}
|
|
4440
|
+
/** Return the node for given row index. */
|
|
4441
|
+
_getNodeByRowIdx(idx) {
|
|
4388
4442
|
// TODO: start searching from active node (reverse)
|
|
4443
|
+
let node = null;
|
|
4389
4444
|
this.visitRows((n) => {
|
|
4390
|
-
if (n._rowIdx ===
|
|
4445
|
+
if (n._rowIdx === idx) {
|
|
4391
4446
|
node = n;
|
|
4392
4447
|
return false;
|
|
4393
4448
|
}
|
|
4394
4449
|
});
|
|
4395
4450
|
return node;
|
|
4396
4451
|
}
|
|
4397
|
-
/** Return the
|
|
4452
|
+
/** Return the topmost visible node in the viewport. */
|
|
4453
|
+
_firstNodeInView(complete = true) {
|
|
4454
|
+
let topIdx;
|
|
4455
|
+
const gracePy = 1; // ignore subpixel scrolling
|
|
4456
|
+
if (complete) {
|
|
4457
|
+
topIdx = Math.ceil((this.scrollContainer.scrollTop - gracePy) / ROW_HEIGHT);
|
|
4458
|
+
}
|
|
4459
|
+
else {
|
|
4460
|
+
topIdx = Math.floor(this.scrollContainer.scrollTop / ROW_HEIGHT);
|
|
4461
|
+
}
|
|
4462
|
+
return this._getNodeByRowIdx(topIdx);
|
|
4463
|
+
}
|
|
4464
|
+
/** Return the lowest visible node in the viewport. */
|
|
4398
4465
|
_lastNodeInView(complete = true) {
|
|
4399
|
-
let bottomIdx
|
|
4466
|
+
let bottomIdx;
|
|
4400
4467
|
if (complete) {
|
|
4401
4468
|
bottomIdx =
|
|
4402
4469
|
Math.floor((this.scrollContainer.scrollTop + this.scrollContainer.clientHeight) /
|
|
@@ -4407,16 +4474,10 @@
|
|
|
4407
4474
|
Math.ceil((this.scrollContainer.scrollTop + this.scrollContainer.clientHeight) /
|
|
4408
4475
|
ROW_HEIGHT) - 1;
|
|
4409
4476
|
}
|
|
4410
|
-
|
|
4411
|
-
this.
|
|
4412
|
-
if (n._rowIdx === bottomIdx) {
|
|
4413
|
-
node = n;
|
|
4414
|
-
return false;
|
|
4415
|
-
}
|
|
4416
|
-
});
|
|
4417
|
-
return node;
|
|
4477
|
+
bottomIdx = Math.min(bottomIdx, this.count(true) - 1);
|
|
4478
|
+
return this._getNodeByRowIdx(bottomIdx);
|
|
4418
4479
|
}
|
|
4419
|
-
/** Return preceeding visible node in the viewport */
|
|
4480
|
+
/** Return preceeding visible node in the viewport. */
|
|
4420
4481
|
_getPrevNodeInView(node, ofs = 1) {
|
|
4421
4482
|
this.visitRows((n) => {
|
|
4422
4483
|
node = n;
|
|
@@ -4426,7 +4487,7 @@
|
|
|
4426
4487
|
}, { reverse: true, start: node || this.getActiveNode() });
|
|
4427
4488
|
return node;
|
|
4428
4489
|
}
|
|
4429
|
-
/** Return following visible node in the viewport */
|
|
4490
|
+
/** Return following visible node in the viewport. */
|
|
4430
4491
|
_getNextNodeInView(node, ofs = 1) {
|
|
4431
4492
|
this.visitRows((n) => {
|
|
4432
4493
|
node = n;
|
|
@@ -4436,10 +4497,15 @@
|
|
|
4436
4497
|
}, { reverse: false, start: node || this.getActiveNode() });
|
|
4437
4498
|
return node;
|
|
4438
4499
|
}
|
|
4500
|
+
/**
|
|
4501
|
+
* Append (or insert) a list of toplevel nodes.
|
|
4502
|
+
*
|
|
4503
|
+
* @see {@link WunderbaumNode.addChildren}
|
|
4504
|
+
*/
|
|
4439
4505
|
addChildren(nodeData, options) {
|
|
4440
4506
|
return this.root.addChildren(nodeData, options);
|
|
4441
4507
|
}
|
|
4442
|
-
|
|
4508
|
+
/**
|
|
4443
4509
|
* Apply a modification or navigation operation.
|
|
4444
4510
|
*
|
|
4445
4511
|
* Most of these commands simply map to a node or tree method.
|
|
@@ -4564,16 +4630,17 @@
|
|
|
4564
4630
|
this.root.children = null;
|
|
4565
4631
|
this.keyMap.clear();
|
|
4566
4632
|
this.refKeyMap.clear();
|
|
4567
|
-
this.viewNodes.clear();
|
|
4633
|
+
// this.viewNodes.clear();
|
|
4634
|
+
this.treeRowCount = 0;
|
|
4568
4635
|
this.activeNode = null;
|
|
4569
4636
|
this.focusNode = null;
|
|
4570
4637
|
// this.types = {};
|
|
4571
4638
|
// this. columns =[];
|
|
4572
4639
|
// this._columnsById = {};
|
|
4573
4640
|
// Modification Status
|
|
4574
|
-
this.changedSince = 0;
|
|
4575
|
-
this.changes.clear();
|
|
4576
|
-
this.changedNodes.clear();
|
|
4641
|
+
// this.changedSince = 0;
|
|
4642
|
+
// this.changes.clear();
|
|
4643
|
+
// this.changedNodes.clear();
|
|
4577
4644
|
// // --- FILTER ---
|
|
4578
4645
|
// public filterMode: FilterModeType = null;
|
|
4579
4646
|
// // --- KEYNAV ---
|
|
@@ -4601,10 +4668,11 @@
|
|
|
4601
4668
|
/**
|
|
4602
4669
|
* Return `tree.option.NAME` (also resolving if this is a callback).
|
|
4603
4670
|
*
|
|
4604
|
-
* See also
|
|
4605
|
-
* `tree.types[node.type].NAME`.
|
|
4671
|
+
* See also {@link WunderbaumNode.getOption|WunderbaumNode.getOption()}
|
|
4672
|
+
* to consider `node.NAME` setting and `tree.types[node.type].NAME`.
|
|
4606
4673
|
*
|
|
4607
|
-
* @param name option name (use dot notation to access extension option, e.g.
|
|
4674
|
+
* @param name option name (use dot notation to access extension option, e.g.
|
|
4675
|
+
* `filter.mode`)
|
|
4608
4676
|
*/
|
|
4609
4677
|
getOption(name, defaultValue) {
|
|
4610
4678
|
let ext;
|
|
@@ -4648,18 +4716,14 @@
|
|
|
4648
4716
|
}
|
|
4649
4717
|
/** Run code, but defer `updateViewport()` until done. */
|
|
4650
4718
|
runWithoutUpdate(func, hint = null) {
|
|
4651
|
-
// const prev = this._disableUpdate;
|
|
4652
|
-
// const start = Date.now();
|
|
4653
|
-
// this._disableUpdate = Date.now();
|
|
4654
4719
|
try {
|
|
4655
4720
|
this.enableUpdate(false);
|
|
4656
|
-
|
|
4721
|
+
const res = func();
|
|
4722
|
+
assert(!(res instanceof Promise));
|
|
4723
|
+
return res;
|
|
4657
4724
|
}
|
|
4658
4725
|
finally {
|
|
4659
4726
|
this.enableUpdate(true);
|
|
4660
|
-
// if (!prev && this._disableUpdate === start) {
|
|
4661
|
-
// this._disableUpdate = 0;
|
|
4662
|
-
// }
|
|
4663
4727
|
}
|
|
4664
4728
|
}
|
|
4665
4729
|
/** Recursively expand all expandable nodes (triggers lazy load id needed). */
|
|
@@ -4677,11 +4741,12 @@
|
|
|
4677
4741
|
/** Return the number of nodes in the data model.*/
|
|
4678
4742
|
count(visible = false) {
|
|
4679
4743
|
if (visible) {
|
|
4680
|
-
return this.
|
|
4744
|
+
return this.treeRowCount;
|
|
4745
|
+
// return this.viewNodes.size;
|
|
4681
4746
|
}
|
|
4682
4747
|
return this.keyMap.size;
|
|
4683
4748
|
}
|
|
4684
|
-
|
|
4749
|
+
/** @internal sanity check. */
|
|
4685
4750
|
_check() {
|
|
4686
4751
|
let i = 0;
|
|
4687
4752
|
this.visit((n) => {
|
|
@@ -4692,25 +4757,30 @@
|
|
|
4692
4757
|
}
|
|
4693
4758
|
// util.assert(this.keyMap.size === i);
|
|
4694
4759
|
}
|
|
4695
|
-
/**
|
|
4760
|
+
/**
|
|
4761
|
+
* Find all nodes that matches condition.
|
|
4696
4762
|
*
|
|
4697
4763
|
* @param match title string to search for, or a
|
|
4698
4764
|
* callback function that returns `true` if a node is matched.
|
|
4699
|
-
*
|
|
4765
|
+
*
|
|
4766
|
+
* @see {@link WunderbaumNode.findAll}
|
|
4700
4767
|
*/
|
|
4701
4768
|
findAll(match) {
|
|
4702
4769
|
return this.root.findAll(match);
|
|
4703
4770
|
}
|
|
4704
|
-
/**
|
|
4771
|
+
/**
|
|
4772
|
+
* Find first node that matches condition.
|
|
4705
4773
|
*
|
|
4706
4774
|
* @param match title string to search for, or a
|
|
4707
4775
|
* callback function that returns `true` if a node is matched.
|
|
4708
|
-
* @see
|
|
4776
|
+
* @see {@link WunderbaumNode.findFirst}
|
|
4777
|
+
*
|
|
4709
4778
|
*/
|
|
4710
4779
|
findFirst(match) {
|
|
4711
4780
|
return this.root.findFirst(match);
|
|
4712
4781
|
}
|
|
4713
|
-
/**
|
|
4782
|
+
/**
|
|
4783
|
+
* Find the next visible node that starts with `match`, starting at `startNode`
|
|
4714
4784
|
* and wrap-around at the end.
|
|
4715
4785
|
*/
|
|
4716
4786
|
findNextNode(match, startNode) {
|
|
@@ -4740,7 +4810,8 @@
|
|
|
4740
4810
|
}
|
|
4741
4811
|
return res;
|
|
4742
4812
|
}
|
|
4743
|
-
/**
|
|
4813
|
+
/**
|
|
4814
|
+
* Find a node relative to another node.
|
|
4744
4815
|
*
|
|
4745
4816
|
* @param node
|
|
4746
4817
|
* @param where 'down', 'first', 'last', 'left', 'parent', 'right', or 'up'.
|
|
@@ -4750,7 +4821,7 @@
|
|
|
4750
4821
|
*/
|
|
4751
4822
|
findRelatedNode(node, where, includeHidden = false) {
|
|
4752
4823
|
let res = null;
|
|
4753
|
-
|
|
4824
|
+
const pageSize = Math.floor(this.scrollContainer.clientHeight / ROW_HEIGHT);
|
|
4754
4825
|
switch (where) {
|
|
4755
4826
|
case "parent":
|
|
4756
4827
|
if (node.parent && node.parent.parent) {
|
|
@@ -4806,9 +4877,9 @@
|
|
|
4806
4877
|
res = this._getNextNodeInView(node);
|
|
4807
4878
|
break;
|
|
4808
4879
|
case "pageDown":
|
|
4809
|
-
|
|
4810
|
-
// this.logDebug(where
|
|
4811
|
-
if (
|
|
4880
|
+
const bottomNode = this._lastNodeInView();
|
|
4881
|
+
// this.logDebug(`${where}(${node}) -> ${bottomNode}`);
|
|
4882
|
+
if (node._rowIdx < bottomNode._rowIdx) {
|
|
4812
4883
|
res = bottomNode;
|
|
4813
4884
|
}
|
|
4814
4885
|
else {
|
|
@@ -4816,12 +4887,13 @@
|
|
|
4816
4887
|
}
|
|
4817
4888
|
break;
|
|
4818
4889
|
case "pageUp":
|
|
4819
|
-
if (
|
|
4820
|
-
res =
|
|
4890
|
+
if (node._rowIdx === 0) {
|
|
4891
|
+
res = node;
|
|
4821
4892
|
}
|
|
4822
4893
|
else {
|
|
4823
|
-
|
|
4824
|
-
|
|
4894
|
+
const topNode = this._firstNodeInView();
|
|
4895
|
+
// this.logDebug(`${where}(${node}) -> ${topNode}`);
|
|
4896
|
+
if (node._rowIdx > topNode._rowIdx) {
|
|
4825
4897
|
res = topNode;
|
|
4826
4898
|
}
|
|
4827
4899
|
else {
|
|
@@ -4835,7 +4907,7 @@
|
|
|
4835
4907
|
return res;
|
|
4836
4908
|
}
|
|
4837
4909
|
/**
|
|
4838
|
-
* Return the active cell of the currently active node or null.
|
|
4910
|
+
* Return the active cell (`span.wb-col`) of the currently active node or null.
|
|
4839
4911
|
*/
|
|
4840
4912
|
getActiveColElem() {
|
|
4841
4913
|
if (this.activeNode && this.activeColIdx >= 0) {
|
|
@@ -4901,7 +4973,7 @@
|
|
|
4901
4973
|
}
|
|
4902
4974
|
else {
|
|
4903
4975
|
// Somewhere near the title
|
|
4904
|
-
if (event.type !== "mousemove") {
|
|
4976
|
+
if (event.type !== "mousemove" && !(event instanceof KeyboardEvent)) {
|
|
4905
4977
|
console.warn("getEventInfo(): not found", event, res);
|
|
4906
4978
|
}
|
|
4907
4979
|
return res;
|
|
@@ -4933,7 +5005,8 @@
|
|
|
4933
5005
|
isEditing() {
|
|
4934
5006
|
return this._callMethod("edit.isEditingTitle");
|
|
4935
5007
|
}
|
|
4936
|
-
/**
|
|
5008
|
+
/**
|
|
5009
|
+
* Return true if any node is currently beeing loaded, i.e. a Ajax request is pending.
|
|
4937
5010
|
*/
|
|
4938
5011
|
isLoading() {
|
|
4939
5012
|
var res = false;
|
|
@@ -4960,7 +5033,7 @@
|
|
|
4960
5033
|
console.error.apply(console, args);
|
|
4961
5034
|
}
|
|
4962
5035
|
}
|
|
4963
|
-
|
|
5036
|
+
/** Log to console if opts.debugLevel >= 3 */
|
|
4964
5037
|
logInfo(...args) {
|
|
4965
5038
|
if (this.options.debugLevel >= 3) {
|
|
4966
5039
|
Array.prototype.unshift.call(args, this.toString());
|
|
@@ -4987,75 +5060,6 @@
|
|
|
4987
5060
|
console.warn.apply(console, args);
|
|
4988
5061
|
}
|
|
4989
5062
|
}
|
|
4990
|
-
/** */
|
|
4991
|
-
render(opts) {
|
|
4992
|
-
const label = this.logTime("render");
|
|
4993
|
-
let idx = 0;
|
|
4994
|
-
let top = 0;
|
|
4995
|
-
const height = ROW_HEIGHT;
|
|
4996
|
-
let modified = false;
|
|
4997
|
-
let start = opts === null || opts === void 0 ? void 0 : opts.startIdx;
|
|
4998
|
-
let end = opts === null || opts === void 0 ? void 0 : opts.endIdx;
|
|
4999
|
-
const obsoleteViewNodes = this.viewNodes;
|
|
5000
|
-
const newNodesOnly = !!getOption(opts, "newNodesOnly");
|
|
5001
|
-
this.viewNodes = new Set();
|
|
5002
|
-
let viewNodes = this.viewNodes;
|
|
5003
|
-
// this.debug("render", opts);
|
|
5004
|
-
assert(start != null && end != null);
|
|
5005
|
-
// Make sure start is always even, so the alternating row colors don't
|
|
5006
|
-
// change when scrolling:
|
|
5007
|
-
if (start % 2) {
|
|
5008
|
-
start--;
|
|
5009
|
-
}
|
|
5010
|
-
this.visitRows(function (node) {
|
|
5011
|
-
const prevIdx = node._rowIdx;
|
|
5012
|
-
viewNodes.add(node);
|
|
5013
|
-
obsoleteViewNodes.delete(node);
|
|
5014
|
-
if (prevIdx !== idx) {
|
|
5015
|
-
node._rowIdx = idx;
|
|
5016
|
-
modified = true;
|
|
5017
|
-
}
|
|
5018
|
-
if (idx < start || idx > end) {
|
|
5019
|
-
node._callEvent("discard");
|
|
5020
|
-
node.removeMarkup();
|
|
5021
|
-
}
|
|
5022
|
-
else if (!node._rowElem || !newNodesOnly) {
|
|
5023
|
-
node.render({ top: top });
|
|
5024
|
-
// }else{
|
|
5025
|
-
// node.log("ignrored render")
|
|
5026
|
-
}
|
|
5027
|
-
idx++;
|
|
5028
|
-
top += height;
|
|
5029
|
-
});
|
|
5030
|
-
for (const prevNode of obsoleteViewNodes) {
|
|
5031
|
-
prevNode._callEvent("discard");
|
|
5032
|
-
prevNode.removeMarkup();
|
|
5033
|
-
}
|
|
5034
|
-
// Resize tree container
|
|
5035
|
-
this.nodeListElement.style.height = "" + top + "px";
|
|
5036
|
-
// this.log("render()", this.nodeListElement.style.height);
|
|
5037
|
-
this.logTimeEnd(label);
|
|
5038
|
-
return modified;
|
|
5039
|
-
}
|
|
5040
|
-
/**Recalc and apply header columns from `this.columns`. */
|
|
5041
|
-
renderHeader() {
|
|
5042
|
-
if (!this.headerElement) {
|
|
5043
|
-
return;
|
|
5044
|
-
}
|
|
5045
|
-
const headerRow = this.headerElement.querySelector(".wb-row");
|
|
5046
|
-
assert(headerRow);
|
|
5047
|
-
headerRow.innerHTML = "<span class='wb-col'></span>".repeat(this.columns.length);
|
|
5048
|
-
for (let i = 0; i < this.columns.length; i++) {
|
|
5049
|
-
const col = this.columns[i];
|
|
5050
|
-
const colElem = headerRow.children[i];
|
|
5051
|
-
colElem.style.left = col._ofsPx + "px";
|
|
5052
|
-
colElem.style.width = col._widthPx + "px";
|
|
5053
|
-
// colElem.textContent = col.title || col.id;
|
|
5054
|
-
const title = escapeHtml(col.title || col.id);
|
|
5055
|
-
colElem.innerHTML = `<span class="wb-col-title">${title}</span> <span class="wb-col-resizer"></span>`;
|
|
5056
|
-
// colElem.innerHTML = `${title} <span class="wb-col-resizer"></span>`;
|
|
5057
|
-
}
|
|
5058
|
-
}
|
|
5059
5063
|
/**
|
|
5060
5064
|
* Make sure that this node is scrolled into the viewport.
|
|
5061
5065
|
*
|
|
@@ -5089,24 +5093,12 @@
|
|
|
5089
5093
|
this.setModified(ChangeType.vscroll);
|
|
5090
5094
|
}
|
|
5091
5095
|
}
|
|
5092
|
-
/**
|
|
5093
|
-
|
|
5094
|
-
|
|
5095
|
-
|
|
5096
|
-
|
|
5097
|
-
|
|
5098
|
-
}
|
|
5099
|
-
const prevMode = this.navMode;
|
|
5100
|
-
const cellMode = mode !== NavigationMode.row;
|
|
5101
|
-
this.navMode = mode;
|
|
5102
|
-
if (cellMode && prevMode === NavigationMode.row) {
|
|
5103
|
-
this.setColumn(0);
|
|
5104
|
-
}
|
|
5105
|
-
this.element.classList.toggle("wb-cell-mode", cellMode);
|
|
5106
|
-
this.element.classList.toggle("wb-cell-edit-mode", mode === NavigationMode.cellEdit);
|
|
5107
|
-
this.setModified(ChangeType.row, this.activeNode);
|
|
5108
|
-
}
|
|
5109
|
-
/** */
|
|
5096
|
+
/**
|
|
5097
|
+
* Set column #colIdx to 'active'.
|
|
5098
|
+
*
|
|
5099
|
+
* This higlights the column header and -cells by adding the `wb-active` class.
|
|
5100
|
+
* Available in cell-nav and cell-edit mode, not in row-mode.
|
|
5101
|
+
*/
|
|
5110
5102
|
setColumn(colIdx) {
|
|
5111
5103
|
assert(this.navMode !== NavigationMode.row);
|
|
5112
5104
|
assert(0 <= colIdx && colIdx < this.columns.length);
|
|
@@ -5131,7 +5123,7 @@
|
|
|
5131
5123
|
}
|
|
5132
5124
|
}
|
|
5133
5125
|
}
|
|
5134
|
-
/** */
|
|
5126
|
+
/** Set or remove keybaord focus to the tree container. */
|
|
5135
5127
|
setFocus(flag = true) {
|
|
5136
5128
|
if (flag) {
|
|
5137
5129
|
this.element.focus();
|
|
@@ -5140,20 +5132,24 @@
|
|
|
5140
5132
|
this.element.blur();
|
|
5141
5133
|
}
|
|
5142
5134
|
}
|
|
5143
|
-
/* */
|
|
5144
5135
|
setModified(change, node, options) {
|
|
5136
|
+
if (this._disableUpdateCount) {
|
|
5137
|
+
// Assuming that we redraw all when enableUpdate() is re-enabled.
|
|
5138
|
+
// this.log(
|
|
5139
|
+
// `IGNORED setModified(${change}) node=${node} (disable level ${this._disableUpdateCount})`
|
|
5140
|
+
// );
|
|
5141
|
+
return;
|
|
5142
|
+
}
|
|
5143
|
+
// this.log(`setModified(${change}) node=${node}`);
|
|
5145
5144
|
if (!(node instanceof WunderbaumNode)) {
|
|
5146
5145
|
options = node;
|
|
5147
5146
|
}
|
|
5148
|
-
if (this._disableUpdate) {
|
|
5149
|
-
return;
|
|
5150
|
-
}
|
|
5151
5147
|
const immediate = !!getOption(options, "immediate");
|
|
5152
5148
|
switch (change) {
|
|
5153
5149
|
case ChangeType.any:
|
|
5154
5150
|
case ChangeType.structure:
|
|
5155
5151
|
case ChangeType.header:
|
|
5156
|
-
this.
|
|
5152
|
+
this.changeRedrawRequestPending = true;
|
|
5157
5153
|
this.updateViewport(immediate);
|
|
5158
5154
|
break;
|
|
5159
5155
|
case ChangeType.vscroll:
|
|
@@ -5170,84 +5166,111 @@
|
|
|
5170
5166
|
default:
|
|
5171
5167
|
error(`Invalid change type ${change}`);
|
|
5172
5168
|
}
|
|
5173
|
-
// if (!this.changedSince) {
|
|
5174
|
-
// this.changedSince = Date.now();
|
|
5175
|
-
// }
|
|
5176
|
-
// this.changes.add(change);
|
|
5177
|
-
// if (change === ChangeType.structure) {
|
|
5178
|
-
// this.changedNodes.clear();
|
|
5179
|
-
// } else if (node && !this.changes.has(ChangeType.structure)) {
|
|
5180
|
-
// if (this.changedNodes.size < MAX_CHANGED_NODES) {
|
|
5181
|
-
// this.changedNodes.add(node);
|
|
5182
|
-
// } else {
|
|
5183
|
-
// this.changes.add(ChangeType.structure);
|
|
5184
|
-
// this.changedNodes.clear();
|
|
5185
|
-
// }
|
|
5186
|
-
// }
|
|
5187
|
-
// this.log("setModified(" + change + ")", node);
|
|
5188
5169
|
}
|
|
5170
|
+
/** Set the tree's navigation mode. */
|
|
5171
|
+
setNavigationMode(mode) {
|
|
5172
|
+
// util.assert(this.cellNavMode);
|
|
5173
|
+
// util.assert(0 <= colIdx && colIdx < this.columns.length);
|
|
5174
|
+
if (mode === this.navMode) {
|
|
5175
|
+
return;
|
|
5176
|
+
}
|
|
5177
|
+
const prevMode = this.navMode;
|
|
5178
|
+
const cellMode = mode !== NavigationMode.row;
|
|
5179
|
+
this.navMode = mode;
|
|
5180
|
+
if (cellMode && prevMode === NavigationMode.row) {
|
|
5181
|
+
this.setColumn(0);
|
|
5182
|
+
}
|
|
5183
|
+
this.element.classList.toggle("wb-cell-mode", cellMode);
|
|
5184
|
+
this.element.classList.toggle("wb-cell-edit-mode", mode === NavigationMode.cellEdit);
|
|
5185
|
+
this.setModified(ChangeType.row, this.activeNode);
|
|
5186
|
+
}
|
|
5187
|
+
/** Display tree status (ok, loading, error, noData) using styles and a dummy root node. */
|
|
5189
5188
|
setStatus(status, message, details) {
|
|
5190
5189
|
return this.root.setStatus(status, message, details);
|
|
5191
5190
|
}
|
|
5192
5191
|
/** Update column headers and width. */
|
|
5193
5192
|
updateColumns(opts) {
|
|
5194
|
-
|
|
5195
|
-
|
|
5196
|
-
|
|
5193
|
+
opts = Object.assign({ calculateCols: true, updateRows: true }, opts);
|
|
5194
|
+
const minWidth = 4;
|
|
5195
|
+
const vpWidth = this.element.clientWidth;
|
|
5197
5196
|
let totalWeight = 0;
|
|
5198
5197
|
let fixedWidth = 0;
|
|
5199
|
-
|
|
5200
|
-
|
|
5201
|
-
|
|
5202
|
-
this._columnsById
|
|
5203
|
-
let
|
|
5204
|
-
|
|
5205
|
-
|
|
5206
|
-
|
|
5207
|
-
|
|
5208
|
-
|
|
5209
|
-
|
|
5210
|
-
|
|
5211
|
-
|
|
5212
|
-
|
|
5213
|
-
|
|
5214
|
-
|
|
5215
|
-
|
|
5216
|
-
|
|
5217
|
-
col._widthPx
|
|
5198
|
+
let modified = false;
|
|
5199
|
+
if (opts.calculateCols) {
|
|
5200
|
+
// Gather width requests
|
|
5201
|
+
this._columnsById = {};
|
|
5202
|
+
for (let col of this.columns) {
|
|
5203
|
+
this._columnsById[col.id] = col;
|
|
5204
|
+
let cw = col.width;
|
|
5205
|
+
if (!cw || cw === "*") {
|
|
5206
|
+
col._weight = 1.0;
|
|
5207
|
+
totalWeight += 1.0;
|
|
5208
|
+
}
|
|
5209
|
+
else if (typeof cw === "number") {
|
|
5210
|
+
col._weight = cw;
|
|
5211
|
+
totalWeight += cw;
|
|
5212
|
+
}
|
|
5213
|
+
else if (typeof cw === "string" && cw.endsWith("px")) {
|
|
5214
|
+
col._weight = 0;
|
|
5215
|
+
let px = parseFloat(cw.slice(0, -2));
|
|
5216
|
+
if (col._widthPx != px) {
|
|
5217
|
+
modified = true;
|
|
5218
|
+
col._widthPx = px;
|
|
5219
|
+
}
|
|
5220
|
+
fixedWidth += px;
|
|
5221
|
+
}
|
|
5222
|
+
else {
|
|
5223
|
+
error("Invalid column width: " + cw);
|
|
5218
5224
|
}
|
|
5219
|
-
fixedWidth += px;
|
|
5220
5225
|
}
|
|
5221
|
-
|
|
5222
|
-
|
|
5223
|
-
|
|
5224
|
-
|
|
5225
|
-
|
|
5226
|
-
|
|
5227
|
-
|
|
5228
|
-
|
|
5229
|
-
|
|
5230
|
-
|
|
5231
|
-
if (col._widthPx != px) {
|
|
5232
|
-
modified = true;
|
|
5233
|
-
col._widthPx = px;
|
|
5226
|
+
// Share remaining space between non-fixed columns
|
|
5227
|
+
const restPx = Math.max(0, vpWidth - fixedWidth);
|
|
5228
|
+
let ofsPx = 0;
|
|
5229
|
+
for (let col of this.columns) {
|
|
5230
|
+
if (col._weight) {
|
|
5231
|
+
const px = Math.max(minWidth, (restPx * col._weight) / totalWeight);
|
|
5232
|
+
if (col._widthPx != px) {
|
|
5233
|
+
modified = true;
|
|
5234
|
+
col._widthPx = px;
|
|
5235
|
+
}
|
|
5234
5236
|
}
|
|
5237
|
+
col._ofsPx = ofsPx;
|
|
5238
|
+
ofsPx += col._widthPx;
|
|
5235
5239
|
}
|
|
5236
|
-
col._ofsPx = ofsPx;
|
|
5237
|
-
ofsPx += col._widthPx;
|
|
5238
5240
|
}
|
|
5239
5241
|
// Every column has now a calculated `_ofsPx` and `_widthPx`
|
|
5240
5242
|
// this.logInfo("UC", this.columns, vpWidth, this.element.clientWidth, this.element);
|
|
5241
5243
|
// console.trace();
|
|
5242
5244
|
// util.error("BREAK");
|
|
5243
5245
|
if (modified) {
|
|
5244
|
-
this.
|
|
5245
|
-
if (opts.
|
|
5246
|
-
this.
|
|
5246
|
+
this._renderHeaderMarkup();
|
|
5247
|
+
if (opts.updateRows) {
|
|
5248
|
+
this._updateRows();
|
|
5247
5249
|
}
|
|
5248
5250
|
}
|
|
5249
5251
|
}
|
|
5250
|
-
/**
|
|
5252
|
+
/** Create/update header markup from `this.columns` definition.
|
|
5253
|
+
* @internal
|
|
5254
|
+
*/
|
|
5255
|
+
_renderHeaderMarkup() {
|
|
5256
|
+
if (!this.headerElement) {
|
|
5257
|
+
return;
|
|
5258
|
+
}
|
|
5259
|
+
const headerRow = this.headerElement.querySelector(".wb-row");
|
|
5260
|
+
assert(headerRow);
|
|
5261
|
+
headerRow.innerHTML = "<span class='wb-col'></span>".repeat(this.columns.length);
|
|
5262
|
+
for (let i = 0; i < this.columns.length; i++) {
|
|
5263
|
+
const col = this.columns[i];
|
|
5264
|
+
const colElem = headerRow.children[i];
|
|
5265
|
+
colElem.style.left = col._ofsPx + "px";
|
|
5266
|
+
colElem.style.width = col._widthPx + "px";
|
|
5267
|
+
// colElem.textContent = col.title || col.id;
|
|
5268
|
+
const title = escapeHtml(col.title || col.id);
|
|
5269
|
+
colElem.innerHTML = `<span class="wb-col-title">${title}</span> <span class="wb-col-resizer"></span>`;
|
|
5270
|
+
// colElem.innerHTML = `${title} <span class="wb-col-resizer"></span>`;
|
|
5271
|
+
}
|
|
5272
|
+
}
|
|
5273
|
+
/** Render header and all rows that are visible in the viewport (async, throttled). */
|
|
5251
5274
|
updateViewport(immediate = false) {
|
|
5252
5275
|
// Call the `throttle` wrapper for `this._updateViewport()` which will
|
|
5253
5276
|
// execute immediately on the leading edge of a sequence:
|
|
@@ -5256,42 +5279,163 @@
|
|
|
5256
5279
|
this._updateViewportThrottled.flush();
|
|
5257
5280
|
}
|
|
5258
5281
|
}
|
|
5282
|
+
/**
|
|
5283
|
+
* This is the actual update method, which is wrapped inside a throttle method.
|
|
5284
|
+
* This protected method should not be called directly but via
|
|
5285
|
+
* `tree.updateViewport()` or `tree.setModified()`.
|
|
5286
|
+
* It calls `updateColumns()` and `_updateRows()`.
|
|
5287
|
+
* @internal
|
|
5288
|
+
*/
|
|
5259
5289
|
_updateViewport() {
|
|
5260
|
-
if (this.
|
|
5290
|
+
if (this._disableUpdateCount) {
|
|
5291
|
+
this.log(`IGNORED _updateViewport() disable level: ${this._disableUpdateCount}`);
|
|
5261
5292
|
return;
|
|
5262
5293
|
}
|
|
5263
|
-
const newNodesOnly = !this.
|
|
5264
|
-
this.
|
|
5294
|
+
const newNodesOnly = !this.changeRedrawRequestPending;
|
|
5295
|
+
this.changeRedrawRequestPending = false;
|
|
5265
5296
|
let height = this.scrollContainer.clientHeight;
|
|
5266
|
-
// We cannot get the height for
|
|
5297
|
+
// We cannot get the height for absolute positioned parent, so look at first col
|
|
5267
5298
|
// let headerHeight = this.headerElement.clientHeight
|
|
5268
5299
|
// let headerHeight = this.headerElement.children[0].children[0].clientHeight;
|
|
5269
5300
|
const headerHeight = this.options.headerHeightPx;
|
|
5270
5301
|
const wantHeight = this.element.clientHeight - headerHeight;
|
|
5271
|
-
const ofs = this.scrollContainer.scrollTop;
|
|
5272
5302
|
if (Math.abs(height - wantHeight) > 1.0) {
|
|
5273
5303
|
// this.log("resize", height, wantHeight);
|
|
5274
5304
|
this.scrollContainer.style.height = wantHeight + "px";
|
|
5275
5305
|
height = wantHeight;
|
|
5276
5306
|
}
|
|
5277
|
-
this.updateColumns({
|
|
5278
|
-
this.
|
|
5279
|
-
startIdx: Math.max(0, ofs / ROW_HEIGHT - RENDER_MAX_PREFETCH),
|
|
5280
|
-
endIdx: Math.max(0, (ofs + height) / ROW_HEIGHT + RENDER_MAX_PREFETCH),
|
|
5281
|
-
newNodesOnly: newNodesOnly,
|
|
5282
|
-
});
|
|
5307
|
+
this.updateColumns({ updateRows: false });
|
|
5308
|
+
this._updateRows({ newNodesOnly: newNodesOnly });
|
|
5283
5309
|
this._callEvent("update");
|
|
5284
5310
|
}
|
|
5285
|
-
/**
|
|
5311
|
+
/**
|
|
5312
|
+
* Assert that TR order matches the natural node order
|
|
5313
|
+
* @internal
|
|
5314
|
+
*/
|
|
5315
|
+
_validateRows() {
|
|
5316
|
+
let trs = this.nodeListElement.childNodes;
|
|
5317
|
+
let i = 0;
|
|
5318
|
+
let prev = -1;
|
|
5319
|
+
let ok = true;
|
|
5320
|
+
trs.forEach((element) => {
|
|
5321
|
+
const tr = element;
|
|
5322
|
+
const top = Number.parseInt(tr.style.top);
|
|
5323
|
+
const n = tr._wb_node;
|
|
5324
|
+
// if (i < 4) {
|
|
5325
|
+
// console.info(
|
|
5326
|
+
// `TR#${i}, rowIdx=${n._rowIdx} , top=${top}px: '${n.title}'`
|
|
5327
|
+
// );
|
|
5328
|
+
// }
|
|
5329
|
+
if (top <= prev) {
|
|
5330
|
+
console.warn(`TR order mismatch at index ${i}: top=${top}px, node=${n}`);
|
|
5331
|
+
// throw new Error("fault");
|
|
5332
|
+
ok = false;
|
|
5333
|
+
}
|
|
5334
|
+
prev = top;
|
|
5335
|
+
i++;
|
|
5336
|
+
});
|
|
5337
|
+
return ok;
|
|
5338
|
+
}
|
|
5339
|
+
/*
|
|
5340
|
+
* - Traverse all *visible* of the whole tree, i.e. skip collapsed nodes.
|
|
5341
|
+
* - Store count of rows to `tree.treeRowCount`.
|
|
5342
|
+
* - Renumber `node._rowIdx` for all visible nodes.
|
|
5343
|
+
* - Calculate the index range that must be rendered to fill the viewport
|
|
5344
|
+
* (including upper and lower prefetch)
|
|
5345
|
+
* -
|
|
5346
|
+
*/
|
|
5347
|
+
_updateRows(opts) {
|
|
5348
|
+
const label = this.logTime("_updateRows");
|
|
5349
|
+
opts = Object.assign({ newNodesOnly: false }, opts);
|
|
5350
|
+
const newNodesOnly = !!opts.newNodesOnly;
|
|
5351
|
+
const row_height = ROW_HEIGHT;
|
|
5352
|
+
const vp_height = this.scrollContainer.clientHeight;
|
|
5353
|
+
const prefetch = RENDER_MAX_PREFETCH;
|
|
5354
|
+
const ofs = this.scrollContainer.scrollTop;
|
|
5355
|
+
let startIdx = Math.max(0, ofs / row_height - prefetch);
|
|
5356
|
+
startIdx = Math.floor(startIdx);
|
|
5357
|
+
// Make sure start is always even, so the alternating row colors don't
|
|
5358
|
+
// change when scrolling:
|
|
5359
|
+
if (startIdx % 2) {
|
|
5360
|
+
startIdx--;
|
|
5361
|
+
}
|
|
5362
|
+
let endIdx = Math.max(0, (ofs + vp_height) / row_height + prefetch);
|
|
5363
|
+
endIdx = Math.ceil(endIdx);
|
|
5364
|
+
// const obsoleteViewNodes = this.viewNodes;
|
|
5365
|
+
// this.viewNodes = new Set();
|
|
5366
|
+
// const viewNodes = this.viewNodes;
|
|
5367
|
+
// this.debug("render", opts);
|
|
5368
|
+
const obsoleteNodes = new Set();
|
|
5369
|
+
this.nodeListElement.childNodes.forEach((elem) => {
|
|
5370
|
+
const tr = elem;
|
|
5371
|
+
obsoleteNodes.add(tr._wb_node);
|
|
5372
|
+
});
|
|
5373
|
+
let idx = 0;
|
|
5374
|
+
let top = 0;
|
|
5375
|
+
let modified = false;
|
|
5376
|
+
let prevElem = "first";
|
|
5377
|
+
this.visitRows(function (node) {
|
|
5378
|
+
// console.log("visit", node)
|
|
5379
|
+
const rowDiv = node._rowElem;
|
|
5380
|
+
// Renumber all expanded nodes
|
|
5381
|
+
if (node._rowIdx !== idx) {
|
|
5382
|
+
node._rowIdx = idx;
|
|
5383
|
+
modified = true;
|
|
5384
|
+
}
|
|
5385
|
+
if (idx < startIdx || idx > endIdx) {
|
|
5386
|
+
// row is outside viewport bounds
|
|
5387
|
+
if (rowDiv) {
|
|
5388
|
+
prevElem = rowDiv;
|
|
5389
|
+
}
|
|
5390
|
+
}
|
|
5391
|
+
else if (rowDiv && newNodesOnly) {
|
|
5392
|
+
obsoleteNodes.delete(node);
|
|
5393
|
+
// no need to update existing node markup
|
|
5394
|
+
rowDiv.style.top = idx * ROW_HEIGHT + "px";
|
|
5395
|
+
prevElem = rowDiv;
|
|
5396
|
+
}
|
|
5397
|
+
else {
|
|
5398
|
+
obsoleteNodes.delete(node);
|
|
5399
|
+
// Create new markup
|
|
5400
|
+
node.render({ top: top, after: prevElem });
|
|
5401
|
+
// console.log("render", top, prevElem, "=>", node._rowElem);
|
|
5402
|
+
prevElem = node._rowElem;
|
|
5403
|
+
}
|
|
5404
|
+
idx++;
|
|
5405
|
+
top += row_height;
|
|
5406
|
+
});
|
|
5407
|
+
this.treeRowCount = idx;
|
|
5408
|
+
for (const n of obsoleteNodes) {
|
|
5409
|
+
n._callEvent("discard");
|
|
5410
|
+
n.removeMarkup();
|
|
5411
|
+
}
|
|
5412
|
+
// Resize tree container
|
|
5413
|
+
this.nodeListElement.style.height = `${top}px`;
|
|
5414
|
+
// this.log(
|
|
5415
|
+
// `render(scrollOfs:${ofs}, ${startIdx}..${endIdx})`,
|
|
5416
|
+
// this.nodeListElement.style.height
|
|
5417
|
+
// );
|
|
5418
|
+
this.logTimeEnd(label);
|
|
5419
|
+
this._validateRows();
|
|
5420
|
+
return modified;
|
|
5421
|
+
}
|
|
5422
|
+
/**
|
|
5423
|
+
* Call callback(node) for all nodes in hierarchical order (depth-first).
|
|
5286
5424
|
*
|
|
5287
5425
|
* @param {function} callback the callback function.
|
|
5288
|
-
* Return false to stop iteration, return "skip" to skip this node and
|
|
5426
|
+
* Return false to stop iteration, return "skip" to skip this node and
|
|
5427
|
+
* children only.
|
|
5289
5428
|
* @returns {boolean} false, if the iterator was stopped.
|
|
5290
5429
|
*/
|
|
5291
5430
|
visit(callback) {
|
|
5292
5431
|
return this.root.visit(callback, false);
|
|
5293
5432
|
}
|
|
5294
|
-
/**
|
|
5433
|
+
/**
|
|
5434
|
+
* Call fn(node) for all nodes in vertical order, top down (or bottom up).
|
|
5435
|
+
*
|
|
5436
|
+
* Note that this considers expansion state, i.e. children of collapsed nodes
|
|
5437
|
+
* are skipped.
|
|
5438
|
+
*
|
|
5295
5439
|
* Stop iteration, if fn() returns false.<br>
|
|
5296
5440
|
* Return false if iteration was stopped.
|
|
5297
5441
|
*
|
|
@@ -5371,7 +5515,8 @@
|
|
|
5371
5515
|
}
|
|
5372
5516
|
return true;
|
|
5373
5517
|
}
|
|
5374
|
-
/**
|
|
5518
|
+
/**
|
|
5519
|
+
* Call fn(node) for all nodes in vertical order, bottom up.
|
|
5375
5520
|
* @internal
|
|
5376
5521
|
*/
|
|
5377
5522
|
_visitRowsUp(callback, opts) {
|
|
@@ -5415,19 +5560,36 @@
|
|
|
5415
5560
|
}
|
|
5416
5561
|
return true;
|
|
5417
5562
|
}
|
|
5418
|
-
/**
|
|
5563
|
+
/**
|
|
5564
|
+
* Reload the tree with a new source.
|
|
5565
|
+
*
|
|
5566
|
+
* Previous data is cleared.
|
|
5567
|
+
* Pass `options.columns` to define a header (may also be part of `source.columns`).
|
|
5568
|
+
*/
|
|
5419
5569
|
load(source, options = {}) {
|
|
5420
5570
|
this.clear();
|
|
5421
5571
|
const columns = options.columns || source.columns;
|
|
5422
5572
|
if (columns) {
|
|
5423
5573
|
this.columns = options.columns;
|
|
5424
|
-
this.
|
|
5425
|
-
|
|
5574
|
+
// this._renderHeaderMarkup();
|
|
5575
|
+
this.updateColumns({ calculateCols: false });
|
|
5426
5576
|
}
|
|
5427
5577
|
return this.root.load(source);
|
|
5428
5578
|
}
|
|
5429
5579
|
/**
|
|
5580
|
+
* Disable render requests during operations that would trigger many updates.
|
|
5430
5581
|
*
|
|
5582
|
+
* ```js
|
|
5583
|
+
* try {
|
|
5584
|
+
* tree.enableUpdate(false);
|
|
5585
|
+
* // ... (long running operation that would trigger many updates)
|
|
5586
|
+
* foo();
|
|
5587
|
+
* // ... NOTE: make sure that async operations have finished
|
|
5588
|
+
* await foo();
|
|
5589
|
+
* } finally {
|
|
5590
|
+
* tree.enableUpdate(true);
|
|
5591
|
+
* }
|
|
5592
|
+
* ```
|
|
5431
5593
|
*/
|
|
5432
5594
|
enableUpdate(flag) {
|
|
5433
5595
|
/*
|
|
@@ -5435,20 +5597,22 @@
|
|
|
5435
5597
|
1 >-------------------------------------<
|
|
5436
5598
|
2 >--------------------<
|
|
5437
5599
|
3 >--------------------------<
|
|
5438
|
-
|
|
5439
|
-
5
|
|
5440
|
-
|
|
5441
5600
|
*/
|
|
5442
|
-
// this.logDebug( `enableUpdate(${flag}): count=${this._disableUpdateCount}...` );
|
|
5443
5601
|
if (flag) {
|
|
5444
|
-
assert(this._disableUpdateCount > 0);
|
|
5602
|
+
assert(this._disableUpdateCount > 0, "enableUpdate(true) was called too often");
|
|
5445
5603
|
this._disableUpdateCount--;
|
|
5604
|
+
// this.logDebug(
|
|
5605
|
+
// `enableUpdate(${flag}): count -> ${this._disableUpdateCount}...`
|
|
5606
|
+
// );
|
|
5446
5607
|
if (this._disableUpdateCount === 0) {
|
|
5447
5608
|
this.updateViewport();
|
|
5448
5609
|
}
|
|
5449
5610
|
}
|
|
5450
5611
|
else {
|
|
5451
5612
|
this._disableUpdateCount++;
|
|
5613
|
+
// this.logDebug(
|
|
5614
|
+
// `enableUpdate(${flag}): count -> ${this._disableUpdateCount}...`
|
|
5615
|
+
// );
|
|
5452
5616
|
// this._disableUpdate = Date.now();
|
|
5453
5617
|
}
|
|
5454
5618
|
// return !flag; // return previous value
|
|
@@ -5481,8 +5645,10 @@
|
|
|
5481
5645
|
return this.extensions.filter.updateFilter();
|
|
5482
5646
|
}
|
|
5483
5647
|
}
|
|
5484
|
-
Wunderbaum.version = "v0.0.2"; // Set to semver by 'grunt release'
|
|
5485
5648
|
Wunderbaum.sequence = 0;
|
|
5649
|
+
/** Wunderbaum release version number "MAJOR.MINOR.PATCH". */
|
|
5650
|
+
Wunderbaum.version = "v0.0.3"; // Set to semver by 'grunt release'
|
|
5651
|
+
/** Expose some useful methods of the util.ts module as `Wunderbaum.util`. */
|
|
5486
5652
|
Wunderbaum.util = util;
|
|
5487
5653
|
|
|
5488
5654
|
exports.Wunderbaum = Wunderbaum;
|