jqtree 1.8.4 → 1.8.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,25 +1,25 @@
1
- import __version__ from "./version";
1
+ import DataLoader, { HandleFinishedLoading } from "./dataLoader";
2
2
  import { DragAndDropHandler } from "./dragAndDropHandler";
3
3
  import ElementsRenderer from "./elementsRenderer";
4
- import DataLoader, { HandleFinishedLoading } from "./dataLoader";
4
+ import { OnFinishOpenNode } from "./jqtreeMethodTypes";
5
+ import { JQTreeOptions } from "./jqtreeOptions";
5
6
  import KeyHandler from "./keyHandler";
6
7
  import MouseHandler from "./mouseHandler";
7
8
  import { PositionInfo } from "./mouseUtils";
9
+ import { Node } from "./node";
10
+ import NodeElement from "./nodeElement";
11
+ import FolderElement from "./nodeElement/folderElement";
12
+ import { getPosition } from "./position";
8
13
  import SaveStateHandler, { SavedState } from "./saveStateHandler";
9
14
  import ScrollHandler from "./scrollHandler";
10
15
  import SelectNodeHandler from "./selectNodeHandler";
11
16
  import SimpleWidget from "./simple.widget";
12
17
  import { getOffsetTop, isFunction } from "./util";
13
- import { Node } from "./node";
14
- import { getPosition } from "./position";
15
- import NodeElement from "./nodeElement";
16
- import FolderElement from "./nodeElement/folderElement";
17
- import { OnFinishOpenNode } from "./jqtreeMethodTypes";
18
- import { JQTreeOptions } from "./jqtreeOptions";
18
+ import __version__ from "./version";
19
19
 
20
20
  interface SelectNodeOptions {
21
- mustToggle?: boolean;
22
21
  mustSetFocus?: boolean;
22
+ mustToggle?: boolean;
23
23
  }
24
24
 
25
25
  const NODE_PARAM_IS_EMPTY = "Node parameter is empty";
@@ -65,323 +65,488 @@ export class JqTreeWidget extends SimpleWidget<JQTreeOptions> {
65
65
  useContextMenu: true,
66
66
  };
67
67
 
68
- private element: JQuery;
69
- private isInitialized: boolean;
70
- private tree: Node;
71
-
72
68
  private dataLoader: DataLoader;
73
69
  private dndHandler: DragAndDropHandler;
70
+ private element: JQuery;
71
+
72
+ private isInitialized: boolean;
74
73
  private keyHandler: KeyHandler;
75
74
  private mouseHandler: MouseHandler;
76
75
  private renderer: ElementsRenderer;
77
76
  private saveStateHandler: SaveStateHandler;
78
77
  private scrollHandler: ScrollHandler;
79
78
  private selectNodeHandler: SelectNodeHandler;
79
+ private tree: Node;
80
80
 
