datafolder 1.0.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.
Files changed (182) hide show
  1. package/README.md +169 -0
  2. package/index.js +3 -0
  3. package/package.json +27 -0
  4. package/sample.js +37 -0
  5. package/scripts/airbnb.js +73 -0
  6. package/scripts/airbnb2.js +74 -0
  7. package/scripts/airbnb2_queries.js +231 -0
  8. package/scripts/airbnb_queries.js +264 -0
  9. package/scripts/downloadFile.js +42 -0
  10. package/scripts/express.js +46 -0
  11. package/scripts/fastify.js +79 -0
  12. package/scripts/flights.js +75 -0
  13. package/scripts/flights2.js +81 -0
  14. package/scripts/flights2_queries.js +453 -0
  15. package/scripts/flights_queries.js +403 -0
  16. package/src/QyAcl.js +252 -0
  17. package/src/QyAclSaver.js +14 -0
  18. package/src/QyAclSaver_Worker.js +29 -0
  19. package/src/QyAclWriter.js +19 -0
  20. package/src/QyBatch.js +501 -0
  21. package/src/QyBinWriter.js +140 -0
  22. package/src/QyCD.js +61 -0
  23. package/src/QyCache.js +212 -0
  24. package/src/QyCompressor.js +60 -0
  25. package/src/QyCompressor_Worker.js +13 -0
  26. package/src/QyDB.js +122 -0
  27. package/src/QyDefaultOptions.js +59 -0
  28. package/src/QyDir.js +192 -0
  29. package/src/QyFetch.js +38 -0
  30. package/src/QyFile.js +124 -0
  31. package/src/QyFileContentUtils.js +175 -0
  32. package/src/QyFileLogger.js +58 -0
  33. package/src/QyIndex.js +237 -0
  34. package/src/QyIndexPropTree.js +92 -0
  35. package/src/QyIntersectNode.js +48 -0
  36. package/src/QyItem.js +105 -0
  37. package/src/QyJsonSaver.js +20 -0
  38. package/src/QyKVData.js +289 -0
  39. package/src/QyKVDataCleaner.js +17 -0
  40. package/src/QyKVDataCleaner_Worker.js +94 -0
  41. package/src/QyKVDataLoader.js +65 -0
  42. package/src/QyKVDataSaver.js +17 -0
  43. package/src/QyKVDataSaver_Worker.js +81 -0
  44. package/src/QyLogFileSaver.js +14 -0
  45. package/src/QyLogFileSaver_Worker.js +26 -0
  46. package/src/QyLogger.js +76 -0
  47. package/src/QyMessageWorker.js +41 -0
  48. package/src/QyMessager.js +84 -0
  49. package/src/QyMessager_Run.js +13 -0
  50. package/src/QyMod.js +15 -0
  51. package/src/QyOpRunner.js +73 -0
  52. package/src/QyQueue.js +60 -0
  53. package/src/QySaver.js +83 -0
  54. package/src/QySnapshotCompactor.js +17 -0
  55. package/src/QySnapshotCompactor_Worker.js +151 -0
  56. package/src/QySnapshotSaver.js +17 -0
  57. package/src/QySnapshotSaver_Worker.js +164 -0
  58. package/src/QySnapshots.js +354 -0
  59. package/src/QyStructure.js +50 -0
  60. package/src/QyTrigger.js +133 -0
  61. package/src/QyUnionNode.js +105 -0
  62. package/src/QyUtils.js +250 -0
  63. package/src/QyWorker.js +33 -0
  64. package/src/hackernews.js +93 -0
  65. package/src/rpc/evalCode.js +9 -0
  66. package/src/rpc/getFileContent.js +16 -0
  67. package/src/rpc/getSubItems.js +32 -0
  68. package/src/rpc/queryFiles.js +33 -0
  69. package/src/rpc/reloadRpc.js +1 -0
  70. package/src/simulate.js +114 -0
  71. package/src/simulate2.js +110 -0
  72. package/src/soc_pokec.js +43 -0
  73. package/static/bootstrap-5.3.8-dist/css/bootstrap.min.css +6 -0
  74. package/static/bootstrap-5.3.8-dist/js/bootstrap.bundle.js +6312 -0
  75. package/static/datafolderui/QyStyledElement.js +30 -0
  76. package/static/datafolderui/QyUIFolderTree.js +203 -0
  77. package/static/datafolderui/QyUIUtils.js +42 -0
  78. package/static/datafolderui/display-tree.js +108 -0
  79. package/static/datafolderui/explore-tree.js +122 -0
  80. package/static/datafolderui/folder-tree.js +5 -0
  81. package/static/datafolderui/hscrpt.js +25 -0
  82. package/static/datafolderui/index.css +6 -0
  83. package/static/datafolderui/index.html +20 -0
  84. package/static/datafolderui/json-view.js +74 -0
  85. package/static/datafolderui/kv-input.js +66 -0
  86. package/static/datafolderui/list-input.js +52 -0
  87. package/static/datafolderui/main-page.js +98 -0
  88. package/static/datafolderui/num-input.js +27 -0
  89. package/static/datafolderui/query-editor.js +229 -0
  90. package/static/datafolderui/querySelectorShadowRoot.js +24 -0
  91. package/static/datafolderui/resizable-input.js +67 -0
  92. package/static/favicon.ico +0 -0
  93. package/static/icons-1.13.1/font/bootstrap-icons.min.css +5 -0
  94. package/static/icons-1.13.1/font/fonts/bootstrap-icons.woff +0 -0
  95. package/static/icons-1.13.1/font/fonts/bootstrap-icons.woff2 +0 -0
  96. package/static/json-view/dist/jsonview.js +433 -0
  97. package/static/tree.js/tree.css +106 -0
  98. package/static/tree.js/tree.js +374 -0
  99. package/static/w2ui/dist/w2ui.min.css +2 -0
  100. package/static/w2ui/src/less/icons/icons.json +1 -0
  101. package/static/w2ui/src/less/icons/preview.html +33 -0
  102. package/static/w2ui/src/less/icons/readme.md +15 -0
  103. package/static/w2ui/src/less/icons/svg/box.svg +3 -0
  104. package/static/w2ui/src/less/icons/svg/check.svg +3 -0
  105. package/static/w2ui/src/less/icons/svg/colors.svg +3 -0
  106. package/static/w2ui/src/less/icons/svg/columns.svg +5 -0
  107. package/static/w2ui/src/less/icons/svg/cross.svg +4 -0
  108. package/static/w2ui/src/less/icons/svg/drop.svg +6 -0
  109. package/static/w2ui/src/less/icons/svg/empty.svg +3 -0
  110. package/static/w2ui/src/less/icons/svg/eye-dropper.svg +4 -0
  111. package/static/w2ui/src/less/icons/svg/info.svg +8 -0
  112. package/static/w2ui/src/less/icons/svg/paste.svg +4 -0
  113. package/static/w2ui/src/less/icons/svg/pencil.svg +5 -0
  114. package/static/w2ui/src/less/icons/svg/plus.svg +3 -0
  115. package/static/w2ui/src/less/icons/svg/reload.svg +5 -0
  116. package/static/w2ui/src/less/icons/svg/search.svg +3 -0
  117. package/static/w2ui/src/less/icons/svg/settings.svg +8 -0
  118. package/static/w2ui/src/less/icons/w2ui-font.css +36 -0
  119. package/static/w2ui/src/less/icons/w2ui-font.woff +0 -0
  120. package/static/w2ui/src/less/src/buttons.less +184 -0
  121. package/static/w2ui/src/less/src/common.less +541 -0
  122. package/static/w2ui/src/less/src/fields.less +302 -0
  123. package/static/w2ui/src/less/src/form.less +319 -0
  124. package/static/w2ui/src/less/src/grid.less +1057 -0
  125. package/static/w2ui/src/less/src/icons.less +37 -0
  126. package/static/w2ui/src/less/src/layout.less +86 -0
  127. package/static/w2ui/src/less/src/mixins.less +87 -0
  128. package/static/w2ui/src/less/src/popup.less +277 -0
  129. package/static/w2ui/src/less/src/sidebar.less +343 -0
  130. package/static/w2ui/src/less/src/tabs.less +145 -0
  131. package/static/w2ui/src/less/src/toolbar.less +219 -0
  132. package/static/w2ui/src/less/src/tooltip.less +948 -0
  133. package/static/w2ui/src/less/w2ui.less +196 -0
  134. package/static/w2ui/src/locale/az-az.json +114 -0
  135. package/static/w2ui/src/locale/ba-ba.json +114 -0
  136. package/static/w2ui/src/locale/bg-bg.json +114 -0
  137. package/static/w2ui/src/locale/ca-es.json +114 -0
  138. package/static/w2ui/src/locale/da-dk.json +114 -0
  139. package/static/w2ui/src/locale/de-de.json +114 -0
  140. package/static/w2ui/src/locale/en-gb.json +17 -0
  141. package/static/w2ui/src/locale/en-us.json +17 -0
  142. package/static/w2ui/src/locale/es-ar.json +115 -0
  143. package/static/w2ui/src/locale/es-cr.json +119 -0
  144. package/static/w2ui/src/locale/es-es.json +114 -0
  145. package/static/w2ui/src/locale/es-mx.json +114 -0
  146. package/static/w2ui/src/locale/fr-fr.json +114 -0
  147. package/static/w2ui/src/locale/gl-es.json +114 -0
  148. package/static/w2ui/src/locale/hr-hr.json +114 -0
  149. package/static/w2ui/src/locale/hu-hu.json +114 -0
  150. package/static/w2ui/src/locale/id-id.json +114 -0
  151. package/static/w2ui/src/locale/it-it.json +114 -0
  152. package/static/w2ui/src/locale/ja-jp.json +114 -0
  153. package/static/w2ui/src/locale/kk-kz.json +116 -0
  154. package/static/w2ui/src/locale/ko-kr.json +114 -0
  155. package/static/w2ui/src/locale/lt-lt.json +114 -0
  156. package/static/w2ui/src/locale/nl-nl.json +114 -0
  157. package/static/w2ui/src/locale/no-no.json +114 -0
  158. package/static/w2ui/src/locale/pl-pl.json +114 -0
  159. package/static/w2ui/src/locale/pt-br.json +114 -0
  160. package/static/w2ui/src/locale/ru-ru.json +116 -0
  161. package/static/w2ui/src/locale/sk-sk.json +114 -0
  162. package/static/w2ui/src/locale/sl-si.json +116 -0
  163. package/static/w2ui/src/locale/sr-rs.json +114 -0
  164. package/static/w2ui/src/locale/tr-tr.json +114 -0
  165. package/static/w2ui/src/locale/uk-ua.json +114 -0
  166. package/static/w2ui/src/locale/zh-cn.json +114 -0
  167. package/static/w2ui/src/locale/zh-tw.json +114 -0
  168. package/static/w2ui/src/query.js +583 -0
  169. package/static/w2ui/src/readme.md +11 -0
  170. package/static/w2ui/src/w2base.js +311 -0
  171. package/static/w2ui/src/w2compat.js +207 -0
  172. package/static/w2ui/src/w2field.js +1854 -0
  173. package/static/w2ui/src/w2form.js +2411 -0
  174. package/static/w2ui/src/w2grid.js +8916 -0
  175. package/static/w2ui/src/w2layout.js +1194 -0
  176. package/static/w2ui/src/w2locale.js +129 -0
  177. package/static/w2ui/src/w2popup.js +909 -0
  178. package/static/w2ui/src/w2sidebar.js +1769 -0
  179. package/static/w2ui/src/w2tabs.js +669 -0
  180. package/static/w2ui/src/w2toolbar.js +1180 -0
  181. package/static/w2ui/src/w2tooltip.js +3134 -0
  182. package/static/w2ui/src/w2utils.js +2457 -0
