wunderbaum 0.13.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/dist/wunderbaum.css +31 -11
- package/dist/wunderbaum.css.map +1 -1
- package/dist/wunderbaum.d.ts +535 -296
- package/dist/wunderbaum.esm.js +619 -221
- package/dist/wunderbaum.esm.min.js +28 -28
- package/dist/wunderbaum.esm.min.js.map +1 -1
- package/dist/wunderbaum.umd.js +619 -221
- package/dist/wunderbaum.umd.min.js +32 -32
- package/dist/wunderbaum.umd.min.js.map +1 -1
- package/package.json +3 -2
- package/src/common.ts +23 -4
- package/src/types.ts +86 -40
- package/src/util.ts +75 -2
- package/src/wb_ext_dnd.ts +21 -20
- package/src/wb_ext_edit.ts +1 -1
- package/src/wb_ext_filter.ts +6 -2
- package/src/wb_ext_grid.ts +1 -1
- package/src/wb_extension_base.ts +16 -1
- package/src/wb_node.ts +222 -106
- package/src/wb_options.ts +169 -108
- package/src/wunderbaum.ts +351 -127
package/src/wb_node.ts
CHANGED
|
@@ -13,7 +13,6 @@ import {
|
|
|
13
13
|
ApplyCommandType,
|
|
14
14
|
ChangeType,
|
|
15
15
|
CheckboxOption,
|
|
16
|
-
ColumnDefinition,
|
|
17
16
|
ColumnEventInfoMap,
|
|
18
17
|
ExpandAllOptions,
|
|
19
18
|
IconOption,
|
|
@@ -37,7 +36,7 @@ import {
|
|
|
37
36
|
SetStatusOptions,
|
|
38
37
|
SortByPropertyOptions,
|
|
39
38
|
SortCallback,
|
|
40
|
-
|
|
39
|
+
SortOptions,
|
|
41
40
|
SourceType,
|
|
42
41
|
TooltipOption,
|
|
43
42
|
TristateType,
|
|
@@ -49,9 +48,11 @@ import {
|
|
|
49
48
|
ICON_WIDTH,
|
|
50
49
|
KEY_TO_NAVIGATION_MAP,
|
|
51
50
|
makeNodeTitleMatcher,
|
|
51
|
+
NODE_TYPE_FOLDER,
|
|
52
52
|
nodeTitleSorter,
|
|
53
53
|
RESERVED_TREE_SOURCE_KEYS,
|
|
54
|
-
|
|
54
|
+
TEST_FILE_PATH,
|
|
55
|
+
TEST_HTML,
|
|
55
56
|
TITLE_SPAN_PAD_Y,
|
|
56
57
|
} from "./common";
|
|
57
58
|
import { Deferred } from "./deferred";
|
|
@@ -186,13 +187,13 @@ export class WunderbaumNode {
|
|
|
186
187
|
_rowIdx: number | undefined = 0;
|
|
187
188
|
_rowElem: HTMLDivElement | undefined = undefined;
|
|
188
189
|
|
|
189
|
-
constructor(tree: Wunderbaum, parent: WunderbaumNode, data:
|
|
190
|
+
constructor(tree: Wunderbaum, parent: WunderbaumNode, data: WbNodeData) {
|
|
190
191
|
util.assert(!parent || parent.tree === tree, `Invalid parent: ${parent}`);
|
|
191
192
|
util.assert(!data.children, "'children' not allowed here");
|
|
192
193
|
|
|
193
194
|
this.tree = tree;
|
|
194
195
|
this.parent = parent;
|
|
195
|
-
this.key =
|
|
196
|
+
this.key = tree._calculateKey(data, parent);
|
|
196
197
|
this.title = "" + (data.title ?? "<" + this.key + ">");
|
|
197
198
|
this.expanded = !!data.expanded;
|
|
198
199
|
this.lazy = !!data.lazy;
|
|
@@ -332,12 +333,19 @@ export class WunderbaumNode {
|
|
|
332
333
|
nodeData = [<WbNodeData>nodeData];
|
|
333
334
|
}
|
|
334
335
|
const forceExpand =
|
|
335
|
-
applyMinExpanLevel && _level < tree.options.minExpandLevel
|
|
336
|
+
applyMinExpanLevel && _level < tree.options.minExpandLevel;
|
|
336
337
|
for (const child of <WbNodeData[]>nodeData) {
|
|
337
338
|
const subChildren = child.children;
|
|
339
|
+
// Remove children property from source data because it should not be
|
|
340
|
+
// passed to the constructor of WunderbaumNode:
|
|
338
341
|
delete child.children;
|
|
339
342
|
|
|
340
343
|
const n = new WunderbaumNode(tree, this, child);
|
|
344
|
+
|
|
345
|
+
// Set `children` property again, so it can be used in `reload()`
|
|
346
|
+
if (subChildren != null) {
|
|
347
|
+
child.children = subChildren;
|
|
348
|
+
}
|
|
341
349
|
if (forceExpand && !n.isUnloaded()) {
|
|
342
350
|
n.expanded = true;
|
|
343
351
|
}
|
|
@@ -804,7 +812,7 @@ export class WunderbaumNode {
|
|
|
804
812
|
}
|
|
805
813
|
return l;
|
|
806
814
|
}
|
|
807
|
-
/** Return a string representing the
|
|
815
|
+
/** Return a string representing the hierarchical node path, e.g. "a/b/c".
|
|
808
816
|
* @param includeSelf
|
|
809
817
|
* @param part property name or callback
|
|
810
818
|
* @param separator
|
|
@@ -814,10 +822,6 @@ export class WunderbaumNode {
|
|
|
814
822
|
part: keyof WunderbaumNode | NodeAnyCallback = "title",
|
|
815
823
|
separator: string = "/"
|
|
816
824
|
) {
|
|
817
|
-
// includeSelf = includeSelf !== false;
|
|
818
|
-
// part = part || "title";
|
|
819
|
-
// separator = separator || "/";
|
|
820
|
-
|
|
821
825
|
let val;
|
|
822
826
|
const path: string[] = [];
|
|
823
827
|
const isFunc = typeof part === "function";
|
|
@@ -834,7 +838,7 @@ export class WunderbaumNode {
|
|
|
834
838
|
return path.join(separator);
|
|
835
839
|
}
|
|
836
840
|
|
|
837
|
-
/** Return the
|
|
841
|
+
/** Return the preceding node (under the same parent) or null. */
|
|
838
842
|
getPrevSibling(): WunderbaumNode | null {
|
|
839
843
|
const ac = this.parent.children!;
|
|
840
844
|
const idx = ac.indexOf(this);
|
|
@@ -866,7 +870,7 @@ export class WunderbaumNode {
|
|
|
866
870
|
return this.classes ? this.classes.has(className) : false;
|
|
867
871
|
}
|
|
868
872
|
|
|
869
|
-
/** Return true if node
|
|
873
|
+
/** Return true if node is the currently focused node. @since 0.9.0 */
|
|
870
874
|
hasFocus(): boolean {
|
|
871
875
|
return this.tree.focusNode === this;
|
|
872
876
|
}
|
|
@@ -927,7 +931,7 @@ export class WunderbaumNode {
|
|
|
927
931
|
* an expand operation is currently possible.
|
|
928
932
|
*/
|
|
929
933
|
isExpandable(andCollapsed = false): boolean {
|
|
930
|
-
// `false` is never expandable (
|
|
934
|
+
// `false` is never expandable (unofficial)
|
|
931
935
|
if ((andCollapsed && this.expanded) || <any>this.children === false) {
|
|
932
936
|
return false;
|
|
933
937
|
}
|
|
@@ -1002,12 +1006,12 @@ export class WunderbaumNode {
|
|
|
1002
1006
|
return !this.selected && !!this._partsel;
|
|
1003
1007
|
}
|
|
1004
1008
|
|
|
1005
|
-
/** Return true if this node has DOM
|
|
1009
|
+
/** Return true if this node has DOM representation, i.e. is displayed in the viewport. */
|
|
1006
1010
|
isRadio(): boolean {
|
|
1007
1011
|
return !!this.parent.radiogroup || this.getOption("checkbox") === "radio";
|
|
1008
1012
|
}
|
|
1009
1013
|
|
|
1010
|
-
/** Return true if this node has DOM
|
|
1014
|
+
/** Return true if this node has DOM representation, i.e. is displayed in the viewport. */
|
|
1011
1015
|
isRendered(): boolean {
|
|
1012
1016
|
return !!this._rowElem;
|
|
1013
1017
|
}
|
|
@@ -1645,7 +1649,7 @@ export class WunderbaumNode {
|
|
|
1645
1649
|
protected _render_markup(opts: RenderOptions) {
|
|
1646
1650
|
const tree = this.tree;
|
|
1647
1651
|
const treeOptions = tree.options;
|
|
1648
|
-
const rowHeight = treeOptions.rowHeightPx
|
|
1652
|
+
const rowHeight = treeOptions.rowHeightPx;
|
|
1649
1653
|
const checkbox = this.getOption("checkbox");
|
|
1650
1654
|
const columns = tree.columns;
|
|
1651
1655
|
const level = this.getLevel();
|
|
@@ -1866,11 +1870,11 @@ export class WunderbaumNode {
|
|
|
1866
1870
|
const rowDiv = this._rowElem!;
|
|
1867
1871
|
|
|
1868
1872
|
// Row markup already exists
|
|
1869
|
-
const
|
|
1870
|
-
const
|
|
1873
|
+
const nodeSpan = rowDiv.querySelector("span.wb-node") as HTMLSpanElement;
|
|
1874
|
+
const expanderElem = nodeSpan.querySelector(
|
|
1871
1875
|
"i.wb-expander"
|
|
1872
1876
|
) as HTMLLIElement;
|
|
1873
|
-
const
|
|
1877
|
+
const checkboxElem = nodeSpan.querySelector(
|
|
1874
1878
|
"i.wb-checkbox"
|
|
1875
1879
|
) as HTMLLIElement;
|
|
1876
1880
|
|
|
@@ -1903,7 +1907,7 @@ export class WunderbaumNode {
|
|
|
1903
1907
|
rowDiv.classList.add(...typeInfo.classes);
|
|
1904
1908
|
}
|
|
1905
1909
|
|
|
1906
|
-
if (
|
|
1910
|
+
if (expanderElem) {
|
|
1907
1911
|
let image = null;
|
|
1908
1912
|
if (this._isLoading) {
|
|
1909
1913
|
image = iconMap.loading;
|
|
@@ -1918,14 +1922,17 @@ export class WunderbaumNode {
|
|
|
1918
1922
|
}
|
|
1919
1923
|
|
|
1920
1924
|
if (image == null) {
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1925
|
+
expanderElem.className = "wb-expander";
|
|
1926
|
+
expanderElem.classList.add("wb-indent");
|
|
1927
|
+
} else if (TEST_HTML.test(image)) {
|
|
1928
|
+
expanderElem.replaceWith(util.elemFromHtml(image));
|
|
1929
|
+
} else if (TEST_FILE_PATH.test(image)) {
|
|
1930
|
+
expanderElem.style.backgroundImage = `url('${image}')`;
|
|
1924
1931
|
} else {
|
|
1925
|
-
|
|
1932
|
+
expanderElem.className = "wb-expander " + image;
|
|
1926
1933
|
}
|
|
1927
1934
|
}
|
|
1928
|
-
if (
|
|
1935
|
+
if (checkboxElem) {
|
|
1929
1936
|
let cbclass = "wb-checkbox ";
|
|
1930
1937
|
if (this.isRadio()) {
|
|
1931
1938
|
cbclass += "wb-radio ";
|
|
@@ -1945,7 +1952,7 @@ export class WunderbaumNode {
|
|
|
1945
1952
|
cbclass += iconMap.checkUnchecked;
|
|
1946
1953
|
}
|
|
1947
1954
|
}
|
|
1948
|
-
|
|
1955
|
+
checkboxElem.className = cbclass;
|
|
1949
1956
|
}
|
|
1950
1957
|
// Fix active cell in cell-nav mode
|
|
1951
1958
|
if (!opts.isNew) {
|
|
@@ -1955,9 +1962,9 @@ export class WunderbaumNode {
|
|
|
1955
1962
|
colSpan.classList.remove("wb-error", "wb-invalid");
|
|
1956
1963
|
}
|
|
1957
1964
|
// Update icon (if not opts.isNew, which would rebuild markup anyway)
|
|
1958
|
-
const iconSpan =
|
|
1965
|
+
const iconSpan = nodeSpan.querySelector("i.wb-icon") as HTMLElement;
|
|
1959
1966
|
if (iconSpan) {
|
|
1960
|
-
this._createIcon(
|
|
1967
|
+
this._createIcon(nodeSpan, iconSpan, !expanderElem);
|
|
1961
1968
|
}
|
|
1962
1969
|
}
|
|
1963
1970
|
// Adjust column width
|
|
@@ -2217,6 +2224,7 @@ export class WunderbaumNode {
|
|
|
2217
2224
|
async setExpanded(flag: boolean = true, options?: SetExpandedOptions) {
|
|
2218
2225
|
const { force, scrollIntoView, immediate, resetLazy } = options ?? {};
|
|
2219
2226
|
const sendEvents = !options?.noEvents; // Default: send events
|
|
2227
|
+
|
|
2220
2228
|
if (
|
|
2221
2229
|
!flag &&
|
|
2222
2230
|
this.isExpanded() &&
|
|
@@ -2283,6 +2291,32 @@ export class WunderbaumNode {
|
|
|
2283
2291
|
setKey(key: string | null, refKey: string | null) {
|
|
2284
2292
|
throw new Error("Not yet implemented");
|
|
2285
2293
|
}
|
|
2294
|
+
// /**
|
|
2295
|
+
// * Calculate a *stable*, unique key for this node from its refKey (or title).
|
|
2296
|
+
// * We also add information from the parent, because a refKey may occur multiple
|
|
2297
|
+
// * times in a tree.
|
|
2298
|
+
// */
|
|
2299
|
+
// calcUniqueKey() {
|
|
2300
|
+
// // Assuming that the parent's key was calculated the same way, we implicitly
|
|
2301
|
+
// // involve the whole refKey-path:
|
|
2302
|
+
// const s = this.key + (this.refKey || this.title);
|
|
2303
|
+
// // 32-bit has a high probability of collisions, so we pump up to 64-bit
|
|
2304
|
+
// // https://security.stackexchange.com/q/209882/207588
|
|
2305
|
+
// const h1 = util.murmurHash3(s, true);
|
|
2306
|
+
// return "id_" + h1 + util.murmurHash3(h1 + s, true);
|
|
2307
|
+
// // const l = [];
|
|
2308
|
+
// // // eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
2309
|
+
// // let node: WunderbaumNode = this;
|
|
2310
|
+
// // while (node.parent) {
|
|
2311
|
+
// // l.unshift(node.refKey || node.key);
|
|
2312
|
+
// // node = node.parent;
|
|
2313
|
+
// // }
|
|
2314
|
+
// // const path = l.join("/");
|
|
2315
|
+
// // 32-bit has a high probability of collisions, so we pump up to 64-bit
|
|
2316
|
+
// // https://security.stackexchange.com/q/209882/207588
|
|
2317
|
+
// // const h1 = util.murmurHash3(path, true);
|
|
2318
|
+
// // return "id_" + h1 + util.murmurHash3(h1 + path, true);
|
|
2319
|
+
// }
|
|
2286
2320
|
|
|
2287
2321
|
/**
|
|
2288
2322
|
* Trigger a repaint, typically after a status or data change.
|
|
@@ -2320,6 +2354,24 @@ export class WunderbaumNode {
|
|
|
2320
2354
|
return nodeList;
|
|
2321
2355
|
}
|
|
2322
2356
|
|
|
2357
|
+
/**
|
|
2358
|
+
* Return an array of refKey values.
|
|
2359
|
+
*
|
|
2360
|
+
* RefKeys are unique identifiers for a node data, and are used to identify
|
|
2361
|
+
* clones.
|
|
2362
|
+
* If more than one node has the same refKey, it is only returned once.
|
|
2363
|
+
* @param selected if true, only return refKeys of selected nodes.
|
|
2364
|
+
*/
|
|
2365
|
+
getRefKeys(selected = false): string[] {
|
|
2366
|
+
const refKeys = new Set<string>();
|
|
2367
|
+
this.visit((node) => {
|
|
2368
|
+
if (node.refKey != null && (!selected || node.selected)) {
|
|
2369
|
+
refKeys.add(node.refKey);
|
|
2370
|
+
}
|
|
2371
|
+
});
|
|
2372
|
+
return Array.from(refKeys);
|
|
2373
|
+
}
|
|
2374
|
+
|
|
2323
2375
|
/** Toggle the check/uncheck state. */
|
|
2324
2376
|
toggleSelected(options?: SetSelectedOptions): TristateType {
|
|
2325
2377
|
let flag = this.isSelected();
|
|
@@ -2521,9 +2573,11 @@ export class WunderbaumNode {
|
|
|
2521
2573
|
this.selected = flag;
|
|
2522
2574
|
if (selectMode === "hier") {
|
|
2523
2575
|
this.fixSelection3AfterClick();
|
|
2524
|
-
} else if (selectMode === "single") {
|
|
2576
|
+
} else if (selectMode === "single" && flag) {
|
|
2525
2577
|
tree.visit((n) => {
|
|
2526
|
-
n
|
|
2578
|
+
if (n !== this) {
|
|
2579
|
+
n.selected = false;
|
|
2580
|
+
}
|
|
2527
2581
|
});
|
|
2528
2582
|
}
|
|
2529
2583
|
}
|
|
@@ -2592,7 +2646,7 @@ export class WunderbaumNode {
|
|
|
2592
2646
|
_setStatusNode({
|
|
2593
2647
|
statusNodeType: status,
|
|
2594
2648
|
title:
|
|
2595
|
-
tree.options.strings
|
|
2649
|
+
tree.options.strings.loading +
|
|
2596
2650
|
(message ? " (" + message + ")" : ""),
|
|
2597
2651
|
checkbox: false,
|
|
2598
2652
|
colspan: true,
|
|
@@ -2605,7 +2659,7 @@ export class WunderbaumNode {
|
|
|
2605
2659
|
_setStatusNode({
|
|
2606
2660
|
statusNodeType: status,
|
|
2607
2661
|
title:
|
|
2608
|
-
tree.options.strings
|
|
2662
|
+
tree.options.strings.loadError +
|
|
2609
2663
|
(message ? " (" + message + ")" : ""),
|
|
2610
2664
|
checkbox: false,
|
|
2611
2665
|
colspan: true,
|
|
@@ -2618,7 +2672,7 @@ export class WunderbaumNode {
|
|
|
2618
2672
|
case "noData":
|
|
2619
2673
|
_setStatusNode({
|
|
2620
2674
|
statusNodeType: status,
|
|
2621
|
-
title: message || tree.options.strings
|
|
2675
|
+
title: message || tree.options.strings.noData,
|
|
2622
2676
|
checkbox: false,
|
|
2623
2677
|
colspan: true,
|
|
2624
2678
|
tooltip: details,
|
|
@@ -2646,35 +2700,19 @@ export class WunderbaumNode {
|
|
|
2646
2700
|
this.update();
|
|
2647
2701
|
}
|
|
2648
2702
|
|
|
2649
|
-
_sortChildren(cmp: SortCallback, deep: boolean): void {
|
|
2650
|
-
const cl = this.children;
|
|
2651
|
-
|
|
2652
|
-
if (!cl) {
|
|
2653
|
-
return;
|
|
2654
|
-
}
|
|
2655
|
-
cl.sort(cmp);
|
|
2656
|
-
if (deep) {
|
|
2657
|
-
for (let i = 0, l = cl.length; i < l; i++) {
|
|
2658
|
-
if (cl[i].children) {
|
|
2659
|
-
cl[i]._sortChildren(cmp, deep);
|
|
2660
|
-
}
|
|
2661
|
-
}
|
|
2662
|
-
}
|
|
2663
|
-
}
|
|
2664
|
-
|
|
2665
2703
|
/**
|
|
2666
2704
|
* Sort child list by title or custom criteria.
|
|
2667
2705
|
* @param {function} cmp custom compare function(a, b) that returns -1, 0, or 1
|
|
2668
2706
|
* (defaults to sorting by title).
|
|
2669
2707
|
* @param {boolean} deep pass true to sort all descendant nodes recursively
|
|
2708
|
+
* @deprecated use {@link sort}
|
|
2670
2709
|
*/
|
|
2671
2710
|
sortChildren(
|
|
2672
2711
|
cmp: SortCallback | null = nodeTitleSorter,
|
|
2673
2712
|
deep: boolean = false
|
|
2674
2713
|
): void {
|
|
2675
|
-
this.
|
|
2676
|
-
this.
|
|
2677
|
-
// this.triggerModify("sort"); // TODO
|
|
2714
|
+
this.tree.logDeprecate("node.sortChildren()", { since: "0.14.0" });
|
|
2715
|
+
return this.sort({ cmp: cmp ? cmp : undefined, deep: deep });
|
|
2678
2716
|
}
|
|
2679
2717
|
|
|
2680
2718
|
/**
|
|
@@ -2699,82 +2737,159 @@ export class WunderbaumNode {
|
|
|
2699
2737
|
/**
|
|
2700
2738
|
* Convenience method to implement column sorting.
|
|
2701
2739
|
* @since 0.11.0
|
|
2740
|
+
* @deprecated use {@link sort}
|
|
2702
2741
|
*/
|
|
2703
2742
|
sortByProperty(options: SortByPropertyOptions) {
|
|
2704
|
-
|
|
2705
|
-
|
|
2743
|
+
this.tree.logDeprecate("node.sortByProperty()", { since: "0.14.0" });
|
|
2744
|
+
return this.sort(options);
|
|
2745
|
+
}
|
|
2746
|
+
|
|
2747
|
+
/**
|
|
2748
|
+
* Implement column sorting.
|
|
2749
|
+
* @since 0.14.0
|
|
2750
|
+
*/
|
|
2751
|
+
sort(options: SortOptions) {
|
|
2752
|
+
const tree = this.tree;
|
|
2753
|
+
let {
|
|
2754
|
+
propName = undefined,
|
|
2706
2755
|
deep = true,
|
|
2707
|
-
|
|
2756
|
+
key = undefined,
|
|
2757
|
+
order = undefined,
|
|
2758
|
+
caseInsensitive = true,
|
|
2759
|
+
cmp = undefined,
|
|
2760
|
+
// Support click on column sort header:
|
|
2708
2761
|
updateColInfo = false,
|
|
2762
|
+
nativeOrderPropName = "_nativeIndex",
|
|
2763
|
+
colId = undefined,
|
|
2709
2764
|
} = options;
|
|
2710
2765
|
|
|
2711
|
-
|
|
2712
|
-
|
|
2766
|
+
propName ??= colId;
|
|
2767
|
+
if (propName === "*") {
|
|
2768
|
+
propName = "title";
|
|
2769
|
+
}
|
|
2770
|
+
|
|
2771
|
+
const isFolder =
|
|
2772
|
+
tree.options.sortFoldersFirst === true
|
|
2773
|
+
? (node: WunderbaumNode) =>
|
|
2774
|
+
node.hasChildren() !== false || node.type === NODE_TYPE_FOLDER
|
|
2775
|
+
: tree.options.sortFoldersFirst;
|
|
2713
2776
|
|
|
2714
2777
|
if (updateColInfo) {
|
|
2715
|
-
colDef = this.tree["_columnsById"][options.colId!];
|
|
2778
|
+
const colDef = this.tree["_columnsById"][options.colId!];
|
|
2716
2779
|
util.assert(colDef, `Invalid colId specified: ${options.colId}`);
|
|
2717
|
-
order
|
|
2718
|
-
options.order ??
|
|
2719
|
-
util.rotate(colDef!.sortOrder, ["asc", "desc", undefined]);
|
|
2780
|
+
order ??= util.rotate(colDef.sortOrder, ["asc", "desc", undefined]);
|
|
2720
2781
|
|
|
2721
2782
|
for (const col of this.tree.columns) {
|
|
2722
2783
|
col.sortOrder = col === colDef ? order : undefined;
|
|
2723
2784
|
}
|
|
2724
|
-
|
|
2785
|
+
if (order === undefined) {
|
|
2786
|
+
propName = nativeOrderPropName;
|
|
2787
|
+
order = "asc";
|
|
2788
|
+
}
|
|
2725
2789
|
this.tree.update(ChangeType.colStructure);
|
|
2726
2790
|
} else {
|
|
2727
|
-
|
|
2791
|
+
propName ??= "title";
|
|
2792
|
+
order ??= "asc";
|
|
2728
2793
|
}
|
|
2729
2794
|
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
propName = "title";
|
|
2733
|
-
}
|
|
2734
|
-
if (order == null) {
|
|
2735
|
-
propName = nativeOrderPropName;
|
|
2736
|
-
order = "asc";
|
|
2737
|
-
}
|
|
2738
|
-
this.logDebug(`sortByProperty(), propName=${propName}, ${order}`, options);
|
|
2739
|
-
util.assert(propName, "No property name specified");
|
|
2795
|
+
this.logDebug(`sort(), propName=${propName}, ${order}`, options);
|
|
2796
|
+
util.assert(propName || cmp || key, "No `propName` or `key` specified");
|
|
2740
2797
|
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2798
|
+
// Define a key callback from the parameters we have
|
|
2799
|
+
if (key == null && cmp == null) {
|
|
2800
|
+
key = (node) => {
|
|
2801
|
+
let val;
|
|
2802
|
+
if (NODE_DICT_PROPS.has(<string>propName)) {
|
|
2803
|
+
val = node[propName as keyof WunderbaumNode];
|
|
2804
|
+
} else {
|
|
2805
|
+
val = node.data[propName!];
|
|
2806
|
+
}
|
|
2807
|
+
if (caseInsensitive && typeof val === "string") {
|
|
2808
|
+
val = val.toLowerCase();
|
|
2809
|
+
}
|
|
2810
|
+
return val;
|
|
2811
|
+
};
|
|
2812
|
+
}
|
|
2813
|
+
// Define a compare callback that uses the key callback
|
|
2814
|
+
if (cmp) {
|
|
2815
|
+
util.assert(!key, "`key` and `cmp` are mutually exclusive");
|
|
2816
|
+
tree.logDeprecate("SortOptions.cmp", {
|
|
2817
|
+
since: "0.14.0",
|
|
2818
|
+
hint: "use the `key` callback instead",
|
|
2819
|
+
});
|
|
2820
|
+
} else {
|
|
2821
|
+
if (options.propName || options.caseInsensitive) {
|
|
2822
|
+
tree.logWarn("sort(): ignoring propName, caseInsensitive");
|
|
2762
2823
|
}
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2824
|
+
|
|
2825
|
+
cmp = (a, b) => {
|
|
2826
|
+
if (isFolder) {
|
|
2827
|
+
const isFolderA = isFolder(a);
|
|
2828
|
+
if (isFolderA !== isFolder(b)) {
|
|
2829
|
+
return isFolderA ? -1 : 1;
|
|
2830
|
+
}
|
|
2831
|
+
}
|
|
2832
|
+
let x = key!(a);
|
|
2833
|
+
let y = key!(b);
|
|
2834
|
+
// Assure we have reasonable comparisons with null values:
|
|
2835
|
+
if (x == null) {
|
|
2836
|
+
x = typeof y === "string" ? "" : 0;
|
|
2837
|
+
} else if (typeof x === "boolean") {
|
|
2838
|
+
x = x ? 1 : 0;
|
|
2839
|
+
}
|
|
2840
|
+
if (y == null) {
|
|
2841
|
+
y = typeof x === "string" ? "" : 0;
|
|
2842
|
+
} else if (typeof y === "boolean") {
|
|
2843
|
+
y = y ? 1 : 0;
|
|
2766
2844
|
}
|
|
2767
|
-
|
|
2768
|
-
|
|
2845
|
+
|
|
2846
|
+
if (order === "desc") {
|
|
2847
|
+
return x === y ? 0 : x > y ? -1 : 1;
|
|
2769
2848
|
}
|
|
2849
|
+
return x === y ? 0 : x > y ? 1 : -1;
|
|
2850
|
+
};
|
|
2851
|
+
}
|
|
2852
|
+
|
|
2853
|
+
function _sortChildren(cl: WunderbaumNode[]): void {
|
|
2854
|
+
if (!cl) {
|
|
2855
|
+
return;
|
|
2770
2856
|
}
|
|
2771
|
-
|
|
2772
|
-
|
|
2857
|
+
cl.sort(cmp);
|
|
2858
|
+
if (deep) {
|
|
2859
|
+
for (let i = 0, l = cl.length; i < l; i++) {
|
|
2860
|
+
if (cl[i].children) {
|
|
2861
|
+
_sortChildren(cl[i].children!);
|
|
2862
|
+
}
|
|
2863
|
+
}
|
|
2773
2864
|
}
|
|
2774
|
-
|
|
2775
|
-
|
|
2865
|
+
}
|
|
2866
|
+
if (this.children) {
|
|
2867
|
+
_sortChildren(this.children);
|
|
2868
|
+
}
|
|
2869
|
+
this.tree.update(ChangeType.structure);
|
|
2870
|
+
// this.triggerModify("sort"); // TODO
|
|
2871
|
+
}
|
|
2776
2872
|
|
|
2777
|
-
|
|
2873
|
+
/**
|
|
2874
|
+
* Re-apply current sorting if any (use after lazy load).
|
|
2875
|
+
* Example:
|
|
2876
|
+
* ```js
|
|
2877
|
+
* load: function (e) {
|
|
2878
|
+
* // Whe loading a lazy branch, apply current sort order if any
|
|
2879
|
+
* e.node.resort();
|
|
2880
|
+
* },
|
|
2881
|
+
* ```
|
|
2882
|
+
* @since 0.14.0
|
|
2883
|
+
*/
|
|
2884
|
+
resort(options: SortOptions = {}): void {
|
|
2885
|
+
for (const colDef of this.tree.columns) {
|
|
2886
|
+
if (colDef.sortOrder) {
|
|
2887
|
+
options.colId = colDef.id;
|
|
2888
|
+
options.order = colDef.sortOrder;
|
|
2889
|
+
this.sort(options);
|
|
2890
|
+
break;
|
|
2891
|
+
}
|
|
2892
|
+
}
|
|
2778
2893
|
}
|
|
2779
2894
|
|
|
2780
2895
|
/**
|
|
@@ -2821,7 +2936,8 @@ export class WunderbaumNode {
|
|
|
2821
2936
|
* @param {function} callback the callback function.
|
|
2822
2937
|
* Return false to stop iteration, return "skip" to skip this node and
|
|
2823
2938
|
* its children only.
|
|
2824
|
-
* @see
|
|
2939
|
+
* @see `wb_node.WunderbaumNode.IterableIterator<WunderbaumNode>`
|
|
2940
|
+
* @see {@link Wunderbaum.visit}.
|
|
2825
2941
|
*/
|
|
2826
2942
|
visit(
|
|
2827
2943
|
callback: NodeVisitCallback,
|