78
78
</tr >
79
79
</thead >
80
80
<tbody >
81
- <template v-for =" (item , itemIndex ) in computedItems " :key =" itemIndex " >
81
+ <template v-for =" (item , itemIndex ) in computedDisplayItems " :key =" itemIndex " >
82
82
<tr
83
83
:class =" getRowClasses(item)"
84
84
@click =" !filterEvent($event) && onRowClick(item, itemIndex, $event)"
117
117
:toggle-details =" () => toggleRowDetails(item)"
118
118
:details-showing =" item._showDetails"
119
119
/>
120
- <template v-else >{{ itemHelper. renderItem(item, field) }}</template >
120
+ <template v-else >{{ renderItem(item, field) }}</template >
121
121
</td >
122
122
</tr >
123
123
191
191
import {computed , onMounted , ref , useSlots , watch } from ' vue'
192
192
import {useBooleanish } from ' ../../composables'
193
193
import {cloneDeepAsync } from ' ../../utils/object'
194
- import {titleCase } from ' ../../utils/stringUtils '
194
+ import {isObject , startCase , titleCase } from ' ../../utils'
195
195
import BSpinner from ' ../BSpinner.vue'
196
- import type {
197
- Booleanish ,
198
- Breakpoint ,
199
- BTableProvider ,
200
- BTableSortCompare ,
201
- ColorVariant ,
202
- TableField ,
203
- TableFieldObject ,
204
- TableItem ,
205
- VerticalAlign ,
206
- } from ' ../../types'
196
+ import type {TableField , TableFieldObject , TableItem } from ' ../../types'
197
+ import type {BTableProps } from ' ./table'
207
198
import BTableSimple from ' ./BTableSimple.vue'
208
199
import {filterEvent } from ' ./helpers/filter-event'
209
- import useItemHelper from ' ./itemHelper'
210
200
import {useVModel } from ' @vueuse/core'
201
+ import {renderItem , useTableItems } from ' ./table-items'
211
202
212
203
type NoProviderTypes = ' paging' | ' sorting' | ' filtering'
213
204
214
- const props = withDefaults (
215
- defineProps <{
216
- align? : VerticalAlign
217
- caption? : string
218
- captionTop? : Booleanish
219
- borderless? : Booleanish
220
- bordered? : Booleanish
221
- borderVariant? : ColorVariant | null
222
- dark? : Booleanish
223
- fields? : TableField []
224
- footClone? : Booleanish
225
- hover? : Booleanish
226
- items? : TableItem []
227
- provider? : BTableProvider
228
- sortCompare? : BTableSortCompare
229
- noProvider? : NoProviderTypes []
230
- noProviderPaging? : Booleanish
231
- noProviderSorting? : Booleanish
232
- noProviderFiltering? : Booleanish
233
- responsive? : boolean | Breakpoint
234
- small? : Booleanish
235
- striped? : Booleanish
236
- stacked? : boolean | Breakpoint
237
- labelStacked? : boolean
238
- variant? : ColorVariant | null
239
- sortBy? : string
240
- sortDesc? : Booleanish
241
- sortInternal? : Booleanish
242
- selectable? : Booleanish
243
- stickySelect? : Booleanish
244
- selectHead? : boolean | string
245
- selectMode? : ' multi' | ' single' | ' range'
246
- selectionVariant? : ColorVariant | null
247
- stickyHeader? : Booleanish
248
- busy? : Booleanish
249
- showEmpty? : Booleanish
250
- perPage? : number
251
- currentPage? : number
252
- filter? : string
253
- filterable? : string []
254
- emptyText? : string
255
- emptyFilteredText? : string
256
- }>(),
257
- {
258
- perPage: undefined ,
259
- sortBy: undefined ,
260
- variant: undefined ,
261
- borderVariant: undefined ,
262
- caption: undefined ,
263
- align: undefined ,
264
- filter: undefined ,
265
- filterable: undefined ,
266
- provider: undefined ,
267
- sortCompare: undefined ,
268
- noProvider: undefined ,
269
- noProviderPaging: undefined ,
270
- noProviderSorting: undefined ,
271
- noProviderFiltering: undefined ,
272
- captionTop: false ,
273
- borderless: false ,
274
- bordered: false ,
275
- dark: false ,
276
- fields : () => [],
277
- footClone: false ,
278
- hover: false ,
279
- items : () => [],
280
- responsive: false ,
281
- small: false ,
282
- striped: false ,
283
- labelStacked: false ,
284
- stacked: false ,
285
- sortDesc: false ,
286
- sortInternal: true ,
287
- selectable: false ,
288
- stickySelect: false ,
289
- selectHead: true ,
290
- selectMode: ' single' ,
291
- selectionVariant: ' primary' ,
292
- stickyHeader: false ,
293
- busy: false ,
294
- showEmpty: false ,
295
- currentPage: 1 ,
296
- emptyText: ' There are no records to show' ,
297
- emptyFilteredText: ' There are no records matching your request' ,
298
- }
299
- )
205
+ const props = withDefaults (defineProps <BTableProps >(), {
206
+ perPage: undefined ,
207
+ sortBy: undefined ,
208
+ variant: undefined ,
209
+ borderVariant: undefined ,
210
+ caption: undefined ,
211
+ align: undefined ,
212
+ filter: undefined ,
213
+ filterable: undefined ,
214
+ provider: undefined ,
215
+ sortCompare: undefined ,
216
+ noProvider: undefined ,
217
+ noProviderPaging: false ,
218
+ noProviderSorting: false ,
219
+ noProviderFiltering: false ,
220
+ captionTop: false ,
221
+ borderless: false ,
222
+ bordered: false ,
223
+ dark: false ,
224
+ fields : () => [],
225
+ footClone: false ,
226
+ hover: false ,
227
+ items : () => [],
228
+ responsive: false ,
229
+ small: false ,
230
+ striped: false ,
231
+ labelStacked: false ,
232
+ stacked: false ,
233
+ sortDesc: false ,
234
+ sortInternal: true ,
235
+ selectable: false ,
236
+ stickySelect: false ,
237
+ selectHead: true ,
238
+ selectMode: ' single' ,
239
+ selectionVariant: ' primary' ,
240
+ stickyHeader: false ,
241
+ busy: false ,
242
+ showEmpty: false ,
243
+ currentPage: 1 ,
244
+ emptyText: ' There are no records to show' ,
245
+ emptyFilteredText: ' There are no records matching your request' ,
246
+ })
300
247
301
248
const emit = defineEmits <{
302
249
' headClicked' : [
@@ -325,8 +272,6 @@ const sortDescModel = useVModel(props, 'sortDesc', emit, {passive: true})
325
272
326
273
const slots = useSlots ()
327
274
328
- const itemHelper = useItemHelper ()
329
-
330
275
const footCloneBoolean = useBooleanish (() => props .footClone )
331
276
const sortDescBoolean = useBooleanish (sortDescModel )
332
277
const sortInternalBoolean = useBooleanish (() => props .sortInternal )
@@ -339,14 +284,13 @@ const noProviderPagingBoolean = useBooleanish(() => props.noProviderPaging)
339
284
const noProviderSortingBoolean = useBooleanish (() => props .noProviderSorting )
340
285
const noProviderFilteringBoolean = useBooleanish (() => props .noProviderFiltering )
341
286
342
- itemHelper .filterEvent .value = async (items ) => {
343
- if (usesProvider .value ) {
344
- await callItemsProvider ()
345
- return
346
- }
347
- const clone = await cloneDeepAsync (items )
348
- emit (' filtered' , clone )
349
- }
287
+ const isFilterableTable = computed (() => props .filter !== undefined && props .filter !== ' ' )
288
+
289
+ const isSortable = computed (
290
+ () =>
291
+ props .fields .filter ((field ) => (typeof field === ' string' ? false : field .sortable )).length > 0
292
+ )
293
+ const usesProvider = computed (() => props .provider !== undefined )
350
294
351
295
const selectedItems = ref <Set <TableItem >>(new Set ([]))
352
296
const isSelecting = computed (() => selectedItems .value .size > 0 )
@@ -378,47 +322,49 @@ const containerAttrs = computed(() => ({
378
322
stickyHeader: props .stickyHeader ,
379
323
}))
380
324
381
- const computedFields = computed (() => itemHelper . normaliseFields (props .fields , props .items ))
325
+ const computedFields = computed (() => normaliseFields (props .fields , props .items ))
382
326
const computedFieldsTotal = computed (
383
327
() => computedFields .value .length + (selectableBoolean .value ? 1 : 0 )
384
328
)
385
329
386
- const isFilterableTable = computed (() => props .filter !== undefined && props .filter !== ' ' )
387
- const usesProvider = computed (() => props .provider !== undefined )
388
-
389
330
const addSelectableCell = computed (
390
331
() => selectableBoolean .value && (!! props .selectHead || slots .selectHead !== undefined )
391
332
)
392
333
393
- const isSortable = computed (
394
- () =>
395
- props .fields .filter ((field ) => (typeof field === ' string' ? false : field .sortable )).length > 0
334
+ const {
335
+ computedItems,
336
+ computedDisplayItems,
337
+ updateInternalItems,
338
+ filteredHandler,
339
+ notifyFilteredItems,
340
+ } = useTableItems (
341
+ props ,
342
+ {
343
+ footCloneBoolean ,
344
+ sortDescBoolean ,
345
+ sortInternalBoolean ,
346
+ selectableBoolean ,
347
+ stickySelectBoolean ,
348
+ labelStackedBoolean ,
349
+ busyBoolean ,
350
+ showEmptyBoolean ,
351
+ noProviderPagingBoolean ,
352
+ noProviderSortingBoolean ,
353
+ noProviderFilteringBoolean ,
354
+ isFilterableTable ,
355
+ isSortable ,
356
+ },
357
+ usesProvider .value
396
358
)
397
359
398
- const requireItemsMapping = computed (() => isSortable .value && sortInternalBoolean .value === true )
399
- const computedItems = computed (() => {
400
- const items = usesProvider .value
401
- ? itemHelper .internalItems .value
402
- : requireItemsMapping .value
403
- ? itemHelper .mapItems (props .fields , props .items , props , {
404
- isSortable ,
405
- isFilterableTable ,
406
- sortDescBoolean ,
407
- })
408
- : props .items
409
-
410
- if (usesProvider .value && ! noProviderPagingBoolean .value ) {
411
- return items
412
- }
413
-
414
- if (props .perPage !== undefined ) {
415
- const startIndex = (props .currentPage - 1 ) * props .perPage
416
- const endIndex =
417
- startIndex + props .perPage > items .length ? items .length : startIndex + props .perPage
418
- return items .slice (startIndex , endIndex )
360
+ filteredHandler .value = async (items ) => {
361
+ if (usesProvider .value ) {
362
+ await callItemsProvider ()
363
+ return
419
364
}
420
- return items
421
- })
365
+ const clone = await cloneDeepAsync (items )
366
+ emit (' filtered' , clone )
367
+ }
422
368
423
369
const getFieldHeadLabel = (field : TableField ) => {
424
370
if (typeof field === ' string' ) return titleCase (field )
@@ -427,6 +373,28 @@ const getFieldHeadLabel = (field: TableField) => {
427
373
return field .key
428
374
}
429
375
376
+ const normaliseFields = (origFields : TableField [], items : TableItem []): TableFieldObject [] => {
377
+ const fields: TableFieldObject [] = []
378
+
379
+ if (! origFields ?.length && items ?.length ) {
380
+ Object .keys (items [0 ]).forEach ((k ) => fields .push ({key: k , label: startCase (k )}))
381
+ return fields
382
+ }
383
+
384
+ if (Array .isArray (origFields )) {
385
+ origFields .forEach ((f ) => {
386
+ if (typeof f === ' string' ) {
387
+ fields .push ({key: f , label: startCase (f )})
388
+ } else if (isObject (f ) && f .key && typeof f .key === ' string' ) {
389
+ fields .push ({... f })
390
+ }
391
+ // todo handle Shortcut object (i.e. { 'foo_bar': 'This is Foo Bar' }
392
+ })
393
+ return fields
394
+ }
395
+ return fields
396
+ }
397
+
430
398
const headerClicked = (field : TableField , event : MouseEvent , isFooter = false ) => {
431
399
const fieldKey = typeof field === ' string' ? field : field .key
432
400
emit (' headClicked' , fieldKey , field , event , isFooter )
@@ -531,13 +499,13 @@ const callItemsProvider = async () => {
531
499
},
532
500
}
533
501
)
534
- const response = props .provider (context , itemHelper . updateInternalItems )
502
+ const response = props .provider (context , updateInternalItems )
535
503
if (response === undefined ) return
536
504
if (response instanceof Promise ) {
537
505
try {
538
506
const items = await response
539
507
if (! Array .isArray (items )) return
540
- const internalItems = await itemHelper . updateInternalItems (items )
508
+ const internalItems = await updateInternalItems (items )
541
509
return internalItems
542
510
} finally {
543
511
if (busyBoolean .value ) {
@@ -547,7 +515,7 @@ const callItemsProvider = async () => {
547
515
}
548
516
549
517
try {
550
- const internalItems = await itemHelper . updateInternalItems (response )
518
+ const internalItems = await updateInternalItems (response )
551
519
return internalItems
552
520
} finally {
553
521
if (busyBoolean .value ) {
@@ -650,7 +618,7 @@ const providerPropsWatch = async (prop: string, val: any, oldVal: any) => {
650
618
651
619
await callItemsProvider ()
652
620
653
- if (notifyFiltered ) itemHelper . notifyFilteredItems ()
621
+ if (notifyFiltered ) notifyFilteredItems ()
654
622
}
655
623
656
624
watch (
0 commit comments