django-smartbase-admin 0.2.54__py3-none-any.whl → 1.0.38__py3-none-any.whl

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 (179) hide show
  1. django_smartbase_admin/actions/admin_action_list.py +74 -38
  2. django_smartbase_admin/actions/advanced_filters.py +24 -1
  3. django_smartbase_admin/admin/admin_base.py +401 -96
  4. django_smartbase_admin/admin/site.py +93 -35
  5. django_smartbase_admin/admin/widgets.py +589 -26
  6. django_smartbase_admin/apps.py +2 -0
  7. django_smartbase_admin/engine/actions.py +34 -16
  8. django_smartbase_admin/engine/admin_base_view.py +252 -115
  9. django_smartbase_admin/engine/configuration.py +186 -4
  10. django_smartbase_admin/engine/const.py +6 -0
  11. django_smartbase_admin/engine/dashboard.py +44 -23
  12. django_smartbase_admin/engine/fake_inline.py +15 -11
  13. django_smartbase_admin/engine/field.py +42 -12
  14. django_smartbase_admin/engine/field_formatter.py +22 -8
  15. django_smartbase_admin/engine/filter_widgets.py +309 -20
  16. django_smartbase_admin/engine/menu_item.py +8 -5
  17. django_smartbase_admin/engine/modal_view.py +12 -7
  18. django_smartbase_admin/engine/request.py +2 -0
  19. django_smartbase_admin/integration/__init__.py +0 -0
  20. django_smartbase_admin/integration/django_cms.py +43 -0
  21. django_smartbase_admin/locale/sk/LC_MESSAGES/django.mo +0 -0
  22. django_smartbase_admin/locale/sk/LC_MESSAGES/django.po +268 -37
  23. django_smartbase_admin/migrations/0005_sbadminuserconfiguration.py +26 -0
  24. django_smartbase_admin/migrations/0006_alter_sbadminuserconfiguration_color_scheme.py +18 -0
  25. django_smartbase_admin/models.py +22 -0
  26. django_smartbase_admin/monkeypatch/admin_readonly_field_monkeypatch.py +96 -0
  27. django_smartbase_admin/monkeypatch/fake_inline_monkeypatch.py +1 -1
  28. django_smartbase_admin/querysets.py +3 -0
  29. django_smartbase_admin/services/configuration.py +30 -0
  30. django_smartbase_admin/services/thread_local.py +6 -19
  31. django_smartbase_admin/services/views.py +80 -13
  32. django_smartbase_admin/services/xlsx_export.py +6 -0
  33. django_smartbase_admin/static/sb_admin/build/tailwind.config.js +1 -0
  34. django_smartbase_admin/static/sb_admin/build/tailwind_config_partials/colors.js +4 -0
  35. django_smartbase_admin/static/sb_admin/build/tailwind_config_partials/spacing.js +1 -0
  36. django_smartbase_admin/static/sb_admin/build/webpack.common.js +11 -8
  37. django_smartbase_admin/static/sb_admin/css/ckeditor/ckeditor_content_dark.css +208 -0
  38. django_smartbase_admin/static/sb_admin/css/coloris/coloris.min.css +1 -0
  39. django_smartbase_admin/static/sb_admin/dist/calendar.js +1 -0
  40. django_smartbase_admin/static/sb_admin/dist/calendar_style.css +1 -0
  41. django_smartbase_admin/static/sb_admin/dist/calendar_style.js +0 -0
  42. django_smartbase_admin/static/sb_admin/dist/chart.js +1 -1
  43. django_smartbase_admin/static/sb_admin/dist/main.js +1 -1
  44. django_smartbase_admin/static/sb_admin/dist/main_style.css +1 -1
  45. django_smartbase_admin/static/sb_admin/dist/table.js +1 -1
  46. django_smartbase_admin/static/sb_admin/dist/tree_widget.js +1 -0
  47. django_smartbase_admin/static/sb_admin/dist/tree_widget_style.css +1 -0
  48. django_smartbase_admin/static/sb_admin/dist/tree_widget_style.js +0 -0
  49. django_smartbase_admin/static/sb_admin/fancytree/jquery.fancytree-all-deps.min.js +1 -0
  50. django_smartbase_admin/static/sb_admin/images/file_types/file-csv.svg +11 -0
  51. django_smartbase_admin/static/sb_admin/images/file_types/file-doc.svg +11 -0
  52. django_smartbase_admin/static/sb_admin/images/file_types/file-docx.svg +11 -0
  53. django_smartbase_admin/static/sb_admin/images/file_types/file-other.svg +13 -0
  54. django_smartbase_admin/static/sb_admin/images/file_types/file-pdf.svg +11 -0
  55. django_smartbase_admin/static/sb_admin/images/file_types/file-ppt.svg +11 -0
  56. django_smartbase_admin/static/sb_admin/images/file_types/file-xls.svg +11 -0
  57. django_smartbase_admin/static/sb_admin/images/file_types/file-xlsx.svg +11 -0
  58. django_smartbase_admin/static/sb_admin/images/file_types/file-zip.svg +18 -0
  59. django_smartbase_admin/static/sb_admin/images/flags/de-at.png +0 -0
  60. django_smartbase_admin/static/sb_admin/images/flags/de-ch.png +0 -0
  61. django_smartbase_admin/static/sb_admin/images/logo_light.svg +21 -0
  62. django_smartbase_admin/static/sb_admin/js/coloris/coloris.min.js +6 -0
  63. django_smartbase_admin/static/sb_admin/js/fullcalendar.min.js +14804 -0
  64. django_smartbase_admin/static/sb_admin/sprites/sb_admin/Bolt-one.svg +3 -0
  65. django_smartbase_admin/static/sb_admin/sprites/sb_admin/Calendar.svg +3 -0
  66. django_smartbase_admin/static/sb_admin/sprites/sb_admin/Caution.svg +3 -0
  67. django_smartbase_admin/static/sb_admin/sprites/sb_admin/Electric-drill.svg +3 -0
  68. django_smartbase_admin/static/sb_admin/sprites/sb_admin/Fire-extinguisher.svg +3 -0
  69. django_smartbase_admin/static/sb_admin/sprites/sb_admin/Gas.svg +3 -0
  70. django_smartbase_admin/static/sb_admin/sprites/sb_admin/Lightning-fill.svg +3 -0
  71. django_smartbase_admin/static/sb_admin/sprites/sb_admin/Moon.svg +3 -0
  72. django_smartbase_admin/static/sb_admin/sprites/sb_admin/Phone-telephone.svg +3 -0
  73. django_smartbase_admin/static/sb_admin/sprites/sb_admin/Printer.svg +3 -0
  74. django_smartbase_admin/static/sb_admin/sprites/sb_admin/Pull.svg +3 -0
  75. django_smartbase_admin/static/sb_admin/sprites/sb_admin/Sun-one.svg +3 -0
  76. django_smartbase_admin/static/sb_admin/sprites/sb_admin/Time.svg +3 -0
  77. django_smartbase_admin/static/sb_admin/src/css/_base.css +5 -1
  78. django_smartbase_admin/static/sb_admin/src/css/_colors.css +257 -82
  79. django_smartbase_admin/static/sb_admin/src/css/_components.css +61 -13
  80. django_smartbase_admin/static/sb_admin/src/css/_datepicker.css +8 -1
  81. django_smartbase_admin/static/sb_admin/src/css/_filer.css +60 -0
  82. django_smartbase_admin/static/sb_admin/src/css/_inlines.css +51 -10
  83. django_smartbase_admin/static/sb_admin/src/css/_tabulator.css +8 -2
  84. django_smartbase_admin/static/sb_admin/src/css/calendar.css +162 -0
  85. django_smartbase_admin/static/sb_admin/src/css/components/_button.css +41 -1
  86. django_smartbase_admin/static/sb_admin/src/css/components/_dropdown.css +26 -8
  87. django_smartbase_admin/static/sb_admin/src/css/components/_input.css +62 -20
  88. django_smartbase_admin/static/sb_admin/src/css/components/_modal.css +1 -1
  89. django_smartbase_admin/static/sb_admin/src/css/components/_query-builder.css +21 -2
  90. django_smartbase_admin/static/sb_admin/src/css/components/_toggle.css +12 -1
  91. django_smartbase_admin/static/sb_admin/src/css/components/_tooltip.css +8 -22
  92. django_smartbase_admin/static/sb_admin/src/css/style.css +17 -0
  93. django_smartbase_admin/static/sb_admin/src/css/tree_widget.css +411 -0
  94. django_smartbase_admin/static/sb_admin/src/js/autocomplete.js +63 -5
  95. django_smartbase_admin/static/sb_admin/src/js/calendar.js +56 -0
  96. django_smartbase_admin/static/sb_admin/src/js/chart.js +8 -22
  97. django_smartbase_admin/static/sb_admin/src/js/choices.js +18 -8
  98. django_smartbase_admin/static/sb_admin/src/js/datepicker.js +97 -336
  99. django_smartbase_admin/static/sb_admin/src/js/datepicker_plugins.js +357 -0
  100. django_smartbase_admin/static/sb_admin/src/js/main.js +304 -31
  101. django_smartbase_admin/static/sb_admin/src/js/multiselect.js +50 -41
  102. django_smartbase_admin/static/sb_admin/src/js/range.js +3 -2
  103. django_smartbase_admin/static/sb_admin/src/js/table.js +34 -5
  104. django_smartbase_admin/static/sb_admin/src/js/table_modules/advanced_filter_module.js +43 -20
  105. django_smartbase_admin/static/sb_admin/src/js/table_modules/data_edit_module.js +8 -10
  106. django_smartbase_admin/static/sb_admin/src/js/table_modules/filter_module.js +3 -3
  107. django_smartbase_admin/static/sb_admin/src/js/table_modules/header_tabs_module.js +11 -11
  108. django_smartbase_admin/static/sb_admin/src/js/table_modules/selection_module.js +28 -8
  109. django_smartbase_admin/static/sb_admin/src/js/table_modules/table_params_module.js +6 -0
  110. django_smartbase_admin/static/sb_admin/src/js/table_modules/views_module.js +6 -0
  111. django_smartbase_admin/static/sb_admin/src/js/tree_widget.js +406 -0
  112. django_smartbase_admin/static/sb_admin/src/js/utils.js +56 -21
  113. django_smartbase_admin/templates/sb_admin/actions/change_form.html +169 -114
  114. django_smartbase_admin/templates/sb_admin/actions/dashboard.html +2 -2
  115. django_smartbase_admin/templates/sb_admin/actions/list.html +79 -39
  116. django_smartbase_admin/templates/sb_admin/actions/partials/action_link.html +14 -0
  117. django_smartbase_admin/templates/sb_admin/actions/partials/tabulator_header_v2.html +2 -2
  118. django_smartbase_admin/templates/sb_admin/actions/tree_list.html +63 -0
  119. django_smartbase_admin/templates/sb_admin/authentification/login_base.html +5 -1
  120. django_smartbase_admin/templates/sb_admin/components/columns.html +1 -1
  121. django_smartbase_admin/templates/sb_admin/components/filters_v2.html +99 -85
  122. django_smartbase_admin/templates/sb_admin/dashboard/calendar_widget.html +69 -0
  123. django_smartbase_admin/templates/sb_admin/dashboard/chart_widget.html +21 -2
  124. django_smartbase_admin/templates/sb_admin/dashboard/list_widget.html +6 -0
  125. django_smartbase_admin/templates/sb_admin/dashboard/widget_base.html +1 -1
  126. django_smartbase_admin/templates/sb_admin/filter_widgets/advanced_filters/date_field.html +18 -8
  127. django_smartbase_admin/templates/sb_admin/filter_widgets/advanced_filters/multiple_choice_field.html +1 -1
  128. django_smartbase_admin/templates/sb_admin/filter_widgets/advanced_filters/tree_select_filter.html +2 -0
  129. django_smartbase_admin/templates/sb_admin/filter_widgets/date_field.html +18 -4
  130. django_smartbase_admin/templates/sb_admin/filter_widgets/multiple_choice_field.html +14 -0
  131. django_smartbase_admin/templates/sb_admin/filter_widgets/partials/clear.html +10 -5
  132. django_smartbase_admin/templates/sb_admin/filter_widgets/radio_choice_field.html +2 -2
  133. django_smartbase_admin/templates/sb_admin/filter_widgets/tree_select_filter.html +16 -0
  134. django_smartbase_admin/templates/sb_admin/includes/change_form_title.html +3 -1
  135. django_smartbase_admin/templates/sb_admin/includes/inline_fieldset.html +48 -39
  136. django_smartbase_admin/templates/sb_admin/includes/notifications.html +2 -1
  137. django_smartbase_admin/templates/sb_admin/includes/readonly_boolean_field.html +9 -0
  138. django_smartbase_admin/templates/sb_admin/includes/readonly_field.html +12 -0
  139. django_smartbase_admin/templates/sb_admin/includes/table_inline_delete_button.html +4 -5
  140. django_smartbase_admin/templates/sb_admin/inlines/stacked_inline.html +68 -40
  141. django_smartbase_admin/templates/sb_admin/inlines/table_inline.html +76 -34
  142. django_smartbase_admin/templates/sb_admin/integrations/filer/folder_list.html +18 -0
  143. django_smartbase_admin/templates/sb_admin/navigation.html +166 -158
  144. django_smartbase_admin/templates/sb_admin/partials/modal/modal_content.html +2 -6
  145. django_smartbase_admin/templates/sb_admin/sb_admin_base.html +49 -4
  146. django_smartbase_admin/templates/sb_admin/sb_admin_base_no_sidebar.html +27 -11
  147. django_smartbase_admin/templates/sb_admin/sb_admin_js_trans.html +3 -0
  148. django_smartbase_admin/templates/sb_admin/sprites/sb_admin.svg +1 -1
  149. django_smartbase_admin/templates/sb_admin/tailwind_whitelist.html +6 -3
  150. django_smartbase_admin/templates/sb_admin/widgets/array.html +0 -1
  151. django_smartbase_admin/templates/sb_admin/widgets/attributes.html +68 -0
  152. django_smartbase_admin/templates/sb_admin/widgets/autocomplete.html +13 -2
  153. django_smartbase_admin/templates/sb_admin/widgets/{checkbox_select.html → checkbox_dropdown.html} +2 -2
  154. django_smartbase_admin/templates/sb_admin/widgets/clearable_file_input.html +2 -2
  155. django_smartbase_admin/templates/sb_admin/widgets/color_field.html +30 -0
  156. django_smartbase_admin/templates/sb_admin/widgets/date.html +8 -1
  157. django_smartbase_admin/templates/sb_admin/widgets/filer_file.html +84 -0
  158. django_smartbase_admin/templates/sb_admin/widgets/includes/related_item_buttons.html +38 -0
  159. django_smartbase_admin/templates/sb_admin/widgets/multiwidget.html +1 -1
  160. django_smartbase_admin/templates/sb_admin/widgets/radio.html +3 -2
  161. django_smartbase_admin/templates/sb_admin/widgets/radio_dropdown.html +30 -0
  162. django_smartbase_admin/templates/sb_admin/widgets/read_only_password_hash.html +3 -0
  163. django_smartbase_admin/templates/sb_admin/widgets/time.html +8 -1
  164. django_smartbase_admin/templates/sb_admin/widgets/toggle.html +1 -1
  165. django_smartbase_admin/templates/sb_admin/widgets/tree_base.html +59 -0
  166. django_smartbase_admin/templates/sb_admin/widgets/tree_select.html +24 -0
  167. django_smartbase_admin/templates/sb_admin/widgets/tree_select_inline.html +12 -0
  168. django_smartbase_admin/templatetags/sb_admin_tags.py +85 -4
  169. django_smartbase_admin/utils.py +22 -3
  170. django_smartbase_admin/views/dashboard_view.py +6 -0
  171. django_smartbase_admin/views/global_filter_view.py +8 -2
  172. django_smartbase_admin/views/translations_view.py +12 -5
  173. django_smartbase_admin/views/user_config_view.py +52 -0
  174. django_smartbase_admin-1.0.38.dist-info/METADATA +166 -0
  175. {django_smartbase_admin-0.2.54.dist-info → django_smartbase_admin-1.0.38.dist-info}/RECORD +177 -115
  176. {django_smartbase_admin-0.2.54.dist-info → django_smartbase_admin-1.0.38.dist-info}/WHEEL +1 -1
  177. django_smartbase_admin/templates/sb_admin/integrations/sorting/change_list.html +0 -401
  178. django_smartbase_admin-0.2.54.dist-info/METADATA +0 -25
  179. {django_smartbase_admin-0.2.54.dist-info → django_smartbase_admin-1.0.38.dist-info}/LICENSE.md +0 -0