@@ -0,0 +1,1769 @@
1
+ /**
2
+ * Part of w2ui 2.0 library
3
+ * - Dependencies: mQuery, w2utils, w2base, w2tooltip, w2menu
4
+ *
5
+ * == TODO ==
6
+ * - dbl click should be like it is in grid (with timer not HTML dbl click event)
7
+ * - node.style is misleading - should be there to apply color for example
8
+ * - node.plus - is not working
9
+ *
10
+ * == 2.0 changes
11
+ * - remove jQuery dependency
12
+ * - deprecarted obj.img, node.img
13
+ * - CSP - fixed inline events
14
+ * - observeResize for the box
15
+ * - search(..., compare) - comparison function
16
+ * - editable = true
17
+ * - edit(id) - new method
18
+ * - onEdit, onRename - new events
19
+ * - reorder = true - to allow reorder
20
+ * - mouseDown - for reorder
21
+ * - onReorder, onDragStart, onDragOver - events
22
+ * - this.mutlti - for multi select (ctrl for one at a time and shift for range)
23
+ * - onSelect, onUnselect - new events
24
+ * - prev(), next(), getChain()
25
+ */
26
+
27
+ import { w2base } from './w2base.js'
28
+ import { w2ui, w2utils } from './w2utils.js'
29
+ import { query } from './query.js'
30
+ import { w2tooltip, w2menu } from './w2tooltip.js'
31
+
32
+ class w2sidebar extends w2base {
33
+ constructor(options) {
34
+ super(options.name)
35
+ this.name = null
36
+ this.box = null
37
+ this.sidebar = null
38
+ this.parent = null
39
+ this.nodes = [] // Sidebar child nodes
40
+ this.menu = []
41
+ this.routeData = {} // data for dynamic routes
42
+ this.selected = null // current selected node (readonly)
43
+ this.icon = null
44
+ this.style = ''
45
+ this.topHTML = ''
46
+ this.bottomHTML = ''
47
+ this.multi = false
48
+ this.editable = false
49
+ this.reorder = false
50
+ this.flatButton = false
51
+ this.keyboard = true
52
+ this.flat = false
53
+ this.hasFocus = false
54
+ this.levelPadding = 12
55
+ this.toggleAlign = 'right' // can be left or right
56
+ this.skipRefresh = false
57
+ this.tabIndex = null // will only be set if > 0 and not null
58
+ this.handle = { width: 0, style: '', text: '', tooltip: '' },
59
+ this.badge = null
60
+ this.onClick = null // Fire when user click on Node Text
61
+ this.onSelect = null
62
+ this.onUnselect = null
63
+ this.onDblClick = null // Fire when user dbl clicks
64
+ this.onMouseEnter = null // mouse enter/leave over an item
65
+ this.onMouseLeave = null
66
+ this.onContextMenu = null
67
+ this.onMenuClick = null // when context menu item selected
68
+ this.onExpand = null // Fire when node expands
69
+ this.onCollapse = null // Fire when node collapses
70
+ this.onKeydown = null
71
+ this.onRender = null
72
+ this.onRefresh = null
73
+ this.onResize = null
74
+ this.onDestroy = null
75
+ this.onFocus = null
76
+ this.onBlur = null
77
+ this.onFlat = null
78
+ this.onEdit = null
79
+ this.onRename = null
80
+ this.onReorder = null
81
+ this.onDragStart = null
82
+ this.onDragOver = null
83
+ this.node_template = {
84
+ id: null,
85
+ text: '',
86
+ order: null,
87
+ count: null,
88
+ icon: null,
89
+ nodes: [],
90
+ style: '', // additional style for subitems
91
+ route: null,
92
+ selected: false,
93
+ expanded: false,
94
+ hidden: false,
95
+ disabled: false,
96
+ group: false, // if true, it will build as a group
97
+ groupShowHide: true,
98
+ collapsible: false,
99
+ plus: false, // if true, plus will be shown even if there is no sub nodes
100
+ childOffset: 0,
101
+ // events
102
+ onClick: null,
103
+ onDblClick: null,
104
+ onContextMenu: null,
105
+ onExpand: null,
106
+ onCollapse: null,
107
+ // internal
108
+ parent: null, // node object
109
+ sidebar: null
110
+ }
111
+ this.last = {
112
+ badge: {},
113
+ renaming: false,
114
+ move: null, // object, move details
115
+ }
116
+ let nodes = options.nodes
117
+ delete options.nodes
118
+ // mix in options
119
+ Object.assign(this, options)
120
+ // add item via method to makes sure item_template is applied
121
+ if (Array.isArray(nodes)) this.add(nodes)
122
+ // need to reassign back to keep it in config
123
+ options.nodes = nodes
124
+
125
+ // render if box specified
126
+ if (typeof this.box == 'string') this.box = query(this.box).get(0)
127
+ if (this.box) this.render(this.box)
128
+ }
129
+
130
+ add(parent, nodes) {
131
+ if (arguments.length == 1) {
132
+ // need to be in reverse order
133
+ nodes = arguments[0]
134
+ parent = this
135
+ }
136
+ if (typeof parent == 'string') parent = this.get(parent)
137
+ if (parent == null || parent == '') parent = this
138
+ return this.insert(parent, null, nodes)
139
+ }
140
+
141
+ insert(parent, before, nodes) {
142
+ let txt, ind, tmp, node, nd
143
+ if (arguments.length == 2 && typeof parent == 'string') {
144
+ // need to be in reverse order
145
+ nodes = arguments[1]
146
+ before = arguments[0]
147
+ if (before != null) {
148
+ ind = this.get(before)
149
+ if (ind == null) {
150
+ if (!Array.isArray(nodes)) nodes = [nodes]
151
+ if (nodes[0].caption != null && nodes[0].text == null) {
152
+ console.log('NOTICE: sidebar node.caption property is deprecated, please use node.text. Node -> ', nodes[0])
153
+ nodes[0].text = nodes[0].caption
154
+ }
155
+ txt = nodes[0].text
156
+ console.log('ERROR: Cannot insert node "'+ txt +'" because cannot find node "'+ before +'" to insert before.')
157
+ return null
158
+ }
159
+ parent = this.get(before).parent
160
+ } else {
161
+ parent = this
162
+ }
163
+ }
164
+ if (typeof parent == 'string') parent = this.get(parent)
165
+ if (parent == null || parent == '') parent = this
166
+ if (!Array.isArray(nodes)) nodes = [nodes]
167
+ for (let o = 0; o < nodes.length; o++) {
168
+ node = nodes[o]
169
+ if (node.caption != null && node.text == null) {
170
+ console.log('NOTICE: sidebar node.caption property is deprecated, please use node.text')
171
+ node.text = node.caption
172
+ }
173
+ if (typeof node.id == null) {
174
+ txt = node.text
175
+ console.log('ERROR: Cannot insert node "'+ txt +'" because it has no id.')
176
+ continue
177
+ }
178
+ if (this.get(this, node.id) != null) {
179
+ console.log('ERROR: Cannot insert node with id='+ node.id +' (text: '+ node.text + ') because another node with the same id already exists.')
180
+ continue
181
+ }
182
+ tmp = Object.assign({}, this.node_template, node)
183
+ tmp.sidebar = this
184
+ tmp.parent = parent
185
+ nd = tmp.nodes || []
186
+ tmp.nodes = [] // very important to re-init empty nodes array
187
+ if (before == null) { // append to the end
188
+ parent.nodes.push(tmp)
189
+ } else {
190
+ ind = this.get(parent, before, true)
191
+ if (ind == null) {
192
+ console.log('ERROR: Cannot insert node "'+ node.text +'" because cannot find node "'+ before +'" to insert before.')
193
+ return null
194
+ }
195
+ parent.nodes.splice(ind, 0, tmp)
196
+ }
197
+ if (nd.length > 0) {
198
+ this.insert(tmp, null, nd)
199
+ }
200
+ }
201
+ if (!this.skipRefresh) this.refresh(parent.id)
202
+ return tmp
203
+ }
204
+
205
+ remove() { // multiple arguments
206
+ let effected = 0
207
+ let node
208
+ Array.from(arguments).forEach(arg => {
209
+ node = this.get(arg)
210
+ if (node == null) return
211
+ if (this.selected != null) {
212
+ if (Array.isArray(this.selected)) {
213
+ this.selected.splice(this.selected.indexOf(node.id), 1)
214
+ } else if (this.selected === node.id) {
215
+ this.selected = null
216
+ }
217
+ }
218
+ let ind = this.get(node.parent, arg, true)
219
+ if (ind == null) return
220
+ if (node.parent.nodes[ind].selected) node.sidebar.unselect(node.id)
221
+ node.parent.nodes.splice(ind, 1)
222
+ node.parent.collapsible = node.parent.nodes.length > 0
223
+ effected++
224
+ })
225
+ if (!this.skipRefresh) {
226
+ if (effected > 0 && arguments.length == 1) this.refresh(node.parent.id); else this.refresh()
227
+ }
228
+ return effected
229
+ }
230
+
231
+ set(parent, id, node) {
232
+ if (arguments.length == 2) {
233
+ // need to be in reverse order
234
+ node = id
235
+ id = parent
236
+ parent = this
237
+ }
238
+ // searches all nested nodes
239
+ if (typeof parent == 'string') parent = this.get(parent)
240
+ if (parent.nodes == null) return null
241
+ for (let i = 0; i < parent.nodes.length; i++) {
242
+ if (parent.nodes[i].id === id) {
243
+ // see if quick update is possible
244
+ let res = this.update(id, node)
245
+ if (Object.keys(res).length != 0) {
246
+ // make sure nodes inserted correctly
247
+ let nodes = node.nodes
248
+ w2utils.extend(parent.nodes[i], node, (nodes != null ? { nodes: [] } : {}))
249
+ if (nodes != null) {
250
+ this.add(parent.nodes[i], nodes)
251
+ }
252
+ if (!this.skipRefresh) this.refresh(id)
253
+ }
254
+ return true
255
+ } else {
256
+ let rv = this.set(parent.nodes[i], id, node)
257
+ if (rv) return true
258
+ }
259
+ }
260
+ return false
261
+ }
262
+
263
+ get(parent, id, returnIndex) { // can be just called get(id) or get(id, true)
264
+ if (arguments.length === 0) {
265
+ let all = []
266
+ let tmp = this.find({})
267
+ for (let t = 0; t < tmp.length; t++) {
268
+ if (tmp[t].id != null) all.push(tmp[t].id)
269
+ }
270
+ return all
271
+ } else {
272
+ if (arguments.length == 1 || (arguments.length == 2 && id === true) ) {
273
+ // need to be in reverse order
274
+ returnIndex = id
275
+ id = parent
276
+ parent = this
277
+ }
278
+ // searches all nested nodes
279
+ if (typeof parent == 'string') parent = this.get(parent)
280
+ if (parent.nodes == null) return null
281
+ for (let i = 0; i < parent.nodes.length; i++) {
282
+ if (parent.nodes[i].id == id) {
283
+ if (returnIndex === true) return i; else return parent.nodes[i]
284
+ } else {
285
+ let rv = this.get(parent.nodes[i], id, returnIndex)
286
+ if (rv || rv === 0) return rv
287
+ }
288
+ }
289
+ return null
290
+ }
291
+ }
292
+
293
+ setCount(id, count, options = {}) {
294
+ let node = this.get(id)
295
+ if (node.group) {
296
+ console.log(`Node "${id}" is a group and groups cannot have counts or badges.`)
297
+ return
298
+ }
299
+ this.last.badge[id] = {
300
+ className: options.className ?? '',
301
+ style: options.style ?? ''
302
+ }
303
+ let btn = query(this.box).find(`#node_${w2utils.escapeId(id)} .w2ui-node-badge`)
304
+ if (btn.length > 0) {
305
+ btn.removeClass()
306
+ .addClass(`w2ui-node-badge ${options.className ?? 'w2ui-node-count'}`)
307
+ .text(count)
308
+ .get(0).style.cssText = options.style || ''
309
+ let item = this.get(id)
310
+ item.count = count
311
+ } else if (!options.noRepeat) {
312
+ this.set(id, { count })
313
+ options.noRepeat = true
314
+ queueMicrotask(() => this.setCount(id, count, options)) // to update styles
315
+ }
316
+ }
317
+
318
+ find(parent, params, results) { // can be just called find({ selected: true })
319
+ // TODO: rewrite with this.each()
320
+ if (arguments.length == 1) {
321
+ // need to be in reverse order
322
+ params = parent
323
+ parent = this
324
+ }
325
+ if (!results) results = []
326
+ // searches all nested nodes
327
+ if (typeof parent == 'string') parent = this.get(parent)
328
+ if (parent.nodes == null) return results
329
+ for (let i = 0; i < parent.nodes.length; i++) {
330
+ let match = true
331
+ for (let prop in params) { // params is an object
332
+ if (parent.nodes[i][prop] != params[prop]) match = false
333
+ }
334
+ if (match) results.push(parent.nodes[i])
335
+ if (parent.nodes[i].nodes.length > 0) results = this.find(parent.nodes[i], params, results)
336
+ }
337
+ return results
338
+ }
339
+
340
+ sort(options, nodes) {
341
+ // default options
342
+ if (!options || typeof options != 'object') options = {}
343
+ if (options.foldersFirst == null) options.foldersFirst = true
344
+ if (options.caseSensitive == null) options.caseSensitive = false
345
+ if (options.reverse == null) options.reverse = false
346
+
347
+ if (nodes == null) {
348
+ nodes = this.nodes
349
+ }
350
+ nodes.sort((a, b) => {
351
+ // folders first
352
+ let isAfolder = (a.nodes && a.nodes.length > 0)
353
+ let isBfolder = (b.nodes && b.nodes.length > 0)
354
+ // both folder or both not folders
355
+ if (options.foldersFirst === false || (!isAfolder && !isBfolder) || (isAfolder && isBfolder)) {
356
+ let aText = a.text
357
+ let bText = b.text
358
+ if (a.order != null) aText = a.order
359
+ if (b.order != null) bText = b.order
360
+ if (!options.caseSensitive) {
361
+ aText = aText.toLowerCase()
362
+ bText = bText.toLowerCase()
363
+ }
364
+ let cmp = w2utils.naturalCompare(aText, bText)
365
+ return (cmp === 1 || cmp === -1) & options.reverse ? -cmp : cmp
366
+ }
367
+ if (isAfolder && !isBfolder) {
368
+ return !options.reverse ? -1 : 1
369
+ }
370
+ if (!isAfolder && isBfolder) {
371
+ return !options.reverse ? 1 : -1
372
+ }
373
+ })
374
+ nodes.forEach(node => {
375
+ if (node.nodes && node.nodes.length > 0) {
376
+ this.sort(options, node.nodes)
377
+ }
378
+ })
379
+ }
380
+
381
+ each(fn, nodes) {
382
+ if (nodes == null) nodes = this.nodes
383
+ nodes.forEach((node) => {
384
+ fn.call(this, node)
385
+ if (node.nodes && node.nodes.length > 0) {
386
+ this.each(fn, node.nodes)
387
+ }
388
+ })
389
+ }
390
+
391
+ search(str, compare = null) {
392
+ let count = 0
393
+ let str2 = str.toLowerCase()
394
+ this.each((node) => {
395
+ let match = false
396
+ if (typeof compare == 'function') {
397
+ match = compare(str, node)
398
+ } else {
399
+ match = !(node.text.toLowerCase().indexOf(str2) === -1)
400
+ }
401
+ if (match) {
402
+ count++
403
+ showParents(node)
404
+ node.hidden = false
405
+ } else {
406
+ node.hidden = true
407
+ }
408
+ })
409
+ this.refresh()
410
+ return count
411
+
412
+ function showParents(node) {
413
+ if (node.parent) {
414
+ node.parent.hidden = false
415
+ showParents(node.parent)
416
+ }
417
+ }
418
+ }
419
+
420
+ show() { // multiple arguments
421
+ let effected = []
422
+ Array.from(arguments).forEach(it => {
423
+ let node = this.get(it)
424
+ if (node == null || node.hidden === false) return
425
+ node.hidden = false
426
+ effected.push(node.id)
427
+ })
428
+ if (effected.length > 0) {
429
+ if (arguments.length == 1) this.refresh(arguments[0]); else this.refresh()
430
+ }
431
+ return effected
432
+ }
433
+
434
+ hide() { // multiple arguments
435
+ let effected = []
436
+ Array.from(arguments).forEach(it => {
437
+ let node = this.get(it)
438
+ if (node == null || node.hidden === true) return
439
+ node.hidden = true
440
+ effected.push(node.id)
441
+ })
442
+ if (effected.length > 0) {
443
+ if (arguments.length == 1) this.refresh(arguments[0]); else this.refresh()
444
+ }
445
+ return effected
446
+ }
447
+
448
+ enable() { // multiple arguments
449
+ let effected = []
450
+ Array.from(arguments).forEach(it => {
451
+ let node = this.get(it)
452
+ if (node == null || node.disabled === false) return
453
+ node.disabled = false
454
+ effected.push(node.id)
455
+ })
456
+ if (effected.length > 0) {
457
+ if (arguments.length == 1) this.refresh(arguments[0]); else this.refresh()
458
+ }
459
+ return effected
460
+ }
461
+
462
+ disable() { // multiple arguments
463
+ let effected = []
464
+ Array.from(arguments).forEach(it => {
465
+ let node = this.get(it)
466
+ if (node == null || node.disabled === true) return
467
+ node.disabled = true
468
+ if (node.selected) this.unselect(node.id)
469
+ effected.push(node.id)
470
+ })
471
+ if (effected.length > 0) {
472
+ if (arguments.length == 1) this.refresh(arguments[0]); else this.refresh()
473
+ }
474
+ return effected
475
+ }
476
+
477
+ select(id) {
478
+ if (Array.isArray(id)) {
479
+ [...id].forEach(id => this.select(id))
480
+ return
481
+ }
482
+ let new_node = this.get(id)
483
+ if (!new_node) return false
484
+ // event before
485
+ let edata = this.trigger('select', { target: id, id, node: new_node })
486
+ if (edata.isCancelled === true) {
487
+ return true
488
+ }
489
+ // if already selected
490
+ if (!this.multi && this.selected == id && new_node.selected) {
491
+ return false
492
+ }
493
+ let $el = query(this.box).find('#node_'+ w2utils.escapeId(id))
494
+ $el.addClass('w2ui-selected')
495
+ .find('.w2ui-icon')
496
+ .addClass('w2ui-icon-selected')
497
+ if ($el.length > 0) {
498
+ if (!this.inView(id)) this.scrollIntoView(id)
499
+ }
500
+ new_node.selected = true
501
+ if (this.multi) {
502
+ if (!Array.isArray(this.selected)) {
503
+ this.selected = this.selected ? [this.selected] : []
504
+ }
505
+ this.selected.push(id)
506
+ } else {
507
+ this.selected = this.multi ? [id] : id
508
+ }
509
+ edata.finish()
510
+ return true
511
+ }
512
+
513
+ unselect(id) {
514
+ // if no arguments provided, unselect selected node
515
+ if (arguments.length === 0) {
516
+ id = this.selected
517
+ }
518
+ if (Array.isArray(id)) {
519
+ [...id].forEach(id => this.unselect(id))
520
+ return
521
+ }
522
+ let current = this.get(id)
523
+ if (!current) return false
524
+ // event before
525
+ let edata = this.trigger('unselect', { target: id, id, node: current })
526
+ if (edata.isCancelled === true) {
527
+ return true
528
+ }
529
+ current.selected = false
530
+ query(this.box).find('#node_'+ w2utils.escapeId(id))
531
+ .removeClass('w2ui-selected')
532
+ .find('.w2ui-icon').removeClass('w2ui-icon-selected')
533
+ if (typeof this.selected == 'string' && this.selected == id) {
534
+ this.selected = null
535
+ }
536
+ if (this.multi && Array.isArray(this.selected)) {
537
+ let ind = this.selected.indexOf(id)
538
+ if (ind != -1) this.selected.splice(ind, 1)
539
+ }
540
+ edata.finish()
541
+ return true
542
+ }
543
+
544
+ async toggle(id) {
545
+ let nd = this.get(id)
546
+ if (nd == null) return false;
547
+ if(!nd._subItemsLoaded && globalThis.qydbUI?.loadSubItems) await globalThis.qydbUI.loadSubItems(nd);
548
+ if (nd.plus) {
549
+ this.set(id, { plus: false })
550
+ this.expand(id)
551
+ this.refresh(id)
552
+ return
553
+ }
554
+ if (nd.nodes.length === 0) return false
555
+ if (!nd.collapsible) return false
556
+ if (this.get(id).expanded) return this.collapse(id); else return this.expand(id)
557
+ }
558
+
559
+ collapse(id) {
560
+ let nd = this.get(id)
561
+ if (nd == null) return false
562
+ // event before
563
+ let edata = this.trigger('collapse', { target: id, object: nd, node: nd })
564
+ if (edata.isCancelled === true) return
565
+ // default action
566
+ query(this.box).find('#node_'+ w2utils.escapeId(id) +'_sub').hide()
567
+ query(this.box).find('#node_'+ w2utils.escapeId(id) +' .w2ui-expanded')
568
+ .removeClass('w2ui-expanded')
569
+ .addClass('w2ui-collapsed')
570
+ nd.expanded = false
571
+ // event after
572
+ edata.finish()
573
+ this.refresh(id)
574
+ return true
575
+ }
576
+
577
+ expand(id) {
578
+ let nd = this.get(id)
579
+ // event before
580
+ let edata = this.trigger('expand', { target: id, object: nd, node: nd })
581
+ if (edata.isCancelled === true) return
582
+ // default action
583
+ query(this.box).find('#node_'+ w2utils.escapeId(id) +'_sub')
584
+ .show()
585
+ query(this.box).find('#node_'+ w2utils.escapeId(id) +' .w2ui-collapsed')
586
+ .removeClass('w2ui-collapsed')
587
+ .addClass('w2ui-expanded')
588
+ nd.expanded = true
589
+ // event after
590
+ edata.finish()
591
+ this.refresh(id)
592
+ return true
593
+ }
594
+
595
+ collapseAll(parent) {
596
+ if (parent == null) parent = this
597
+ if (typeof parent == 'string') parent = this.get(parent)
598
+ if (parent.nodes == null) return false
599
+ for (let i = 0; i < parent.nodes.length; i++) {
600
+ if (parent.nodes[i].expanded === true) parent.nodes[i].expanded = false
601
+ if (parent.nodes[i].nodes && parent.nodes[i].nodes.length > 0) this.collapseAll(parent.nodes[i])
602
+ }
603
+ this.refresh(parent.id)
604
+ return true
605
+ }
606
+
607
+ expandAll(parent) {
608
+ if (parent == null) parent = this
609
+ if (typeof parent == 'string') parent = this.get(parent)
610
+ if (parent.nodes == null) return false
611
+ for (let i = 0; i < parent.nodes.length; i++) {
612
+ if (parent.nodes[i].expanded === false) parent.nodes[i].expanded = true
613
+ if (parent.nodes[i].nodes && parent.nodes[i].nodes.length > 0) this.expandAll(parent.nodes[i])
614
+ }
615
+ this.refresh(parent.id)
616
+ }
617
+
618
+ expandParents(id) {
619
+ let node = this.get(id)
620
+ if (node == null) return false
621
+ if (node.parent) {
622
+ if (!node.parent.expanded) {
623
+ node.parent.expanded = true
624
+ this.refresh(node.parent.id)
625
+ }
626
+ this.expandParents(node.parent.id)
627
+ }
628
+ return true
629
+ }
630
+
631
+ click(id, event) {
632
+ let obj = this
633
+ let nd = this.get(id)
634
+ if (nd == null) return
635
+ if (nd.disabled || nd.group) {
636
+ // even if disabled, it should still emit click event
637
+ let edata = obj.trigger('click', { target: id, originalEvent: event, node: nd, object: nd })
638
+ edata.finish()
639
+ return
640
+ }
641
+ // select new one
642
+ let newNode = query(obj.box).find('#node_'+ w2utils.escapeId(id))
643
+ newNode.addClass('w2ui-selected').find('.w2ui-icon').addClass('w2ui-icon-selected')
644
+ // need timeout to allow rendering
645
+ setTimeout(() => {
646
+ // event before
647
+ let edata = obj.trigger('click', { target: id, originalEvent: event, node: nd, object: nd })
648
+ if (edata.isCancelled === true) {
649
+ // restore selection
650
+ newNode.removeClass('w2ui-selected').find('.w2ui-icon').removeClass('w2ui-icon-selected')
651
+ return
652
+ }
653
+ // default action
654
+ if (this.multi) {
655
+ /**
656
+ * Multi select with shift or ctrl/meta
657
+ */
658
+ let isShift = event?.shiftKey ?? false
659
+ let isCtrl = (event?.ctrlKey || event?.metaKey) ?? false
660
+ if (typeof this.selected == 'string') {
661
+ this.selected = [this.selected]
662
+ }
663
+ if (isCtrl && !isShift) { // only Ctrl
664
+ if (this.selected?.includes(id)) {
665
+ this.unselect(id)
666
+ return
667
+ } else {
668
+ this.select(id)
669
+ }
670
+ } else if (!isCtrl && isShift) { // only Shift
671
+ // select range in between
672
+ let chain = this.getChain()
673
+ let ind1 = Math.min(this.selected.map(sel => chain.indexOf(sel))) // first item in selection
674
+ let ind2 = chain.indexOf(id)
675
+ for (let i = Math.min(ind1, ind2); i < chain.length && i <= Math.max(ind1, ind2); i++) {
676
+ let node = this.get(chain[i])
677
+ if (!this.selected.includes(chain[i]) && node.hidden != true) {
678
+ this.select(chain[i])
679
+ }
680
+ }
681
+
682
+ } else { // neither
683
+ let ids = this.selected?.filter(sid => sid != id && this.selected.includes(sid))
684
+ this.unselect(ids)
685
+ // only select if it is not selected
686
+ if (!this.selected?.includes(id)) {
687
+ this.select(id)
688
+ }
689
+ }
690
+
691
+ } else if (this.selected !== id) {
692
+ /**
693
+ * Single selection at a time
694
+ */
695
+ if (this.selected != null) this.unselect(this.selected)
696
+ this.select(id)
697
+ // route processing
698
+ if (typeof nd.route == 'string') {
699
+ let route = nd.route !== '' ? String('/'+ nd.route).replace(/\/{2,}/g, '/') : ''
700
+ let info = w2utils.parseRoute(route)
701
+ if (info.keys.length > 0) {
702
+ for (let k = 0; k < info.keys.length; k++) {
703
+ if (obj.routeData[info.keys[k].name] == null) continue
704
+ route = route.replace((new RegExp(':'+ info.keys[k].name, 'g')), obj.routeData[info.keys[k].name])
705
+ }
706
+ }
707
+ setTimeout(() => { window.location.hash = route }, 1)
708
+ }
709
+ // if sidebar is flat - show menu
710
+ if (this.flat) {
711
+ let items = _getItems(nd.nodes)
712
+ if (items.length > 0) {
713
+ this.flatMenu(newNode, items)
714
+ }
715
+
716
+ function _getItems(nodes) {
717
+ let items = nodes.map(it => {
718
+ let items = it.nodes.length > 0 ? _getItems(it.nodes) : null
719
+ return { id: it.id, text: it.text, icon: it.icon, items }
720
+ })
721
+ return items
722
+ }
723
+ }
724
+ }
725
+ // event after
726
+ edata.finish()
727
+ }, 1)
728
+ }
729
+
730
+ flatMenu(el, items) {
731
+ let self = this
732
+ let $el = query(el).find('.w2ui-node-data')
733
+ w2menu.show({
734
+ anchor: $el.get(0),
735
+ name: this.name + '_flat-menu',
736
+ items,
737
+ // class: 'w2ui-dark',
738
+ position: 'right|left',
739
+ onSelect(event) {
740
+ self.unselect()
741
+ self.click(event.detail.item.id, event.detail.originalEvent)
742
+ },
743
+ onHide(event) {
744
+ self.unselect()
745
+ }
746
+ })
747
+ w2tooltip.hide(this.name + '_tooltip')
748
+ }
749
+
750
+ focus(event) {
751
+ let self = this
752
+ // event before
753
+ let edata = this.trigger('focus', { target: this.name, originalEvent: event })
754
+ if (edata.isCancelled === true) return false
755
+ // default behaviour
756
+ this.hasFocus = true
757
+ query(this.box).find('.w2ui-sidebar-body').addClass('w2ui-focus')
758
+ setTimeout(() => {
759
+ let input = query(self.box).find('#sidebar_'+ self.name + '_focus').get(0)
760
+ if (document.activeElement != input) input.focus()
761
+ }, 10)
762
+ // event after
763
+ edata.finish()
764
+ }
765
+
766
+ blur(event) {
767
+ // event before
768
+ let edata = this.trigger('blur', { target: this.name, originalEvent: event })
769
+ if (edata.isCancelled === true) return false
770
+ // default behaviour
771
+ this.hasFocus = false
772
+ query(this.box).find('.w2ui-sidebar-body').removeClass('w2ui-focus')
773
+ // event after
774
+ edata.finish()
775
+ }
776
+
777
+ next(node, noSubs) {
778
+ if (node == null) return null
779
+ let parent = node.parent
780
+ let ind = this.get(node.id, true)
781
+ let nextNode = null
782
+ // jump inside
783
+ if (node.expanded && node.nodes.length > 0 && noSubs !== true) {
784
+ let nd = node.nodes[0]
785
+ if (nd.hidden || nd.disabled || nd.group) nextNode = this.next(nd); else nextNode = nd
786
+ } else {
787
+ if (parent && ind + 1 < parent.nodes.length) {
788
+ nextNode = parent.nodes[ind + 1]
789
+ } else {
790
+ nextNode = this.next(parent, true) // jump to the parent
791
+ }
792
+ }
793
+ if (nextNode != null && (nextNode.hidden || nextNode.disabled || nextNode.group)) nextNode = this.next(nextNode)
794
+ return nextNode
795
+ }
796
+
797
+ prev(node) {
798
+ if (node == null) return null
799
+ let parent = node.parent
800
+ let ind = this.get(node.id, true)
801
+ let lastChild = (node) => {
802
+ if (node.expanded && node.nodes.length > 0) {
803
+ let nd = node.nodes[node.nodes.length - 1]
804
+ if (nd.hidden || nd.disabled || nd.group) return this.prev(nd); else return lastChild(nd)
805
+ }
806
+ return node
807
+ }
808
+ let prevNode = (ind > 0) ? lastChild(parent.nodes[ind - 1]) : parent
809
+ if (prevNode != null && (prevNode.hidden || prevNode.disabled || prevNode.group)) prevNode = this.prev(prevNode)
810
+ return prevNode
811
+ }
812
+
813
+ // returns ids of expanded elements as a flat array
814
+ getChain(nodes, options = {}) {
815
+ options.returnDisabled ??= false
816
+ options.returnGroups ??= false
817
+ let ids = []
818
+ if (nodes == null) nodes = this.nodes
819
+ nodes.forEach(node => {
820
+ // can skip disabled if needed
821
+ if ((!node.disabled && !node.group) || (node.disabled && options.returnDisabled) || (node.group && options.returnGroups)) {
822
+ ids.push(node.id)
823
+ }
824
+ if (Array.isArray(node.nodes) && node.expanded) {
825
+ ids.push(...this.getChain(node.nodes, options))
826
+ }
827
+ })
828
+ return ids
829
+ }
830
+
831
+ keydown(event) {
832
+ let self = this
833
+ let first = Array.isArray(this.selected) ? this.selected[0] : this.selected
834
+ let nd = self.get(first)
835
+ if (this.keyboard !== true) return
836
+ if (!nd) nd = this.nodes[0]
837
+ // if user hits esc and there is active move
838
+ if (event.keyCode == 27) {
839
+ let mv = this.last.move
840
+ if (mv?.reorder && mv?.moved) {
841
+ mv.restore()
842
+ return
843
+ }
844
+ }
845
+ // trigger event
846
+ let edata = this.trigger('keydown', { target: this.name, originalEvent: event })
847
+ if (edata.isCancelled === true) return
848
+ // default behaviour
849
+ if (event.keyCode == 13 || event.keyCode == 32) { // enter or space
850
+ if (event.keyCode == 13 && this.editable && !event.ctrlKey && !event.metaKey) {
851
+ this.edit(first)
852
+ } else {
853
+ if (nd.nodes.length > 0) {
854
+ this.toggle(first)
855
+ }
856
+ }
857
+ }
858
+ if (event.keyCode == 37) { // left
859
+ if (nd.nodes.length > 0 && nd.expanded) {
860
+ this.collapse(first)
861
+ } else {
862
+ selectNode(nd.parent)
863
+ if (!nd.parent.group) this.collapse(nd.parent.id)
864
+ }
865
+ }
866
+ if (event.keyCode == 39) { // right
867
+ if ((nd.nodes.length > 0 || nd.plus) && !nd.expanded) this.expand(first)
868
+ }
869
+ if (event.keyCode == 38) { // up
870
+ if (this.get(first) == null) {
871
+ selectNode(this.nodes[0] || null)
872
+ } else {
873
+ selectNode(neighbor(nd, this.prev))
874
+ }
875
+ }
876
+ if (event.keyCode == 40) { // down
877
+ if (this.get(first) == null) {
878
+ selectNode(this.nodes[0] || null)
879
+ } else {
880
+ selectNode(neighbor(nd, this.next))
881
+ }
882
+ }
883
+ // cancel event if needed
884
+ if ([13, 32, 37, 38, 39, 40].includes(event.keyCode)) {
885
+ if (event.preventDefault) event.preventDefault()
886
+ if (event.stopPropagation) event.stopPropagation()
887
+ }
888
+ // event after
889
+ edata.finish()
890
+
891
+ function selectNode(node, event) {
892
+ if (node != null && !node.hidden && !node.disabled && !node.group) {
893
+ self.click(node.id, event)
894
+ if (!self.inView(node.id)) self.scrollIntoView(node.id)
895
+ }
896
+ }
897
+
898
+ function neighbor(node, neighborFunc) {
899
+ node = neighborFunc.call(self, node)
900
+ while (node != null && (node.hidden || node.disabled)) {
901
+ if (node.group) break; else node = neighborFunc(node)
902
+ }
903
+ return node
904
+ }
905
+ }
906
+
907
+ inView(id) {
908
+ let item = query(this.box).find('#node_'+ w2utils.escapeId(id)).get(0)
909
+ if (!item) {
910
+ return false
911
+ }
912
+ let div = query(this.box).find('.w2ui-sidebar-body').get(0)
913
+ if (item.offsetTop < div.scrollTop || (item.offsetTop + item.clientHeight > div.clientHeight + div.scrollTop)) {
914
+ return false
915
+ }
916
+ return true
917
+ }
918
+
919
+ scrollIntoView(id, instant) {
920
+ return new Promise((resolve, reject) => {
921
+ if (id == null) id = Array.isArray(this.selected) ? this.selected[0] : this.selected
922
+ let nd = this.get(id)
923
+ if (nd == null) return
924
+ let item = query(this.box).find('#node_'+ w2utils.escapeId(id)).get(0)
925
+ item.scrollIntoView({ block: 'center', inline: 'center', behavior: instant ? 'atuo' : 'smooth' })
926
+ setTimeout(() => { this.resize(); resolve() }, instant ? 0 : 500)
927
+ })
928
+ }
929
+
930
+ dblClick(id, event) {
931
+ let nd = this.get(id)
932
+ // event before
933
+ let edata = this.trigger('dblClick', { target: id, originalEvent: event, object: nd })
934
+ if (edata.isCancelled === true) return
935
+ // default action
936
+ if (this.editable) {
937
+ this.edit(id)
938
+ } else if (!this.flat) {
939
+ this.toggle(id)
940
+ }
941
+ // event after
942
+ edata.finish()
943
+ }
944
+
945
+ /**
946
+ * This is needed for not reorder
947
+ */
948
+ mouseDown(id, event) {
949
+ let self = this
950
+ if (this.reorder) {
951
+ this.last.move = {
952
+ x: event.screenX,
953
+ y: event.screenY,
954
+ divX: 0,
955
+ divY: 0,
956
+ reorder: true,
957
+ moved: false
958
+ }
959
+ // display empty record and ghost record
960
+ let mv = this.last.move
961
+ let body = query(this.box).find('.w2ui-sidebar-body')
962
+ if (!mv.ghost) {
963
+ let node = query(this.box).find(`#node_${w2utils.escapeId(id)}`)
964
+ mv.offsetY = event.offsetY
965
+ mv.target = id
966
+ mv.pos = { top: node.get(0).offsetTop - 1, left: node.get(0).offsetLeft }
967
+ // ghost content
968
+ let clone = query(node.find('.w2ui-node-data').get(0).cloneNode(true))
969
+ mv.node = node
970
+ mv.nodeSub = node.next()
971
+ body.append('<div id="sidebar_'+ this.name + '_ghost" class="w2ui-node w2ui-ghost"></div>')
972
+ query(this.box).find('#sidebar_'+ this.name + '_ghost').append(clone)
973
+ mv.ghost = query(this.box).find('#sidebar_'+ this.name + '_ghost')
974
+ mv.ghost.css({ display: 'none' })
975
+ mv.restore = () => {
976
+ mv.resetReorder()
977
+ this.refresh()
978
+ }
979
+ mv.resetReorder = () => {
980
+ this.last.move = null
981
+ query(this.box).find(`#sidebar_${this.name}_ghost`).remove()
982
+ query(document).off(`.w2ui-${this.name}-reorder`)
983
+ }
984
+ }
985
+ // add mouse move and stop events
986
+ query(document)
987
+ .on(`mousemove.w2ui-${this.name}-reorder`, _mouseMove)
988
+ .on(`mouseup.w2ui-${this.name}-reorder`, _mouseStop)
989
+ }
990
+
991
+ function _mouseMove(event) {
992
+ if (!event.target.tagName) {
993
+ // element has no tagName - most likely the target is the #document itself
994
+ // this can happen is you click+drag and move the mouse out of the DOM area,
995
+ // e.g. into the browser's toolbar area
996
+ return
997
+ }
998
+ let mv = self.last.move
999
+ mv.divX = (event.screenX - mv.x)
1000
+ mv.divY = (event.screenY - mv.y)
1001
+ if (Math.abs(mv.divX) <= 1 && Math.abs(mv.divY) <= 1) return // only if moved more then 1px
1002
+
1003
+ if (self.reorder == true && mv.reorder && !mv.moved) {
1004
+ let edata = self.trigger('dragStart', { target: mv.target, moved: true, node: self.get(mv.target), mv, originalEvent: event })
1005
+ if (edata.isCancelled === true) {
1006
+ mv.restore()
1007
+ return
1008
+ }
1009
+ let rect = mv.node.get(0).getBoundingClientRect()
1010
+ mv.moved = true
1011
+ mv.node.html('')
1012
+ .removeAttr('id', 'data-id')
1013
+ .addClass('w2ui-reorder-empty')
1014
+ .css({ height: rect.height + 'px' })
1015
+ // if there are children
1016
+ if (mv.node.next().css('display') !== 'none') {
1017
+ let rect = mv.node.next().get(0).getBoundingClientRect()
1018
+ mv.node.next()
1019
+ .html('<div class="w2ui-reorder-empty-sub"></div>')
1020
+ .css({ height: rect.height + 'px' })
1021
+ }
1022
+ mv.ghost.css({ display: 'block' })
1023
+ // event after
1024
+ edata.finish()
1025
+ }
1026
+ // move ghost mode
1027
+ mv.ghost.css({
1028
+ top: (mv.pos.top + mv.divY) + 'px',
1029
+ left: 0
1030
+ })
1031
+ let over = query(event.target).closest('.w2ui-node, .w2ui-node-group')
1032
+ let id = over.attr('data-id')
1033
+ // append to the end
1034
+ if (query(event.target).hasClass('w2ui-sidebar-body') && event.layerY > 5 && !mv.append) {
1035
+ let edata = self.trigger('dragOver', { target: mv.target, append: true, mv, originalEvent: event })
1036
+ if (edata.isCancelled === true) {
1037
+ return
1038
+ }
1039
+ mv.ghost.before(mv.node)
1040
+ mv.ghost.before(mv.nodeSub)
1041
+ mv.append = true
1042
+ mv.moveBefore = null
1043
+ // event after
1044
+ edata.finish()
1045
+ } else if (id != null && id != mv.moveBefore) {
1046
+ mv.append = false
1047
+ mv.moveBefore = id
1048
+ // reorder nodes
1049
+ let edata = self.trigger('dragOver', { target: mv.target, moveBefore: id, mv, originalEvent: event })
1050
+ if (edata.isCancelled === true) {
1051
+ return
1052
+ }
1053
+ let el = query(self.box).find(`#node_${w2utils.escapeId(id)}`)
1054
+ el.before(mv.node)
1055
+ el.before(mv.nodeSub)
1056
+ // event after
1057
+ edata.finish()
1058
+ }
1059
+ }
1060
+
1061
+ function _mouseStop(event) {
1062
+ let mv = self.last.move
1063
+ mv.resetReorder()
1064
+ if (mv.moved) {
1065
+ if (((mv.moveBefore != null && mv.target != mv.moveBefore) || mv.append)) {
1066
+ let edata = self.trigger('reorder', { target: mv.target, moveBefore: mv.moveBefore, append: mv.append, originalEvent: event })
1067
+ if (edata.isCancelled === true) {
1068
+ self.refresh()
1069
+ return
1070
+ }
1071
+ // remove
1072
+ let target = self.get(mv.target)
1073
+ let targetInd = target.parent.nodes.indexOf(target)
1074
+ let cut = target.parent.nodes.splice(targetInd, 1)
1075
+ // insert
1076
+ if (mv.append) {
1077
+ self.nodes.push(...cut)
1078
+ cut.forEach(nd => nd.parent = self)
1079
+ } else {
1080
+ let before = self.get(mv.moveBefore)
1081
+ let beforeInd = before.parent.nodes.indexOf(before)
1082
+ cut.forEach(nd => nd.parent = before.parent)
1083
+ before.parent.nodes.splice(beforeInd, 0, ...cut)
1084
+ }
1085
+ // refresh
1086
+ self.refresh()
1087
+ // event after
1088
+ edata.finish()
1089
+ } else {
1090
+ self.refresh()
1091
+ }
1092
+ }
1093
+ }
1094
+ }
1095
+
1096
+ edit(id) {
1097
+ let self = this
1098
+ let node = query(this.box).find('#node_'+ w2utils.escapeId(id))
1099
+ let text = node.find('.w2ui-node-text')
1100
+ // event before
1101
+ let edata = this.trigger('edit', { target: id, el: node, textEl: text })
1102
+ if (edata.isCancelled === true) {
1103
+ return
1104
+ }
1105
+ this.last.renaming = true
1106
+ node.addClass('w2ui-editing')
1107
+ text.addClass('w2ui-focus')
1108
+ .css('pointer-events', 'all')
1109
+ .attr('contenteditable', w2utils.isFirefox ? 'true' : 'plaintext-only')
1110
+ .on('blur.node-editing', event => {
1111
+ // timeout is needed to add to the end of the event loop
1112
+ setTimeout(_rename, 0)
1113
+ })
1114
+ .on('keydown.node-editing', event => {
1115
+ if (event.keyCode == 13) _rename(event)
1116
+ if (event.keyCode == 27) _rename(event, true)
1117
+ })
1118
+ .get(0).focus()
1119
+
1120
+ let original = text.text()
1121
+ // select everything inside
1122
+ w2utils.setCursorPosition(text[0], 0, text.text().length)
1123
+ // event after
1124
+ edata.finish()
1125
+
1126
+ return text.get(0) // return editable input
1127
+
1128
+ function _rename(event, cancel) {
1129
+ let renameTo = text.text()
1130
+ node.removeClass('w2ui-editing')
1131
+ text.removeClass('w2ui-focus')
1132
+ .css('pointer-events', 'none')
1133
+ .removeAttr('contenteditable')
1134
+ .off('.node-editing')
1135
+ // send event if it was not cancelled
1136
+ if (!cancel && self.last.renaming && original !== renameTo) {
1137
+ let edata = self.trigger('rename', { target: id, text_previous: original, text_new: renameTo, originalEvent: event })
1138
+ if (edata.isCancelled === true) {
1139
+ text.text(original)
1140
+ self.last.renaming = false
1141
+ self.focus()
1142
+ return
1143
+ }
1144
+ self.set(id, { text: renameTo })
1145
+ edata.finish()
1146
+ }
1147
+ if (cancel) {
1148
+ self.set(id, { text: original })
1149
+ }
1150
+ self.last.renaming = false
1151
+ self.focus()
1152
+ }
1153
+ }
1154
+
1155
+ contextMenu(id, event) {
1156
+ let nd = this.get(id)
1157
+ if (Array.isArray(this.selected)) {
1158
+ if (!this.selected.includes(id)) this.click(id)
1159
+ } else {
1160
+ if (id != this.selected) this.click(id)
1161
+ }
1162
+ // event before
1163
+ let edata = this.trigger('contextMenu', { target: id, originalEvent: event, object: nd, allowOnDisabled: false })
1164
+ if (edata.isCancelled === true) return
1165
+ // default action
1166
+ if (nd.disabled && !edata.allowOnDisabled) return
1167
+ if (this.menu.length > 0) {
1168
+ w2menu.hide(this.name + '_menu') // hide previous if any needed when other item's menu is shown
1169
+ w2menu.show({
1170
+ name: this.name + '_menu',
1171
+ anchor: document.body,
1172
+ contextMenu: true,
1173
+ items: this.menu,
1174
+ originalEvent: event
1175
+ })
1176
+ .select(evt => {
1177
+ this.menuClick(id, evt.detail)
1178
+ })
1179
+ }
1180
+ // prevent default context menu
1181
+ if (event.preventDefault) event.preventDefault()
1182
+ // event after
1183
+ edata.finish()
1184
+ }
1185
+
1186
+ menuClick(itemId, detail = {}) {
1187
+ // event before
1188
+ let edata = this.trigger('menuClick', { target: itemId, ...detail })
1189
+ if (edata.isCancelled === true) return
1190
+ // default action
1191
+ // -- empty
1192
+ // event after
1193
+ edata.finish()
1194
+ }
1195
+
1196
+ goFlat() {
1197
+ // event before
1198
+ let edata = this.trigger('flat', { goFlat: !this.flat })
1199
+ if (edata.isCancelled === true) return
1200
+ // default action
1201
+ this.flat = !this.flat
1202
+ this.refresh()
1203
+ if (this.flat) {
1204
+ // collapse all unless it is a group
1205
+ this.nodes.forEach(node => {
1206
+ if (!node.group) {
1207
+ this.collapse(node.id)
1208
+ this.collapseAll(node.id) // sub items too
1209
+ }
1210
+ })
1211
+ this.unselect() // unselects all
1212
+ } else {
1213
+ // expand all unless it is a group
1214
+ this.nodes.forEach(node => {
1215
+ if (!node.group) {
1216
+ this.expand(node.id)
1217
+ this.expandAll(node.id) // sub items too
1218
+ }
1219
+ })
1220
+ }
1221
+ // event after
1222
+ edata.finish()
1223
+ }
1224
+
1225
+ render(box) {
1226
+ let time = Date.now()
1227
+ let obj = this
1228
+ if (typeof box == 'string') box = query(box).get(0)
1229
+ // event before
1230
+ let edata = this.trigger('render', { target: this.name, box: box ?? this.box })
1231
+ if (edata.isCancelled === true) return
1232
+ // default action
1233
+ if (box != null) {
1234
+ this.unmount() // clean previous control
1235
+ this.box = box
1236
+ }
1237
+ if (!this.box) return
1238
+ query(this.box)
1239
+ .attr('name', this.name)
1240
+ .addClass('w2ui-reset w2ui-sidebar')
1241
+ .html(`<div>
1242
+ <div class="w2ui-sidebar-top"></div>
1243
+ <input id="sidebar_${this.name}_focus" ${(this.tabIndex ? 'tabindex="' + this.tabIndex + '"' : '')}
1244
+ style="position: absolute; top: 0; right: 0; width: 1px; z-index: -1; opacity: 0"
1245
+ ${(w2utils.isMobile ? 'readonly' : '')}/>
1246
+ <div class="w2ui-sidebar-body"></div>
1247
+ <div class="w2ui-sidebar-bottom"></div>
1248
+ </div>`)
1249
+ let rect = query(this.box).get(0).getBoundingClientRect()
1250
+ query(this.box).find(':scope > div').css({
1251
+ width : rect.width + 'px',
1252
+ height : rect.height + 'px'
1253
+ })
1254
+ query(this.box).get(0).style.cssText += this.style
1255
+ // focus
1256
+ let kbd_timer
1257
+ query(this.box).find('#sidebar_'+ this.name + '_focus')
1258
+ .on('focus', function(event) {
1259
+ clearTimeout(kbd_timer)
1260
+ if (!obj.hasFocus) obj.focus(event)
1261
+ })
1262
+ .on('blur', function(event) {
1263
+ kbd_timer = setTimeout(() => {
1264
+ if (obj.hasFocus) { obj.blur(event) }
1265
+ }, 100)
1266
+ })
1267
+ .on('keydown', function(event) {
1268
+ // do not cancel tab key (keyCode=9) so that event is dispatched to self
1269
+ w2ui[obj.name].keydown.call(w2ui[obj.name], event)
1270
+ })
1271
+ query(this.box).off('mousedown')
1272
+ .on('mousedown', function(event) {
1273
+ // set focus to sidebar
1274
+ setTimeout(() => {
1275
+ // if input then do not focus
1276
+ if (['INPUT', 'TEXTAREA', 'SELECT'].indexOf(event.target.tagName.toUpperCase()) == -1) {
1277
+ let $input = query(obj.box).find('#sidebar_'+ obj.name + '_focus')
1278
+ if (document.activeElement != $input.get(0) && $input.length > 0) {
1279
+ $input.get(0).focus()
1280
+ }
1281
+ }
1282
+ }, 1)
1283
+ })
1284
+ /**
1285
+ * FlatHTML is always present and in .refresh() it is just refreshed. However topHTML and buttomHTML should be here
1286
+ * because it should never be refreshed, as it could create recursive refresh loop
1287
+ */
1288
+ let flatHTML = `<div class="w2ui-flat w2ui-flat-${(this.flat ? 'right' : 'left')}" ${this.flatButton == false ? 'style="display: none"' : ''}></div>`
1289
+ if (this.topHTML !== '' || flatHTML !== '') {
1290
+ query(this.box).find('.w2ui-sidebar-top').html(this.topHTML + flatHTML)
1291
+ query(this.box).find('.w2ui-sidebar-body')
1292
+ .css('top', query(this.box).find('.w2ui-sidebar-top').get(0)?.clientHeight + 'px')
1293
+ query(this.box).find('.w2ui-flat')
1294
+ .off('click')
1295
+ .on('click', event => { this.goFlat() })
1296
+ }
1297
+ if (this.bottomHTML !== '') {
1298
+ query(this.box).find('.w2ui-sidebar-bottom').html(this.bottomHTML)
1299
+ query(this.box).find('.w2ui-sidebar-body')
1300
+ .css('bottom', query(this.box).find('.w2ui-sidebar-bottom').get(0)?.clientHeight + 'px')
1301
+ }
1302
+
1303
+ // observe div resize
1304
+ this.last.observeResize = new ResizeObserver(() => { this.resize() })
1305
+ this.last.observeResize.observe(this.box)
1306
+ // event after
1307
+ edata.finish()
1308
+ // ---
1309
+ this.refresh()
1310
+ return Date.now() - time
1311
+ }
1312
+
1313
+ update(id, options = {}) {
1314
+ // quick function to refresh just this item (not sub nodes)
1315
+ // - icon, class, style, text, count
1316
+ let nd = this.get(id)
1317
+ let level
1318
+ if (nd) {
1319
+ let $el = query(this.box).find('#node_'+ w2utils.escapeId(nd.id))
1320
+ if (nd.group) {
1321
+ if (options.text) {
1322
+ nd.text = options.text
1323
+ $el.find('.w2ui-group-text').replace(typeof nd.text == 'function'
1324
+ ? nd.text.call(this, nd)
1325
+ : '<span class="w2ui-group-text">'+ nd.text +'</span>')
1326
+ delete options.text
1327
+ }
1328
+ if (options.class) {
1329
+ nd.class = options.class
1330
+ level = $el.data('level')
1331
+ $el.get(0).className = 'w2ui-node-group w2ui-level-'+ level +(nd.class ? ' ' + nd.class : '')
1332
+ delete options.class
1333
+ }
1334
+ if (options.style) {
1335
+ nd.style = options.style
1336
+ $el.get(0).nextElementSibling.style = nd.style +';'+ (!nd.hidden && nd.expanded ? '' : 'display: none;')
1337
+ delete options.style
1338
+ }
1339
+ } else {
1340
+ if (options.icon) {
1341
+ let $icon = $el.find('.w2ui-node-image > span')
1342
+ if ($icon.length > 0) {
1343
+ nd.icon = options.icon
1344
+ $icon[0].className = (typeof nd.icon == 'function' ? nd.icon.call(this, nd) : nd.icon)
1345
+ delete options.icon
1346
+ }
1347
+ }
1348
+ if (options.count != null) {
1349
+ nd.count = options.count
1350
+ // update counts
1351
+ let txt = nd.count ?? this.badge.text
1352
+ let style = this.badge?.style
1353
+ let last = this.last.badge[nd.id]
1354
+ if (typeof txt == 'function') txt = txt.call(this, nd, level)
1355
+ $el.find('.w2ui-node-badge')
1356
+ .html(txt)
1357
+ .attr('style', `${style}; ${last?.style ?? ''}`)
1358
+ if ($el.find('.w2ui-node-badge').length > 0) delete options.count
1359
+ }
1360
+ if (options.class && $el.length > 0) {
1361
+ nd.class = options.class
1362
+ level = $el.data('level')
1363
+ $el[0].className = 'w2ui-node w2ui-level-'+ level + (nd.selected ? ' w2ui-selected' : '') + (nd.disabled ? ' w2ui-disabled' : '') + (nd.class ? ' ' + nd.class : '')
1364
+ delete options.class
1365
+ }
1366
+ if (options.text != null) {
1367
+ nd.text = options.text
1368
+ $el.find('.w2ui-node-text').html(typeof nd.text == 'function' ? nd.text.call(this, nd) : nd.text)
1369
+ delete options.text
1370
+ }
1371
+ if (options.style && $el.length > 0) {
1372
+ let $txt = $el.find('.w2ui-node-text')
1373
+ nd.style = options.style
1374
+ $txt[0].style = nd.style
1375
+ delete options.style
1376
+ }
1377
+ }
1378
+ }
1379
+ // return what was not set
1380
+ return options
1381
+ }
1382
+
1383
+ refresh(id, options = {}) {
1384
+ if (this.box == null) return
1385
+ let body = query(this.box).find(':scope > div > .w2ui-sidebar-body').get(0)
1386
+ let { scrollTop, scrollLeft } = body ?? {}
1387
+ let time = Date.now()
1388
+ let self = this
1389
+ // event before
1390
+ let edata = this.trigger('refresh', {
1391
+ target: (id != null ? id : this.name),
1392
+ nodeId: (id != null ? id : null),
1393
+ fullRefresh: (id != null ? false : true)
1394
+ })
1395
+ if (edata.isCancelled === true) return
1396
+ if (this.flatButton == true) {
1397
+ query(this.box).find('.w2ui-sidebar-top .w2ui-flat').show()
1398
+ .removeClass('w2ui-flat-left w2ui-flat-right')
1399
+ .addClass(` w2ui-flat-${(this.flat ? 'right' : 'left')}`)
1400
+
1401
+ } else {
1402
+ query(this.box).find('.w2ui-sidebar-top .w2ui-flat').hide()
1403
+ }
1404
+ // default action
1405
+ query(this.box).find(':scope > div').removeClass('w2ui-sidebar-flat').addClass(this.flat ? 'w2ui-sidebar-flat' : '').css({
1406
+ width : query(this.box).get(0)?.clientWidth + 'px',
1407
+ height: query(this.box).get(0)?.clientHeight + 'px'
1408
+ })
1409
+ // if no parent - reset nodes
1410
+ if (this.nodes.length > 0 && this.nodes[0].parent == null) {
1411
+ let tmp = this.nodes
1412
+ this.nodes = []
1413
+ this.add(this, tmp)
1414
+ }
1415
+ let obj = this
1416
+ let node
1417
+ let nodeSubId
1418
+ if (id == null) {
1419
+ node = this
1420
+ nodeSubId = '.w2ui-sidebar-body'
1421
+ } else {
1422
+ node = this.get(id)
1423
+ if (node == null) return
1424
+ nodeSubId = '#node_'+ w2utils.escapeId(node.id) + '_sub'
1425
+ }
1426
+ let nodeId = '#node_'+ w2utils.escapeId(node.id)
1427
+ let nodeHTML
1428
+ if (node !== this) {
1429
+ nodeHTML = getNodeHTML(node)
1430
+ query(this.box).find(nodeId).before('<div id="sidebar_'+ this.name + '_tmp"></div>')
1431
+ query(this.box).find(nodeId).remove()
1432
+ query(this.box).find(nodeSubId).remove()
1433
+ query(this.box).find('#sidebar_'+ this.name + '_tmp').before(nodeHTML)
1434
+ query(this.box).find('#sidebar_'+ this.name + '_tmp').remove()
1435
+ }
1436
+ // remember scroll position
1437
+ let div = query(this.box).find(':scope > div').get(0)
1438
+ let scroll = {
1439
+ top: div?.scrollTop,
1440
+ left: div?.scrollLeft
1441
+ }
1442
+ // refresh sub nodes
1443
+ query(this.box).find(nodeSubId).html('')
1444
+ for (let i = 0; i < node.nodes.length; i++) {
1445
+ let subNode = node.nodes[i]
1446
+ nodeHTML = getNodeHTML(subNode)
1447
+ query(this.box).find(nodeSubId).append(nodeHTML)
1448
+ if (subNode.nodes.length !== 0) {
1449
+ // TODO: here
1450
+ this.refresh(subNode.id, { recursive: true, })
1451
+ } else {
1452
+ // trigger event
1453
+ let edata2 = this.trigger('refresh', { target: subNode.id })
1454
+ if (edata2.isCancelled === true) return
1455
+ // event after
1456
+ edata2.finish()
1457
+ }
1458
+ }
1459
+ // reset scroll
1460
+ if (div) {
1461
+ div.scrollTop = scroll.top
1462
+ div.scrollLeft = scroll.left
1463
+ }
1464
+ // bind events
1465
+ if (!options.recursive) {
1466
+ let els = query(this.box).find(`${nodeId}, ${nodeId} .w2ui-eaction, ${nodeSubId} .w2ui-eaction`)
1467
+ w2utils.bindEvents(els, this)
1468
+ // restore scroll position
1469
+ query(body).prop({ scrollLeft, scrollTop })
1470
+ }
1471
+ // event after
1472
+ edata.finish()
1473
+ return Date.now() - time
1474
+
1475
+ function getNodeHTML(nd) {
1476
+ let html = ''
1477
+ let icon = nd.icon
1478
+ if (icon == null) icon = obj.icon
1479
+ // -- find out level
1480
+ let tmp = nd.parent
1481
+ let level = 0
1482
+ while (tmp && tmp.parent != null) {
1483
+ // if (tmp.group) level--;
1484
+ tmp = tmp.parent
1485
+ level++
1486
+ }
1487
+ if (nd.caption != null && nd.text == null) nd.text = nd.caption
1488
+ if (nd.caption != null) {
1489
+ console.log('NOTICE: sidebar node.caption property is deprecated, please use node.text. Node -> ', nd)
1490
+ nd.text = nd.caption
1491
+ }
1492
+ if (Array.isArray(nd.nodes) && nd.nodes.length > 0) nd.collapsible = true
1493
+ if (nd.group) {
1494
+ let text = w2utils.lang(typeof nd.text == 'function' ? nd.text.call(obj, nd, level) : nd.text)
1495
+ if (String(text).substr(0, 5) != '<span') {
1496
+ text = `<span class="w2ui-group-text">${text}</span>`
1497
+ }
1498
+ html = `
1499
+ <div id="node_${nd.id}" data-id="${nd.id}" data-level="${level}" style="${nd.hidden ? 'display: none' : ''}"
1500
+ class="w2ui-node-group w2ui-level-${level} ${nd.class ? nd.class : ''} w2ui-eaction"
1501
+ data-click="toggle|${nd.id}"
1502
+ data-contextmenu="contextMenu|${nd.id}|event"
1503
+ data-mouseenter="showPlus|this|inherit"
1504
+ data-mouseleave="showPlus|this|transparent">
1505
+ ${nd.groupShowHide && nd.collapsible
1506
+ ? `<span>${!nd.hidden && nd.expanded ? w2utils.lang('Hide') : w2utils.lang('Show')}</span>`
1507
+ : '<span></span>'
1508
+ } ${text}
1509
+ </div>
1510
+ <div class="w2ui-node-sub" id="node_${nd.id}_sub" style="${nd.style}; ${!nd.hidden && nd.expanded ? '' : 'display: none;'}">
1511
+ </div>`
1512
+ if (obj.flat) {
1513
+ html = `
1514
+ <div class="w2ui-node-group" id="node_${nd.id}" data-id="${nd.id}"><span>&#160;</span></div>
1515
+ <div id="node_${nd.id}_sub" style="${nd.style}; ${!nd.hidden && nd.expanded ? '' : 'display: none;'}"></div>`
1516
+ }
1517
+ } else {
1518
+ if (nd.selected && !nd.disabled) {
1519
+ if (obj.multi) {
1520
+ obj.selected ??= []
1521
+ if (!obj.selected.includes(nd.id)) {
1522
+ obj.selected.push(nd.id)
1523
+ }
1524
+ } else {
1525
+ obj.selected = nd.id
1526
+ }
1527
+ }
1528
+ // icon or image
1529
+ let image = ''
1530
+ if (icon) {
1531
+ if (icon instanceof Object) {
1532
+ let text = (typeof icon.text == 'function' ? (icon.text.call(obj, nd, level) ?? '') : icon.text)
1533
+ image = `
1534
+ <div class="w2ui-node-image w2ui-eaction" style="${obj.icon.style ?? ''}; pointer-events: all"
1535
+ data-mouseEnter="mouseAction|Enter|this|${nd.id}|event|icon"
1536
+ data-mouseLeave="mouseAction|Leave|this|${nd.id}|event|icon"
1537
+ data-click="mouseAction|click|this|${nd.id}|event|icon">
1538
+ ${text}
1539
+ </div>
1540
+ `
1541
+ } else {
1542
+ image = `
1543
+ <div class="w2ui-node-image">
1544
+ <span class="${typeof icon == 'function' ? icon.call(obj, nd, level) : icon}"></span>
1545
+ </div>`
1546
+ }
1547
+ }
1548
+ let expand = ''
1549
+ let counts = ''
1550
+ if (self.badge != null || nd.count != null) {
1551
+ let txt = nd.count ?? self.badge?.text
1552
+ let style = self.badge?.style
1553
+ let last = obj.last.badge[nd.id]
1554
+ if (typeof txt == 'function') txt = txt.call(self, nd, level)
1555
+ if (txt || txt === 0) { // can be number 0
1556
+ counts = `
1557
+ <div class="w2ui-node-badge w2ui-eaction ${nd.count != null ? 'w2ui-node-count' : ''} ${last?.className ?? ''}"
1558
+ style="${style ?? ''};${last?.style ?? ''}"
1559
+ data-mouseEnter="mouseAction|Enter|this|${nd.id}|event|badge"
1560
+ data-mouseLeave="mouseAction|Leave|this|${nd.id}|event|badge"
1561
+ data-click="mouseAction|click|this|${nd.id}|event|badge"
1562
+ >
1563
+ ${txt}
1564
+ </div>`
1565
+ }
1566
+ }
1567
+ // array with classes
1568
+ let classes = ['w2ui-node', `w2ui-level-${level}`, 'w2ui-eaction']
1569
+ if (nd.selected) classes.push('w2ui-selected')
1570
+ if (nd.disabled) classes.push('w2ui-disabled')
1571
+ if (nd.class) classes.push(nd.class)
1572
+ // collapsible
1573
+ if (nd.collapsible === true) {
1574
+ let toggleClasses = ['w2ui-sb-toggle', 'w2ui-eaction', (nd.expanded ? 'w2ui-expanded' : 'w2ui-collapsed')]
1575
+ if (self.toggleAlign == 'left') toggleClasses.push('w2ui-left-toggle')
1576
+ expand = `<div class="${toggleClasses.join(' ')}" data-click="toggle|${nd.id}"><span></span></div>`
1577
+ classes.push('w2ui-has-children')
1578
+ }
1579
+ let text = w2utils.lang(typeof nd.text == 'function' ? nd.text.call(obj, nd, level) : nd.text)
1580
+ let nodeOffset = nd.parent?.childOffset ?? 0
1581
+ // if (level === 0 && nd.collapsible === true && self.toggleAlign == 'left') {
1582
+ nodeOffset += 12
1583
+ // }
1584
+ html = `
1585
+ <div id="node_${nd.id}" class="${classes.join(' ')}" data-id="${nd.id}" data-level="${level}"
1586
+ style="${nd.hidden ? 'display: none;' : ''}"
1587
+ data-click="click|${nd.id}|event"
1588
+ data-dblclick="dblClick|${nd.id}|event"
1589
+ data-mouseDown="mouseDown|${nd.id}|event"
1590
+ data-contextmenu="contextMenu|${nd.id}|event"
1591
+ data-mouseEnter="mouseAction|Enter|this|${nd.id}|event"
1592
+ data-mouseLeave="mouseAction|Leave|this|${nd.id}|event"
1593
+ >
1594
+ ${obj.handle.text
1595
+ ? `<div class="w2ui-node-handle w2ui-eaction" style="width: ${obj.handle.width}px; ${obj.handle.style}"
1596
+ data-mouseEnter="mouseAction|Enter|this|${nd.id}|event|handle"
1597
+ data-mouseLeave="mouseAction|Leave|this|${nd.id}|event|handle"
1598
+ data-click="mouseAction|click|this|${nd.id}|event|handle"
1599
+ >
1600
+ ${typeof obj.handle.text == 'function' ? obj.handle.text.call(obj, nd, level) ?? '' : obj.handle.text}
1601
+ </div>`
1602
+ : ''
1603
+ }
1604
+ <div class="w2ui-node-data" style="margin-left: ${(level * obj.levelPadding) + nodeOffset + obj.handle.width}px">
1605
+ ${expand} ${image} ${counts}
1606
+ <div class="w2ui-node-text ${!image ? 'no-icon' : ''}" style="${nd.style || ''}">${text}</div>
1607
+ </div>
1608
+ </div>
1609
+ <div class="w2ui-node-sub" id="node_${nd.id}_sub" style="${nd.style}; ${!nd.hidden && nd.expanded ? '' : 'display: none;'}"></div>`
1610
+ if (obj.flat) {
1611
+ html = `
1612
+ <div id="node_${nd.id}" class="${classes.join(' ')} w2ui-node-flat" data-id="${nd.id}" style="${nd.hidden ? 'display: none;' : ''}"
1613
+ data-click="click|${nd.id}|event"
1614
+ data-dblclick="dblClick|${nd.id}|event"
1615
+ data-contextmenu="contextMenu|${nd.id}|event"
1616
+ data-mouseEnter="mouseAction|Enter|this|${nd.id}|event|tooltip"
1617
+ data-mouseLeave="mouseAction|Leave|this|${nd.id}|event|tooltip"
1618
+ >
1619
+ <div class="w2ui-node-data">${image}</div>
1620
+ </div>
1621
+ <div class="w2ui-node-sub" id="node_${nd.id}_sub" style="${nd.style}; ${!nd.hidden && nd.expanded ? '' : 'display: none;'}"></div>`
1622
+ }
1623
+ }
1624
+ return html
1625
+ }
1626
+ }
1627
+
1628
+ mouseAction(action, anchor, nodeId, event, type) {
1629
+ let edata
1630
+ let node = this.get(nodeId)
1631
+ if (type == null) {
1632
+ edata = this.trigger('mouse' + action, { target: node.id, node, originalEvent: event })
1633
+ }
1634
+ if (type == 'tooltip') {
1635
+ // this tooltip shows for flat sidebars
1636
+ let text = w2utils.lang(typeof node.text == 'function' ? node.text.call(this, node) : node.text)
1637
+ let tooltip = text + (node.count != null
1638
+ ? ' - <span class="w2ui-node-badge w2ui-node-count">'+ node.count +'</span>'
1639
+ : '')
1640
+ if (action == 'Leave' || this.selected == node.id) tooltip = ''
1641
+ this.tooltip(anchor, tooltip)
1642
+ }
1643
+ if (type == 'handle') {
1644
+ if (action == 'click') {
1645
+ let onClick = this.handle.onClick
1646
+ if (typeof onClick == 'function') {
1647
+ onClick.call(this, node, event)
1648
+ }
1649
+ } else {
1650
+ let tooltip = this.handle.tooltip
1651
+ if (typeof tooltip == 'function') {
1652
+ tooltip = tooltip.call(this, node, event)
1653
+ }
1654
+ if (action == 'Leave') tooltip = ''
1655
+ this.otherTooltip(anchor, tooltip)
1656
+ }
1657
+ }
1658
+ if (type == 'icon') {
1659
+ if (action == 'click') {
1660
+ let onClick = this.icon.onClick
1661
+ if (typeof onClick == 'function') {
1662
+ onClick.call(this, node, event)
1663
+ }
1664
+ } else {
1665
+ let tooltip = this.icon.tooltip
1666
+ if (typeof tooltip == 'function') {
1667
+ tooltip = tooltip.call(this, node, event)
1668
+ }
1669
+ if (action == 'Leave') tooltip = ''
1670
+ this.otherTooltip(anchor, tooltip)
1671
+ }
1672
+ }
1673
+ if (type == 'badge') {
1674
+ if (action == 'click') {
1675
+ let onClick = this.badge?.onClick
1676
+ if (typeof onClick == 'function') {
1677
+ onClick.call(this, node, event)
1678
+ }
1679
+ } else {
1680
+ let tooltip = this.badge?.tooltip
1681
+ if (typeof tooltip == 'function') {
1682
+ tooltip = tooltip.call(this, node, event)
1683
+ }
1684
+ if (action == 'Leave') tooltip = ''
1685
+ this.otherTooltip(anchor, tooltip)
1686
+ }
1687
+ }
1688
+ edata?.finish()
1689
+ }
1690
+
1691
+ tooltip(el, text) {
1692
+ let $el = query(el).find('.w2ui-node-data')
1693
+ if (text !== '') {
1694
+ w2tooltip.show({
1695
+ anchor: $el.get(0),
1696
+ name: this.name + '_tooltip',
1697
+ html: text,
1698
+ position: 'right|left'
1699
+ })
1700
+ } else {
1701
+ w2tooltip.hide(this.name + '_tooltip')
1702
+ }
1703
+ }
1704
+
1705
+ otherTooltip(el, text) {
1706
+ if (text !== '') {
1707
+ w2tooltip.show({
1708
+ anchor: el,
1709
+ name: this.name + '_tooltip',
1710
+ html: text,
1711
+ position: 'top|bottom'
1712
+ })
1713
+ } else {
1714
+ w2tooltip.hide(this.name + '_tooltip')
1715
+ }
1716
+ }
1717
+
1718
+ showPlus(el, color) {
1719
+ query(el).find('span:nth-child(1)').css('color', color)
1720
+ }
1721
+
1722
+ resize() {
1723
+ let time = Date.now()
1724
+ // event before
1725
+ let edata = this.trigger('resize', { target: this.name })
1726
+ if (edata.isCancelled === true) return
1727
+ // default action
1728
+ if (this.box != null) {
1729
+ let rect = query(this.box).get(0).getBoundingClientRect()
1730
+ query(this.box).css('overflow', 'hidden') // container should have no overflow
1731
+ query(this.box).find(':scope > div').css({
1732
+ width : rect.width + 'px',
1733
+ height : rect.height + 'px'
1734
+ })
1735
+ }
1736
+ // event after
1737
+ edata.finish()
1738
+ return Date.now() - time
1739
+ }
1740
+
1741
+ destroy() {
1742
+ // event before
1743
+ let edata = this.trigger('destroy', { target: this.name })
1744
+ if (edata.isCancelled === true) return
1745
+ // clean up
1746
+ if (query(this.box).find('.w2ui-sidebar-body').length > 0) {
1747
+ this.unmount()
1748
+ }
1749
+ delete w2ui[this.name]
1750
+ // event after
1751
+ edata.finish()
1752
+ }
1753
+
1754
+ unmount() {
1755
+ super.unmount()
1756
+ this.last.observeResize?.disconnect()
1757
+ }
1758
+
1759
+ lock(msg, showSpinner) {
1760
+ let args = Array.from(arguments)
1761
+ args.unshift(this.box)
1762
+ w2utils.lock(...args)
1763
+ }
1764
+
1765
+ unlock(speed) {
1766
+ w2utils.unlock(this.box, speed)
1767
+ }
1768
+ }
1769
+ export { w2sidebar }