仅在应用过滤器时显示 bootstrap-vue b-table 中的项目

Posted

技术标签:

【中文标题】仅在应用过滤器时显示 bootstrap-vue b-table 中的项目【英文标题】:Only display items in bootstrap-vue b-table when filter is applied 【发布时间】:2020-09-02 19:39:41 【问题描述】:

当用户应用了过滤器(在输入中输入了一个值)时,有没有办法只在 bootstrap-vue b-tables 上显示项目?例如。如果“filteredItems”不存在,什么都不显示?这主要是为了防止我的表渲染所有行(> 2k行)并妨碍性能。

jsfiddle 与 b 表: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>

【问题讨论】:

【参考方案1】:

在表格上使用 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

【讨论】:

以上是关于仅在应用过滤器时显示 bootstrap-vue b-table 中的项目的主要内容,如果未能解决你的问题,请参考以下文章

仅在 kivy 首次启动时显示设置屏幕

如何仅在初始启动时显示屏幕

如何仅在应用程序首次启动时显示警报显示?

仅在应用程序需要时显示锁屏小部件以控制应用程序

今日 iOS 小部件仅在调试时显示

Android仅在状态栏展开时显示通知