line_push/node_modules/vuetify/lib/components/VDataTable/VDataTable.js
2022-07-17 13:16:16 +08:00

592 lines
19 KiB
JavaScript

import "../../../src/components/VDataTable/VDataTable.sass"; // Components
import { VData } from '../VData';
import { VDataFooter, VDataIterator } from '../VDataIterator';
import VBtn from '../VBtn';
import VDataTableHeader from './VDataTableHeader'; // import VVirtualTable from './VVirtualTable'
import VIcon from '../VIcon';
import Row from './Row';
import RowGroup from './RowGroup';
import VSimpleCheckbox from '../VCheckbox/VSimpleCheckbox';
import VSimpleTable from './VSimpleTable';
import MobileRow from './MobileRow'; // Mixins
import Loadable from '../../mixins/loadable'; // Directives
import ripple from '../../directives/ripple'; // Helpers
import mixins from '../../util/mixins';
import { deepEqual, getObjectValueByPath, getPrefixedScopedSlots, getSlot, defaultFilter, camelizeObjectKeys, getPropertyFromItem } from '../../util/helpers';
import { breaking } from '../../util/console';
import { mergeClasses } from '../../util/mergeData';
function filterFn(item, search, filter) {
return header => {
const value = getObjectValueByPath(item, header.value);
return header.filter ? header.filter(value, search, item) : filter(value, search, item);
};
}
function searchTableItems(items, search, headersWithCustomFilters, headersWithoutCustomFilters, customFilter) {
search = typeof search === 'string' ? search.trim() : null;
return items.filter(item => {
// Headers with custom filters are evaluated whether or not a search term has been provided.
// We need to match every filter to be included in the results.
const matchesColumnFilters = headersWithCustomFilters.every(filterFn(item, search, defaultFilter)); // Headers without custom filters are only filtered by the `search` property if it is defined.
// We only need a single column to match the search term to be included in the results.
const matchesSearchTerm = !search || headersWithoutCustomFilters.some(filterFn(item, search, customFilter));
return matchesColumnFilters && matchesSearchTerm;
});
}
/* @vue/component */
export default mixins(VDataIterator, Loadable).extend({
name: 'v-data-table',
// https://github.com/vuejs/vue/issues/6872
directives: {
ripple
},
props: {
headers: {
type: Array,
default: () => []
},
showSelect: Boolean,
showExpand: Boolean,
showGroupBy: Boolean,
// TODO: Fix
// virtualRows: Boolean,
height: [Number, String],
hideDefaultHeader: Boolean,
caption: String,
dense: Boolean,
headerProps: Object,
calculateWidths: Boolean,
fixedHeader: Boolean,
headersLength: Number,
expandIcon: {
type: String,
default: '$expand'
},
customFilter: {
type: Function,
default: defaultFilter
},
itemClass: {
type: [String, Function],
default: () => ''
},
loaderHeight: {
type: [Number, String],
default: 4
}
},
data() {
return {
internalGroupBy: [],
openCache: {},
widths: []
};
},
computed: {
computedHeaders() {
if (!this.headers) return [];
const headers = this.headers.filter(h => h.value === undefined || !this.internalGroupBy.find(v => v === h.value));
const defaultHeader = {
text: '',
sortable: false,
width: '1px'
};
if (this.showSelect) {
const index = headers.findIndex(h => h.value === 'data-table-select');
if (index < 0) headers.unshift({ ...defaultHeader,
value: 'data-table-select'
});else headers.splice(index, 1, { ...defaultHeader,
...headers[index]
});
}
if (this.showExpand) {
const index = headers.findIndex(h => h.value === 'data-table-expand');
if (index < 0) headers.unshift({ ...defaultHeader,
value: 'data-table-expand'
});else headers.splice(index, 1, { ...defaultHeader,
...headers[index]
});
}
return headers;
},
colspanAttrs() {
return this.isMobile ? undefined : {
colspan: this.headersLength || this.computedHeaders.length
};
},
columnSorters() {
return this.computedHeaders.reduce((acc, header) => {
if (header.sort) acc[header.value] = header.sort;
return acc;
}, {});
},
headersWithCustomFilters() {
return this.headers.filter(header => header.filter && (!header.hasOwnProperty('filterable') || header.filterable === true));
},
headersWithoutCustomFilters() {
return this.headers.filter(header => !header.filter && (!header.hasOwnProperty('filterable') || header.filterable === true));
},
sanitizedHeaderProps() {
return camelizeObjectKeys(this.headerProps);
},
computedItemsPerPage() {
const itemsPerPage = this.options && this.options.itemsPerPage ? this.options.itemsPerPage : this.itemsPerPage;
const itemsPerPageOptions = this.sanitizedFooterProps.itemsPerPageOptions;
if (itemsPerPageOptions && !itemsPerPageOptions.find(item => typeof item === 'number' ? item === itemsPerPage : item.value === itemsPerPage)) {
const firstOption = itemsPerPageOptions[0];
return typeof firstOption === 'object' ? firstOption.value : firstOption;
}
return itemsPerPage;
}
},
created() {
const breakingProps = [['sort-icon', 'header-props.sort-icon'], ['hide-headers', 'hide-default-header'], ['select-all', 'show-select']];
/* istanbul ignore next */
breakingProps.forEach(([original, replacement]) => {
if (this.$attrs.hasOwnProperty(original)) breaking(original, replacement, this);
});
},
mounted() {
// if ((!this.sortBy || !this.sortBy.length) && (!this.options.sortBy || !this.options.sortBy.length)) {
// const firstSortable = this.headers.find(h => !('sortable' in h) || !!h.sortable)
// if (firstSortable) this.updateOptions({ sortBy: [firstSortable.value], sortDesc: [false] })
// }
if (this.calculateWidths) {
window.addEventListener('resize', this.calcWidths);
this.calcWidths();
}
},
beforeDestroy() {
if (this.calculateWidths) {
window.removeEventListener('resize', this.calcWidths);
}
},
methods: {
calcWidths() {
this.widths = Array.from(this.$el.querySelectorAll('th')).map(e => e.clientWidth);
},
customFilterWithColumns(items, search) {
return searchTableItems(items, search, this.headersWithCustomFilters, this.headersWithoutCustomFilters, this.customFilter);
},
customSortWithHeaders(items, sortBy, sortDesc, locale) {
return this.customSort(items, sortBy, sortDesc, locale, this.columnSorters);
},
createItemProps(item) {
const props = VDataIterator.options.methods.createItemProps.call(this, item);
return Object.assign(props, {
headers: this.computedHeaders
});
},
genCaption(props) {
if (this.caption) return [this.$createElement('caption', [this.caption])];
return getSlot(this, 'caption', props, true);
},
genColgroup(props) {
return this.$createElement('colgroup', this.computedHeaders.map(header => {
return this.$createElement('col', {
class: {
divider: header.divider
}
});
}));
},
genLoading() {
const th = this.$createElement('th', {
staticClass: 'column',
attrs: this.colspanAttrs
}, [this.genProgress()]);
const tr = this.$createElement('tr', {
staticClass: 'v-data-table__progress'
}, [th]);
return this.$createElement('thead', [tr]);
},
genHeaders(props) {
const data = {
props: { ...this.sanitizedHeaderProps,
headers: this.computedHeaders,
options: props.options,
mobile: this.isMobile,
showGroupBy: this.showGroupBy,
someItems: this.someItems,
everyItem: this.everyItem,
singleSelect: this.singleSelect,
disableSort: this.disableSort
},
on: {
sort: props.sort,
group: props.group,
'toggle-select-all': this.toggleSelectAll
}
};
const children = [getSlot(this, 'header', data)];
if (!this.hideDefaultHeader) {
const scopedSlots = getPrefixedScopedSlots('header.', this.$scopedSlots);
children.push(this.$createElement(VDataTableHeader, { ...data,
scopedSlots
}));
}
if (this.loading) children.push(this.genLoading());
return children;
},
genEmptyWrapper(content) {
return this.$createElement('tr', {
staticClass: 'v-data-table__empty-wrapper'
}, [this.$createElement('td', {
attrs: this.colspanAttrs
}, content)]);
},
genItems(items, props) {
const empty = this.genEmpty(props.originalItemsLength, props.pagination.itemsLength);
if (empty) return [empty];
return props.groupedItems ? this.genGroupedRows(props.groupedItems, props) : this.genRows(items, props);
},
genGroupedRows(groupedItems, props) {
return groupedItems.map(group => {
if (!this.openCache.hasOwnProperty(group.name)) this.$set(this.openCache, group.name, true);
if (this.$scopedSlots.group) {
return this.$scopedSlots.group({
group: group.name,
options: props.options,
items: group.items,
headers: this.computedHeaders
});
} else {
return this.genDefaultGroupedRow(group.name, group.items, props);
}
});
},
genDefaultGroupedRow(group, items, props) {
const isOpen = !!this.openCache[group];
const children = [this.$createElement('template', {
slot: 'row.content'
}, this.genRows(items, props))];
const toggleFn = () => this.$set(this.openCache, group, !this.openCache[group]);
const removeFn = () => props.updateOptions({
groupBy: [],
groupDesc: []
});
if (this.$scopedSlots['group.header']) {
children.unshift(this.$createElement('template', {
slot: 'column.header'
}, [this.$scopedSlots['group.header']({
group,
groupBy: props.options.groupBy,
items,
headers: this.computedHeaders,
isOpen,
toggle: toggleFn,
remove: removeFn
})]));
} else {
const toggle = this.$createElement(VBtn, {
staticClass: 'ma-0',
props: {
icon: true,
small: true
},
on: {
click: toggleFn
}
}, [this.$createElement(VIcon, [isOpen ? '$minus' : '$plus'])]);
const remove = this.$createElement(VBtn, {
staticClass: 'ma-0',
props: {
icon: true,
small: true
},
on: {
click: removeFn
}
}, [this.$createElement(VIcon, ['$close'])]);
const column = this.$createElement('td', {
staticClass: 'text-start',
attrs: this.colspanAttrs
}, [toggle, `${props.options.groupBy[0]}: ${group}`, remove]);
children.unshift(this.$createElement('template', {
slot: 'column.header'
}, [column]));
}
if (this.$scopedSlots['group.summary']) {
children.push(this.$createElement('template', {
slot: 'column.summary'
}, [this.$scopedSlots['group.summary']({
group,
groupBy: props.options.groupBy,
items,
headers: this.computedHeaders,
isOpen,
toggle: toggleFn
})]));
}
return this.$createElement(RowGroup, {
key: group,
props: {
value: isOpen
}
}, children);
},
genRows(items, props) {
return this.$scopedSlots.item ? this.genScopedRows(items, props) : this.genDefaultRows(items, props);
},
genScopedRows(items, props) {
const rows = [];
for (let i = 0; i < items.length; i++) {
const item = items[i];
rows.push(this.$scopedSlots.item({ ...this.createItemProps(item),
index: i
}));
if (this.isExpanded(item)) {
rows.push(this.$scopedSlots['expanded-item']({
item,
headers: this.computedHeaders
}));
}
}
return rows;
},
genDefaultRows(items, props) {
return this.$scopedSlots['expanded-item'] ? items.map(item => this.genDefaultExpandedRow(item)) : items.map(item => this.genDefaultSimpleRow(item));
},
genDefaultExpandedRow(item) {
const isExpanded = this.isExpanded(item);
const classes = {
'v-data-table__expanded v-data-table__expanded__row': isExpanded
};
const headerRow = this.genDefaultSimpleRow(item, classes);
const expandedRow = this.$createElement('tr', {
staticClass: 'v-data-table__expanded v-data-table__expanded__content'
}, [this.$scopedSlots['expanded-item']({
item,
headers: this.computedHeaders
})]);
return this.$createElement(RowGroup, {
props: {
value: isExpanded
}
}, [this.$createElement('template', {
slot: 'row.header'
}, [headerRow]), this.$createElement('template', {
slot: 'row.content'
}, [expandedRow])]);
},
genDefaultSimpleRow(item, classes = {}) {
const scopedSlots = getPrefixedScopedSlots('item.', this.$scopedSlots);
const data = this.createItemProps(item);
if (this.showSelect) {
const slot = scopedSlots['data-table-select'];
scopedSlots['data-table-select'] = slot ? () => slot(data) : () => this.$createElement(VSimpleCheckbox, {
staticClass: 'v-data-table__checkbox',
props: {
value: data.isSelected,
disabled: !this.isSelectable(item)
},
on: {
input: val => data.select(val)
}
});
}
if (this.showExpand) {
const slot = scopedSlots['data-table-expand'];
scopedSlots['data-table-expand'] = slot ? () => slot(data) : () => this.$createElement(VIcon, {
staticClass: 'v-data-table__expand-icon',
class: {
'v-data-table__expand-icon--active': data.isExpanded
},
on: {
click: e => {
e.stopPropagation();
data.expand(!data.isExpanded);
}
}
}, [this.expandIcon]);
}
return this.$createElement(this.isMobile ? MobileRow : Row, {
key: getObjectValueByPath(item, this.itemKey),
class: mergeClasses({ ...classes,
'v-data-table__selected': data.isSelected
}, getPropertyFromItem(item, this.itemClass)),
props: {
headers: this.computedHeaders,
item,
rtl: this.$vuetify.rtl
},
scopedSlots,
on: {
// TODO: for click, the first argument should be the event, and the second argument should be data,
// but this is a breaking change so it's for v3
click: () => this.$emit('click:row', item, data),
contextmenu: event => this.$emit('contextmenu:row', event, data),
dblclick: event => this.$emit('dblclick:row', event, data)
}
});
},
genBody(props) {
const data = { ...props,
expand: this.expand,
headers: this.computedHeaders,
isExpanded: this.isExpanded,
isMobile: this.isMobile,
isSelected: this.isSelected,
select: this.select
};
if (this.$scopedSlots.body) {
return this.$scopedSlots.body(data);
}
return this.$createElement('tbody', [getSlot(this, 'body.prepend', data, true), this.genItems(props.items, props), getSlot(this, 'body.append', data, true)]);
},
genFooters(props) {
const data = {
props: {
options: props.options,
pagination: props.pagination,
itemsPerPageText: '$vuetify.dataTable.itemsPerPageText',
...this.sanitizedFooterProps
},
on: {
'update:options': value => props.updateOptions(value)
},
widths: this.widths,
headers: this.computedHeaders
};
const children = [getSlot(this, 'footer', data, true)];
if (!this.hideDefaultFooter) {
children.push(this.$createElement(VDataFooter, { ...data,
scopedSlots: getPrefixedScopedSlots('footer.', this.$scopedSlots)
}));
}
return children;
},
genDefaultScopedSlot(props) {
const simpleProps = {
height: this.height,
fixedHeader: this.fixedHeader,
dense: this.dense
}; // if (this.virtualRows) {
// return this.$createElement(VVirtualTable, {
// props: Object.assign(simpleProps, {
// items: props.items,
// height: this.height,
// rowHeight: this.dense ? 24 : 48,
// headerHeight: this.dense ? 32 : 48,
// // TODO: expose rest of props from virtual table?
// }),
// scopedSlots: {
// items: ({ items }) => this.genItems(items, props) as any,
// },
// }, [
// this.proxySlot('body.before', [this.genCaption(props), this.genHeaders(props)]),
// this.proxySlot('bottom', this.genFooters(props)),
// ])
// }
return this.$createElement(VSimpleTable, {
props: simpleProps
}, [this.proxySlot('top', getSlot(this, 'top', props, true)), this.genCaption(props), this.genColgroup(props), this.genHeaders(props), this.genBody(props), this.proxySlot('bottom', this.genFooters(props))]);
},
proxySlot(slot, content) {
return this.$createElement('template', {
slot
}, content);
}
},
render() {
return this.$createElement(VData, {
props: { ...this.$props,
customFilter: this.customFilterWithColumns,
customSort: this.customSortWithHeaders,
itemsPerPage: this.computedItemsPerPage
},
on: {
'update:options': (v, old) => {
this.internalGroupBy = v.groupBy || [];
!deepEqual(v, old) && this.$emit('update:options', v);
},
'update:page': v => this.$emit('update:page', v),
'update:items-per-page': v => this.$emit('update:items-per-page', v),
'update:sort-by': v => this.$emit('update:sort-by', v),
'update:sort-desc': v => this.$emit('update:sort-desc', v),
'update:group-by': v => this.$emit('update:group-by', v),
'update:group-desc': v => this.$emit('update:group-desc', v),
pagination: (v, old) => !deepEqual(v, old) && this.$emit('pagination', v),
'current-items': v => {
this.internalCurrentItems = v;
this.$emit('current-items', v);
},
'page-count': v => this.$emit('page-count', v)
},
scopedSlots: {
default: this.genDefaultScopedSlot
}
});
}
});
//# sourceMappingURL=VDataTable.js.map