@demos-europe/demosplan-ui 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/buildTokens.js +59 -0
- package/components/DpButton/DpButton.stories.mdx +136 -0
- package/components/DpButton/DpButton.vue +118 -0
- package/components/DpDetails/DpDetails.stories.mdx +55 -0
- package/components/DpDetails/DpDetails.vue +58 -0
- package/components/DpIcon/DpIcon.stories.mdx +396 -0
- package/components/DpIcon/DpIcon.vue +51 -0
- package/components/DpIcon/util/iconVariables.js +148 -0
- package/components/DpInput/DpInput.stories.mdx +127 -0
- package/components/DpInput/DpInput.vue +284 -0
- package/components/DpLabel/DpLabel.stories.mdx +103 -0
- package/components/DpLabel/DpLabel.vue +112 -0
- package/components/DpLoading/DpLoading.stories.mdx +63 -0
- package/components/DpLoading/DpLoading.vue +63 -0
- package/components/core/DpAccordion.vue +108 -0
- package/components/core/DpAnonymizeText.vue +136 -0
- package/components/core/DpAutocomplete.vue +133 -0
- package/components/core/DpBulkEditHeader.vue +53 -0
- package/components/core/DpButtonIcon.vue +47 -0
- package/components/core/DpButtonRow.vue +155 -0
- package/components/core/DpCard.vue +54 -0
- package/components/core/DpChangeStateAtDate.vue +223 -0
- package/components/core/DpCheckboxGroup.vue +93 -0
- package/components/core/DpContextualHelp.vue +54 -0
- package/components/core/DpCopyPasteButton.vue +47 -0
- package/components/core/DpDashboardTaskCard.vue +123 -0
- package/components/core/DpDataTable/DataTableSearch.js +44 -0
- package/components/core/DpDataTable/DpColumnSelector.vue +133 -0
- package/components/core/DpDataTable/DpDataTable.vue +647 -0
- package/components/core/DpDataTable/DpDataTableExtended.vue +377 -0
- package/components/core/DpDataTable/DpResizeHandle.vue +37 -0
- package/components/core/DpDataTable/DpSelectPageItemCount.vue +70 -0
- package/components/core/DpDataTable/DpTableHeader.vue +197 -0
- package/components/core/DpDataTable/DpTableRow.vue +355 -0
- package/components/core/DpDataTable/DpWrapTrigger.vue +48 -0
- package/components/core/DpDataTable/lib/ResizableColumns.js +83 -0
- package/components/core/DpEditableList.vue +161 -0
- package/components/core/DpEditor/DpBoilerPlate.vue +140 -0
- package/components/core/DpEditor/DpBoilerPlateModal.vue +166 -0
- package/components/core/DpEditor/DpEditor.vue +1281 -0
- package/components/core/DpEditor/DpLinkModal.vue +117 -0
- package/components/core/DpEditor/DpRecommendationModal/DpInsertableRecommendation.vue +137 -0
- package/components/core/DpEditor/DpRecommendationModal.vue +283 -0
- package/components/core/DpEditor/DpResizableImage.vue +121 -0
- package/components/core/DpEditor/DpUploadModal.vue +121 -0
- package/components/core/DpEditor/libs/Decoration.js +66 -0
- package/components/core/DpEditor/libs/SegmentRangeChangePlugin.js +35 -0
- package/components/core/DpEditor/libs/editorAnonymize.js +66 -0
- package/components/core/DpEditor/libs/editorBuildSuggestion.js +269 -0
- package/components/core/DpEditor/libs/editorCustomDelete.js +32 -0
- package/components/core/DpEditor/libs/editorCustomImage.js +100 -0
- package/components/core/DpEditor/libs/editorCustomInsert.js +32 -0
- package/components/core/DpEditor/libs/editorCustomLink.js +46 -0
- package/components/core/DpEditor/libs/editorCustomMark.js +32 -0
- package/components/core/DpEditor/libs/editorInsertAtCursorPos.js +41 -0
- package/components/core/DpEditor/libs/editorObscure.js +60 -0
- package/components/core/DpEditor/libs/editorUnAnonymize.js +56 -0
- package/components/core/DpEditor/libs/handleWordPaste.js +360 -0
- package/components/core/DpEditor/libs/preventDrop.js +31 -0
- package/components/core/DpEditor/libs/preventKeyboardInput.js +27 -0
- package/components/core/DpEditor/libs/preventPaste.js +28 -0
- package/components/core/DpFlyout.vue +119 -0
- package/components/core/DpInlineNotification.vue +116 -0
- package/components/core/DpModal.vue +208 -0
- package/components/core/DpObscure.vue +29 -0
- package/components/core/DpPager.vue +139 -0
- package/components/core/DpProgressBar.vue +67 -0
- package/components/core/DpRegisterFlyout.vue +58 -0
- package/components/core/DpResettableInput.vue +140 -0
- package/components/core/DpSkeletonBox.vue +32 -0
- package/components/core/DpSlidebar.vue +86 -0
- package/components/core/DpSlidingPagination.vue +45 -0
- package/components/core/DpSplitButton.vue +77 -0
- package/components/core/DpSwitcher.vue +62 -0
- package/components/core/DpTableCardList/DpTableCard.vue +61 -0
- package/components/core/DpTableCardList/DpTableCardListHeader.vue +83 -0
- package/components/core/DpTabs/DpTab.vue +52 -0
- package/components/core/DpTabs/DpTabs.vue +165 -0
- package/components/core/DpTextWrapper.vue +65 -0
- package/components/core/DpToggleForm.vue +72 -0
- package/components/core/DpTooltipIcon.vue +52 -0
- package/components/core/DpTransitionExpand.vue +87 -0
- package/components/core/DpTreeList/DpTreeList.vue +334 -0
- package/components/core/DpTreeList/DpTreeListCheckbox.vue +79 -0
- package/components/core/DpTreeList/DpTreeListNode.vue +348 -0
- package/components/core/DpTreeList/DpTreeListToggle.vue +71 -0
- package/components/core/DpTreeList/utils/constants.js +14 -0
- package/components/core/DpUpload/DpUpload.vue +223 -0
- package/components/core/DpUpload/DpUploadFiles.vue +269 -0
- package/components/core/DpUpload/DpUploadedFile.vue +80 -0
- package/components/core/DpUpload/DpUploadedFileList.vue +56 -0
- package/components/core/DpUpload/utils/GetFileIdsByHash.js +42 -0
- package/components/core/DpUpload/utils/UppyTranslations.js +31 -0
- package/components/core/DpVideoPlayer.vue +115 -0
- package/components/core/HeightLimit.vue +121 -0
- package/components/core/MultistepNav.vue +89 -0
- package/components/core/form/DpCheckbox.vue +108 -0
- package/components/core/form/DpDateRangePicker.vue +186 -0
- package/components/core/form/DpDatepicker.vue +160 -0
- package/components/core/form/DpDatetimePicker.vue +194 -0
- package/components/core/form/DpFormRow.vue +79 -0
- package/components/core/form/DpMultiselect.vue +164 -0
- package/components/core/form/DpRadio.vue +128 -0
- package/components/core/form/DpSearchField.vue +110 -0
- package/components/core/form/DpSelect.vue +149 -0
- package/components/core/form/DpTextArea.vue +152 -0
- package/components/core/form/DpTimePicker.vue +374 -0
- package/components/core/form/DpToggle.vue +78 -0
- package/components/core/index.js +132 -0
- package/components/core/notify/DpNotifyContainer.vue +122 -0
- package/components/core/notify/DpNotifyMessage.vue +95 -0
- package/components/core/shared/DpStickyElement.vue +95 -0
- package/components/index.js +24 -0
- package/components/shared/translations.js +15 -0
- package/directives/CleanHtml/CleanHtml.js +50 -0
- package/directives/CleanHtml/CleanHtml.stories.mdx +64 -0
- package/directives/Tooltip/Tooltip.js +40 -0
- package/directives/Tooltip/Tooltip.stories.mdx +42 -0
- package/directives/index.js +17 -0
- package/lib/index.js +14 -0
- package/lib/prefixClass.js +47 -0
- package/mixins/index.js +14 -0
- package/mixins/prefixClassMixin.js +22 -0
- package/package.json +52 -0
- package/shared/props.js +86 -0
- package/style/index.css +7 -0
- package/tailwind.config.js +24 -0
- package/tokens/color.json +358 -0
- package/tokens/color.stories.mdx +45 -0
- package/tokens/fontSize.json +100 -0
- package/tokens/space.json +33 -0
- package/utils/lengthHint.js +69 -0
|
@@ -0,0 +1,647 @@
|
|
|
1
|
+
<license>
|
|
2
|
+
(c) 2010-present DEMOS E-Partizipation GmbH.
|
|
3
|
+
|
|
4
|
+
This file is part of the package @demos-europe/demosplan-ui,
|
|
5
|
+
for more information see the license file.
|
|
6
|
+
|
|
7
|
+
All rights reserved
|
|
8
|
+
</license>
|
|
9
|
+
|
|
10
|
+
<documentation>
|
|
11
|
+
<!-- The most simple way to use DpDataTable is the following -->
|
|
12
|
+
<!-- NOTICE: the elements in the items array must have a key which matches a field in the header-fields array -->
|
|
13
|
+
<!-- The following example defines a table with two equally sized columns named "E-Mail" a,d "Name":
|
|
14
|
+
headerFields: [
|
|
15
|
+
{
|
|
16
|
+
field: 'mail',
|
|
17
|
+
label: 'E-Mail',
|
|
18
|
+
colClass: 'u-1-of-2', // can be omitted, renders a colgroup > col element
|
|
19
|
+
tooltip: 'longer.text.then.label' // can be omitted
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
field: 'name',
|
|
23
|
+
label: 'Name'
|
|
24
|
+
},
|
|
25
|
+
],
|
|
26
|
+
items: [{mail: 'test@domain.dev', name: 'Testname'}]
|
|
27
|
+
-->
|
|
28
|
+
<usage>
|
|
29
|
+
<dp-data-table
|
|
30
|
+
has-flyout="true|false"
|
|
31
|
+
header-fields="[{field, label}]"
|
|
32
|
+
is-selectable="true|false"
|
|
33
|
+
items="[{fieldName}]"
|
|
34
|
+
search-string="String"
|
|
35
|
+
track-by="ID (used for checkbox identification)" />
|
|
36
|
+
</usage>
|
|
37
|
+
<!-- -->
|
|
38
|
+
<!-- -->
|
|
39
|
+
<!-- Advanced setup with scoped slots -->
|
|
40
|
+
<usage>
|
|
41
|
+
<dp-data-table
|
|
42
|
+
search-string="searchString"
|
|
43
|
+
header-fields="headerInput (see above)"
|
|
44
|
+
table-class="additional classes for the table element"
|
|
45
|
+
items="inputItems (see above)"
|
|
46
|
+
track-by="(see above)">
|
|
47
|
+
<!-- header slots -->
|
|
48
|
+
<template
|
|
49
|
+
v-for="element in headerInput /* iterate over the HeaderFields */"
|
|
50
|
+
v-slot:[`header-${element.field}`]="headerData">
|
|
51
|
+
<div :key="element.id">
|
|
52
|
+
{{ headerData.value }}
|
|
53
|
+
</div>
|
|
54
|
+
</template>
|
|
55
|
+
<!-- field slots -->
|
|
56
|
+
<template
|
|
57
|
+
v-for="(element, i) in inputItems"
|
|
58
|
+
v-slot:[element.field]="rowData">
|
|
59
|
+
<!-- For highlighting text in a slot we have to do something like this, where the `highlightText()` method
|
|
60
|
+
has to handle the highlighting from outside -->
|
|
61
|
+
<span
|
|
62
|
+
:key="`${element.field}:${i}`"
|
|
63
|
+
v-cleanhtml="highlightText(rowData[element.field])" />
|
|
64
|
+
</template>
|
|
65
|
+
</dp-data-table>
|
|
66
|
+
</usage>
|
|
67
|
+
</documentation>
|
|
68
|
+
|
|
69
|
+
<script>
|
|
70
|
+
import { CleanHtml } from 'demosplan-ui/directives'
|
|
71
|
+
import DomPurify from 'dompurify'
|
|
72
|
+
import { DpLoading } from 'demosplan-ui/components'
|
|
73
|
+
import DpTableHeader from './DpTableHeader'
|
|
74
|
+
import DpTableRow from './DpTableRow'
|
|
75
|
+
import draggable from 'vuedraggable'
|
|
76
|
+
|
|
77
|
+
export default {
|
|
78
|
+
name: 'DpDataTable',
|
|
79
|
+
|
|
80
|
+
components: {
|
|
81
|
+
DpLoading,
|
|
82
|
+
DpTableHeader,
|
|
83
|
+
DpTableRow,
|
|
84
|
+
draggable
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
directives: {
|
|
88
|
+
cleanhtml: CleanHtml
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
props: {
|
|
92
|
+
// Adds flyout menu
|
|
93
|
+
hasFlyout: {
|
|
94
|
+
type: Boolean,
|
|
95
|
+
required: false,
|
|
96
|
+
default: false
|
|
97
|
+
},
|
|
98
|
+
|
|
99
|
+
// The first table row (consisting of column headers) is being fixed to the top of the outer table element.
|
|
100
|
+
hasStickyHeader: {
|
|
101
|
+
type: Boolean,
|
|
102
|
+
required: false,
|
|
103
|
+
default: false
|
|
104
|
+
},
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* The header of every column of the table is defined here.
|
|
108
|
+
*
|
|
109
|
+
* Each column is represented by an object with a `field` key whose value should match
|
|
110
|
+
* a key of the objects inside `items`. The `label` key controls the header of the column.
|
|
111
|
+
* The header can also have a tooltip. To define the width the column is initially rendered with
|
|
112
|
+
* when `isResizable` is used, the keys `initialWidth`, `initialMaxWidth` and `initialMinWidth` take a px value.
|
|
113
|
+
*/
|
|
114
|
+
headerFields: {
|
|
115
|
+
type: Array,
|
|
116
|
+
required: true
|
|
117
|
+
},
|
|
118
|
+
|
|
119
|
+
initSelectedItems: {
|
|
120
|
+
type: Array,
|
|
121
|
+
required: false,
|
|
122
|
+
default: () => []
|
|
123
|
+
},
|
|
124
|
+
|
|
125
|
+
isDraggable: {
|
|
126
|
+
type: Boolean,
|
|
127
|
+
required: false,
|
|
128
|
+
default: false
|
|
129
|
+
},
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Rows may be expandable to show additional content inside another row.
|
|
133
|
+
* The `#expandedContent` slot can be utilized to style the content area.
|
|
134
|
+
*/
|
|
135
|
+
isExpandable: {
|
|
136
|
+
type: Boolean,
|
|
137
|
+
required: false,
|
|
138
|
+
default: false
|
|
139
|
+
},
|
|
140
|
+
|
|
141
|
+
isLoading: {
|
|
142
|
+
type: Boolean,
|
|
143
|
+
required: false,
|
|
144
|
+
default: false
|
|
145
|
+
},
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Make table columns resizable.
|
|
149
|
+
*/
|
|
150
|
+
isResizable: {
|
|
151
|
+
type: Boolean,
|
|
152
|
+
required: false,
|
|
153
|
+
default: false
|
|
154
|
+
},
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Display a checkbox in front of each row.
|
|
158
|
+
*/
|
|
159
|
+
isSelectable: {
|
|
160
|
+
type: Boolean,
|
|
161
|
+
required: false,
|
|
162
|
+
default: false
|
|
163
|
+
},
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* If specified, the checkbox will have a "name" attr with this value, and a value of [trackBy].
|
|
167
|
+
*/
|
|
168
|
+
isSelectableName: {
|
|
169
|
+
type: String,
|
|
170
|
+
required: false,
|
|
171
|
+
default: null
|
|
172
|
+
},
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Make table rows truncatable.
|
|
176
|
+
* Cell content is truncated after 1 line, but expandable to its original size.
|
|
177
|
+
* To make this work, no custom css must be applied to the cells that hold the
|
|
178
|
+
* content to be truncated.
|
|
179
|
+
* At the moment, `isTruncatable` and `isExpandable` use the same icon as trigger visual.
|
|
180
|
+
* See https://yaits.demos-deutschland.de/T11301#413638 for possible advancement.
|
|
181
|
+
*/
|
|
182
|
+
isTruncatable: {
|
|
183
|
+
type: Boolean,
|
|
184
|
+
required: false,
|
|
185
|
+
default: false
|
|
186
|
+
},
|
|
187
|
+
|
|
188
|
+
items: {
|
|
189
|
+
type: Array,
|
|
190
|
+
required: true
|
|
191
|
+
},
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* When selection on multiple pages is supported, this variable forces the "Check all" checkbox into a "checked"
|
|
195
|
+
* state, because instead of passing all checked items into here (which we can't in multi-page-selection suzenario),
|
|
196
|
+
* we just pass the info "all items are selected".
|
|
197
|
+
*/
|
|
198
|
+
multiPageAllSelected: {
|
|
199
|
+
type: Boolean,
|
|
200
|
+
required: false,
|
|
201
|
+
default: false
|
|
202
|
+
},
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Use a Boolean Property of the Item to set the Checkbox to a locked state.
|
|
206
|
+
* This should only be set if `isSelectable` is true.
|
|
207
|
+
*/
|
|
208
|
+
lockCheckboxBy: {
|
|
209
|
+
type: String,
|
|
210
|
+
required: false,
|
|
211
|
+
default: null
|
|
212
|
+
},
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* When selection on multiple pages is supported, this variable holds number of items currently toggled.
|
|
216
|
+
* It is used for calculating indeterminate state of the "check all" checkbox.
|
|
217
|
+
*/
|
|
218
|
+
multiPageSelectionItemsToggled: {
|
|
219
|
+
type: Number,
|
|
220
|
+
required: false,
|
|
221
|
+
default: 0
|
|
222
|
+
},
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* When selection on multiple pages is supported, this variable holds the absolute number of items available.
|
|
226
|
+
* It is used for calculating indeterminate state of the "check all" checkbox.
|
|
227
|
+
*/
|
|
228
|
+
multiPageSelectionItemsTotal: {
|
|
229
|
+
type: Number,
|
|
230
|
+
required: false,
|
|
231
|
+
default: 0
|
|
232
|
+
},
|
|
233
|
+
|
|
234
|
+
searchString: {
|
|
235
|
+
type: [String, null],
|
|
236
|
+
required: false,
|
|
237
|
+
default: null
|
|
238
|
+
},
|
|
239
|
+
|
|
240
|
+
// This allows item selection to be forced from outside. It will override any internal selection state.
|
|
241
|
+
shouldBeSelectedItems: {
|
|
242
|
+
type: Object,
|
|
243
|
+
required: false,
|
|
244
|
+
default: () => ({})
|
|
245
|
+
},
|
|
246
|
+
|
|
247
|
+
tableClass: {
|
|
248
|
+
type: String,
|
|
249
|
+
required: false,
|
|
250
|
+
default: 'c-data-table'
|
|
251
|
+
},
|
|
252
|
+
|
|
253
|
+
trackBy: {
|
|
254
|
+
type: String,
|
|
255
|
+
required: true
|
|
256
|
+
},
|
|
257
|
+
|
|
258
|
+
translations: {
|
|
259
|
+
type: Object,
|
|
260
|
+
required: false,
|
|
261
|
+
default: () => ({})
|
|
262
|
+
}
|
|
263
|
+
},
|
|
264
|
+
|
|
265
|
+
data () {
|
|
266
|
+
return {
|
|
267
|
+
allExpanded: false,
|
|
268
|
+
allWrapped: false,
|
|
269
|
+
defaultTranslations: {
|
|
270
|
+
footerSelectedElement: Translator.trans('entry.selected'),
|
|
271
|
+
footerSelectedElements: Translator.trans('entries.selected'),
|
|
272
|
+
headerExpandHint: Translator.trans('aria.expand.all'),
|
|
273
|
+
headerSelectHint: Translator.trans('aria.select.all'),
|
|
274
|
+
lockedForSelection: Translator.trans('item.lockedForSelection'),
|
|
275
|
+
searchNoResults: (searchTerm) => Translator.trans('search.no.results', { searchterm: searchTerm }),
|
|
276
|
+
tableLoadingData: Translator.trans('loading.data'),
|
|
277
|
+
tableNoElements: Translator.trans('explanation.noentries')
|
|
278
|
+
},
|
|
279
|
+
elementSelections: {},
|
|
280
|
+
expandedElements: {},
|
|
281
|
+
mergedTranslations: {},
|
|
282
|
+
selectedElements: [],
|
|
283
|
+
tableEl: undefined,
|
|
284
|
+
wrappedElements: {}
|
|
285
|
+
}
|
|
286
|
+
},
|
|
287
|
+
|
|
288
|
+
computed: {
|
|
289
|
+
allSelected () {
|
|
290
|
+
if (this.multiPageSelectionItemsTotal > 0) {
|
|
291
|
+
return this.multiPageSelectionItemsToggled === this.multiPageSelectionItemsTotal || this.multiPageAllSelected
|
|
292
|
+
} else {
|
|
293
|
+
return this.items.filter(item => this.elementSelections[item[this.trackBy]]).length === this.items.length
|
|
294
|
+
}
|
|
295
|
+
},
|
|
296
|
+
|
|
297
|
+
indeterminate () {
|
|
298
|
+
if (this.isSelectable === false) {
|
|
299
|
+
return
|
|
300
|
+
}
|
|
301
|
+
if (this.multiPageSelectionItemsTotal > 0) {
|
|
302
|
+
return this.multiPageSelectionItemsToggled > 0 && !this.allSelected
|
|
303
|
+
} else {
|
|
304
|
+
return this.selectedElements.length > 0 && !this.allSelected
|
|
305
|
+
}
|
|
306
|
+
},
|
|
307
|
+
|
|
308
|
+
searchTerm () {
|
|
309
|
+
if (this.searchString === null || this.searchString.length < 1) {
|
|
310
|
+
return new RegExp()
|
|
311
|
+
}
|
|
312
|
+
const searchTerm = this.searchString.replace(/\s*/ig, '\\s*')
|
|
313
|
+
return new RegExp(searchTerm, 'ig')
|
|
314
|
+
}
|
|
315
|
+
},
|
|
316
|
+
|
|
317
|
+
watch: {
|
|
318
|
+
shouldBeSelectedItems () {
|
|
319
|
+
this.forceElementSelections(this.shouldBeSelectedItems)
|
|
320
|
+
},
|
|
321
|
+
|
|
322
|
+
indeterminate () {
|
|
323
|
+
this.setIndeterminate()
|
|
324
|
+
}
|
|
325
|
+
},
|
|
326
|
+
|
|
327
|
+
methods: {
|
|
328
|
+
extractTranslations (keys) {
|
|
329
|
+
return keys.reduce((acc, key) => {
|
|
330
|
+
const tmp = this.mergedTranslations[key] ? { [key]: this.mergedTranslations[key] } : {}
|
|
331
|
+
return { ...acc, ...tmp }
|
|
332
|
+
}, {})
|
|
333
|
+
},
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Transforms and filters the list of selected items across all pages.
|
|
337
|
+
*
|
|
338
|
+
* Return only the `trackBy` of the items
|
|
339
|
+
*
|
|
340
|
+
* @returns {string[]}
|
|
341
|
+
*/
|
|
342
|
+
filterElementSelections () {
|
|
343
|
+
return Object.entries(this.elementSelections)
|
|
344
|
+
.filter(selectedItem => selectedItem[1]) // True or false
|
|
345
|
+
.map(selectedItem => selectedItem[0]) // TrackBy of the item
|
|
346
|
+
},
|
|
347
|
+
|
|
348
|
+
forceElementSelections (itemsStatusObject) {
|
|
349
|
+
this.elementSelections = itemsStatusObject
|
|
350
|
+
this.selectedElements = this.filterElementSelections()
|
|
351
|
+
},
|
|
352
|
+
|
|
353
|
+
setIndeterminate () {
|
|
354
|
+
if (this.isSelectable) {
|
|
355
|
+
this.$refs.selectAll.indeterminate = this.indeterminate
|
|
356
|
+
}
|
|
357
|
+
},
|
|
358
|
+
|
|
359
|
+
resetSelection () {
|
|
360
|
+
this.toggleSelectAll(false)
|
|
361
|
+
},
|
|
362
|
+
|
|
363
|
+
setElementSelections (elements, status) {
|
|
364
|
+
return elements.reduce((acc, el) => {
|
|
365
|
+
return {
|
|
366
|
+
...acc,
|
|
367
|
+
...{ [el[this.trackBy]]: status }
|
|
368
|
+
}
|
|
369
|
+
}, this.elementSelections)
|
|
370
|
+
},
|
|
371
|
+
|
|
372
|
+
toggleExpand (id) {
|
|
373
|
+
this.expandedElements = { ...this.expandedElements, [id]: !this.expandedElements[id] }
|
|
374
|
+
},
|
|
375
|
+
|
|
376
|
+
toggleExpandAll (status = this.allExpanded === false) {
|
|
377
|
+
this.expandedElements = this.items.reduce((acc, item) => {
|
|
378
|
+
return {
|
|
379
|
+
...acc,
|
|
380
|
+
...{ [item[this.trackBy]]: status }
|
|
381
|
+
}
|
|
382
|
+
}, {})
|
|
383
|
+
this.allExpanded = status
|
|
384
|
+
},
|
|
385
|
+
|
|
386
|
+
toggleSelect (id) {
|
|
387
|
+
this.elementSelections = { ...this.elementSelections, ...{ [id]: !this.elementSelections[id] } }
|
|
388
|
+
this.selectedElements = this.filterElementSelections()
|
|
389
|
+
|
|
390
|
+
this.$emit('items-selected', this.selectedElements)
|
|
391
|
+
this.$emit('items-toggled', [{ id }], this.elementSelections[id])
|
|
392
|
+
},
|
|
393
|
+
|
|
394
|
+
toggleSelectAll (status = this.allSelected === false) {
|
|
395
|
+
this.elementSelections = this.setElementSelections(this.items, status)
|
|
396
|
+
this.selectedElements = this.filterElementSelections()
|
|
397
|
+
this.$emit('items-selected', this.selectedElements)
|
|
398
|
+
this.$emit('items-toggled', this.items.map(el => { return { id: el[this.trackBy] } }), status)
|
|
399
|
+
|
|
400
|
+
// Used by multi-page selection in SegmentsList to determine whether to track selected or deselected items.
|
|
401
|
+
this.$emit('select-all', status)
|
|
402
|
+
},
|
|
403
|
+
|
|
404
|
+
toggleWrap (id) {
|
|
405
|
+
this.wrappedElements = { ...this.wrappedElements, [id]: !this.wrappedElements[id] }
|
|
406
|
+
},
|
|
407
|
+
|
|
408
|
+
toggleWrapAll (status = this.allWrapped === false) {
|
|
409
|
+
this.wrappedElements = this.items.reduce((acc, item) => {
|
|
410
|
+
return {
|
|
411
|
+
...acc,
|
|
412
|
+
...{ [item[this.trackBy]]: status }
|
|
413
|
+
}
|
|
414
|
+
}, {})
|
|
415
|
+
|
|
416
|
+
this.allWrapped = status
|
|
417
|
+
}
|
|
418
|
+
},
|
|
419
|
+
|
|
420
|
+
created () {
|
|
421
|
+
this.elementSelections = this.setElementSelections(this.initSelectedItems, true)
|
|
422
|
+
|
|
423
|
+
// The searchNoResults translation key needs to be a function, therefore make it a function before merging with defaultTranslations
|
|
424
|
+
let tmpTranslations = { ...this.translations }
|
|
425
|
+
const noResults = this.translations.searchNoResults ? { searchNoResults: () => this.translations.searchNoResults } : {}
|
|
426
|
+
tmpTranslations = { ...tmpTranslations, ...noResults }
|
|
427
|
+
|
|
428
|
+
this.mergedTranslations = { ...this.defaultTranslations, ...tmpTranslations }
|
|
429
|
+
},
|
|
430
|
+
|
|
431
|
+
mounted () {
|
|
432
|
+
this.tableEl = this.$refs.tableEl
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Why is this here you may ask?
|
|
436
|
+
* Tables and overflow are difficult to handle.
|
|
437
|
+
* When truncating cell we want a table cell to respect the max-width of our table.
|
|
438
|
+
* This can be achieved through table-layout: fixed;
|
|
439
|
+
* However, we want to have automatic cell sizing dependent on content.
|
|
440
|
+
* Therefore, we have a normal table, get the auto-sized cell width and set the layout to fixed afterwards.
|
|
441
|
+
*/
|
|
442
|
+
if (this.isResizable || this.isTruncatable) {
|
|
443
|
+
const firstRow = this.tableEl.firstChild
|
|
444
|
+
const tableHeaders = Array.prototype.slice.call(firstRow.childNodes)
|
|
445
|
+
tableHeaders.forEach(tableHeader => {
|
|
446
|
+
const width = tableHeader.getBoundingClientRect().width
|
|
447
|
+
tableHeader.style.width = width + 'px'
|
|
448
|
+
})
|
|
449
|
+
|
|
450
|
+
this.tableEl.style.tableLayout = 'fixed'
|
|
451
|
+
this.tableEl.classList.add('is-fixed')
|
|
452
|
+
|
|
453
|
+
// Remove styles set by initialMaxWidth and initialWidth after copying rendered width into th styles
|
|
454
|
+
if (this.isResizable) {
|
|
455
|
+
const tableRows = Array.from(this.tableEl.children[1].children)
|
|
456
|
+
tableRows.forEach(tableRow => {
|
|
457
|
+
Array.from(tableRow.children).forEach(cell => {
|
|
458
|
+
cell.firstChild.style.width = null
|
|
459
|
+
cell.firstChild.style.maxWidth = null
|
|
460
|
+
cell.firstChild.style.minWidth = null
|
|
461
|
+
})
|
|
462
|
+
})
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* It makes no sense to have both props `isExpandable` and `isTruncatable` activated on an instance
|
|
468
|
+
* of DpDataTable from a UX perspective, since both do roughly the same, but based on a different kind
|
|
469
|
+
* of presentation.
|
|
470
|
+
*/
|
|
471
|
+
if (this.isExpandable && this.isTruncatable) {
|
|
472
|
+
console.error('`isExpandable` and `isTruncatable` should not be activated at the same time when using DpDataTable.')
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
this.forceElementSelections(this.shouldBeSelectedItems)
|
|
476
|
+
this.setIndeterminate()
|
|
477
|
+
},
|
|
478
|
+
|
|
479
|
+
render: function (h) {
|
|
480
|
+
const self = this
|
|
481
|
+
const scopedSlots = this.$scopedSlots
|
|
482
|
+
const fields = self.headerFields.map(hf => hf.field)
|
|
483
|
+
const items = this.items
|
|
484
|
+
const headerTranslations = this.extractTranslations(['headerSelectHint'])
|
|
485
|
+
|
|
486
|
+
const rowItems = items.map((item, idx) => {
|
|
487
|
+
return h(DpTableRow, {
|
|
488
|
+
props: {
|
|
489
|
+
checked: self.elementSelections[item[self.trackBy]] || false,
|
|
490
|
+
expanded: self.expandedElements[item[self.trackBy]] || false,
|
|
491
|
+
fields: fields,
|
|
492
|
+
hasFlyout: self.hasFlyout,
|
|
493
|
+
headerFields: self.headerFields,
|
|
494
|
+
index: idx,
|
|
495
|
+
isDraggable: self.isDraggable,
|
|
496
|
+
isExpandable: self.isExpandable,
|
|
497
|
+
isLoading: self.isLoading && self.items.length > 0,
|
|
498
|
+
isLocked: self.lockCheckboxBy ? item[self.lockCheckboxBy] : false,
|
|
499
|
+
isLockedMessage: self.mergedTranslations.lockedForSelection,
|
|
500
|
+
isResizable: self.isResizable,
|
|
501
|
+
isSelectable: self.isSelectable,
|
|
502
|
+
isSelectableName: self.isSelectableName,
|
|
503
|
+
isTruncatable: self.isTruncatable,
|
|
504
|
+
item: item,
|
|
505
|
+
searchTerm: self.searchTerm,
|
|
506
|
+
trackBy: self.trackBy,
|
|
507
|
+
wrapped: self.wrappedElements[item[self.trackBy]] || false
|
|
508
|
+
},
|
|
509
|
+
on: {
|
|
510
|
+
toggleExpand: self.toggleExpand,
|
|
511
|
+
toggleSelect: self.toggleSelect,
|
|
512
|
+
toggleWrap: self.toggleWrap
|
|
513
|
+
},
|
|
514
|
+
scopedSlots: {
|
|
515
|
+
...scopedSlots
|
|
516
|
+
}
|
|
517
|
+
})
|
|
518
|
+
})
|
|
519
|
+
|
|
520
|
+
const tableHeaderData = {
|
|
521
|
+
props: {
|
|
522
|
+
checked: self.allSelected,
|
|
523
|
+
hasFlyout: self.hasFlyout,
|
|
524
|
+
headerFields: self.headerFields,
|
|
525
|
+
indeterminate: self.indeterminate,
|
|
526
|
+
isDraggable: self.isDraggable,
|
|
527
|
+
isExpandable: self.isExpandable,
|
|
528
|
+
isResizable: self.isResizable,
|
|
529
|
+
isSelectable: self.isSelectable,
|
|
530
|
+
isSticky: self.hasStickyHeader,
|
|
531
|
+
isTruncatable: self.isTruncatable,
|
|
532
|
+
translations: headerTranslations
|
|
533
|
+
},
|
|
534
|
+
on: {
|
|
535
|
+
toggleExpandAll: self.toggleExpandAll,
|
|
536
|
+
toggleSelectAll: self.toggleSelectAll,
|
|
537
|
+
toggleWrapAll: self.toggleWrapAll
|
|
538
|
+
},
|
|
539
|
+
scopedSlots: {
|
|
540
|
+
...scopedSlots
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
let noEntriesItem, noResultsItem
|
|
545
|
+
|
|
546
|
+
// Generate placeholder items if there are no other items to display
|
|
547
|
+
if (rowItems.length === 0) {
|
|
548
|
+
const noEntriesData = {}
|
|
549
|
+
|
|
550
|
+
noEntriesData.attrs = {
|
|
551
|
+
class: 'u-pt',
|
|
552
|
+
colspan: fields.length + (self.isSelectable && 1) || 0
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
const loadingEl = h(DpLoading, {
|
|
556
|
+
props: {
|
|
557
|
+
isLoading: true
|
|
558
|
+
},
|
|
559
|
+
attrs: {
|
|
560
|
+
class: 'u-mt',
|
|
561
|
+
colspan: fields.length + (self.isSelectable && 1) || 0
|
|
562
|
+
}
|
|
563
|
+
})
|
|
564
|
+
|
|
565
|
+
noEntriesItem = self.isLoading ? h('td', [loadingEl]) : h('td', noEntriesData, self.mergedTranslations.tableNoElements)
|
|
566
|
+
|
|
567
|
+
// If there is no searchTerm an empty RegexEp() object with source '(?:)' is returned
|
|
568
|
+
const searchTermSet = self.searchTerm.source !== '(?:)'
|
|
569
|
+
if (searchTermSet) {
|
|
570
|
+
const noResultsData = { ...noEntriesData }
|
|
571
|
+
noResultsData.domProps = {
|
|
572
|
+
// The searchNoResults translation has to be a function -> code in created() ensures that it will be a function
|
|
573
|
+
innerHTML: self.mergedTranslations.searchNoResults(DomPurify.sanitize('"' + this.searchString + '"'))
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
noResultsItem = h('td', noResultsData)
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
/**
|
|
581
|
+
* If self.headerFields include at least one item with a `colClass` property defined, this is treated as a Css class
|
|
582
|
+
* and rendered into a colgroup to enable equal column sizing of multiple instances of DpDataTable.
|
|
583
|
+
* Colgroup does not work in tandem with `is-resizable` if the class defines a width.
|
|
584
|
+
* Note that a very limited set of Css properties apply to columns (see https://www.w3.org/TR/CSS21/tables.html#columns).
|
|
585
|
+
*/
|
|
586
|
+
let colGroup
|
|
587
|
+
|
|
588
|
+
if (self.headerFields.filter(field => field.colClass).length > 0) {
|
|
589
|
+
const cols = self.headerFields.map(field => {
|
|
590
|
+
return h('col', { attrs: { class: field.colClass } })
|
|
591
|
+
})
|
|
592
|
+
|
|
593
|
+
const emptyCol = h('col')
|
|
594
|
+
|
|
595
|
+
/*
|
|
596
|
+
* Prepend a col element for each of these props set to true, as they
|
|
597
|
+
* introduce additional td elements by themselves.
|
|
598
|
+
*/
|
|
599
|
+
for (const condition of [self.isDraggable, self.isSelectable]) {
|
|
600
|
+
if (condition) {
|
|
601
|
+
cols.unshift(emptyCol)
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
/*
|
|
606
|
+
* Append a col element for each of these props set to true, as they
|
|
607
|
+
* introduce additional td elements by themselves.
|
|
608
|
+
*/
|
|
609
|
+
for (const condition of [self.hasFlyout, self.isExpandable, self.isTruncatable]) {
|
|
610
|
+
if (condition) {
|
|
611
|
+
cols.push(emptyCol)
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
colGroup = h('colgroup', cols)
|
|
616
|
+
if (this.isResizable) {
|
|
617
|
+
console.warn('"isResizable" will not work with "colClass" property set in headerFields when applying width definitions.')
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
let bodyEl = 'tbody'
|
|
622
|
+
let bodyData = {}
|
|
623
|
+
if (self.isDraggable) {
|
|
624
|
+
bodyEl = draggable
|
|
625
|
+
bodyData = {
|
|
626
|
+
props: {
|
|
627
|
+
tag: 'tbody',
|
|
628
|
+
value: items,
|
|
629
|
+
handle: '[data-handle]'
|
|
630
|
+
},
|
|
631
|
+
on: {
|
|
632
|
+
change: (e) => self.$emit('changed-order', e)
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
return h('div',
|
|
638
|
+
[
|
|
639
|
+
h('table', { ref: 'tableEl', class: self.tableClass }, [
|
|
640
|
+
colGroup,
|
|
641
|
+
h(DpTableHeader, tableHeaderData),
|
|
642
|
+
h(bodyEl, bodyData, (rowItems.length && rowItems) || [noResultsItem || noEntriesItem])
|
|
643
|
+
])
|
|
644
|
+
])
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
</script>
|