@@ -0,0 +1,406 @@
1
+ const loadValue = function ($inputEl, treeWidgetData, treeInstance) {
2
+ let value = $inputEl.val()
3
+ if (value !== undefined) {
4
+ treeInstance.selectAll(false)
5
+ }
6
+
7
+ let selectedKeys = [value]
8
+ if (treeWidgetData.filter === true) {
9
+ let selectedData = []
10
+ selectedKeys = []
11
+ try {
12
+ selectedData = JSON.parse(value)
13
+ } catch(e) {
14
+ selectedData = []
15
+ }
16
+ finally {
17
+ selectedData.forEach(function (item) {
18
+ selectedKeys.push(item.value)
19
+ })
20
+ }
21
+ } else if (treeWidgetData.multiselect === 2) {
22
+ try {
23
+ selectedKeys = JSON.parse(value)
24
+ } catch (e) {
25
+ selectedKeys = []
26
+ }
27
+ }
28
+ selectedKeys.forEach(function (key) {
29
+ let selectedNode = treeInstance.getNodeByKey(key)
30
+ if (selectedNode) {
31
+ selectedNode.getParentList().forEach(function (node) {
32
+ // Expand all parents of selected node
33
+ node.setExpanded(true)
34
+ })
35
+ selectedNode.setSelected(true)
36
+ }
37
+ })
38
+ }
39
+
40
+
41
+ 'use strict'
42
+ {
43
+ $(function () {
44
+ const initTree = function ($treeEl) {
45
+ if ($treeEl.hasClass('fancytree-container')) {
46
+ return
47
+ }
48
+ $treeEl.on('change', (e) => {
49
+ const changeTarget = e.target.dataset['changeTarget']
50
+ if(changeTarget) {
51
+ const inlineWrapper = e.target.closest('.djn-inline-form')
52
+ if (inlineWrapper) {
53
+ const changeTargetEl = inlineWrapper.querySelector(changeTarget)
54
+ if(changeTargetEl) {
55
+ changeTargetEl.value = e.target.value
56
+ }
57
+ }
58
+ }
59
+ })
60
+ let filterData = {}
61
+ const filterByTableData = (treeInstance) => {
62
+ if(!filterData["isFiltered"]) {
63
+ treeInstance.filterNodes((node) => {
64
+ node.setExpanded(false)
65
+ return true
66
+ }, { autoExpand: false })
67
+ return
68
+ }
69
+
70
+ const tableData = {}
71
+ filterData['data'].forEach(item => tableData[item.path] = item)
72
+
73
+ treeInstance.filterNodes((node) => {
74
+ if(tableData[node.key]) {
75
+ return true
76
+ }
77
+ }, { autoExpand: true })
78
+ }
79
+
80
+ const $treeDataEl = $('#' + $treeEl.data('tree-data-id'))
81
+ const $treeAdditionalColumnsDataEl = $('#' + $treeEl.data('tree-additional-columns-id'))
82
+ const $treeStringsDataEl = $('#' + $treeEl.data('tree-strings-id'))
83
+ let treeWidgetData = {}
84
+ let additionalColumns = []
85
+ let treeStrings = {loading: "Loading...", loadError: "Load error!", moreData: "More...", noData: "No data."}
86
+ try {
87
+ treeWidgetData = JSON.parse($treeDataEl.text()) || {}
88
+ additionalColumns = JSON.parse($treeAdditionalColumnsDataEl.text()) || []
89
+ treeStrings = JSON.parse($treeStringsDataEl.text())
90
+ } catch (e) {
91
+ console.error(e)
92
+ }
93
+ const $inputEl = $('#' + treeWidgetData.input_id)
94
+ const $searchEl = $('#' + treeWidgetData.input_id + '_search')
95
+ const $matchesEl = $('#' + treeWidgetData.input_id + '_matches')
96
+ const $label = $('#' + treeWidgetData.input_id + '_label')
97
+ const tableConfig = {
98
+ indentation: 32,
99
+ nodeColumnIdx: 0,
100
+ }
101
+ const fancytreeFilterSettings = window.loadJSONScriptData("fancytree_filter_settings")
102
+ if (treeWidgetData.checkbox) {
103
+ tableConfig.checkboxColumnIdx = 0
104
+ tableConfig.nodeColumnIdx = 1
105
+ }
106
+ let extensions = ["table", "gridnav", "filter"]
107
+ if (treeWidgetData.reorder_url) {
108
+ extensions.push("dnd5")
109
+ }
110
+ $treeEl.fancytree({
111
+ source: {
112
+ url: treeWidgetData.data_url,
113
+ },
114
+ extensions: extensions,
115
+ checkbox: treeWidgetData.checkbox,
116
+ selectMode: treeWidgetData.multiselect,
117
+ dnd5: {
118
+ preventVoidMoves: true,
119
+ preventRecursion: true,
120
+ autoExpandMS: 400,
121
+ dropMarkerParent: '.tree-list-view',
122
+ dropMarkerOffsetX: -8,
123
+ dropMarkerInsertOffsetX: 0,
124
+ dragStart: function () {
125
+ return true
126
+ },
127
+ dragEnter: function () {
128
+ return true
129
+ },
130
+ dragDrop: function (node, data) {
131
+ data.otherNode.moveTo(node, data.hitMode)
132
+ },
133
+ },
134
+ filter: {
135
+ autoApply: true, // Re-apply last filter if lazy data is loaded
136
+ autoExpand: true, // Expand all branches that contain matches while filtered
137
+ counter: false, // Show a badge with number of matching child nodes near parent icons
138
+ fuzzy: false, // Match single characters in order, e.g. 'fb' will match 'FooBar'
139
+ hideExpandedCounter: true, // Hide counter badge if parent is expanded
140
+ hideExpanders: true, // Hide expanders if all child nodes are hidden by filter
141
+ highlight: true, // Highlight matches by wrapping inside <mark> tags
142
+ leavesOnly: false, // Match end nodes only
143
+ nodata: true, // Display a 'no data' status node if result is empty
144
+ mode: "hide", // Grayout unmatched nodes (pass "hide" to remove unmatched node instead)
145
+ ...fancytreeFilterSettings,
146
+ },
147
+ table: tableConfig,
148
+ gridnav: {
149
+ autofocusInput: false,
150
+ handleCursorKeys: true,
151
+ },
152
+ strings: treeStrings,
153
+ postProcess: function (event, data) {
154
+ setTimeout(function () {
155
+ loadValue($inputEl, treeWidgetData, data.tree)
156
+ }, 1)
157
+ },
158
+ select: function (event, data) {
159
+ if($inputEl.length > 0) {
160
+ let value = null
161
+ if (treeWidgetData.filter) {
162
+ value = data.tree.getSelectedNodes().map(function (node) {
163
+ return {'value': node.key, 'label': node.title}
164
+ })
165
+ $inputEl.val(JSON.stringify(value))
166
+ } else {
167
+ value = data.tree.getSelectedNodes().map(function (node) {
168
+ return node.key
169
+ }).join(", ")
170
+ if (treeWidgetData.multiselect === 2) {
171
+ value = JSON.stringify(data.tree.getSelectedNodes().map(function (node) {
172
+ return node.key
173
+ }))
174
+ }
175
+ let label = data.tree.getSelectedNodes().map(function (node) {
176
+ return node.title
177
+ }).join(", ")
178
+ $label.text(label)
179
+ $inputEl.val(value)
180
+ }
181
+ $inputEl[0].dispatchEvent(new CustomEvent('SBAutocompleteChange'))
182
+ $inputEl[0].dispatchEvent(new Event('change', {bubbles: true}))
183
+ return
184
+ }
185
+
186
+ if(treeWidgetData.filter_by_table_data && data.originalEvent) {
187
+ const selectedKeys = data.tree.getSelectedNodes().map(function (node) {
188
+ return node.data.id
189
+ })
190
+ document.body.dispatchEvent(new CustomEvent("treeRowSelected", {detail: {data: selectedKeys}}))
191
+ }
192
+ },
193
+ enhanceTitle: function(event, data) {
194
+ const reorderActive = treeWidgetData.reorder_url
195
+ const nodeRendered = data.node.rendered
196
+ const filteringActive = data.node.tree.enableFilter
197
+ const detailUrlPresent = treeWidgetData.detail_url
198
+ const validVisibleNode = data.node.span && data.node.span.classList.contains("fancytree-node") && !data.node.tr.classList.contains("fancytree-hide")
199
+ if (detailUrlPresent && (!nodeRendered || filteringActive) && !reorderActive && validVisibleNode) {
200
+ const title = data.node.span.querySelector('.fancytree-title')
201
+ const titleLink = document.createElement('a')
202
+ const url = treeWidgetData.detail_url.replace(-1, data.node.data.id)
203
+ titleLink.innerHTML = title.innerHTML
204
+ titleLink.classList.add('link')
205
+ titleLink.href = url
206
+ title.innerHTML = ''
207
+ title.append(titleLink)
208
+ title.addEventListener('mousedown', function (e) {
209
+ e.preventDefault()
210
+ e.stopPropagation()
211
+ }, true)
212
+ }
213
+ },
214
+ renderNode: function (event, data) {
215
+ const reorderActive = treeWidgetData.reorder_url
216
+ const nodeRendered = data.node.rendered
217
+ const validVisibleNode = data.node.span && data.node.span.classList.contains("fancytree-node") && !data.node.tr.classList.contains("fancytree-hide")
218
+ if (nodeRendered && !reorderActive) {
219
+ return
220
+ }
221
+ let isLastSib = data.node && data.node.tr && data.node.tr.classList.contains('fancytree-lastsib')
222
+ isLastSib = isLastSib === undefined ? false : isLastSib
223
+ if (data.node.parent) {
224
+ data.node.treeLevel = data.node.parent.treeLevel + 1
225
+ data.node.treeAdditionalLevel = [{
226
+ level: data.node.treeLevel,
227
+ lastSib: isLastSib
228
+ }, ...data.node.parent.treeAdditionalLevel]
229
+ } else {
230
+ data.node.treeLevel = 0
231
+ data.node.treeAdditionalLevel = []
232
+ }
233
+ data.node.rendered = true
234
+ if (validVisibleNode) {
235
+ const expander = data.node.span.querySelector('.fancytree-expander')
236
+ const level = data.node.treeLevel
237
+ const additionalLevel = data.node.treeAdditionalLevel
238
+ expander.innerHTML = ""
239
+ expander.className = 'fancytree-expander'
240
+ expander.classList.add('level-' + level)
241
+ if (data.node.parent.expanded) {
242
+ expander.classList.add('expanded-parent')
243
+ }
244
+ additionalLevel.forEach((item, i) => {
245
+ if (i !== 0 && item.lastSib) {
246
+ return
247
+ }
248
+ const expanderAdditional = document.createElement('span')
249
+ expanderAdditional.classList.add('expander-additional')
250
+ if (i === 0) {
251
+ expanderAdditional.classList.add('expander-additional-last')
252
+ }
253
+ expanderAdditional.style.left = -(50 + 100 * i) + '%'
254
+ expander.append(expanderAdditional)
255
+ })
256
+ const expanderBorder = document.createElement('span')
257
+ expanderBorder.classList.add('expander-border')
258
+ expander.append(expanderBorder)
259
+ expander.append(document.createElement('div'))
260
+ }
261
+ },
262
+ renderColumns: function (event, data) {
263
+ const $tdList = $(data.node.tr).find(">td")
264
+ additionalColumns.forEach(function (column, index) {
265
+ let value = data.node.data[column.key]
266
+ if (column.hide_zero && value === 0) {
267
+ value = ""
268
+ }
269
+ $tdList.eq(1 + tableConfig.nodeColumnIdx + index).html(value)
270
+ })
271
+ },
272
+ init: function (event, data) {
273
+ if(treeWidgetData.filter_by_table_data) {
274
+ filterByTableData(data.tree)
275
+ }
276
+ if (treeWidgetData.allow_select_all) {
277
+ appendSelectAllCheckbox($treeEl, data.tree)
278
+ }
279
+ }
280
+ })
281
+
282
+ const clearSearchInput = function () {
283
+ $searchEl.val("")
284
+ $matchesEl.text("")
285
+ treeInstance.visit(function (node) {
286
+ node.rendered = false
287
+ })
288
+ treeInstance.clearFilter()
289
+ }
290
+
291
+ const treeInstance = $.ui.fancytree.getTree('#' + treeWidgetData.input_id + '_tree')
292
+ $inputEl.on('SBTableFilterFormLoad', () => {
293
+ loadValue($inputEl, treeWidgetData, treeInstance)
294
+ })
295
+ $inputEl.on('clear', () => {
296
+ treeInstance.getSelectedNodes().map(node => {
297
+ node.setSelected(false)
298
+ })
299
+ })
300
+ $searchEl.on("keyup", function (e) {
301
+ let match = $(this).val()
302
+ let n = treeInstance.filterNodes(match)
303
+
304
+ if (e && e.which === $.ui.keyCode.ESCAPE || $.trim(match) === "") {
305
+ clearSearchInput()
306
+ return
307
+ }
308
+ $matchesEl.text("(" + n + " matches)")
309
+ }).trigger("focus")
310
+
311
+ $searchEl.on('search', function (e) {
312
+ // handle clear event of search input only if clicked on X
313
+ if (e.target.value) {
314
+ return
315
+ }
316
+ clearSearchInput()
317
+ })
318
+
319
+ treeInstance.saveTreeOrder = function () {
320
+ const order = JSON.stringify(treeInstance.toDict())
321
+ fetch(treeWidgetData.reorder_url, {
322
+ method: 'POST',
323
+ headers: {
324
+ "X-CSRFToken": window.csrf_token,
325
+ },
326
+ body: order
327
+ }).then(response => response.json())
328
+ .then(res => {
329
+ document.getElementById("notification-messages").innerHTML = res.messages
330
+ window.htmx.process(document.getElementById("notification-messages"))
331
+ })
332
+ }
333
+
334
+ if(treeWidgetData.filter_by_table_data) {
335
+ document.body.addEventListener('tableDataProcessed', (e) => {
336
+ filterData = e.detail
337
+ if(treeInstance.getRootNode().getChildren()[0]["statusNodeType"] === "loading") {
338
+ return
339
+ }
340
+ filterByTableData(treeInstance)
341
+ })
342
+
343
+ document.body.addEventListener('treeDeselectAllRows', () => {
344
+ treeInstance.selectAll(false)
345
+ })
346
+ document.body.addEventListener('treeSelectAllRows', () => {
347
+ treeInstance.selectAll(true)
348
+ })
349
+ }
350
+ }
351
+
352
+ const appendSelectAllCheckbox = function ($treeEl, treeInstance) {
353
+ const $selectAllWrapper = $treeEl.find(".select-all-checkbox-wrapper")
354
+
355
+ $selectAllWrapper.append(`
356
+ <span role="checkbox" aria-label="Select All" class="fancytree-checkbox" id="fancytree-row-select-all"></span>
357
+ `)
358
+
359
+ $("#fancytree-row-select-all").on("click", function () {
360
+ const selectedClassName = "fancytree-selected"
361
+ const isChecked = $(this).parent().hasClass(selectedClassName)
362
+
363
+ const visibleNodes = treeInstance.getRootNode().findAll(node => node.match)
364
+ visibleNodes.forEach(node => node.setSelected(!isChecked))
365
+
366
+ if (!isChecked) {
367
+ $(this).parent().addClass(selectedClassName)
368
+ } else {
369
+ $(this).parent().removeClass(selectedClassName)
370
+ }
371
+ })
372
+ }
373
+
374
+ const initAllTrees = function () {
375
+ $('.js-tree-widget').each(function (index, element) {
376
+ const $treeEl = $(element)
377
+ if ($treeEl.hasClass('fancytree-container') || element.closest('.djn-empty-form')) {
378
+ return
379
+ }
380
+ const $dropdownMenu = $treeEl.closest('.dropdown-menu')
381
+ if ($dropdownMenu.length > 0) {
382
+ const initTreeOnShow = function () {
383
+ initTree($treeEl)
384
+ $dropdownMenu.prev().off('show.bs.dropdown', initTreeOnShow)
385
+ }
386
+ $dropdownMenu.prev().on('show.bs.dropdown', initTreeOnShow)
387
+ return
388
+ }
389
+ initTree($treeEl)
390
+ })
391
+ }
392
+ initAllTrees()
393
+
394
+ document.addEventListener('formset:added', () => {
395
+ initAllTrees()
396
+ })
397
+
398
+ const queryBuilderEl$ = $(".query-builder-advanced")
399
+ queryBuilderEl$.on("afterCreateRuleInput.queryBuilder", function () {
400
+ setTimeout(() => {
401
+ //next tick
402
+ initAllTrees()
403
+ }, 0)
404
+ })
405
+ })
406
+ }
@@ -54,8 +54,11 @@ export const filterInputValueChangeListener = (inputSelector, callbackFunction)
54
54
  input.addEventListener('change', callbackFunction)
