Skip to content
This repository was archived by the owner on Mar 27, 2025. It is now read-only.

Commit c6e004f

Browse files
committed
BREAKING CHANGE(BTable): Updated the items processing logic, Now items are fully reactive and can be updated directly through the item's cell slot.
1 parent bfed14c commit c6e004f

File tree

4 files changed

+335
-300
lines changed

4 files changed

+335
-300
lines changed

packages/bootstrap-vue-next/src/components/BTable/BTable.vue

Lines changed: 113 additions & 145 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@
7878
</tr>
7979
</thead>
8080
<tbody>
81-
<template v-for="(item, itemIndex) in computedItems" :key="itemIndex">
81+
<template v-for="(item, itemIndex) in computedDisplayItems" :key="itemIndex">
8282
<tr
8383
:class="getRowClasses(item)"
8484
@click="!filterEvent($event) && onRowClick(item, itemIndex, $event)"
@@ -117,7 +117,7 @@
117117
:toggle-details="() => toggleRowDetails(item)"
118118
:details-showing="item._showDetails"
119119
/>
120-
<template v-else>{{ itemHelper.renderItem(item, field) }}</template>
120+
<template v-else>{{ renderItem(item, field) }}</template>
121121
</td>
122122
</tr>
123123

@@ -191,112 +191,59 @@
191191
import {computed, onMounted, ref, useSlots, watch} from 'vue'
192192
import {useBooleanish} from '../../composables'
193193
import {cloneDeepAsync} from '../../utils/object'
194-
import {titleCase} from '../../utils/stringUtils'
194+
import {isObject, startCase, titleCase} from '../../utils'
195195
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'
207198
import BTableSimple from './BTableSimple.vue'
208199
import {filterEvent} from './helpers/filter-event'
209-
import useItemHelper from './itemHelper'
210200
import {useVModel} from '@vueuse/core'
201+
import {renderItem, useTableItems} from './table-items'
211202
212203
type NoProviderTypes = 'paging' | 'sorting' | 'filtering'
213204
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+
})
300247
301248
const emit = defineEmits<{
302249
'headClicked': [
@@ -325,8 +272,6 @@ const sortDescModel = useVModel(props, 'sortDesc', emit, {passive: true})
325272
326273
const slots = useSlots()
327274
328-
const itemHelper = useItemHelper()
329-
330275
const footCloneBoolean = useBooleanish(() => props.footClone)
331276
const sortDescBoolean = useBooleanish(sortDescModel)
332277
const sortInternalBoolean = useBooleanish(() => props.sortInternal)
@@ -339,14 +284,13 @@ const noProviderPagingBoolean = useBooleanish(() => props.noProviderPaging)
339284
const noProviderSortingBoolean = useBooleanish(() => props.noProviderSorting)
340285
const noProviderFilteringBoolean = useBooleanish(() => props.noProviderFiltering)
341286
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)
350294
351295
const selectedItems = ref<Set<TableItem>>(new Set([]))
352296
const isSelecting = computed(() => selectedItems.value.size > 0)
@@ -378,47 +322,49 @@ const containerAttrs = computed(() => ({
378322
stickyHeader: props.stickyHeader,
379323
}))
380324
381-
const computedFields = computed(() => itemHelper.normaliseFields(props.fields, props.items))
325+
const computedFields = computed(() => normaliseFields(props.fields, props.items))
382326
const computedFieldsTotal = computed(
383327
() => computedFields.value.length + (selectableBoolean.value ? 1 : 0)
384328
)
385329
386-
const isFilterableTable = computed(() => props.filter !== undefined && props.filter !== '')
387-
const usesProvider = computed(() => props.provider !== undefined)
388-
389330
const addSelectableCell = computed(
390331
() => selectableBoolean.value && (!!props.selectHead || slots.selectHead !== undefined)
391332
)
392333
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
396358
)
397359
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
419364
}
420-
return items
421-
})
365+
const clone = await cloneDeepAsync(items)
366+
emit('filtered', clone)
367+
}
422368
423369
const getFieldHeadLabel = (field: TableField) => {
424370
if (typeof field === 'string') return titleCase(field)
@@ -427,6 +373,28 @@ const getFieldHeadLabel = (field: TableField) => {
427373
return field.key
428374
}
429375
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+
430398
const headerClicked = (field: TableField, event: MouseEvent, isFooter = false) => {
431399
const fieldKey = typeof field === 'string' ? field : field.key
432400
emit('headClicked', fieldKey, field, event, isFooter)
@@ -531,13 +499,13 @@ const callItemsProvider = async () => {
531499
},
532500
}
533501
)
534-
const response = props.provider(context, itemHelper.updateInternalItems)
502+
const response = props.provider(context, updateInternalItems)
535503
if (response === undefined) return
536504
if (response instanceof Promise) {
537505
try {
538506
const items = await response
539507
if (!Array.isArray(items)) return
540-
const internalItems = await itemHelper.updateInternalItems(items)
508+
const internalItems = await updateInternalItems(items)
541509
return internalItems
542510
} finally {
543511
if (busyBoolean.value) {
@@ -547,7 +515,7 @@ const callItemsProvider = async () => {
547515
}
548516
549517
try {
550-
const internalItems = await itemHelper.updateInternalItems(response)
518+
const internalItems = await updateInternalItems(response)
551519
return internalItems
552520
} finally {
553521
if (busyBoolean.value) {
@@ -650,7 +618,7 @@ const providerPropsWatch = async (prop: string, val: any, oldVal: any) => {
650618
651619
await callItemsProvider()
652620
653-
if (notifyFiltered) itemHelper.notifyFilteredItems()
621+
if (notifyFiltered) notifyFilteredItems()
654622
}
655623
656624
watch(

0 commit comments

Comments
 (0)