neo.mjs 5.16.5 → 5.17.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/apps/ServiceWorker.mjs +2 -2
- package/examples/ServiceWorker.mjs +2 -2
- package/examples/form/field/fileupload/MainContainer.mjs +12 -2
- package/examples/treeSelectionModel/MainContainer.mjs +142 -0
- package/examples/treeSelectionModel/app.mjs +6 -0
- package/examples/treeSelectionModel/index.html +11 -0
- package/examples/treeSelectionModel/neo-config.json +7 -0
- package/examples/treeSelectionModel/tree.json +112 -0
- package/package.json +3 -3
- package/resources/scss/src/examples/treeSelectionModel/MainContainer.scss +24 -0
- package/resources/scss/src/form/field/FileUpload.scss +16 -2
- package/resources/scss/src/tree/Accordion.scss +128 -0
- package/src/DefaultConfig.mjs +2 -2
- package/src/core/Base.mjs +6 -0
- package/src/form/field/FileUpload.mjs +130 -43
- package/src/selection/TreeAccordionModel.mjs +293 -0
- package/src/selection/TreeModel.mjs +3 -3
- package/src/tree/Accordion.mjs +280 -0
- package/src/tree/List.mjs +31 -22
@@ -134,7 +134,7 @@ class FileUpload extends Base {
|
|
134
134
|
* An Object containing a default set of headers to be passed to the server on every HTTP request.
|
135
135
|
* @member {Object} headers
|
136
136
|
*/
|
137
|
-
|
137
|
+
headers : {},
|
138
138
|
|
139
139
|
/**
|
140
140
|
* An Object which allows the status text returned from the {@link #property-documentStatusUrl} to be
|
@@ -143,12 +143,19 @@ class FileUpload extends Base {
|
|
143
143
|
*/
|
144
144
|
documentStatusMap : {
|
145
145
|
SCANNING : 'scanning',
|
146
|
+
|
147
|
+
// The server doing its own secondary upload to the final storage location may return this.
|
148
|
+
// We enter the same state as scanning. A spinner shows for the duration of this state
|
149
|
+
UPLOADING : 'scanning',
|
150
|
+
|
146
151
|
MALWARE_DETECTED : 'scan-failed',
|
147
152
|
UN_DOWNLOADABLE : 'not-downloadable',
|
148
153
|
DOWNLOADABLE : 'downloadable',
|
149
154
|
DELETED : 'deleted'
|
150
155
|
},
|
151
156
|
|
157
|
+
document_ : null,
|
158
|
+
|
152
159
|
/**
|
153
160
|
* If this widget should reference an existing document, configure the widget with a documentId
|
154
161
|
* so that it can initialize in the correct "uploaded" state.
|
@@ -178,7 +185,7 @@ class FileUpload extends Base {
|
|
178
185
|
* The document status request URL must be configured in {@link #member-documentStatusUrl}
|
179
186
|
* @member {String} uploadUrl
|
180
187
|
*/
|
181
|
-
|
188
|
+
uploadUrl : null,
|
182
189
|
|
183
190
|
/**
|
184
191
|
* The name of the JSON property in which the document id is returned in the upload response
|
@@ -210,7 +217,7 @@ class FileUpload extends Base {
|
|
210
217
|
*
|
211
218
|
* @member {String} downloadUrl
|
212
219
|
*/
|
213
|
-
|
220
|
+
downloadUrl : null,
|
214
221
|
|
215
222
|
/**
|
216
223
|
* The URL of the file status reporting service.
|
@@ -274,8 +281,6 @@ class FileUpload extends Base {
|
|
274
281
|
*/
|
275
282
|
documentDeleteUrl : null,
|
276
283
|
|
277
|
-
headers_ : {},
|
278
|
-
|
279
284
|
/**
|
280
285
|
* @member {String} state_=null
|
281
286
|
*/
|
@@ -289,7 +294,27 @@ class FileUpload extends Base {
|
|
289
294
|
/**
|
290
295
|
* @member {String|Number} maxSize
|
291
296
|
*/
|
292
|
-
maxSize_: null
|
297
|
+
maxSize_: null,
|
298
|
+
|
299
|
+
/**
|
300
|
+
* The error text to show below the widget
|
301
|
+
* @member {String} error
|
302
|
+
*/
|
303
|
+
error_ : null,
|
304
|
+
|
305
|
+
// UI strings which can be overridden for other languages
|
306
|
+
documentText : 'Document',
|
307
|
+
pleaseUseTheseTypes : 'Please use these file types',
|
308
|
+
fileSizeMoreThan : 'File size exceeds',
|
309
|
+
documentDeleteError : 'Document delete service error',
|
310
|
+
isNoLongerAvailable : 'is no longer available',
|
311
|
+
documentStatusError : 'Document status service error',
|
312
|
+
uploadFailed : 'Upload failed',
|
313
|
+
scanning : 'Scanning',
|
314
|
+
malwareFoundInFile : 'Malware found in file',
|
315
|
+
pleaseCheck : 'Please check the file and try again',
|
316
|
+
successfullyUploaded : 'Successfully uploaded',
|
317
|
+
fileWasDeleted : 'File was deleted'
|
293
318
|
}
|
294
319
|
|
295
320
|
/**
|
@@ -304,13 +329,13 @@ class FileUpload extends Base {
|
|
304
329
|
{ input : me.onInputValueChange, scope: me},
|
305
330
|
{ click : me.onActionButtonClick, delegate : '.neo-file-upload-action-button', scope : me}
|
306
331
|
]);
|
332
|
+
}
|
307
333
|
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
}
|
334
|
+
/**
|
335
|
+
* @returns {Object}
|
336
|
+
*/
|
337
|
+
getInputEl() {
|
338
|
+
return this.vdom.cn[3];
|
314
339
|
}
|
315
340
|
|
316
341
|
async clear() {
|
@@ -327,7 +352,7 @@ class FileUpload extends Base {
|
|
327
352
|
|
328
353
|
// We have to wait for the DOM to have changed, and the input field to be visible
|
329
354
|
await new Promise(resolve => setTimeout(resolve, 100));
|
330
|
-
me.focus(me.
|
355
|
+
me.focus(me.getInputEl().id);
|
331
356
|
}
|
332
357
|
|
333
358
|
/**
|
@@ -346,10 +371,10 @@ class FileUpload extends Base {
|
|
346
371
|
type = pointPos > -1 ? file.name.slice(pointPos + 1) : '';
|
347
372
|
|
348
373
|
if (me.types && !types[type]) {
|
349
|
-
me.error =
|
374
|
+
me.error = `${me.pleaseUseTheseTypes}: .${Object.keys(types).join(' .')}`;
|
350
375
|
}
|
351
376
|
else if (file.size > me.maxSize) {
|
352
|
-
me.error =
|
377
|
+
me.error = `${me.fileSizeMoreThan} ${String(me._maxSize).toUpperCase()}`;
|
353
378
|
}
|
354
379
|
// If it passes the type and maxSize check, upload it
|
355
380
|
else {
|
@@ -395,7 +420,6 @@ class FileUpload extends Base {
|
|
395
420
|
|
396
421
|
/**
|
397
422
|
* This event fires before every HTTP request is sent to the server via any of the configured URLs.
|
398
|
-
*
|
399
423
|
*
|
400
424
|
* @event beforeRequest
|
401
425
|
* @param {Object} event The event
|
@@ -480,6 +504,10 @@ class FileUpload extends Base {
|
|
480
504
|
me.abortUpload();
|
481
505
|
break;
|
482
506
|
|
507
|
+
// While processing we just have to wait until it's succeeded or failed..
|
508
|
+
case 'processing':
|
509
|
+
break;
|
510
|
+
|
483
511
|
// If the upload or the scan failed, the document will not have been
|
484
512
|
// saved, so we just go back to ready state
|
485
513
|
case 'upload-failed':
|
@@ -488,13 +516,17 @@ class FileUpload extends Base {
|
|
488
516
|
me.state = 'ready';
|
489
517
|
break;
|
490
518
|
|
491
|
-
//
|
519
|
+
// For stored documents, we need to tell the server the document
|
492
520
|
// is not required.
|
493
521
|
case 'processing':
|
494
522
|
case 'downloadable':
|
495
523
|
case 'not-downloadable':
|
496
524
|
me.deleteDocument();
|
497
525
|
break;
|
526
|
+
case 'deleted':
|
527
|
+
me.clear();
|
528
|
+
me.state = 'ready';
|
529
|
+
break;
|
498
530
|
}
|
499
531
|
}
|
500
532
|
|
@@ -504,18 +536,15 @@ class FileUpload extends Base {
|
|
504
536
|
|
505
537
|
async deleteDocument() {
|
506
538
|
const
|
507
|
-
me
|
508
|
-
headers =
|
509
|
-
url = me.createUrl(me.documentDeleteUrl, {
|
510
|
-
[me.documentIdParameter] : me.documentId
|
511
|
-
});
|
539
|
+
me = this,
|
540
|
+
{ headers } = me;
|
512
541
|
|
513
542
|
me.fire('beforeRequest', {
|
514
543
|
headers
|
515
544
|
});
|
516
545
|
|
517
546
|
// We ask the server to delete using our this.documentId
|
518
|
-
const statusResponse = await fetch(
|
547
|
+
const statusResponse = await fetch(me.documentDeleteUrl, {
|
519
548
|
headers
|
520
549
|
});
|
521
550
|
|
@@ -525,24 +554,21 @@ class FileUpload extends Base {
|
|
525
554
|
me.state = 'ready';
|
526
555
|
}
|
527
556
|
else {
|
528
|
-
me.error =
|
557
|
+
me.error = `${me.documentDeleteError}: ${statusResponse.statusText}`;
|
529
558
|
}
|
530
559
|
}
|
531
560
|
|
532
561
|
async checkDocumentStatus() {
|
533
562
|
const
|
534
|
-
me
|
535
|
-
headers =
|
536
|
-
url = me.createUrl(me.documentStatusUrl, {
|
537
|
-
[me.documentIdParameter] : me.documentId
|
538
|
-
});
|
563
|
+
me = this,
|
564
|
+
{ headers } = me;
|
539
565
|
|
540
566
|
if (me.state === 'processing') {
|
541
567
|
me.fire('beforeRequest', {
|
542
568
|
headers
|
543
569
|
});
|
544
570
|
|
545
|
-
const statusResponse = await fetch(
|
571
|
+
const statusResponse = await fetch(me.documentStatusUrl, {
|
546
572
|
headers
|
547
573
|
});
|
548
574
|
|
@@ -559,7 +585,7 @@ class FileUpload extends Base {
|
|
559
585
|
setTimeout(() => me.checkDocumentStatus(), me.statusScanInterval);
|
560
586
|
break;
|
561
587
|
case 'deleted':
|
562
|
-
me.error =
|
588
|
+
me.error = `${me.documentText} ${me.documentId} ${isNoLongerAvailable}`;
|
563
589
|
me.state = 'ready';
|
564
590
|
break;
|
565
591
|
default:
|
@@ -573,11 +599,24 @@ class FileUpload extends Base {
|
|
573
599
|
}
|
574
600
|
}
|
575
601
|
else {
|
576
|
-
me.error =
|
602
|
+
me.error = `${documentStatusError}: ${statusResponse.statusText}`;
|
577
603
|
}
|
578
604
|
}
|
579
605
|
}
|
580
606
|
|
607
|
+
afterSetDocument(document) {
|
608
|
+
if (document) {
|
609
|
+
const
|
610
|
+
me = this;
|
611
|
+
|
612
|
+
me.preExistingDocument = true;
|
613
|
+
me.documentId = document.id;
|
614
|
+
me.fileSize = me.formatSize(document.size);
|
615
|
+
me.vdom.cn[1].cn[0].innerHTML = document.fileName;
|
616
|
+
me.state = me.documentStatusMap[document.status];
|
617
|
+
}
|
618
|
+
}
|
619
|
+
|
581
620
|
/**
|
582
621
|
* Triggered after the state config got changed
|
583
622
|
* @param {String} value
|
@@ -593,20 +632,23 @@ class FileUpload extends Base {
|
|
593
632
|
anchor = vdom.cn[1].cn[0],
|
594
633
|
status = vdom.cn[1].cn[1];
|
595
634
|
|
635
|
+
delete vdom.inert;
|
636
|
+
|
596
637
|
switch (value) {
|
597
638
|
case 'ready':
|
598
639
|
anchor.tag = 'div';
|
599
640
|
anchor.href = '';
|
600
641
|
break;
|
601
642
|
case 'upload-failed':
|
602
|
-
status.innerHTML =
|
643
|
+
status.innerHTML = `${me.uploadFailed}... (${Math.round(me.progress * 100)}%)`;
|
603
644
|
break;
|
604
645
|
case 'processing':
|
605
|
-
status.innerHTML =
|
646
|
+
status.innerHTML = `${me.scanning}... (${me.formatSize(me.uploadSize)})`;
|
647
|
+
vdom.inert = true;
|
606
648
|
break;
|
607
649
|
case 'scan-failed':
|
608
|
-
status.innerHTML =
|
609
|
-
me.error =
|
650
|
+
status.innerHTML = `${me.malwareFoundInFile}. \u2022 ${me.fileSize}`;
|
651
|
+
me.error = me.pleaseCheck;
|
610
652
|
break;
|
611
653
|
case 'downloadable':
|
612
654
|
anchor.tag = 'a';
|
@@ -616,9 +658,14 @@ class FileUpload extends Base {
|
|
616
658
|
status.innerHTML = me.fileSize;
|
617
659
|
break;
|
618
660
|
case 'not-downloadable':
|
619
|
-
status.innerHTML =
|
661
|
+
status.innerHTML = me.preExistingDocument ?
|
662
|
+
me.fileSize : `${successfullyUploaded} \u2022 ${me.fileSize}`;
|
663
|
+
break;
|
664
|
+
case 'deleted':
|
665
|
+
status.innerHTML = me.fileWasDeleted;
|
620
666
|
}
|
621
667
|
|
668
|
+
me.validate();
|
622
669
|
me.update();
|
623
670
|
|
624
671
|
// Processing above may mutate cls
|
@@ -642,6 +689,28 @@ class FileUpload extends Base {
|
|
642
689
|
return urlPattern;
|
643
690
|
}
|
644
691
|
|
692
|
+
beforeGetHeaders(headers) {
|
693
|
+
return { ...(headers || {}) }
|
694
|
+
}
|
695
|
+
|
696
|
+
beforeGetDocumentStatusUrl(documentStatusUrl) {
|
697
|
+
return typeof documentStatusUrl === 'function'? documentStatusUrl.call(me, me) : me.createUrl(documentStatusUrl, {
|
698
|
+
[me.documentIdParameter] : me.documentId
|
699
|
+
});
|
700
|
+
}
|
701
|
+
|
702
|
+
beforeGetDocumentDeleteUrl(documentDeleteUrl) {
|
703
|
+
return typeof documentDeleteUrl === 'function'? documentDeleteUrl.call(me, me) : me.createUrl(documentDeleteUrl, {
|
704
|
+
[me.documentIdParameter] : me.documentId
|
705
|
+
});
|
706
|
+
}
|
707
|
+
|
708
|
+
beforeGetDownloadUrl(downloadUrl) {
|
709
|
+
return typeof downloadUrl === 'function'? downloadUrl.call(me, me) : me.createUrl(downloadUrl, {
|
710
|
+
[me.documentIdParameter] : me.documentId
|
711
|
+
});
|
712
|
+
}
|
713
|
+
|
645
714
|
beforeGetMaxSize(maxSize) {
|
646
715
|
// Not configured means no limit
|
647
716
|
if (maxSize == null) {
|
@@ -659,21 +728,18 @@ class FileUpload extends Base {
|
|
659
728
|
}
|
660
729
|
}
|
661
730
|
|
662
|
-
|
663
|
-
const { cls } = this;
|
664
|
-
|
731
|
+
afterSetError(text) {
|
665
732
|
if (text) {
|
666
733
|
this.vdom.cn[4].cn = [{
|
667
734
|
vtype : 'text',
|
668
735
|
html : text
|
669
736
|
}];
|
670
|
-
NeoArray.add(cls, 'neo-invalid');
|
671
737
|
}
|
672
738
|
else {
|
673
|
-
|
739
|
+
this.vdom.cn[4].cn = [];
|
674
740
|
}
|
675
741
|
|
676
|
-
this.
|
742
|
+
this.validate();
|
677
743
|
this.update();
|
678
744
|
}
|
679
745
|
|
@@ -687,6 +753,27 @@ class FileUpload extends Base {
|
|
687
753
|
}
|
688
754
|
return 'n/a';
|
689
755
|
}
|
756
|
+
|
757
|
+
/**
|
758
|
+
* @returns {Boolean}
|
759
|
+
*/
|
760
|
+
validate() {
|
761
|
+
const { isValid, cls } = this;
|
762
|
+
|
763
|
+
NeoArray.toggle(cls, 'neo-invalid', !isValid);
|
764
|
+
this.cls = cls;
|
765
|
+
|
766
|
+
return isValid;
|
767
|
+
}
|
768
|
+
|
769
|
+
get isValid() {
|
770
|
+
const me = this;
|
771
|
+
|
772
|
+
return !me.error &&
|
773
|
+
((me.state === 'ready' && !me.required) ||
|
774
|
+
(me.state === 'downloadable') ||
|
775
|
+
(me.state === 'not-downloadable'));
|
776
|
+
}
|
690
777
|
}
|
691
778
|
|
692
779
|
Neo.applyClassConfig(FileUpload);
|
@@ -0,0 +1,293 @@
|
|
1
|
+
import TreeModel from './TreeModel.mjs';
|
2
|
+
import NeoArray from "../util/Array.mjs";
|
3
|
+
|
4
|
+
/**
|
5
|
+
* @class Neo.selection.TreeAccordionModel
|
6
|
+
* @extends Neo.selection.TreeModel
|
7
|
+
*/
|
8
|
+
class TreeAccordionModel extends TreeModel {
|
9
|
+
static config = {
|
10
|
+
/**
|
11
|
+
* @member {String} className='Neo.selection.TreeAccordionModel'
|
12
|
+
* @protected
|
13
|
+
*/
|
14
|
+
className: 'Neo.selection.TreeAccordionModel',
|
15
|
+
/**
|
16
|
+
* @member {String} ntype='selection-treeaccordionmodel'
|
17
|
+
* @protected
|
18
|
+
*/
|
19
|
+
ntype: 'selection-treeaccordionmodel'
|
20
|
+
}
|
21
|
+
|
22
|
+
/**
|
23
|
+
* Tries to find a child and returns it
|
24
|
+
* @param {Object} record
|
25
|
+
* @returns {Object|null}
|
26
|
+
*/
|
27
|
+
checkForChild(record) {
|
28
|
+
const view = this.view,
|
29
|
+
recordId = record[view.getKeyProperty()];
|
30
|
+
let childRecord = null;
|
31
|
+
|
32
|
+
for (const item of view.store.items) {
|
33
|
+
if (item.parentId === recordId) {
|
34
|
+
childRecord = item;
|
35
|
+
break;
|
36
|
+
}
|
37
|
+
}
|
38
|
+
|
39
|
+
return childRecord;
|
40
|
+
}
|
41
|
+
|
42
|
+
/**
|
43
|
+
* Return the parent record if any
|
44
|
+
* @param {Object} record
|
45
|
+
* @returns {Object|null}
|
46
|
+
*/
|
47
|
+
checkForParent(record) {
|
48
|
+
if (record.parentId) {
|
49
|
+
const view = this.view;
|
50
|
+
|
51
|
+
return view.store.get(record.parentId);
|
52
|
+
} else {
|
53
|
+
return null;
|
54
|
+
}
|
55
|
+
}
|
56
|
+
|
57
|
+
/**
|
58
|
+
* Depending on {-1|1} step return
|
59
|
+
* -1: previous record OR parent record
|
60
|
+
* 1: next record or null
|
61
|
+
*
|
62
|
+
* @param {Object} record
|
63
|
+
* @param {Number} step
|
64
|
+
* @returns {Object|null}
|
65
|
+
*/
|
66
|
+
checkForSibling(record, step) {
|
67
|
+
const view = this.view,
|
68
|
+
store = view.store,
|
69
|
+
parentRecordId = record.parentId,
|
70
|
+
recordId = record[view.getKeyProperty()];
|
71
|
+
let hasFoundNext = false,
|
72
|
+
nextItemRecord = null,
|
73
|
+
previousItemRecord;
|
74
|
+
|
75
|
+
for (let item of store.items) {
|
76
|
+
if (hasFoundNext && item.parentId === parentRecordId) {
|
77
|
+
nextItemRecord = item;
|
78
|
+
break;
|
79
|
+
}
|
80
|
+
|
81
|
+
if (!hasFoundNext && item.parentId === parentRecordId) {
|
82
|
+
if (!hasFoundNext && item[view.getKeyProperty()] === recordId) {
|
83
|
+
if (step === -1) break;
|
84
|
+
hasFoundNext = true;
|
85
|
+
} else {
|
86
|
+
previousItemRecord = item;
|
87
|
+
}
|
88
|
+
}
|
89
|
+
}
|
90
|
+
|
91
|
+
return step === 1 ? nextItemRecord : (previousItemRecord || store.get(parentRecordId));
|
92
|
+
}
|
93
|
+
|
94
|
+
/**
|
95
|
+
* Find the next sibling of a parent item
|
96
|
+
* @param {Object} record
|
97
|
+
* @returns {Object|null}
|
98
|
+
*/
|
99
|
+
checkNextParentSibling(record) {
|
100
|
+
const parent = this.view.store.get(record.parentId);
|
101
|
+
let parentSibling = this.checkForSibling(parent, 1);
|
102
|
+
|
103
|
+
if (!parentSibling && parent.parentId) this.checkNextParentSibling(parent);
|
104
|
+
|
105
|
+
return parentSibling;
|
106
|
+
}
|
107
|
+
|
108
|
+
/**
|
109
|
+
* Called by keys (List.mjs:register)
|
110
|
+
* Toggle collapse or if isLeaf select next item
|
111
|
+
* @param {Object} data
|
112
|
+
*/
|
113
|
+
onKeyDownEnter(data) {
|
114
|
+
let me = this,
|
115
|
+
view = me.view,
|
116
|
+
itemId = me.getSelection()[0],
|
117
|
+
record = view.store.get(view.getItemRecordId(itemId));
|
118
|
+
|
119
|
+
if (record.isLeaf || record.collapsed) {
|
120
|
+
me.onKeyDownRight(data);
|
121
|
+
} else {
|
122
|
+
me.onKeyDownLeft(data);
|
123
|
+
}
|
124
|
+
}
|
125
|
+
|
126
|
+
/**
|
127
|
+
* Called by keys (List.mjs:register)
|
128
|
+
* Deselect all and fire event selectPostLastItem
|
129
|
+
* @param {Object} data
|
130
|
+
*/
|
131
|
+
onKeyDownEscape(data) {
|
132
|
+
let me = this;
|
133
|
+
|
134
|
+
me.deselectAll();
|
135
|
+
}
|
136
|
+
|
137
|
+
/**
|
138
|
+
* Collapse folder or select previous
|
139
|
+
* @param {Object} data
|
140
|
+
*/
|
141
|
+
onKeyDownLeft(data) {
|
142
|
+
const me = this,
|
143
|
+
view = me.view,
|
144
|
+
itemId = me.getSelection()[0];
|
145
|
+
|
146
|
+
if (!itemId) {
|
147
|
+
me.selectRoot();
|
148
|
+
return;
|
149
|
+
}
|
150
|
+
|
151
|
+
const record = view.store.get(view.getItemRecordId(itemId));
|
152
|
+
|
153
|
+
if (record.isLeaf || record.collapsed || !view.rootParentsAreCollapsible) {
|
154
|
+
me.onNavKey(data, -1);
|
155
|
+
} else {
|
156
|
+
me.toggleCollapsed(record, itemId, true);
|
157
|
+
}
|
158
|
+
}
|
159
|
+
|
160
|
+
/**
|
161
|
+
* Open folder or select next
|
162
|
+
* @param {Object} data
|
163
|
+
*/
|
164
|
+
onKeyDownRight(data) {
|
165
|
+
const me = this,
|
166
|
+
view = me.view,
|
167
|
+
itemId = me.getSelection()[0];
|
168
|
+
|
169
|
+
if (!itemId) {
|
170
|
+
me.selectRoot();
|
171
|
+
return;
|
172
|
+
}
|
173
|
+
|
174
|
+
const record = view.store.get(view.getItemRecordId(itemId));
|
175
|
+
|
176
|
+
if (record.isLeaf || !record.collapsed) {
|
177
|
+
me.onNavKey(data, 1);
|
178
|
+
} else {
|
179
|
+
me.toggleCollapsed(record, itemId, false);
|
180
|
+
}
|
181
|
+
}
|
182
|
+
|
183
|
+
/**
|
184
|
+
* Handles 'up' and 'down' keys
|
185
|
+
* @param {Object} data
|
186
|
+
* @param {Number} step
|
187
|
+
*/
|
188
|
+
onNavKey(data, step) {
|
189
|
+
const me = this,
|
190
|
+
view = me.view,
|
191
|
+
item = me.getSelection()[0];
|
192
|
+
let newRecord;
|
193
|
+
|
194
|
+
if (item) {
|
195
|
+
const recordId = view.getItemRecordId(item);
|
196
|
+
let record = view.store.get(recordId);
|
197
|
+
|
198
|
+
if (step === 1) {
|
199
|
+
if (!record.isLeaf && !record.collapsed) {
|
200
|
+
// find first child
|
201
|
+
newRecord = this.checkForChild(record);
|
202
|
+
} else {
|
203
|
+
// find next sibling
|
204
|
+
newRecord = this.checkForSibling(record, step);
|
205
|
+
// no ==> loop through parent next siblings until no parent
|
206
|
+
if (!newRecord) {
|
207
|
+
newRecord = this.checkNextParentSibling(record);
|
208
|
+
}
|
209
|
+
}
|
210
|
+
// current item was the last item
|
211
|
+
if (!newRecord) {
|
212
|
+
me.deselectAll();
|
213
|
+
view.fire('selectPostLastItem');
|
214
|
+
}
|
215
|
+
} else if (step === -1) {
|
216
|
+
// check previous sibling
|
217
|
+
newRecord = this.checkForSibling(record, step);
|
218
|
+
// no ==> get parent
|
219
|
+
if (!newRecord) {
|
220
|
+
newRecord = this.checkForParent(record);
|
221
|
+
}
|
222
|
+
// current item was the first item
|
223
|
+
if (!newRecord) {
|
224
|
+
me.deselectAll();
|
225
|
+
view.fire('selectPreFirstItem');
|
226
|
+
}
|
227
|
+
}
|
228
|
+
} else {
|
229
|
+
me.selectRoot();
|
230
|
+
}
|
231
|
+
|
232
|
+
if (newRecord) {
|
233
|
+
const itemId = view.getItemId(newRecord[me.view.getKeyProperty()]);
|
234
|
+
|
235
|
+
me.selectAndScrollIntoView(itemId);
|
236
|
+
}
|
237
|
+
}
|
238
|
+
|
239
|
+
/**
|
240
|
+
* Select an item and scroll the tree to show the item in the center
|
241
|
+
* @param {String} itemId
|
242
|
+
*/
|
243
|
+
selectAndScrollIntoView(itemId) {
|
244
|
+
const me = this;
|
245
|
+
|
246
|
+
me.select(itemId);
|
247
|
+
|
248
|
+
Neo.main.DomAccess.scrollIntoView({
|
249
|
+
id : itemId,
|
250
|
+
block: 'center'
|
251
|
+
});
|
252
|
+
}
|
253
|
+
|
254
|
+
/**
|
255
|
+
* Select the root item of the tree
|
256
|
+
*/
|
257
|
+
selectRoot() {
|
258
|
+
const me = this,
|
259
|
+
view = me.view,
|
260
|
+
store = view.store;
|
261
|
+
let rootItemId;
|
262
|
+
|
263
|
+
for (let record of store.items) {
|
264
|
+
if (!record.parentId) {
|
265
|
+
rootItemId = view.getItemId(record[me.view.getKeyProperty()]);
|
266
|
+
break;
|
267
|
+
}
|
268
|
+
}
|
269
|
+
|
270
|
+
me.selectAndScrollIntoView(rootItemId);
|
271
|
+
}
|
272
|
+
|
273
|
+
/**
|
274
|
+
* Return the parent record if any
|
275
|
+
* @param {Object} record
|
276
|
+
* @param {String} itemId
|
277
|
+
* @param {Boolean} collapse
|
278
|
+
*/
|
279
|
+
toggleCollapsed(record, itemId, collapse) {
|
280
|
+
const me = this,
|
281
|
+
item = me.view.getVdomChild(itemId),
|
282
|
+
clsFn = collapse ? 'remove' : 'add';
|
283
|
+
|
284
|
+
NeoArray[clsFn](item.cls, 'neo-folder-open');
|
285
|
+
me.view.update();
|
286
|
+
|
287
|
+
record.collapsed = collapse;
|
288
|
+
}
|
289
|
+
}
|
290
|
+
|
291
|
+
Neo.applyClassConfig(TreeAccordionModel);
|
292
|
+
|
293
|
+
export default TreeAccordionModel;
|
@@ -22,7 +22,7 @@ class TreeModel extends ListModel {
|
|
22
22
|
* @param {Object} data
|
23
23
|
*/
|
24
24
|
onKeyDownEnter(data) {
|
25
|
-
|
25
|
+
Neo.log('onKeyDownEnter', data)
|
26
26
|
}
|
27
27
|
|
28
28
|
/**
|
@@ -30,10 +30,10 @@ class TreeModel extends ListModel {
|
|
30
30
|
* @param {Number} step
|
31
31
|
*/
|
32
32
|
onNavKey(data, step) {
|
33
|
-
|
33
|
+
Neo.log('onNavKey', data, step)
|
34
34
|
}
|
35
35
|
}
|
36
36
|
|
37
37
|
Neo.applyClassConfig(TreeModel);
|
38
38
|
|
39
|
-
export default TreeModel;
|
39
|
+
export default TreeModel;
|