55
55
  input.addEventListener('keypress', (event) => {
56
56
  if (event.keyCode === 13) {
57
+ // fire change event
58
+ input.blur()
57
59
  event.preventDefault()
58
- callbackFunction(event)
60
+ // refocus
61
+ input.focus()
59
62
  return true
60
63
  }
61
64
  })
@@ -63,6 +66,52 @@ export const filterInputValueChangeListener = (inputSelector, callbackFunction)
63
66
  })
64
67
  }
65
68
 
69
+ const getResultLabel = (valueOrObject, separator=', ') => {
70
+ const labelArray = []
71
+ const entries = Object.values(valueOrObject)
72
+ let hasMaxEntries = false
73
+ for (let [index, item] of entries.entries()) {
74
+ if (index === window.sb_admin_const.MULTISELECT_FILTER_MAX_CHOICES_SHOWN) {
75
+ break
76
+ }
77
+ if(entries.length > 1 && item.value === window.sb_admin_const.SELECT_ALL_KEYWORD) {
78
+ continue
79
+ }
80
+ if (index === window.sb_admin_const.MULTISELECT_FILTER_MAX_CHOICES_SHOWN - 2 && entries[index + 2]) {
81
+ labelArray.push(item.label)
82
+ hasMaxEntries = true
83
+ break
84
+ }
85
+ labelArray.push(item.label)
86
+ }
87
+ let resultLabel = labelArray.join(separator)
88
+ if(hasMaxEntries) {
89
+ resultLabel = resultLabel.substring(0, resultLabel.length)
90
+ resultLabel += `... +${entries.length - window.sb_admin_const.MULTISELECT_FILTER_MAX_CHOICES_SHOWN + 1}`
91
+ }
92
+ return resultLabel
93
+ }
94
+
95
+ export const setDropdownLabel = (dropdownMenuEl, dropdownLabelEl) => {
96
+ if(!dropdownMenuEl) {
97
+ return
98
+ }
99
+ if(!dropdownLabelEl) {
100
+ dropdownLabelEl = dropdownMenuEl.querySelector('.js-dropdown-label')
101
+ }
102
+ if(!dropdownLabelEl) {
103
+ return
104
+ }
105
+ const fields = Array.from(dropdownMenuEl.querySelectorAll('input[type="checkbox"]:checked, input[type="radio"]:checked')).map(el => {
106
+ const label = el.parentElement.querySelector(`label[for="${el.id}"]`)
107
+ return {
108
+ 'value': el.value,
109
+ 'label': label?label.innerHTML:''
110
+ }
111
+ })
112
+ dropdownLabelEl.innerHTML = getResultLabel(fields)
113
+ }
114
+
66
115
  export const filterInputValueChangedUtil = (field) => {
67
116
  const filterId = field.dataset.filterId || field.id
68
117
  const separator = field.dataset.labelSeparator || ', '
@@ -70,6 +119,11 @@ export const filterInputValueChangedUtil = (field) => {
70
119
  if(!valueElem) {
71
120
  return
72
121
  }
122
+ const label = field.dataset.label
123
+ if(label) {
124
+ valueElem.innerHTML = label
125
+ return valueElem
126
+ }
73
127
  const valueOrObject = getObjectOrValue(field.value)
74
128
  if ((field.value === "" || field.value === "[]")) {
75
129
  if(field.dataset.emptyLabel) {
@@ -80,26 +134,7 @@ export const filterInputValueChangedUtil = (field) => {
80
134
  return valueElem
81
135
  }
82
136
  if (typeof valueOrObject === 'object') {
83
- const labelArray = []
84
- const entries = Object.values(valueOrObject)
85
- let hasMaxEntries = false
86
- for (let [index, item] of entries.entries()) {
87
- if (index === window.sb_admin_const.MULTISELECT_FILTER_MAX_CHOICES_SHOWN) {
88
- break
89
- }
90
- if (index === window.sb_admin_const.MULTISELECT_FILTER_MAX_CHOICES_SHOWN - 2 && entries[index + 2]) {
91
- labelArray.push(item.label)
92
- hasMaxEntries = true
93
- break
94
- }
95
- labelArray.push(item.label)
96
- }
97
- let resultLabel = labelArray.join(separator)
98
- if(hasMaxEntries) {
99
- resultLabel = resultLabel.substring(0, resultLabel.length)
100
- resultLabel += `... +${entries.length - window.sb_admin_const.MULTISELECT_FILTER_MAX_CHOICES_SHOWN + 1}`
101
- }
102
- valueElem.innerHTML = resultLabel
137
+ valueElem.innerHTML = getResultLabel(valueOrObject, separator)
103
138
  } else {
104
139
  try {
105
140
  // select