81
- public toggle(node: Node, slideParam: null | boolean = null): JQuery {
82
- if (!node) {
83
- throw Error(NODE_PARAM_IS_EMPTY);
84
- }
85
-
86
- const slide = slideParam ?? this.options.slide;
87
-
88
- if (node.is_open) {
89
- this.closeNode(node, slide);
90
- } else {
91
- this.openNode(node, slide);
92
- }
81
+ private connectHandlers() {
82
+ const {
83
+ autoEscape,
84
+ buttonLeft,
85
+ closedIcon,
86
+ dataFilter,
87
+ dragAndDrop,
88
+ keyboardSupport,
89
+ onCanMove,
90
+ onCanMoveTo,
91
+ onCreateLi,
92
+ onDragMove,
93
+ onDragStop,
94
+ onGetStateFromStorage,
95
+ onIsMoveHandle,
96
+ onLoadFailed,
97
+ onLoading,
98
+ onSetStateFromStorage,
99
+ openedIcon,
100
+ openFolderDelay,
101
+ rtl,
102
+ saveState,
103
+ showEmptyFolder,
104
+ slide,
105
+ tabIndex,
106
+ } = this.options;
93
107
 
94
- return this.element;
95
- }
108
+ const closeNode = this.closeNode.bind(this);
109
+ const getNodeElement = this.getNodeElement.bind(this);
110
+ const getNodeElementForNode = this.getNodeElementForNode.bind(this);
111
+ const getNodeById = this.getNodeById.bind(this);
112
+ const getSelectedNode = this.getSelectedNode.bind(this);
113
+ const getTree = this.getTree.bind(this);
114
+ const isFocusOnTree = this.isFocusOnTree.bind(this);
115
+ const loadData = this.loadData.bind(this);
116
+ const openNode = this.openNodeInternal.bind(this);
117
+ const refreshElements = this.refreshElements.bind(this);
118
+ const refreshHitAreas = this.refreshHitAreas.bind(this);
119
+ const selectNode = this.selectNode.bind(this);
120
+ const $treeElement = this.element;
121
+ const treeElement = this.element.get(0) as HTMLElement;
122
+ const triggerEvent = this.triggerEvent.bind(this);
96
123
 
97
- public getTree(): Node {
98
- return this.tree;
99
- }
124
+ const selectNodeHandler = new SelectNodeHandler({
125
+ getNodeById,
126
+ });
100
127
 
101
- public selectNode(
102
- node: Node | null,
103
- optionsParam?: SelectNodeOptions,
104
- ): JQuery {
105
- this.doSelectNode(node, optionsParam);
106
- return this.element;
107
- }
128
+ const addToSelection =
129
+ selectNodeHandler.addToSelection.bind(selectNodeHandler);
130
+ const getSelectedNodes =
131
+ selectNodeHandler.getSelectedNodes.bind(selectNodeHandler);
132
+ const isNodeSelected =
133
+ selectNodeHandler.isNodeSelected.bind(selectNodeHandler);
134
+ const removeFromSelection =
135
+ selectNodeHandler.removeFromSelection.bind(selectNodeHandler);
136
+ const getMouseDelay = () => this.options.startDndDelay ?? 0;
108
137
 
109
- public getSelectedNode(): Node | false {
110
- return this.selectNodeHandler.getSelectedNode();
111
- }
138
+ const dataLoader = new DataLoader({
139
+ dataFilter,
140
+ loadData,
141
+ onLoadFailed,
142
+ onLoading,
143
+ treeElement,
144
+ triggerEvent,
145
+ });
112
146
 
113
- public toJson(): string {
114
- return JSON.stringify(this.tree.getData());
115
- }
147
+ const saveStateHandler = new SaveStateHandler({
148
+ addToSelection,
149
+ getNodeById,
150
+ getSelectedNodes,
151
+ getTree,
152
+ onGetStateFromStorage,
153
+ onSetStateFromStorage,
154
+ openNode,
155
+ refreshElements,
156
+ removeFromSelection,
157
+ saveState,
158
+ });
116
159
 
117
- public loadData(data: NodeData[], parentNode: Node | null): JQuery {
118
- this.doLoadData(data, parentNode);
119
- return this.element;
120
- }
160
+ const scrollHandler = new ScrollHandler({
161
+ refreshHitAreas,
162
+ treeElement,
163
+ });
121
164
 
122
- /*
123
- signatures:
124
- - loadDataFromUrl(url, parent_node=null, on_finished=null)
125
- loadDataFromUrl('/my_data');
126
- loadDataFromUrl('/my_data', node1);
127
- loadDataFromUrl('/my_data', node1, function() { console.log('finished'); });
128
- loadDataFromUrl('/my_data', null, function() { console.log('finished'); });
165
+ const getScrollLeft = scrollHandler.getScrollLeft.bind(scrollHandler);
129
166
 
130
- - loadDataFromUrl(parent_node=null, on_finished=null)
131
- loadDataFromUrl();
132
- loadDataFromUrl(node1);
133
- loadDataFromUrl(null, function() { console.log('finished'); });
134
- loadDataFromUrl(node1, function() { console.log('finished'); });
135
- */
136
- public loadDataFromUrl(
137
- param1: string | null | Node,
138
- param2?: Node | null | HandleFinishedLoading,
139
- param3?: HandleFinishedLoading,
140
- ): JQuery {
141
- if (typeof param1 === "string") {
142
- // first parameter is url
143
- this.doLoadDataFromUrl(
144
- param1,
145
- param2 as Node | null,
146
- param3 ?? null,
147
- );
148
- } else {
149
- // first parameter is not url
150
- this.doLoadDataFromUrl(
151
- null,
152
- param1,
153
- param2 as HandleFinishedLoading | null,
154
- );
155
- }
167
+ const dndHandler = new DragAndDropHandler({
168
+ autoEscape,
169
+ getNodeElement,
170
+ getNodeElementForNode,
171
+ getScrollLeft,
172
+ getTree,
173
+ onCanMove,
174
+ onCanMoveTo,
175
+ onDragMove,
176
+ onDragStop,
177
+ onIsMoveHandle,
178
+ openFolderDelay,
179
+ openNode,
180
+ refreshElements,
181
+ slide,
182
+ treeElement,
183
+ triggerEvent,
184
+ });
156
185
 
157
- return this.element;
158
- }
186
+ const keyHandler = new KeyHandler({
187
+ closeNode,
188
+ getSelectedNode,
189
+ isFocusOnTree,
190
+ keyboardSupport,
191
+ openNode,
192
+ selectNode,
193
+ });
159
194
 
160
- public reload(onFinished: HandleFinishedLoading | null): JQuery {
161
- this.doLoadDataFromUrl(null, null, onFinished);
162
- return this.element;
163
- }
195
+ const renderer = new ElementsRenderer({
196
+ $element: $treeElement,
197
+ autoEscape,
198
+ buttonLeft,
199
+ closedIcon,
200
+ dragAndDrop,
201
+ getTree,
202
+ isNodeSelected,
203
+ onCreateLi,
204
+ openedIcon,
205
+ rtl,
206
+ showEmptyFolder,
207
+ tabIndex,
208
+ });
164
209
 
165
- public refresh(): JQuery {
166
- this.refreshElements(null);
167
- return this.element;
168
- }
210
+ const getNode = this.getNode.bind(this);
211
+ const onMouseCapture = this.mouseCapture.bind(this);
212
+ const onMouseDrag = this.mouseDrag.bind(this);
213
+ const onMouseStart = this.mouseStart.bind(this);
214
+ const onMouseStop = this.mouseStop.bind(this);
169
215
 
170
- public getNodeById(nodeId: NodeId): Node | null {
171
- return this.tree.getNodeById(nodeId);
172
- }
216
+ const mouseHandler = new MouseHandler({
217
+ element: treeElement,
218
+ getMouseDelay,
219
+ getNode,
220
+ onClickButton: this.toggle.bind(this),
221
+ onClickTitle: this.doSelectNode.bind(this),
222
+ onMouseCapture,
223
+ onMouseDrag,
224
+ onMouseStart,
225
+ onMouseStop,
226
+ triggerEvent,
227
+ useContextMenu: this.options.useContextMenu,
228
+ });
173
229
 
174
- public getNodeByName(name: string): Node | null {
175
- return this.tree.getNodeByName(name);
230
+ this.dataLoader = dataLoader;
231
+ this.dndHandler = dndHandler;
232
+ this.keyHandler = keyHandler;
233
+ this.mouseHandler = mouseHandler;
234
+ this.renderer = renderer;
235
+ this.saveStateHandler = saveStateHandler;
236
+ this.scrollHandler = scrollHandler;
237
+ this.selectNodeHandler = selectNodeHandler;
176
238
  }
177
239
 
178
- public getNodeByNameMustExist(name: string): Node {
179
- return this.tree.getNodeByNameMustExist(name);
180
- }
240
+ private containsElement(element: HTMLElement): boolean {
241
+ const node = this.getNode(element);
181
242
 
182
- public getNodesByProperty(key: string, value: unknown): Node[] {
183
- return this.tree.getNodesByProperty(key, value);
243
+ return node != null && node.tree === this.tree;
184
244
  }
185
245
 
186
- public getNodeByHtmlElement(
187
- inputElement: HTMLElement | JQuery<HTMLElement>,
188
- ): Node | null {
189
- const element =
190
- inputElement instanceof HTMLElement
191
- ? inputElement
192
- : inputElement[0];
193
-
194
- if (!element) {
195
- return null;
196
- }
246
+ private createFolderElement(node: Node) {
247
+ const closedIconElement = this.renderer.closedIconElement;
248
+ const getScrollLeft = this.scrollHandler.getScrollLeft.bind(
249
+ this.scrollHandler,
250
+ );
251
+ const openedIconElement = this.renderer.openedIconElement;
252
+ const tabIndex = this.options.tabIndex;
253
+ const $treeElement = this.element;
254
+ const triggerEvent = this.triggerEvent.bind(this);
197
255
 
198
- return this.getNode(element);
256
+ return new FolderElement({
257
+ $treeElement,
258
+ closedIconElement,
259
+ getScrollLeft,
260
+ node,
261
+ openedIconElement,
262
+ tabIndex,
263
+ triggerEvent,
264
+ });
199
265
  }
200
266
 
201
- public getNodeByCallback(callback: (node: Node) => boolean): Node | null {
202
- return this.tree.getNodeByCallback(callback);
267
+ private createNodeElement(node: Node) {
268
+ const getScrollLeft = this.scrollHandler.getScrollLeft.bind(
269
+ this.scrollHandler,
270
+ );
271
+ const tabIndex = this.options.tabIndex;
272
+ const $treeElement = this.element;
273
+
274
+ return new NodeElement({
275
+ $treeElement,
276
+ getScrollLeft,
277
+ node,
278
+ tabIndex,
279
+ });
203
280
  }
204
281
 
205
- public openNode(
206
- node: Node,
207
- param1?: boolean | OnFinishOpenNode,
208
- param2?: OnFinishOpenNode,
209
- ): JQuery {
210
- if (!node) {
211
- throw Error(NODE_PARAM_IS_EMPTY);
282
+ private deselectCurrentNode(): void {
283
+ const node = this.getSelectedNode();
284
+ if (node) {
285
+ this.removeFromSelection(node);
212
286
  }
287
+ }
213
288
 
214
- const parseParams = (): [boolean, OnFinishOpenNode | undefined] => {
215
- let onFinished: OnFinishOpenNode | null;
216
- let slide: boolean | null;
289
+ private deselectNodes(parentNode: Node): void {
290
+ const selectedNodesUnderParent =
291
+ this.selectNodeHandler.getSelectedNodesUnder(parentNode);
292
+ for (const n of selectedNodesUnderParent) {
293
+ this.selectNodeHandler.removeFromSelection(n);
294
+ }
295
+ }
217
296
 
218
- if (isFunction(param1)) {
219
- onFinished = param1 as OnFinishOpenNode;
220
- slide = null;
297
+ private doLoadData(data: NodeData[] | null, parentNode: Node | null): void {
298
+ if (data) {
299
+ if (parentNode) {
300
+ this.deselectNodes(parentNode);
301
+ this.loadSubtree(data, parentNode);
221
302
  } else {
222
- slide = param1 as boolean;
223
- onFinished = param2 as OnFinishOpenNode;
303
+ this.initTree(data);
224
304
  }
225
305
 
226
- if (slide == null) {
227
- slide = this.options.slide ?? false;
306
+ if (this.isDragging()) {
307
+ this.dndHandler.refresh();
228
308
  }
309
+ }
229
310
 
230
- return [slide, onFinished];
231
- };
311
+ this.triggerEvent("tree.load_data", {
312
+ parent_node: parentNode,
313
+ tree_data: data,
314
+ });
315
+ }
232
316
 
233
- const [slide, onFinished] = parseParams();
317
+ private doLoadDataFromUrl(
318
+ urlInfoParam: JQuery.AjaxSettings | null | string,
319
+ parentNode: Node | null,
320
+ onFinished: HandleFinishedLoading | null,
321
+ ): void {
322
+ const urlInfo = urlInfoParam ?? this.getDataUrlInfo(parentNode);
234
323
 
235
- this.openNodeInternal(node, slide, onFinished);
236
- return this.element;
324
+ this.dataLoader.loadFromUrl(urlInfo, parentNode, onFinished);
237
325
  }
238
326
 
239
- public closeNode(node: Node, slideParam?: null | boolean): JQuery {
327
+ private doSelectNode(
328
+ node: Node | null,
329
+ optionsParam?: SelectNodeOptions,
330
+ ): void {
331
+ const saveState = (): void => {
332
+ if (this.options.saveState) {
333
+ this.saveStateHandler.saveState();
334
+ }
335
+ };
336
+
240
337
  if (!node) {
241
- throw Error(NODE_PARAM_IS_EMPTY);
338
+ // Called with empty node -> deselect current node
339
+ this.deselectCurrentNode();
340
+ saveState();
341
+ return;
242
342
  }
343
+ const defaultOptions = { mustSetFocus: true, mustToggle: true };
344
+ const selectOptions = { ...defaultOptions, ...(optionsParam ?? {}) };
243
345
 
244
- const slide = slideParam ?? this.options.slide;
245
-
246
- if (node.isFolder() || node.isEmptyFolder) {
247
- this.createFolderElement(node).close(
248
- slide,
249
- this.options.animationSpeed,
250
- );
346
+ const canSelect = (): boolean => {
347
+ if (this.options.onCanSelectNode) {
348
+ return (
349
+ this.options.selectable &&
350
+ this.options.onCanSelectNode(node)
351
+ );
352
+ } else {
353
+ return this.options.selectable;
354
+ }
355
+ };
251
356
 
252
- this.saveState();
357
+ if (!canSelect()) {
358
+ return;
253
359
  }
254
360
 
255
- return this.element;
256
- }
361
+ if (this.selectNodeHandler.isNodeSelected(node)) {
362
+ if (selectOptions.mustToggle) {
363
+ this.deselectCurrentNode();
364
+ this.triggerEvent("tree.select", {
365
+ node: null,
366
+ previous_node: node,
367
+ });
368
+ }
369
+ } else {
370
+ const deselectedNode = this.getSelectedNode() || null;
371
+ this.deselectCurrentNode();
372
+ this.addToSelection(node, selectOptions.mustSetFocus);
257
373
 
258
- public isDragging(): boolean {
259
- return this.dndHandler.isDragging;
374
+ this.triggerEvent("tree.select", {
375
+ deselected_node: deselectedNode,
376
+ node,
377
+ });
378
+ this.openParents(node);
379
+ }
380
+
381
+ saveState();
260
382
  }
261
383
 
262
- public refreshHitAreas(): JQuery {
263
- this.dndHandler.refresh();
264
- return this.element;
384
+ private getAutoOpenMaxLevel(): number {
385
+ if (this.options.autoOpen === true) {
386
+ return -1;
387
+ } else if (typeof this.options.autoOpen === "number") {
388
+ return this.options.autoOpen;
389
+ } else if (typeof this.options.autoOpen === "string") {
390
+ return parseInt(this.options.autoOpen, 10);
391
+ } else {
392
+ return 0;
393
+ }
265
394
  }
266
395
 
267
- public addNodeAfter(
268
- newNodeInfo: NodeData,
269
- existingNode: Node,
270
- ): Node | null {
271
- const newNode = existingNode.addAfter(newNodeInfo);
396
+ private getDataUrlInfo(node: Node | null): JQuery.AjaxSettings | null {
397
+ const dataUrl =
398
+ this.options.dataUrl ?? (this.element.data("url") as null | string);
272
399
 
273
- if (newNode) {
274
- this.refreshElements(existingNode.parent);
275
- }
400
+ const getUrlFromString = (url: string): JQuery.AjaxSettings => {
401
+ const urlInfo: JQuery.AjaxSettings = { url };
276
402
 
277
- return newNode;
278
- }
403
+ setUrlInfoData(urlInfo);
279
404
 
280
- public addNodeBefore(
281
- newNodeInfo: NodeData,
282
- existingNode: Node,
283
- ): Node | null {
284
- if (!existingNode) {
285
- throw Error(PARAM_IS_EMPTY + "existingNode");
286
- }
405
+ return urlInfo;
406
+ };
287
407
 
288
- const newNode = existingNode.addBefore(newNodeInfo);
408
+ const setUrlInfoData = (urlInfo: JQuery.AjaxSettings): void => {
409
+ if (node?.id) {
410
+ // Load on demand of a subtree; add node parameter
411
+ const data = { node: node.id };
412
+ urlInfo.data = data;
413
+ } else {
414
+ // Add selected_node parameter
415
+ const selectedNodeId = this.getNodeIdToBeSelected();
416
+ if (selectedNodeId) {
417
+ const data = { selected_node: selectedNodeId };
418
+ urlInfo.data = data;
419
+ }
420
+ }
421
+ };
289
422
 
290
- if (newNode) {
291
- this.refreshElements(existingNode.parent);
423
+ if (typeof dataUrl === "function") {
424
+ return dataUrl(node);
425
+ } else if (typeof dataUrl === "string") {
426
+ return getUrlFromString(dataUrl);
427
+ } else if (dataUrl && typeof dataUrl === "object") {
428
+ setUrlInfoData(dataUrl);
429
+ return dataUrl;
430
+ } else {
431
+ return null;
292
432
  }
293
-
294
- return newNode;
295
433
  }
296
434
 
297
- public addParentNode(
298
- newNodeInfo: NodeData,
299
- existingNode: Node,
300
- ): Node | null {
301
- if (!existingNode) {
302
- throw Error(PARAM_IS_EMPTY + "existingNode");
435
+ private getDefaultClosedIcon(): string {
436
+ if (this.options.rtl) {
437
+ // triangle to the left
438
+ return "&#x25c0;";
439
+ } else {
440
+ // triangle to the right
441
+ return "&#x25ba;";
303
442
  }
443
+ }
304
444
 
305
- const newNode = existingNode.addParent(newNodeInfo);
445
+ private getNode(element: HTMLElement): Node | null {
446
+ const liElement = element.closest("li.jqtree_common");
306
447
 
307
- if (newNode) {
308
- this.refreshElements(newNode.parent);
448
+ if (liElement) {
449
+ return jQuery(liElement).data("node") as Node;
450
+ } else {
451
+ return null;
309
452
  }
310
-
311
- return newNode;
312
453
  }
313
454
 
314
- public removeNode(node: Node): JQuery {
315
- if (!node) {
316
- throw Error(NODE_PARAM_IS_EMPTY);
455
+ private getNodeElement(element: HTMLElement): NodeElement | null {
456
+ const node = this.getNode(element);
457
+ if (node) {
458
+ return this.getNodeElementForNode(node);
459
+ } else {
460
+ return null;
317
461
  }
462
+ }
318
463
 
319
- if (!node.parent) {
320
- throw Error("Node has no parent");
464
+ private getNodeElementForNode(node: Node): NodeElement {
465
+ if (node.isFolder()) {
466
+ return this.createFolderElement(node);
467
+ } else {
468
+ return this.createNodeElement(node);
321
469
  }
322
-
323
- this.selectNodeHandler.removeFromSelection(node, true); // including children
324
-
325
- const parent = node.parent;
326
- node.remove();
327
- this.refreshElements(parent);
328
-
329
- return this.element;
330
470
  }
331
471
 
332
- public appendNode(newNodeInfo: NodeData, parentNodeParam?: Node): Node {
333
- const parentNode = parentNodeParam || this.tree;
334
-
335
- const node = parentNode.append(newNodeInfo);
336
-
337
- this.refreshElements(parentNode);
338
-
339
- return node;
472
+ private getNodeIdToBeSelected(): NodeId | null {
473
+ if (this.options.saveState) {
474
+ return this.saveStateHandler.getNodeIdToBeSelected();
475
+ } else {
476
+ return null;
477
+ }
340
478
  }
341
479
 
342
- public prependNode(newNodeInfo: NodeData, parentNodeParam?: Node): Node {
343
- const parentNode = parentNodeParam ?? this.tree;
480
+ private getRtlOption(): boolean {
481
+ if (this.options.rtl != null) {
482
+ return this.options.rtl;
483
+ } else {
484
+ const dataRtl = this.element.data("rtl") as unknown;
344
485
 
345
- const node = parentNode.prepend(newNodeInfo);
486
+ if (
487
+ dataRtl !== null &&
488
+ dataRtl !== false &&
489
+ dataRtl !== undefined
490
+ ) {
491
+ return true;
492
+ } else {
493
+ return false;
494
+ }
495
+ }
496
+ }
346
497
 
347
- this.refreshElements(parentNode);
498
+ private initData(): void {
499
+ if (this.options.data) {
500
+ this.doLoadData(this.options.data, null);
501
+ } else {
502
+ const dataUrl = this.getDataUrlInfo(null);
348
503
 
349
- return node;
504
+ if (dataUrl) {
505
+ this.doLoadDataFromUrl(null, null, null);
506
+ } else {
507
+ this.doLoadData([], null);
508
+ }
509
+ }
350
510
  }
351
511
 
352
- public updateNode(node: Node, data: NodeData): JQuery {
353
- if (!node) {
354
- throw Error(NODE_PARAM_IS_EMPTY);
355
- }
512
+ private initTree(data: NodeData[]): void {
513
+ const doInit = (): void => {
514
+ if (!this.isInitialized) {
515
+ this.isInitialized = true;
516
+ this.triggerEvent("tree.init");
517
+ }
518
+ };
356
519
 
357
- const idIsChanged =
358
- typeof data === "object" && data.id && data.id !== node.id;
520
+ this.tree = new this.options.nodeClass(
521
+ null,
522
+ true,
523
+ this.options.nodeClass,
524
+ );
359
525
 
360
- if (idIsChanged) {
361
- this.tree.removeNodeFromIndex(node);
362
- }
526
+ this.selectNodeHandler.clear();
363
527
 
364
- node.setData(data);
528
+ this.tree.loadFromData(data);
365
529
 
366
- if (idIsChanged) {
367
- this.tree.addNodeToIndex(node);
368
- }
530
+ const mustLoadOnDemand = this.setInitialState();
369
531
 
370
- if (
371
- typeof data === "object" &&
372
- data["children"] &&
373
- data["children"] instanceof Array
374
- ) {
375
- node.removeChildren();
532
+ this.refreshElements(null);
376
533
 
377
- if (data.children.length) {
378
- node.loadFromData(data.children as Node[]);
379
- }
534
+ if (!mustLoadOnDemand) {
535
+ doInit();
536
+ } else {
537
+ // Load data on demand and then init the tree
538
+ this.setInitialStateOnDemand(doInit);
380
539
  }
540
+ }
381
541
 
382
- this.refreshElements(node);
542
+ private isFocusOnTree(): boolean {
543
+ const activeElement = document.activeElement;
383
544
 
384
- return this.element;
545
+ return Boolean(
546
+ activeElement &&
547
+ activeElement.tagName === "SPAN" &&
548
+ this.containsElement(activeElement as HTMLElement),
549
+ );
385
550
  }
386
551
 
387
552
  private isSelectedNodeInSubtree(subtree: Node): boolean {
@@ -394,121 +559,61 @@ export class JqTreeWidget extends SimpleWidget<JQTreeOptions> {
394
559
  }
395
560
  }
396
561
 
397
- public moveNode(node: Node, targetNode: Node, position: string): JQuery {
398
- if (!node) {
399
- throw Error(NODE_PARAM_IS_EMPTY);
400
- }
401
-
402
- if (!targetNode) {
403
- throw Error(PARAM_IS_EMPTY + "targetNode");
404
- }
405
-
406
- const positionIndex = getPosition(position);
407
-
408
- if (positionIndex !== undefined) {
409
- this.tree.moveNode(node, targetNode, positionIndex);
410
- this.refreshElements(null);
411
- }
412
-
413
- return this.element;
414
- }
562
+ private loadFolderOnDemand(
563
+ node: Node,
564
+ slide = true,
565
+ onFinished?: OnFinishOpenNode,
566
+ ): void {
567
+ node.is_loading = true;
415
568
 
416
- public getStateFromStorage(): SavedState | null {
417
- return this.saveStateHandler.getStateFromStorage();
569
+ this.doLoadDataFromUrl(null, node, () => {
570
+ this.openNodeInternal(node, slide, onFinished);
571
+ });
418
572
  }
419
573
 
420
- public addToSelection(node: Node, mustSetFocus?: boolean): JQuery {
421
- if (!node) {
422
- throw Error(NODE_PARAM_IS_EMPTY);
423
- }
424
-
425
- this.selectNodeHandler.addToSelection(node);
426
- this.openParents(node);
427
-
428
- this.getNodeElementForNode(node).select(
429
- mustSetFocus === undefined ? true : mustSetFocus,
430
- );
431
-
432
- this.saveState();
574
+ private loadSubtree(data: NodeData[], parentNode: Node): void {
575
+ parentNode.loadFromData(data);
433
576
 
434
- return this.element;
435
- }
577
+ parentNode.load_on_demand = false;
578
+ parentNode.is_loading = false;
436
579
 
437
- public getSelectedNodes(): Node[] {
438
- return this.selectNodeHandler.getSelectedNodes();
580
+ this.refreshElements(parentNode);
439
581
  }
440
582
 
441
- public isNodeSelected(node: Node): boolean {
442
- if (!node) {
443
- throw Error(NODE_PARAM_IS_EMPTY);
583
+ private mouseCapture(positionInfo: PositionInfo): boolean | null {
584
+ if (this.options.dragAndDrop) {
585
+ return this.dndHandler.mouseCapture(positionInfo);
586
+ } else {
587
+ return false;
444
588
  }
445
-
446
- return this.selectNodeHandler.isNodeSelected(node);
447
589
  }
448
590
 
449
- public removeFromSelection(node: Node): JQuery {
450
- if (!node) {
451
- throw Error(NODE_PARAM_IS_EMPTY);
452
- }
453
-
454
- this.selectNodeHandler.removeFromSelection(node);
455
-
456
- this.getNodeElementForNode(node).deselect();
457
- this.saveState();
458
-
459
- return this.element;
460
- }
591
+ private mouseDrag(positionInfo: PositionInfo): boolean {
592
+ if (this.options.dragAndDrop) {
593
+ const result = this.dndHandler.mouseDrag(positionInfo);
461
594
 
462
- public scrollToNode(node: Node): JQuery {
463
- if (!node) {
464
- throw Error(NODE_PARAM_IS_EMPTY);
595
+ this.scrollHandler.checkScrolling(positionInfo);
596
+ return result;
597
+ } else {
598
+ return false;
465
599
  }
466
-
467
- const top =
468
- getOffsetTop(node.element) -
469
- getOffsetTop(this.$el.get(0) as HTMLElement);
470
-
471
- this.scrollHandler.scrollToY(top);
472
-
473
- return this.element;
474
- }
475
-
476
- public getState(): SavedState | null {
477
- return this.saveStateHandler.getState();
478
- }
479
-
480
- public setState(state: SavedState): JQuery {
481
- this.saveStateHandler.setInitialState(state);
482
- this.refreshElements(null);
483
-
484
- return this.element;
485
- }
486
-
487
- public setOption(option: string, value: unknown): JQuery {
488
- (this.options as unknown as Record<string, unknown>)[option] = value;
489
- return this.element;
490
600
  }
491
601
 
492
- public moveDown(): JQuery {
493
- const selectedNode = this.getSelectedNode();
494
- if (selectedNode) {
495
- this.keyHandler.moveDown(selectedNode);
602
+ private mouseStart(positionInfo: PositionInfo): boolean {
603
+ if (this.options.dragAndDrop) {
604
+ return this.dndHandler.mouseStart(positionInfo);
605
+ } else {
606
+ return false;
496
607
  }
497
-
498
- return this.element;
499
608
  }
500
609
 
501
- public moveUp(): JQuery {
502
- const selectedNode = this.getSelectedNode();
503
- if (selectedNode) {
504
- this.keyHandler.moveUp(selectedNode);
610
+ private mouseStop(positionInfo: PositionInfo): boolean {
611
+ if (this.options.dragAndDrop) {
612
+ this.scrollHandler.stopScrolling();
613
+ return this.dndHandler.mouseStop(positionInfo);
614
+ } else {
615
+ return false;
505
616
  }
506
-
507
- return this.element;
508
- }
509
-
510
- public getVersion(): string {
511
- return __version__;
512
617
  }
513
618
 
514
619
  private openNodeInternal(
@@ -549,6 +654,14 @@ export class JqTreeWidget extends SimpleWidget<JQTreeOptions> {
549
654
  }
550
655
  }
551
656
 
657
+ private openParents(node: Node) {
658
+ const parent = node.parent;
659
+
660
+ if (parent?.parent && !parent.is_open) {
661
+ this.openNode(parent, false);
662
+ }
663
+ }
664
+
552
665
  /*
553
666
  Redraw the tree or part of the tree.
554
667
  from_node: redraw this subtree
@@ -568,189 +681,17 @@ export class JqTreeWidget extends SimpleWidget<JQTreeOptions> {
568
681
  this.triggerEvent("tree.refresh");
569
682
  }
570
683
 
571
- private getNodeElementForNode(node: Node): NodeElement {
572
- if (node.isFolder()) {
573
- return this.createFolderElement(node);
574
- } else {
575
- return this.createNodeElement(node);
684
+ private saveState(): void {
685
+ if (this.options.saveState) {
686
+ this.saveStateHandler.saveState();
576
687
  }
577
688
  }
578
689
 
579
- private getNodeElement(element: HTMLElement): NodeElement | null {
580
- const node = this.getNode(element);
690
+ private selectCurrentNode(mustSetFocus: boolean): void {
691
+ const node = this.getSelectedNode();
581
692
  if (node) {
582
- return this.getNodeElementForNode(node);
583
- } else {
584
- return null;
585
- }
586
- }
587
-
588
- public init(): void {
589
- super.init();
590
-
591
- this.element = this.$el;
592
- this.isInitialized = false;
593
-
594
- this.options.rtl = this.getRtlOption();
595
-
596
- if (this.options.closedIcon == null) {
597
- this.options.closedIcon = this.getDefaultClosedIcon();
598
- }
599
-
600
- this.connectHandlers();
601
-
602
- this.initData();
603
- }
604
-
605
- public deinit(): void {
606
- this.element.empty();
607
- this.element.off();
608
-
609
- this.keyHandler.deinit();
610
- this.mouseHandler.deinit();
611
-
612
- this.tree = new Node({}, true);
613
-
614
- super.deinit();
615
- }
616
-
617
- private triggerEvent(
618
- eventName: string,
619
- values?: Record<string, unknown>,
620
- ): JQuery.Event {
621
- const event = jQuery.Event(eventName, values);
622
- this.element.trigger(event);
623
- return event;
624
- }
625
-
626
- private mouseCapture(positionInfo: PositionInfo): boolean | null {
627
- if (this.options.dragAndDrop) {
628
- return this.dndHandler.mouseCapture(positionInfo);
629
- } else {
630
- return false;
631
- }
632
- }
633
-
634
- private mouseStart(positionInfo: PositionInfo): boolean {
635
- if (this.options.dragAndDrop) {
636
- return this.dndHandler.mouseStart(positionInfo);
637
- } else {
638
- return false;
639
- }
640
- }
641
-
642
- private mouseDrag(positionInfo: PositionInfo): boolean {
643
- if (this.options.dragAndDrop) {
644
- const result = this.dndHandler.mouseDrag(positionInfo);
645
-
646
- this.scrollHandler.checkScrolling(positionInfo);
647
- return result;
648
- } else {
649
- return false;
650
- }
651
- }
652
-
653
- private mouseStop(positionInfo: PositionInfo): boolean {
654
- if (this.options.dragAndDrop) {
655
- this.scrollHandler.stopScrolling();
656
- return this.dndHandler.mouseStop(positionInfo);
657
- } else {
658
- return false;
659
- }
660
- }
661
-
662
- private initData(): void {
663
- if (this.options.data) {
664
- this.doLoadData(this.options.data, null);
665
- } else {
666
- const dataUrl = this.getDataUrlInfo(null);
667
-
668
- if (dataUrl) {
669
- this.doLoadDataFromUrl(null, null, null);
670
- } else {
671
- this.doLoadData([], null);
672
- }
673
- }
674
- }
675
-
676
- private getDataUrlInfo(node: Node | null): JQuery.AjaxSettings | null {
677
- const dataUrl =
678
- this.options.dataUrl || (this.element.data("url") as string | null);
679
-
680
- const getUrlFromString = (url: string): JQuery.AjaxSettings => {
681
- const urlInfo: JQuery.AjaxSettings = { url };
682
-
683
- setUrlInfoData(urlInfo);
684
-
685
- return urlInfo;
686
- };
687
-
688
- const setUrlInfoData = (urlInfo: JQuery.AjaxSettings): void => {
689
- if (node?.id) {
690
- // Load on demand of a subtree; add node parameter
691
- const data = { node: node.id };
692
- urlInfo["data"] = data;
693
- } else {
694
- // Add selected_node parameter
695
- const selectedNodeId = this.getNodeIdToBeSelected();
696
- if (selectedNodeId) {
697
- const data = { selected_node: selectedNodeId };
698
- urlInfo["data"] = data;
699
- }
700
- }
701
- };
702
-
703
- if (typeof dataUrl === "function") {
704
- return dataUrl(node);
705
- } else if (typeof dataUrl === "string") {
706
- return getUrlFromString(dataUrl);
707
- } else if (dataUrl && typeof dataUrl === "object") {
708
- setUrlInfoData(dataUrl);
709
- return dataUrl;
710
- } else {
711
- return null;
712
- }
713
- }
714
-
715
- private getNodeIdToBeSelected(): NodeId | null {
716
- if (this.options.saveState) {
717
- return this.saveStateHandler.getNodeIdToBeSelected();
718
- } else {
719
- return null;
720
- }
721
- }
722
-
723
- private initTree(data: NodeData[]): void {
724
- const doInit = (): void => {
725
- if (!this.isInitialized) {
726
- this.isInitialized = true;
727
- this.triggerEvent("tree.init");
728
- }
729
- };
730
-
731
- if (!this.options.nodeClass) {
732
- return;
733
- }
734
-
735
- this.tree = new this.options.nodeClass(
736
- null,
737
- true,
738
- this.options.nodeClass,
739
- );
740
-
741
- this.selectNodeHandler.clear();
742
-
743
- this.tree.loadFromData(data);
744
-
745
- const mustLoadOnDemand = this.setInitialState();
746
-
747
- this.refreshElements(null);
748
-
749
- if (!mustLoadOnDemand) {
750
- doInit();
751
- } else {
752
- // Load data on demand and then init the tree
753
- this.setInitialStateOnDemand(doInit);
693
+ const nodeElement = this.getNodeElementForNode(node);
694
+ nodeElement.select(mustSetFocus);
754
695
  }
755
696
  }
756
697
 
@@ -871,412 +812,477 @@ export class JqTreeWidget extends SimpleWidget<JQTreeOptions> {
871
812
  }
872
813
  }
873
814
 
874
- private getAutoOpenMaxLevel(): number {
875
- if (this.options.autoOpen === true) {
876
- return -1;
877
- } else if (typeof this.options.autoOpen === "number") {
878
- return this.options.autoOpen;
879
- } else if (typeof this.options.autoOpen === "string") {
880
- return parseInt(this.options.autoOpen, 10);
881
- } else {
882
- return 0;
883
- }
815
+ private triggerEvent(
816
+ eventName: string,
817
+ values?: Record<string, unknown>,
818
+ ): JQuery.Event {
819
+ const event = jQuery.Event(eventName, values);
820
+ this.element.trigger(event);
821
+ return event;
884
822
  }
885
823
 
886
- private getNode(element: HTMLElement): null | Node {
887
- const liElement = element.closest("li.jqtree_common");
824
+ public addNodeAfter(
825
+ newNodeInfo: NodeData,
826
+ existingNode: Node,
827
+ ): Node | null {
828
+ const newNode = existingNode.addAfter(newNodeInfo);
888
829
 
889
- if (liElement) {
890
- return jQuery(liElement).data("node") as Node;
891
- } else {
892
- return null;
830
+ if (newNode) {
831
+ this.refreshElements(existingNode.parent);
893
832
  }
894
- }
895
833
 
896
- private saveState(): void {
897
- if (this.options.saveState) {
898
- this.saveStateHandler.saveState();
899
- }
834
+ return newNode;
900
835
  }
901
836
 
902
- private selectCurrentNode(mustSetFocus: boolean): void {
903
- const node = this.getSelectedNode();
904
- if (node) {
905
- const nodeElement = this.getNodeElementForNode(node);
906
- if (nodeElement) {
907
- nodeElement.select(mustSetFocus);
908
- }
837
+ public addNodeBefore(
838
+ newNodeInfo: NodeData,
839
+ existingNode?: Node,
840
+ ): Node | null {
841
+ if (!existingNode) {
842
+ throw Error(PARAM_IS_EMPTY + "existingNode");
909
843
  }
910
- }
911
844
 
912
- private deselectCurrentNode(): void {
913
- const node = this.getSelectedNode();
914
- if (node) {
915
- this.removeFromSelection(node);
845
+ const newNode = existingNode.addBefore(newNodeInfo);
846
+
847
+ if (newNode) {
848
+ this.refreshElements(existingNode.parent);
916
849
  }
850
+
851
+ return newNode;
917
852
  }
918
853
 
919
- private getDefaultClosedIcon(): string {
920
- if (this.options.rtl) {
921
- // triangle to the left
922
- return "&#x25c0;";
923
- } else {
924
- // triangle to the right
925
- return "&#x25ba;";
854
+ public addParentNode(
855
+ newNodeInfo: NodeData,
856
+ existingNode?: Node,
857
+ ): Node | null {
858
+ if (!existingNode) {
859
+ throw Error(PARAM_IS_EMPTY + "existingNode");
926
860
  }
927
- }
928
861
 
929
- private getRtlOption(): boolean {
930
- if (this.options.rtl != null) {
931
- return this.options.rtl;
932
- } else {
933
- const dataRtl = this.element.data("rtl") as unknown;
862
+ const newNode = existingNode.addParent(newNodeInfo);
934
863
 
935
- if (
936
- dataRtl !== null &&
937
- dataRtl !== false &&
938
- dataRtl !== undefined
939
- ) {
940
- return true;
941
- } else {
942
- return false;
943
- }
864
+ if (newNode) {
865
+ this.refreshElements(newNode.parent);
944
866
  }
945
- }
946
867
 
947
- private doSelectNode(
948
- node: Node | null,
949
- optionsParam?: SelectNodeOptions,
950
- ): void {
951
- const saveState = (): void => {
952
- if (this.options.saveState) {
953
- this.saveStateHandler.saveState();
954
- }
955
- };
868
+ return newNode;
869
+ }
956
870
 
871
+ public addToSelection(node?: Node, mustSetFocus?: boolean): JQuery {
957
872
  if (!node) {
958
- // Called with empty node -> deselect current node
959
- this.deselectCurrentNode();
960
- saveState();
961
- return;
873
+ throw Error(NODE_PARAM_IS_EMPTY);
962
874
  }
963
- const defaultOptions = { mustSetFocus: true, mustToggle: true };
964
- const selectOptions = { ...defaultOptions, ...(optionsParam || {}) };
965
875
 
966
- const canSelect = (): boolean => {
967
- if (this.options.onCanSelectNode) {
968
- return (
969
- this.options.selectable === true &&
970
- this.options.onCanSelectNode(node)
971
- );
972
- } else {
973
- return this.options.selectable === true;
974
- }
975
- };
876
+ this.selectNodeHandler.addToSelection(node);
877
+ this.openParents(node);
976
878
 
977
- if (!canSelect()) {
978
- return;
879
+ this.getNodeElementForNode(node).select(mustSetFocus ?? true);
880
+
881
+ this.saveState();
882
+
883
+ return this.element;
884
+ }
885
+
886
+ public appendNode(newNodeInfo: NodeData, parentNodeParam?: Node): Node {
887
+ const parentNode = parentNodeParam ?? this.tree;
888
+
889
+ const node = parentNode.append(newNodeInfo);
890
+
891
+ this.refreshElements(parentNode);
892
+
893
+ return node;
894
+ }
895
+
896
+ public closeNode(node?: Node, slideParam?: boolean | null): JQuery {
897
+ if (!node) {
898
+ throw Error(NODE_PARAM_IS_EMPTY);
979
899
  }
980
900
 
981
- if (this.selectNodeHandler.isNodeSelected(node)) {
982
- if (selectOptions.mustToggle) {
983
- this.deselectCurrentNode();
984
- this.triggerEvent("tree.select", {
985
- node: null,
986
- previous_node: node,
987
- });
988
- }
989
- } else {
990
- const deselectedNode = this.getSelectedNode() || null;
991
- this.deselectCurrentNode();
992
- this.addToSelection(node, selectOptions.mustSetFocus);
901
+ const slide = slideParam ?? this.options.slide;
993
902
 
994
- this.triggerEvent("tree.select", {
995
- node,
996
- deselected_node: deselectedNode,
997
- });
998
- this.openParents(node);
903
+ if (node.isFolder() || node.isEmptyFolder) {
904
+ this.createFolderElement(node).close(
905
+ slide,
906
+ this.options.animationSpeed,
907
+ );
908
+
909
+ this.saveState();
999
910
  }
1000
911
 
1001
- saveState();
912
+ return this.element;
1002
913
  }
1003
914
 
1004
- private doLoadData(data: NodeData[] | null, parentNode: Node | null): void {
1005
- if (data) {
1006
- if (parentNode) {
1007
- this.deselectNodes(parentNode);
1008
- this.loadSubtree(data, parentNode);
1009
- } else {
1010
- this.initTree(data);
1011
- }
915
+ public deinit(): void {
916
+ this.element.empty();
917
+ this.element.off();
1012
918
 
1013
- if (this.isDragging()) {
1014
- this.dndHandler.refresh();
1015
- }
1016
- }
919
+ this.keyHandler.deinit();
920
+ this.mouseHandler.deinit();
1017
921
 
1018
- this.triggerEvent("tree.load_data", {
1019
- tree_data: data,
1020
- parent_node: parentNode,
1021
- });
922
+ this.tree = new Node({}, true);
923
+
924
+ super.deinit();
1022
925
  }
1023
926
 
1024
- private deselectNodes(parentNode: Node): void {
1025
- const selectedNodesUnderParent =
1026
- this.selectNodeHandler.getSelectedNodesUnder(parentNode);
1027
- for (const n of selectedNodesUnderParent) {
1028
- this.selectNodeHandler.removeFromSelection(n);
1029
- }
927
+ public getNodeByCallback(callback: (node: Node) => boolean): Node | null {
928
+ return this.tree.getNodeByCallback(callback);
1030
929
  }
1031
930
 
1032
- private loadSubtree(data: NodeData[], parentNode: Node): void {
1033
- parentNode.loadFromData(data);
931
+ public getNodeByHtmlElement(
932
+ inputElement: HTMLElement | JQuery,
933
+ ): Node | null {
934
+ const element =
935
+ inputElement instanceof HTMLElement
936
+ ? inputElement
937
+ : inputElement[0];
1034
938
 
1035
- parentNode.load_on_demand = false;
1036
- parentNode.is_loading = false;
939
+ if (!element) {
940
+ return null;
941
+ }
1037
942
 
1038
- this.refreshElements(parentNode);
943
+ return this.getNode(element);
1039
944
  }
1040
945
 
1041
- private doLoadDataFromUrl(
1042
- urlInfoParam: string | JQuery.AjaxSettings | null,
1043
- parentNode: Node | null,
1044
- onFinished: HandleFinishedLoading | null,
1045
- ): void {
1046
- const urlInfo = urlInfoParam || this.getDataUrlInfo(parentNode);
946
+ public getNodeById(nodeId: NodeId): Node | null {
947
+ return this.tree.getNodeById(nodeId);
948
+ }
1047
949
 
1048
- this.dataLoader.loadFromUrl(urlInfo, parentNode, onFinished);
950
+ public getNodeByName(name: string): Node | null {
951
+ return this.tree.getNodeByName(name);
1049
952
  }
1050
953
 
1051
- private loadFolderOnDemand(
1052
- node: Node,
1053
- slide = true,
1054
- onFinished?: OnFinishOpenNode,
1055
- ): void {
1056
- node.is_loading = true;
954
+ public getNodeByNameMustExist(name: string): Node {
955
+ return this.tree.getNodeByNameMustExist(name);
956
+ }
1057
957
 
1058
- this.doLoadDataFromUrl(null, node, () => {
1059
- this.openNodeInternal(node, slide, onFinished);
1060
- });
958
+ public getNodesByProperty(key: string, value: unknown): Node[] {
959
+ return this.tree.getNodesByProperty(key, value);
1061
960
  }
1062
961
 
1063
- private containsElement(element: HTMLElement): boolean {
1064
- const node = this.getNode(element);
962
+ public getSelectedNode(): false | Node {
963
+ return this.selectNodeHandler.getSelectedNode();
964
+ }
1065
965
 
1066
- return node != null && node.tree === this.tree;
966
+ public getSelectedNodes(): Node[] {
967
+ return this.selectNodeHandler.getSelectedNodes();
1067
968
  }
1068
969
 
1069
- private isFocusOnTree(): boolean {
1070
- const activeElement = document.activeElement;
970
+ public getState(): null | SavedState {
971
+ return this.saveStateHandler.getState();
972
+ }
1071
973
 
1072
- return Boolean(
1073
- activeElement &&
1074
- activeElement.tagName === "SPAN" &&
1075
- this.containsElement(activeElement as HTMLElement),
1076
- );
974
+ public getStateFromStorage(): null | SavedState {
975
+ return this.saveStateHandler.getStateFromStorage();
1077
976
  }
1078
977
 
1079
- private connectHandlers() {
1080
- const {
1081
- autoEscape,
1082
- buttonLeft,
1083
- closedIcon,
1084
- dataFilter,
1085
- dragAndDrop,
1086
- keyboardSupport,
1087
- onCanMove,
1088
- onCanMoveTo,
1089
- onCreateLi,
1090
- onDragMove,
1091
- onDragStop,
1092
- onGetStateFromStorage,
1093
- onIsMoveHandle,
1094
- onLoadFailed,
1095
- onLoading,
1096
- onSetStateFromStorage,
1097
- openedIcon,
1098
- openFolderDelay,
1099
- rtl,
1100
- saveState,
1101
- showEmptyFolder,
1102
- slide,
1103
- tabIndex,
1104
- } = this.options;
978
+ public getTree(): Node {
979
+ return this.tree;
980
+ }
1105
981
 
1106
- const closeNode = this.closeNode.bind(this);
1107
- const getNodeElement = this.getNodeElement.bind(this);
1108
- const getNodeElementForNode = this.getNodeElementForNode.bind(this);
1109
- const getNodeById = this.getNodeById.bind(this);
1110
- const getSelectedNode = this.getSelectedNode.bind(this);
1111
- const getTree = this.getTree.bind(this);
1112
- const isFocusOnTree = this.isFocusOnTree.bind(this);
1113
- const loadData = this.loadData.bind(this);
1114
- const openNode = this.openNodeInternal.bind(this);
1115
- const refreshElements = this.refreshElements.bind(this);
1116
- const refreshHitAreas = this.refreshHitAreas.bind(this);
1117
- const selectNode = this.selectNode.bind(this);
1118
- const $treeElement = this.element;
1119
- const treeElement = this.element.get(0) as HTMLElement;
1120
- const triggerEvent = this.triggerEvent.bind(this);
982
+ public getVersion(): string {
983
+ return __version__;
984
+ }
1121
985
 
1122
- const selectNodeHandler = new SelectNodeHandler({
1123
- getNodeById,
1124
- });
986
+ public init(): void {
987
+ super.init();
1125
988
 
1126
- const addToSelection =
1127
- selectNodeHandler.addToSelection.bind(selectNodeHandler);
1128
- const getSelectedNodes =
1129
- selectNodeHandler.getSelectedNodes.bind(selectNodeHandler);
1130
- const isNodeSelected =
1131
- selectNodeHandler.isNodeSelected.bind(selectNodeHandler);
1132
- const removeFromSelection =
1133
- selectNodeHandler.removeFromSelection.bind(selectNodeHandler);
1134
- const getMouseDelay = () => this.options.startDndDelay ?? 0;
989
+ this.element = this.$el;
990
+ this.isInitialized = false;
1135
991
 
1136
- const dataLoader = new DataLoader({
1137
- dataFilter,
1138
- loadData,
1139
- onLoadFailed,
1140
- onLoading,
1141
- treeElement,
1142
- triggerEvent,
1143
- });
992
+ this.options.rtl = this.getRtlOption();
1144
993
 
1145
- const saveStateHandler = new SaveStateHandler({
1146
- addToSelection,
1147
- getNodeById,
1148
- getSelectedNodes,
1149
- getTree,
1150
- onGetStateFromStorage,
1151
- onSetStateFromStorage,
1152
- openNode,
1153
- refreshElements,
1154
- removeFromSelection,
1155
- saveState,
1156
- });
994
+ if (this.options.closedIcon == null) {
995
+ this.options.closedIcon = this.getDefaultClosedIcon();
996
+ }
1157
997
 
1158
- const scrollHandler = new ScrollHandler({
1159
- refreshHitAreas,
1160
- treeElement,
1161
- });
998
+ this.connectHandlers();
1162
999
 
1163
- const getScrollLeft = scrollHandler.getScrollLeft.bind(scrollHandler);
1000
+ this.initData();
1001
+ }
1164
1002
 
1165
- const dndHandler = new DragAndDropHandler({
1166
- autoEscape,
1167
- getNodeElement,
1168
- getNodeElementForNode,
1169
- getScrollLeft,
1170
- getTree,
1171
- onCanMove,
1172
- onCanMoveTo,
1173
- onDragMove,
1174
- onDragStop,
1175
- onIsMoveHandle,
1176
- openFolderDelay,
1177
- openNode,
1178
- refreshElements,
1179
- slide,
1180
- treeElement,
1181
- triggerEvent,
1182
- });
1003
+ public isDragging(): boolean {
1004
+ return this.dndHandler.isDragging;
1005
+ }
1183
1006
 
1184
- const keyHandler = new KeyHandler({
1185
- closeNode,
1186
- getSelectedNode,
1187
- isFocusOnTree,
1188
- keyboardSupport,
1189
- openNode,
1190
- selectNode,
1191
- });
1007
+ public isNodeSelected(node?: Node): boolean {
1008
+ if (!node) {
1009
+ throw Error(NODE_PARAM_IS_EMPTY);
1010
+ }
1192
1011
 
1193
- const renderer = new ElementsRenderer({
1194
- autoEscape,
1195
- buttonLeft,
1196
- closedIcon,
1197
- dragAndDrop,
1198
- $element: $treeElement,
1199
- getTree,
1200
- isNodeSelected,
1201
- onCreateLi,
1202
- openedIcon,
1203
- rtl,
1204
- showEmptyFolder,
1205
- tabIndex,
1206
- });
1012
+ return this.selectNodeHandler.isNodeSelected(node);
1013
+ }
1207
1014
 
1208
- const getNode = this.getNode.bind(this);
1209
- const onMouseCapture = this.mouseCapture.bind(this);
1210
- const onMouseDrag = this.mouseDrag.bind(this);
1211
- const onMouseStart = this.mouseStart.bind(this);
1212
- const onMouseStop = this.mouseStop.bind(this);
1015
+ public loadData(data: NodeData[], parentNode: Node | null): JQuery {
1016
+ this.doLoadData(data, parentNode);
1017
+ return this.element;
1018
+ }
1213
1019
 
1214
- const mouseHandler = new MouseHandler({
1215
- element: treeElement,
1216
- getMouseDelay,
1217
- getNode,
1218
- onClickButton: this.toggle.bind(this),
1219
- onClickTitle: this.doSelectNode.bind(this),
1220
- onMouseCapture,
1221
- onMouseDrag,
1222
- onMouseStart,
1223
- onMouseStop,
1224
- triggerEvent,
1225
- useContextMenu: this.options.useContextMenu,
1226
- });
1020
+ /*
1021
+ signatures:
1022
+ - loadDataFromUrl(url, parent_node=null, on_finished=null)
1023
+ loadDataFromUrl('/my_data');
1024
+ loadDataFromUrl('/my_data', node1);
1025
+ loadDataFromUrl('/my_data', node1, function() { console.log('finished'); });
1026
+ loadDataFromUrl('/my_data', null, function() { console.log('finished'); });
1227
1027
 
1228
- this.dataLoader = dataLoader;
1229
- this.dndHandler = dndHandler;
1230
- this.keyHandler = keyHandler;
1231
- this.mouseHandler = mouseHandler;
1232
- this.renderer = renderer;
1233
- this.saveStateHandler = saveStateHandler;
1234
- this.scrollHandler = scrollHandler;
1235
- this.selectNodeHandler = selectNodeHandler;
1028
+ - loadDataFromUrl(parent_node=null, on_finished=null)
1029
+ loadDataFromUrl();
1030
+ loadDataFromUrl(node1);
1031
+ loadDataFromUrl(null, function() { console.log('finished'); });
1032
+ loadDataFromUrl(node1, function() { console.log('finished'); });
1033
+ */
1034
+ public loadDataFromUrl(
1035
+ param1: Node | null | string,
1036
+ param2?: HandleFinishedLoading | Node | null,
1037
+ param3?: HandleFinishedLoading,
1038
+ ): JQuery {
1039
+ if (typeof param1 === "string") {
1040
+ // first parameter is url
1041
+ this.doLoadDataFromUrl(
1042
+ param1,
1043
+ param2 as Node | null,
1044
+ param3 ?? null,
1045
+ );
1046
+ } else {
1047
+ // first parameter is not url
1048
+ this.doLoadDataFromUrl(
1049
+ null,
1050
+ param1,
1051
+ param2 as HandleFinishedLoading | null,
1052
+ );
1053
+ }
1054
+
1055
+ return this.element;
1236
1056
  }
1237
1057
 
1238
- private createFolderElement(node: Node) {
1239
- const closedIconElement = this.renderer.closedIconElement;
1240
- const getScrollLeft = this.scrollHandler.getScrollLeft.bind(
1241
- this.scrollHandler,
1242
- );
1243
- const openedIconElement = this.renderer.openedIconElement;
1244
- const tabIndex = this.options.tabIndex;
1245
- const $treeElement = this.element;
1246
- const triggerEvent = this.triggerEvent.bind(this);
1058
+ public moveDown(): JQuery {
1059
+ const selectedNode = this.getSelectedNode();
1060
+ if (selectedNode) {
1061
+ this.keyHandler.moveDown(selectedNode);
1062
+ }
1247
1063
 
1248
- return new FolderElement({
1249
- closedIconElement,
1250
- getScrollLeft,
1251
- node,
1252
- openedIconElement,
1253
- tabIndex,
1254
- $treeElement,
1255
- triggerEvent,
1256
- });
1064
+ return this.element;
1257
1065
  }
1258
1066
 
1259
- private createNodeElement(node: Node) {
1260
- const getScrollLeft = this.scrollHandler.getScrollLeft.bind(
1261
- this.scrollHandler,
1262
- );
1263
- const tabIndex = this.options.tabIndex;
1264
- const $treeElement = this.element;
1067
+ public moveNode(node?: Node, targetNode?: Node, position?: string): JQuery {
1068
+ if (!node) {
1069
+ throw Error(NODE_PARAM_IS_EMPTY);
1070
+ }
1265
1071
 
1266
- return new NodeElement({
1267
- getScrollLeft,
1268
- node,
1269
- tabIndex,
1270
- $treeElement,
1271
- });
1072
+ if (!targetNode) {
1073
+ throw Error(PARAM_IS_EMPTY + "targetNode");
1074
+ }
1075
+
1076
+ if (!position) {
1077
+ throw Error(PARAM_IS_EMPTY + "position");
1078
+ }
1079
+
1080
+ const positionIndex = getPosition(position);
1081
+
1082
+ if (positionIndex !== undefined) {
1083
+ this.tree.moveNode(node, targetNode, positionIndex);
1084
+ this.refreshElements(null);
1085
+ }
1086
+
1087
+ return this.element;
1272
1088
  }
1273
1089
 
1274
- private openParents(node: Node) {
1090
+ public moveUp(): JQuery {
1091
+ const selectedNode = this.getSelectedNode();
1092
+ if (selectedNode) {
1093
+ this.keyHandler.moveUp(selectedNode);
1094
+ }
1095
+
1096
+ return this.element;
1097
+ }
1098
+
1099
+ public openNode(
1100
+ node?: Node,
1101
+ param1?: boolean | OnFinishOpenNode,
1102
+ param2?: OnFinishOpenNode,
1103
+ ): JQuery {
1104
+ if (!node) {
1105
+ throw Error(NODE_PARAM_IS_EMPTY);
1106
+ }
1107
+
1108
+ const parseParams = (): [boolean, OnFinishOpenNode | undefined] => {
1109
+ let onFinished: null | OnFinishOpenNode;
1110
+ let slide: boolean | null;
1111
+
1112
+ if (isFunction(param1)) {
1113
+ onFinished = param1 as OnFinishOpenNode;
1114
+ slide = null;
1115
+ } else {
1116
+ slide = param1 as boolean;
1117
+ onFinished = param2 as OnFinishOpenNode;
1118
+ }
1119
+
1120
+ if (slide == null) {
1121
+ slide = this.options.slide;
1122
+ }
1123
+
1124
+ return [slide, onFinished];
1125
+ };
1126
+
1127
+ const [slide, onFinished] = parseParams();
1128
+
1129
+ this.openNodeInternal(node, slide, onFinished);
1130
+ return this.element;
1131
+ }
1132
+
1133
+ public prependNode(newNodeInfo: NodeData, parentNodeParam?: Node): Node {
1134
+ const parentNode = parentNodeParam ?? this.tree;
1135
+
1136
+ const node = parentNode.prepend(newNodeInfo);
1137
+
1138
+ this.refreshElements(parentNode);
1139
+
1140
+ return node;
1141
+ }
1142
+
1143
+ public refresh(): JQuery {
1144
+ this.refreshElements(null);
1145
+ return this.element;
1146
+ }
1147
+
1148
+ public refreshHitAreas(): JQuery {
1149
+ this.dndHandler.refresh();
1150
+ return this.element;
1151
+ }
1152
+
1153
+ public reload(onFinished: HandleFinishedLoading | null): JQuery {
1154
+ this.doLoadDataFromUrl(null, null, onFinished);
1155
+ return this.element;
1156
+ }
1157
+
1158
+ public removeFromSelection(node?: Node): JQuery {
1159
+ if (!node) {
1160
+ throw Error(NODE_PARAM_IS_EMPTY);
1161
+ }
1162
+
1163
+ this.selectNodeHandler.removeFromSelection(node);
1164
+
1165
+ this.getNodeElementForNode(node).deselect();
1166
+ this.saveState();
1167
+
1168
+ return this.element;
1169
+ }
1170
+
1171
+ public removeNode(node?: Node): JQuery {
1172
+ if (!node) {
1173
+ throw Error(NODE_PARAM_IS_EMPTY);
1174
+ }
1175
+
1176
+ if (!node.parent) {
1177
+ throw Error("Node has no parent");
1178
+ }
1179
+
1180
+ this.selectNodeHandler.removeFromSelection(node, true); // including children
1181
+
1275
1182
  const parent = node.parent;
1183
+ node.remove();
1184
+ this.refreshElements(parent);
1276
1185
 
1277
- if (parent && parent.parent && !parent.is_open) {
1278
- this.openNode(parent, false);
1186
+ return this.element;
1187
+ }
1188
+
1189
+ public scrollToNode(node?: Node): JQuery {
1190
+ if (!node) {
1191
+ throw Error(NODE_PARAM_IS_EMPTY);
1192
+ }
1193
+
1194
+ if (!node.element) {
1195
+ return this.element;
1196
+ }
1197
+
1198
+ const top =
1199
+ getOffsetTop(node.element) -
1200
+ getOffsetTop(this.$el.get(0) as HTMLElement);
1201
+
1202
+ this.scrollHandler.scrollToY(top);
1203
+
1204
+ return this.element;
1205
+ }
1206
+
1207
+ public selectNode(
1208
+ node: Node | null,
1209
+ optionsParam?: SelectNodeOptions,
1210
+ ): JQuery {
1211
+ this.doSelectNode(node, optionsParam);
1212
+ return this.element;
1213
+ }
1214
+
1215
+ public setOption(option: string, value: unknown): JQuery {
1216
+ (this.options as unknown as Record<string, unknown>)[option] = value;
1217
+ return this.element;
1218
+ }
1219
+
1220
+ public setState(state?: SavedState): JQuery {
1221
+ if (state) {
1222
+ this.saveStateHandler.setInitialState(state);
1223
+ this.refreshElements(null);
1279
1224
  }
1225
+
1226
+ return this.element;
1227
+ }
1228
+
1229
+ public toggle(node?: Node, slideParam: boolean | null = null): JQuery {
1230
+ if (!node) {
1231
+ throw Error(NODE_PARAM_IS_EMPTY);
1232
+ }
1233
+
1234
+ const slide = slideParam ?? this.options.slide;
1235
+
1236
+ if (node.is_open) {
1237
+ this.closeNode(node, slide);
1238
+ } else {
1239
+ this.openNode(node, slide);
1240
+ }
1241
+
1242
+ return this.element;
1243
+ }
1244
+
1245
+ public toJson(): string {
1246
+ return JSON.stringify(this.tree.getData());
1247
+ }
1248
+
1249
+ public updateNode(node?: Node, data?: NodeData): JQuery {
1250
+ if (!node) {
1251
+ throw Error(NODE_PARAM_IS_EMPTY);
1252
+ }
1253
+
1254
+ if (!data) {
1255
+ return this.element;
1256
+ }
1257
+
1258
+ const idIsChanged =
1259
+ typeof data === "object" && data.id && data.id !== node.id;
1260
+
1261
+ if (idIsChanged) {
1262
+ this.tree.removeNodeFromIndex(node);
1263
+ }
1264
+
1265
+ node.setData(data);
1266
+
1267
+ if (idIsChanged) {
1268
+ this.tree.addNodeToIndex(node);
1269
+ }
1270
+
1271
+ if (
1272
+ typeof data === "object" &&
1273
+ data.children &&
1274
+ data.children instanceof Array
1275
+ ) {
1276
+ node.removeChildren();
1277
+
1278
+ if (data.children.length) {
1279
+ node.loadFromData(data.children as Node[]);
1280
+ }
1281
+ }
1282
+
1283
+ this.refreshElements(node);
1284
+
1285
+ return this.element;
1280
1286
  }
1281
1287
  }
1282
1288