neo.mjs 5.16.4 → 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.
@@ -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
- console.log('onKeyDownEnter', data)
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
- console.log('onNavKey', data, step)
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;
@@ -0,0 +1,280 @@
1
+ import TreeList from '../tree/List.mjs';
2
+ import TreeAccordionModel from "../selection/TreeAccordionModel.mjs";
3
+ import NeoArray from "../util/Array.mjs";
4
+ import ClassSystemUtil from "../util/ClassSystem.mjs";
5
+
6
+ /**
7
+ * @class Neo.tree.Accordion
8
+ * @extends Neo.tree.List
9
+ */
10
+ class AccordionTree extends TreeList {
11
+ static config = {
12
+ /**
13
+ * @member {String} className='Neo.tree.Accordion'
14
+ * @protected
15
+ */
16
+ className: 'Neo.tree.Accordion',
17
+ /**
18
+ * @member {String} ntype='treeaccordion'
19
+ * @protected
20
+ */
21
+ ntype: 'treeaccordion',
22
+ /**
23
+ * @member {String[]} baseCls=['neo-tree-accordion']
24
+ */
25
+ baseCls: ['neo-tree-list'],
26
+ /**
27
+ * @member {Boolean} showCollapseExpandAllIcons=true
28
+ */
29
+ showCollapseExpandAllIcons: false,
30
+ /**
31
+ * Set to false will auto expand root parent items and disallow collapsing
32
+ * @member {Boolean} rootParentIsCollapsible=false
33
+ */
34
+ rootParentsAreCollapsible_: false,
35
+ /**
36
+ * Set to false to hide the initial root item
37
+ * @member {Boolean} firstParentIsVisible=true
38
+ */
39
+ firstParentIsVisible_: true,
40
+ /**
41
+ * @member {Object} _vdom
42
+ */
43
+ _vdom:
44
+ {
45
+ cn: [
46
+ {tag: 'ul', cls: ['neo-list-container', 'neo-list', 'neo-accordion-style'], tabIndex: -1, cn: []}
47
+ ]
48
+ }
49
+ }
50
+
51
+ onConstructed() {
52
+ super.onConstructed();
53
+ let me = this;
54
+
55
+ me.addDomListeners({
56
+ focusin: me.onFocus,
57
+ scope : me
58
+ })
59
+ }
60
+
61
+ /**
62
+ * Called when changing firstParentIsVisible
63
+ * First store item gets marked and additional css class
64
+ *
65
+ * @param {Boolean} value
66
+ * @param {Boolean} oldValue
67
+ */
68
+ afterSetFirstParentIsVisible(value, oldValue) {
69
+ const toggleFn = !value ? 'addCls' : 'removeCls';
70
+
71
+ this[toggleFn]('first-parent-not-visible');
72
+
73
+ if (this.store.first()) {
74
+ this.store.first().visible = value;
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Called when changing rootParentsAreCollapsible
80
+ * Ensures that root items are expanded if not collapsible
81
+ *
82
+ * @param {Boolean} value
83
+ * @param {Boolean} oldValue
84
+ */
85
+ afterSetRootParentsAreCollapsible(value, oldValue) {
86
+ const me = this,
87
+ toggleFn = !value ? 'addCls' : 'removeCls';
88
+
89
+ me[toggleFn]('root-not-collapsible');
90
+
91
+ if (me.rendered && value === false) {
92
+ const store = me.store;
93
+
94
+ store.items.forEach(record => {
95
+ if (record.parentId === null && !record.isLeaf) {
96
+ me.expandItem(record);
97
+ }
98
+ })
99
+ }
100
+ }
101
+
102
+ /**
103
+ * Triggered before the selectionModel config gets changed.
104
+ * @param {Neo.selection.Model} value
105
+ * @param {Neo.selection.Model} oldValue
106
+ * @returns {Neo.selection.Model}
107
+ * @protected
108
+ */
109
+ beforeSetSelectionModel(value, oldValue) {
110
+ oldValue?.destroy();
111
+
112
+ return ClassSystemUtil.beforeSetInstance(value, TreeAccordionModel);
113
+ }
114
+
115
+ /**
116
+ * @param {String} [parentId] The parent node
117
+ * @param {Object} [vdomRoot] The vdom template root for the current sub tree
118
+ * @param {Number} level The hierarchy level of the tree
119
+ * @returns {Object} vdomRoot
120
+ * @protected
121
+ */
122
+ createItems(parentId, vdomRoot, level) {
123
+ let me = this,
124
+ items = me.store.find('parentId', parentId),
125
+ itemCls = me.itemCls,
126
+ folderCls = me.folderCls,
127
+ cls, tmpRoot;
128
+
129
+ if (items.length > 0) {
130
+ if (!vdomRoot.cn) {
131
+ vdomRoot.cn = [];
132
+ }
133
+
134
+ if (parentId !== null) {
135
+ vdomRoot.cn.push({
136
+ tag : 'ul',
137
+ cls : ['neo-list'],
138
+ cn : [],
139
+ style: {
140
+ paddingLeft: '15px'
141
+ }
142
+ });
143
+
144
+ tmpRoot = vdomRoot.cn[vdomRoot.cn.length - 1];
145
+ } else {
146
+ tmpRoot = vdomRoot;
147
+ }
148
+
149
+ items.forEach(item => {
150
+ cls = [itemCls];
151
+
152
+ if (item.isLeaf) {
153
+ cls.push(itemCls + (item.singleton ? '-leaf-singleton' : '-leaf'));
154
+ } else {
155
+ cls.push(folderCls);
156
+
157
+ if (!item.parentId && !me.rootParentsAreCollapsible) {
158
+
159
+ cls.push('neo-not-collapsible');
160
+ if (item.collapsed) {
161
+ item.collapsed = false;
162
+ }
163
+ }
164
+ if (!item.collapsed) {
165
+ cls.push('neo-folder-open');
166
+ }
167
+ }
168
+
169
+ tmpRoot.cn.push({
170
+ tag : 'li',
171
+ cls,
172
+ id : me.getItemId(item.id),
173
+ cn : [{
174
+ tag : 'span',
175
+ cls : ['neo-accordion-item-icon', item.iconCls],
176
+ removeDom: !item.isLeaf
177
+ }, {
178
+ cls : [itemCls + '-content'],
179
+ style: {pointerEvents: 'none'},
180
+ cn : [{
181
+ tag : 'span',
182
+ cls : [itemCls + '-content-header'],
183
+ innerHTML: item.name
184
+ }, {
185
+ tag : 'span',
186
+ cls : [itemCls + '-content-text'],
187
+ innerHTML: item.content
188
+ }]
189
+ }],
190
+ style: {
191
+ padding : '10px',
192
+ position: item.isLeaf ? null : 'sticky',
193
+ top : item.isLeaf ? null : (level * 38) + 'px',
194
+ zIndex : item.isLeaf ? null : (20 / (level + 1)),
195
+ }
196
+ });
197
+
198
+ tmpRoot = me.createItems(item.id, tmpRoot, level + 1);
199
+ });
200
+ }
201
+
202
+ return vdomRoot;
203
+ }
204
+
205
+
206
+ /**
207
+ * Expands an item based on the reord
208
+ * @param {Object} record
209
+ */
210
+ expandItem(record) {
211
+ const me = this,
212
+ itemId = me.getItemId(record[me.getKeyProperty()]),
213
+ item = me.getVdomChild(itemId);
214
+
215
+ record.collapsed = false;
216
+
217
+ NeoArray.add(item.cls, 'neo-folder-open');
218
+ me.update();
219
+ }
220
+
221
+ /**
222
+ * @param {Object} item
223
+ * @param {Object} data
224
+ */
225
+ onItemClick(item, data) {
226
+ super.onItemClick(item, data);
227
+
228
+ const me = this,
229
+ selectionModel = me.selectionModel,
230
+ itemId = item.id,
231
+ // ! todo make it String
232
+ id = Number(itemId.split('__')[1]),
233
+ record = me.store.get(id);
234
+
235
+ selectionModel.select(item.id);
236
+
237
+ if (!record.isLeaf) {
238
+ /**
239
+ * The folderItemClick event fires when a click occurs on a list item which does have child items.
240
+ * Passes the item record to the event handler.
241
+ * @event folderItemClick
242
+ * @returns {Object} record
243
+ */
244
+ me.fire('folderItemClick', {record});
245
+
246
+ record.collapsed = !record.collapsed
247
+ }
248
+ }
249
+
250
+ /**
251
+ * To place the root item at the correct location
252
+ * @returns {Object}
253
+ */
254
+ getListItemsRoot() {
255
+ return this.vdom.cn[0];
256
+ }
257
+
258
+ /**
259
+ * Accordion gaining focus without selection => setSelection
260
+ * @param {Object} data
261
+ */
262
+ onFocus(data) {
263
+ const me = this,
264
+ selModel = me.selectionModel,
265
+ selection = selModel.getSelection()[0];
266
+
267
+ if (!selection) selModel.selectRoot();
268
+ }
269
+
270
+ // Todo Might be needed
271
+ onStoreLoad() {
272
+ }
273
+
274
+ onStoreRecordChange() {
275
+ }
276
+ }
277
+
278
+ Neo.applyClassConfig(AccordionTree);
279
+
280
+ export default AccordionTree