是否有一种方法,当用户应用过滤器(在输入中输入值)时,仅在自举B表上显示项目?例如。如果“ filteredItems”不存在,什么也不显示?这主要是为了防止我的表呈现所有行(> 2k行)并妨碍性能。
带有b表的jsfiddle:https://jsfiddle.net/asc82spc/
const template = `
<table :id="id || null"
role="grid"
:aria-busy="isBusy ? 'true' : 'false'"
:class="tableClass"
>
<thead :class="headVariant ? ('thead-' + headVariant) : ''">
<tr role="row">
<th v-for="field,key in fields"
@click="headClick($event,field,key)"
@keydown.enter="headClick($event,field,key)"
@keydown.space.prevent="headClick($event,field,key)"
:class="fieldClass(field,key)"
:aria-label="field.sortable ? (sortDesc ? labelSortAsc : labelSortDesc) : null"
:aria-sort="(field.sortable && sortBy === key) ? (sortDesc ? 'descending' : 'ascending') : null"
:tabindex="field.sortable?'0':null"
v-html="field.label"
></th>
</tr>
</thead>
<tfoot v-if="footClone" :class="footVariant ? ('thead-' + footVariant) : ''">
<tr role="row">
<th v-for="field,key in fields"
@click="headClick($event,field,key)"
@keydown.enter="headClick($event,field,key)"
@keydown.space.prevent="headClick($event,field,key)"
:key="key"
:class="fieldClass(field,key)"
:aria-label="field.sortable ? ((sortDesc) ? labelSortAsc : labelSortDesc) : null"
:aria-sort="(field.sortable && sortBy === key) ? (sortDesc ? 'descending' : 'ascending') : null"
:tabindex="field.sortable?'0':null"
v-html="field.label"
></th>
</tr>
</tfoot>
<tbody>
<tr v-for="(item,index) in _items"
role="row"
:key="items_key"
:class="rowClass(item)"
@click="rowClicked($event,item,index)"
>
<td v-for="(field,key) in fields" :class="cellClass(field)">
<slot :name="key" :value="item[key]" :item="item" :index="index">{{item[key]}}</slot>
</td>
</tr>
<tr v-if="showEmpty && _items.length === 0" role="row">
<td :colspan="Object.keys(fields).length">
<div v-if="filter" role="alert" aria-live="polite">
<slot name="emptyfiltered">
<div class="text-center" v-html="emptyFilteredText"></div>
</slot>
</div>
<div v-else role="alert" aria-live="polite">
<slot name="empty">
<div class="text-center" v-html="emptyText"></div>
</slot>
</div>
</td>
</tr>
</tbody>
</table>
`;
const toString = v => {
if (!v) {
return '';
}
if (v instanceof Object) {
return Object.keys(v).map(k => toString(v[k])).join(' ');
}
return String(v);
};
const recToString = v => {
if (!(v instanceof Object)) {
return '';
}
// Exclude these fields from record stringification
const exclude = {
state: true,
_rowVariant: true
};
return toString(Object.keys(v).filter(k => !exclude[k]).reduce((o, k) => {
o[k] = v[k];
return o;
}, {}));
};
const defaultSortCompare = (a, b, sortBy) => {
return toString(a[sortBy]).localeCompare(toString(b[sortBy]), undefined, {
numeric: true
});
};
const bTable = {
template: template,
data() {
return {
sortBy: null,
sortDesc: true,
isBusy: false,
localItems: null
};
},
props: {
id: {
type: String,
default: ''
},
items: {
type: Array,
default: () => []
},
fields: {
type: Object,
default: () => {
return {};
}
},
striped: {
type: Boolean,
default: false
},
bordered: {
type: Boolean,
default: false
},
inverse: {
type: Boolean,
default: false
},
hover: {
type: Boolean,
default: false
},
small: {
type: Boolean,
default: false
},
responsive: {
type: Boolean,
default: false
},
headVariant: {
type: String,
default: ''
},
footVariant: {
type: String,
default: ''
},
perPage: {
type: Number,
default: null
},
items_key: {
type: String,
default: null
},
currentPage: {
type: Number,
default: 1
},
filter: {
type: [String, RegExp, Function],
default: null
},
sortCompare: {
type: Function,
default: null
},
itemsProvider: {
type: Function,
default: null
},
noProviderPaging: {
type: Boolean,
default: false
},
noProviderSorting: {
type: Boolean,
default: false
},
noProviderFiltering: {
type: Boolean,
default: false
},
value: {
type: Array,
default: () => []
},
footClone: {
type: Boolean,
default: false
},
labelSortAsc: {
type: String,
default: 'Click to sort Ascending'
},
labelSortDesc: {
type: String,
default: 'Click to sort Descending'
},
showEmpty: {
type: Boolean,
default: false
},
emptyText: {
type: String,
default: 'There are no records to show'
},
emptyFilteredText: {
type: String,
default: 'There are no records matching your request'
}
},
watch: {
items(newVal, oldVal) {
console.log('items.watch');
if (oldVal === newVal) {
return;
}
this.localItems = this.items;
},
sortDesc(newVal, oldVal) {
console.log('watch sortDesc:', newVal, oldVal);
if (!this.noProviderSorting) {
this.updater(newVal, oldVal);
}
},
sortBy(newVal, oldVal) {
console.log('watch sortBy:', newVal, oldVal);
if (!this.noProviderSorting) {
this.updater(newVal, oldVal);
}
},
perPage(newVal, oldVal) {
console.log('watch perPage:', newVal, oldVal);
if (!this.noProviderPaging) {
this.updater(newVal, oldVal);
}
},
currentPage(newVal, oldVal) {
console.log('watch currentPage:', newVal, oldVal);
if (!this.noProviderPaging) {
this.updater(newVal, oldVal);
}
},
filter(newVal, oldVal) {
console.log('watch filter:', newVal, oldVal);
if (!this.noProviderFiltering) {
this.updater(newVal, oldVal);
}
},
localItems(newVal, oldVal) {
console.log('localItems updated');
}
},
computed: {
tableClass() {
return [
'table',
this.striped ? 'table-striped' : '',
this.hover ? 'table-hover' : '',
this.inverse ? 'table-inverse' : '',
this.bordered ? 'table-bordered' : '',
this.responsive ? 'table-responsive' : '',
this.small ? 'table-sm' : ''
];
},
_items() {
if (!this.localItems) {
if (this.itemsProvider) {
this.updater(1,2);
return this.items || [];
} else {
this.localItems = this.items || [];
}
}
let items = this.localItems.slice();
// Apply local filter
if (this.filter && !(this.itemsProvider && !this.noProviderFiltering)) {
if (this.filter instanceof Function) {
items = items.filter(this.filter);
} else {
let regex;
if (this.filter instanceof RegExp) {
regex = this.filter;
} else {
regex = new RegExp('.*' + this.filter + '.*', 'ig');
}
items = items.filter(item => {
const test = regex.test(recToString(item));
regex.lastIndex = 0;
return test;
});
}
}
// Apply local Sort
const sortCompare = this.sortCompare || defaultSortCompare;
if (this.sortBy && !(this.itemsProvider && !this.noProviderSorting)) {
console.log('b-table sorting...');
items = items.sort((a, b) => {
const r = sortCompare(a, b, this.sortBy);
return this.sortDesc ? r : r * -1;
});
}
// Apply local pagination
if (this.perPage && !(this.itemsProvider && !this.noProviderPaging)) {
items = items.slice((this.currentPage - 1) * this.perPage, this.currentPage * this.perPage);
}
// Clear busy state
this.$nextTick(() => {
this.isBusy = false;
});
// Update the value model with the filtered/sorted/paginated data set
this.$emit('input', items);
return items;
}
},
methods: {
fieldClass(field, key) {
return [
field.sortable ? 'sorting' : '',
(field.sortable && this.sortBy === key) ? 'sorting_' + (this.sortDesc ? 'desc' : 'asc') : '',
field.variant ? ('table-' + field.variant) : '',
field.class ? field.class : ''
];
},
cellClass(field) {
field.variant ? ('table-' + field.variant) : '',
field.class ? field.class : ''
},
rowClass(item) {
// Prefer item._rowVariant over deprecated item.state
const variant = item._rowVariant || item.state || null;
return [
variant ? ('table-' + variant) : ''
];
},
rowClicked(e, item, index) {
if (this.isBusy) {
e.preventDefault();
e.stopPropagation();
return;
}
this.$emit('row-clicked', item, index);
},
headClick(e, field, key) {
if (this.isBusy) {
e.preventDefault();
e.stopPropagation();
return;
}
if (!field.sortable) {
this.sortBy = null;
} else {
if (key === this.sortBy) {
this.sortDesc = !this.sortDesc;
} else {
this.sortDesc = true;
}
this.sortBy = key;
}
this.$emit('head-clicked', key, this.sortDesc);
},
updater(a,b) {
// @TODO: add providerDebounce
if (a === b || !this.itemsProvider || this.isBusy) {
return;
}
// Set busy state
this.isBusy = true;
this.$nextTick(() => {
// If async, we just keep localItems as is, and awaite provider callback
const items = this.itemsProvider(this);
if (items) {
this.localItems = items.slice();
}
});
},
providerCallback(data) {
this.localItems = data ? data.slice() : [];
}
}
};
new Vue({
el: '#app',
components: {bTable},
data: {
fields: {
name: {
label: 'Person Full name',
sortable: true
},
age: {
label: 'Person age',
sortable: true
},
isActive: {
label: 'is Active'
},
actions: {
label: 'Actions'
}
},
currentPage: 1,
perPage: 5,
filter: null,
async: true
},
methods: {
details(item) {
alert(JSON.stringify(item));
},
provider(ctx) {
console.log('provider called', ctx);
let items = [{
isActive: true,
age: 40,
name: {
first: 'Dickerson',
last: 'Macdonald'
}
}, {
isActive: false,
age: 21,
name: {
first: 'Larsen',
last: 'Shaw'
}
}, {
isActive: false,
age: 9,
state: 'success',
name: {
first: 'Minni',
last: 'Navarro'
}
}, {
isActive: false,
age: 102,
name: {
first: 'Woodrow',
last: 'Wilson'
}
}, {
isActive: true,
age: 38,
name: {
first: 'Jami',
last: 'Carney'
}
}, {
isActive: false,
age: 42,
name: {
first: 'Justin',
last: 'Truedeau'
}
}, {
isActive: true,
age: 72,
name: {
first: 'Dickerson',
last: 'Macdonald Sr.'
}
}, {
isActive: false,
age: 12,
name: {
first: 'Larsen',
last: 'Shaw Jr.'
}
}, {
isActive: false,
age: 26,
name: {
first: 'Mitzi',
last: 'Navarro'
}
}, {
isActive: false,
age: 22,
name: {
first: 'Geneva',
last: 'Wilson'
}
}, {
isActive: true,
age: 38,
name: {
first: 'Janice',
last: 'Carney'
}
}, {
isActive: false,
age: 27,
name: {
first: 'Essie',
last: 'Dunlap'
}
}];
if (this.filter) {
const regex = new RegExp('.*' + this.filter + '.*', 'ig');
items = items.filter(item => {
const test = regex.test(recToString(item));
//regex.lastIndex = 0;
return test;
});
}
if(ctx) {
if (ctx.sortBy) {
items = items.sort((a, b) => {
const r = defaultSortCompare(a, b, ctx.sortBy);
return ctx.sortDesc ? r : r * -1;
});
}
items = items.slice((this.currentPage - 1) * this.perPage, this.currentPage * this.perPage);
if (this.async) {
// Emulate async request
const p = new Promise(resolve => setTimeout(resolve, 1000));
p.then(() => {
ctx.providerCallback(items);
});
} else {
// Non Async
return items;
}
} else {
// Our own app is requesting total # rows
return items;
}
}
}
});
#app {
padding: 20px;
height: 500px;
}
html,body { font-size: 14px;}
table[aria-busy="false"] {
opacity: 1;
}
table[aria-busy="true"] {
opacity: .5;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div class="justify-content-center my-1 row">
<b-form-fieldset horizontal label-text-align="right" label="Provider:" class="col-4" :label-size="4">
<b-form-select class="form-control" :options="[{text:'Async',value:true},{text:'Sync',value:false}]" v-model="async">
</b-form-select>
</b-form-fieldset>
<b-form-fieldset horizontal label-text-align="right" label="Page Size:" class="col-4" :label-size="6">
<b-form-select class="form-control" :options="[{text:5,value:5},{text:10,value:10},{text:15,value:15}]" v-model="perPage">
</b-form-select>
</b-form-fieldset>
<b-form-fieldset label-text-align="right" horizontal label="Filter:" class="col-4" :label-size="2">
<b-form-input v-model="filter" placeholder="Type to Search"></b-form-input>
</b-form-fieldset>
</div>
<!-- Main table element -->
<!-- :current-page="currentPage" :per-page="perPage" :filter="filter" no-provider-filtering -->
<b-table striped hover head-variant="inverse" :items-provider="provider" :fields="fields" :filter="filter" :current-page="currentPage" :per-page="perPage" show-empty>
<template slot="name" scope="item">
{{item.value.first}} {{item.value.last}}
</template>
<template slot="isActive" scope="item">
{{item.value?'Yes :)':'No :('}}
</template>
<template slot="actions" scope="item">
<b-btn size="sm" @click="details(item.item)">Details</b-btn>
</template>
</b-table>
<div class="justify-content-center row my-1">
<b-pagination size="md" :total-rows="provider(null).length" :per-page="perPage" v-model="currentPage" />
</div>
</div>
使用表格上的v-if检查过滤器是否有数据。 https://jsfiddle.net/Lsa5qkbt/
<b-table striped hover v-if="filter" head-variant="inverse" :items-provider="provider" :fields="fields" :filter="filter" :current-page="currentPage" :per-page="perPage" show-empty>
v-if在满足/未满足条件时创建/销毁内容,因此在满足条件之前不会使用任何资源。 https://vuejs.org/v2/guide/conditional.html#v-if-vs-v-show