Permalink
Browse files

feat(table): Emit event when local filtering changes number of result…

… items - issue #650 (#652)

* feat(table): Emit event filtered when filter applied
Emits only when local filtering results in a change in number of items.  Supplies one arg to the event which is the list of filtered items.

* fix(table): Igmore underscore props in record stringification

* feat(table): documentation update and enhanced live example
  • Loading branch information...
tmorehouse committed Jul 6, 2017
1 parent 5e1f790 commit 1b2a36ab4e29fb699f99d0e6237dbf93227a7bfb
Showing with 151 additions and 63 deletions.
  1. +124 −55 docs/components/table/README.md
  2. +10 −0 docs/components/table/meta.json
  3. +17 −8 lib/components/table.vue
@@ -1,15 +1,15 @@
# Tables
> For tabular data. Tables support pagination, sorting, custom rendering, and asynchronous data.
> For displaying tabular data. `<b-table>` supports pagination, filtering, sorting,
custom rendering, events, and asynchronous data.
```html
<template>
<div>
<div>
<div class="my-1 row">
<div class="col-6">
<b-form-fieldset horizontal label="Rows per page" :label-cols="6">
<b-form-select :options="[{text:5,value:5},{text:10,value:10},{text:15,value:15}]" v-model="perPage">
</b-form-select>
<b-form-select :options="pageOptions" v-model="perPage"></b-form-select>
</b-form-fieldset>
</div>
<div class="col-6">
@@ -19,72 +19,80 @@
</div>
</div>
<div class="justify-content-center my-1">
<b-pagination size="md" :total-rows="items.length" :per-page="perPage" v-model="currentPage" />
<div class="justify-content-center row my-1">
<b-pagination size="md" :total-rows="totalRows" :per-page="perPage" v-model="currentPage" />
</div>
<!-- Main table element -->
<b-table striped hover
<b-table striped hover show-empty
:items="items"
:fields="fields"
:current-page="currentPage"
:per-page="perPage"
:filter="filter"
@filtered="onFiltered"
>
<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 slot="name" scope="row">{{row.value.first}} {{row.value.last}}</template>
<template slot="isActive" scope="row">{{row.value?'Yes :)':'No :('}}</template>
<template slot="actions" scope="row">
<!-- We use click.stop here to prevent a 'row-clicked' event from also happening -->
<b-btn size="sm" @click.stop="details(row.item,row.index,$event.target)">Details</b-btn>
</template>
</b-table>
</div>
<!-- Details modal -->
<b-modal id="modal1" @hide="resetDetails" ok-only>
<h4 class="my-1 py-1" slot="modal-header">Index: {{ modalDetails.index }}</h4>
<pre>{{ modalDetails.data }}</pre>
</b-modal>
</div>
</template>
<script>
const items = [
{
isActive: true, age: 40, name: { first: 'Dickerson', last: 'Macdonald' }
}, {
isActive: false, age: 21, name: { first: 'Larsen', last: 'Shaw' }
}, {
_rowVariant: 'success',
isActive: false, age: 9, name: { first: 'Mitzi', last: 'Navarro' }
}, {
isActive: false, age: 89, name: { first: 'Geneva', last: 'Wilson' }
}, {
isActive: true, age: 38, name: { first: 'Jami', last: 'Carney' }
}, {
isActive: false, age: 27, name: { first: 'Essie', last: 'Dunlap' }
}, {
isActive: true, age: 40, name: { first: 'Dickerson', last: 'Macdonald' }
}, {
_cellVariants: { age: 'danger', isActive: 'warning' },
isActive: true, age: 87, name: { first: 'Larsen', last: 'Shaw' }
}, {
isActive: false, age: 26, name: { first: 'Mitzi', last: 'Navarro' }
}, {
isActive: false, age: 22, name: { first: 'Geneva', last: 'Wilson' }
}, {
isActive: true, age: 38, name: { first: 'Jami', last: 'Carney' }
}, {
isActive: false, age: 27, name: { first: 'Essie', last: 'Dunlap' }
}
];
export default {
data: {
items: [
{
isActive: true, age: 40, name: { first: 'Dickerson', last: 'Macdonald' }
}, {
isActive: false, age: 21, name: { first: 'Larsen', last: 'Shaw' }
}, {
_rowVariant: 'success',
isActive: false, age: 9, name: { first: 'Mitzi', last: 'Navarro' }
}, {
isActive: false, age: 89, name: { first: 'Geneva', last: 'Wilson' }
}, {
isActive: true, age: 38, name: { first: 'Jami', last: 'Carney' }
}, {
isActive: false, age: 27, name: { first: 'Essie', last: 'Dunlap' }
}, {
isActive: true, age: 40, name: { first: 'Dickerson', last: 'Macdonald' }
}, {
_cellVariants: { age: 'danger', name: 'success' },
isActive: false, age: 21, name: { first: 'Larsen', last: 'Shaw' }
}, {
isActive: false, age: 26, name: { first: 'Mitzi', last: 'Navarro' }
}, {
isActive: false, age: 22, name: { first: 'Geneva', last: 'Wilson' }
}, {
isActive: true, age: 38, name: { first: 'Jami', last: 'Carney' }
}, {
isActive: false, age: 27, name: { first: 'Essie', last: 'Dunlap' }
}
],
items: items,
totalRows: items.length,
fields: {
name: {
label: 'Person Full name',
sortable: true
},
age: {
label: 'Person age',
sortable: true
sortable: true,
'class': 'text-center'
},
isActive: {
label: 'is Active'
@@ -95,11 +103,24 @@ export default {
},
currentPage: 1,
perPage: 5,
filter: null
pageOptions: [{text:5,value:5},{text:10,value:10},{text:15,value:15}],
filter: null,
modalDetails: { index:'', data:'' }
},
methods: {
details(item) {
alert(JSON.stringify(item));
details(item, index, button) {
this.modalDetails.data = JSON.stringify(item, null, 2);
this.modalDetails.index = index;
this.$root.$emit('show::modal','modal1', button);
},
resetModal() {
this.modalDetails.data = '';
this.modalDetails.index = '';
},
onFiltered(filteredItems) {
// Trigger pagination to update the number of buttons/pages due to filtering
this.totalRows = filteredItems.length;
this.currentPage = 1;
}
}
}
@@ -110,7 +131,7 @@ export default {
### `fields` prop
The `fields` prop is used to display table columns.
keys are used to extract real value from each row.
Keys are used to extract real value from each row.
Example format:
```js
{
@@ -268,7 +289,7 @@ will render a table like so:
| 2 | Jane Doe | Female | Jane is 36 years old
The slot's scope variable (`data` in the above example) will have the following properties:
The slot's scope variable (`data` in the above sample) will have the following properties:
| Property | Type | Description
| -------- | ---- | -----------
@@ -278,7 +299,7 @@ The slot's scope variable (`data` in the above example) will have the following
**Note** that `index` will not always be the actual row's index number, as it is
computed after pagination and filtering have been applied to the original
table data. The `index` value will refer to the displayed row number. This
table data. The `index` value will refer to the **displayed row number**. This
number will align with the indexes from the optional `v-model` bound variable.
@@ -331,6 +352,54 @@ original items array.
**Note:** Do not bind any value directly to the `value` prop. Use the `v-model` binding.
### Filtering
Filtering, when used, is aplied to the original items array data, and hence it is not
possible to filter data based on custom rendering of virtual columns. The items row data
is stringified and the filter searches that stringified data (excluding any properties
that begin with an underscore (`_`) and the deprecated property `state`.
The `filter` can be a string, a `RegExp` or a `function` reference. If a function
is provided, the first argument is the original item record data object. The
function should return `true` if the record matches your criteria or `false` if
the record is to be filtered out.
When local filtering is applied, and the resultant number of items change, `<b-table>`
will emit the `filtered` event, passing a single argument which is the complete list of
items passing the filter routine. Treat this argument as read-only.
### Sorting
The built-in default `sort-compare` function sorts the specified field `key` based
on the data in the underlying record object. The field value is first stringified
if it is an object, and then sorted.
The default `sort-compare` routine **cannot** sort neither virtual columns, nor
based on the custom rendering of the field data (which is used only for presentation).
For this reason, you can provide your own custom sort compare routine by passing a
function reference to the prop `sort-compare`.
The `sort-compare` routine is passed three arguments. The first two arguments
(`a` and `b`) are the record objects for the rows being compared, and the third
argument is the field `key` being sorted on. The routine should return
either `-1`, `0`, or `1` based on the result of the comparing of the two records.
The default sort-compare routine works as follows:
```js
if (typeof a[key] === 'number' && typeof b[key] === 'number') {
// If both compared fields are native numbers
if (a[key] < b[key]) {
return -1;
} else if (a[key] > b[key]) {
return 1;
}
return 0;
} else {
// Strinify the field data and use String.localeCompare
return toString(a[key]).localeCompare(toString(b[key]), undefined, {
numeric: true
});
}
```
### Using Items Provider Functions
As mentioned under the `items` prop section, it is possible to use a function to provide
@@ -430,7 +499,7 @@ methods: {
}
```
By default, the items provider function is responsible for **all** paging, filtering, and sorting
By default, the items provider function is responsible for **all paging, filtering, and sorting**
of the data, before passing it to `b-table` for display.
You can disable provider paging, filtering, and sorting (individually) by setting the
@@ -445,10 +514,10 @@ following `b-table` prop(s) to `true`:
When `no-provider-paging` is `false` (default), you should only return at
maximum, `perPage` number of records.
Note that `<b-table>` needs refernce to your pagination and filtering values in order to
Note that `<b-table>` needs reference to your pagination and filtering values in order to
trigger the calling of the provider function. So be sure to bind to the `per-page`,
`current-page` and `filter` props on `b-table` to trigger the provider update function call
(unless you have the `no-provider-` respective prop set to `true`).
(unless you have the respective `no-provider-*` prop set to `true`).
#### Event based refreshing of data:
You may also trigger the refresh of the provider function by emitting the
@@ -54,6 +54,16 @@
}
]
},
{
"event": "filtered",
"description": "Emitted when local fitering causes a change in the number of items.",
"args": [
{
"arg": "filteredItems",
"description": "Array of items after filtering (before pagination occurs)."
}
]
},
{
"event": "refreshed",
"description": "Emitted when the items provider function has returned data."
@@ -93,14 +93,12 @@
return '';
}
// Exclude these fields from record stringification
const exclude = { state: true, _rowVariant: true };
return toString(Object.keys(obj).reduce((o, k) => {
if (!exclude[k]) {
o[k] = obj[k];
}
return o;
// Ignore fields 'state' and ones that start with _
if (!(/^_/.test(k) || k === 'state')) {
o[k] = obj[k];
}
return o;
}, {}));
};
@@ -137,7 +135,7 @@
default() {
if (this && this.itemsProvider) {
// Deprecate itemsProvider
warn('b-table: prop items-provider has been deprecated. Pass a function to items instead');
warn("b-table: prop 'items-provider' has been deprecated. Pass a function to 'items' instead");
return this.itemsProvider;
}
return [];
@@ -344,10 +342,14 @@
return [];
}
// Shallow copy of items, so we don't mutate the original array order/size
items = items.slice();
// Apply local filter
if (filter && !this.providerFiltering) {
// Number of items before filtering
const numOriginalItems = items.length;
if (filter instanceof Function) {
items = items.filter(filter);
} else {
@@ -363,6 +365,11 @@
return test;
});
}
if (numOriginalItems !== items.length) {
// Emit a filtered notification event, as number of items has changed
this.$emit('filtered', items);
}
}
// Apply local Sort
@@ -375,11 +382,13 @@
// Apply local pagination
if (perPage && !this.providerPaging) {
// Grab the current page of data (which may be past filtered items)
items = items.slice((currentPage - 1) * perPage, currentPage * perPage);
}
// Update the value model with the filtered/sorted/paginated data set
this.$emit('input', items);
return items;
}
},

0 comments on commit 1b2a36a

Please sign in to comment.