Skip to content

Commit b66f994

Browse files
authored
feat(table): Refactor field formatter support + optimized sort-compare handling (#991)
* refactor(b-table): use original item object * chore(table.spec): eslint format * feat(b-table): Allow usr provided sortCopmpare to handle only specific fields if user provided sortCompare returns `null` (or `undefined`), then we assume it doesn't handle sorting for the field specified by `sortBy` * ESLint * Always return formattedValue as `value` in scoped slot. Unformatted value is available as property `unformatted` on scoped slot data. * Update docs * Update README.md * Allow formattter to return html content * Update README.md * fix(table-docs): unexpected identifier error * fix(table-docs): typo corrections ⌨💥 * Allow v-html without need for wrapper div via v-if/v-else
1 parent 6b12629 commit b66f994

File tree

3 files changed

+1132
-1076
lines changed

3 files changed

+1132
-1076
lines changed

docs/components/table/README.md

Lines changed: 69 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ const items = [
102102
isActive: true, age: 40, first_name: 'Thor', last_name: 'Macdonald',
103103
_cellVariants: { isActive: 'success', age: 'info', first_name: 'warning' }
104104
},
105-
{ isAactive: false, age: 29, first_name: 'Dick', last_name: 'Dunlap' }
105+
{ isActive: false, age: 29, first_name: 'Dick', last_name: 'Dunlap' }
106106
];
107107
108108
export default {
@@ -198,7 +198,7 @@ export default {
198198
{ isActive: false, age: 21, first_name: 'Larsen', last_name: 'Shaw' },
199199
{ isActive: false, age: 89, first_name: 'Geneva', last_name: 'Wilson' },
200200
{ isActive: true, age: 38, first_name: 'Jami', last_name: 'Carney' },
201-
{ isAactive: false, age: 29, first_name: 'Dick', last_name: 'Dunlap' }
201+
{ isActive: false, age: 29, first_name: 'Dick', last_name: 'Dunlap' }
202202
]
203203
}
204204
};
@@ -212,21 +212,21 @@ When fields is provided as an object, the following field properties are availab
212212
| Property | Type | Description
213213
| ---------| ---- | -----------
214214
| `class` | String or Array | Class name (or array of class names) to add to `<th>` **and** `<td>` in the column
215-
| `formatter` | String or Function | A formatter callback function, can be used instead of slots for real table fields (i.e. fields, that have corresponding data at items array).
215+
| `formatter` | String or Function | A formatter callback function, can be used instead of (or in conjunction with) slots for real table fields (i.e. fields, that have corresponding data at items array).
216216
| `label` | String | Appears in the columns table header (and footer if `foot-clone` is set). Defaults to the field's key
217217
| `sortable` | Boolean | Enable sorting on this column
218218
| `tdClass` | String or Array | Class name (or array of class names) to add to data `<td>` cells in the column
219219
| `thClass` | String or Array | Class name (or array of class names) to add to header/footer `<th>` cell
220220
| `thStyle` | Object | JavaScript object representing CSS styles you would like to apply to the table field `<th>`
221-
| `variant` | String | Apply contextual class to the `<th>` **and** `<td>` in the column (`active`, `success`, `info`, `warning`, `danger`)
221+
| `variant` | String | Apply contextual class to the `<th>` **and** `<td>` in the column - `active`, `success`, `info`, `warning`, `danger` (these variants map to classes `thead-${variant}`, `table-${variant}`, or `bg-${variant}` accordingly)
222222

223-
>**Notes:**
224-
>- _Field properties, if not present, default to `null` unless otherwise stated above._
225-
>- _`thClass` and `tdClass` will not work with classes that are defined in scoped CSS_
226-
>- _For information on the syntax supported by `thStyle`, see
223+
**Notes:**
224+
- _Field properties, if not present, default to `null` unless otherwise stated above._
225+
- _`thClass` and `tdClass` will not work with classes that are defined in scoped CSS_
226+
- _For information on the syntax supported by `thStyle`, see
227227
[Class and Style Bindings](https://vuejs.org/v2/guide/class-and-style.html#Binding-Inline-Styles)
228228
in the Vue.js guide._
229-
>- _Any additional properties added to the field objects will be left intact - so you can access
229+
- _Any additional properties added to the field objects will be left intact - so you can access
230230
them via the named scoped slots for custom data, header, and footer rendering._
231231

232232
For information and usage about scoped slots and formatters, refer to
@@ -306,20 +306,17 @@ The slot's scope variable (`data` in the above sample) will have the following p
306306
| Property | Type | Description
307307
| -------- | ---- | -----------
308308
| `index` | Number | The row number (indexed from zero)
309-
| `item` | Object | The entire record (i.e. `items[index]`) for this row (deep clone)
310-
| `value` | Any | The value for this key in the record (`null` or `undefined` if a virtual column)
309+
| `item` | Object | The entire raw record data (i.e. `items[index]`) for this row (before any formatter is applied)
310+
| `value` | Any | The value for this key in the record (`null` or `undefined` if a virtual column), or the output of thr field's `formatter` function (see below for for information on field `formatter` callback functions)
311+
| `unformatted` | Any | The raw value for this key in the item record (`null` or `undefined` if a virtual column), before being passed to the field's `formtter` function
311312

312313

313-
>**Note:** *`index` will not always be the actual row's index number, as it is
314+
**Notes:**
315+
- *`index` will not always be the actual row's index number, as it is
314316
computed after pagination and filtering have been applied to the original
315317
table data. The `index` value will refer to the **displayed row number**. This
316318
number will align with the indexes from the optional `v-model` bound variable.*
317-
318-
`<b-table>` always deep clones the items array data before pagination, sorting,
319-
filtering and display. Hence any changes made to the item data passed to
320-
the custom rendered slot will **not** affect the original provided items array.
321-
322-
When placing inputs, buttons, selects or links within a data cell scoped slot,
319+
- When placing inputs, buttons, selects or links within a data cell scoped slot,
323320
be sure to add a `@click.stop` handler (which can be empty) to prevent the
324321
click on the input, button, select, or link, from triggering the `row-clicked`
325322
event:
@@ -335,20 +332,23 @@ event:
335332

336333
One more option to customize field output is to use formatter callback function.
337334
To enable this field's property `formatter` is used. Value of this property may be
338-
String or function reference. In case of String value, function must be defined at parent component's methods,
339-
to provide formatter as `Function`, it must be declared at global scope (window or as global mixin at Vue).
335+
String or function reference. In case of a String value, function must be defined at
336+
parent component's methods. Providing formatter as `Function`, it must be declared at
337+
global scope (window or as global mixin at Vue).
340338

341-
Callback function accepts three arguments - `value`, `key`, `row`.
339+
Callback function accepts three arguments - `value`, `key`, and `item`, and should
340+
return the formatted value as a string (basic HTML is supported)
342341

343342
**Example 6: Custom data rendering with formatter callback function**
344343
```html
345344
<template>
346345
<div>
347346
<b-table :fields="fields" :items="items">
348-
<template slot="name" scope="data">
349-
<a :href="data.index+1">{{data.item.name}}</a>
347+
<template slot="name" scope="data">
348+
<a :href="`#${data.value.replace(/[^a-z]+/i,'-').toLowerCase()}`">
349+
{{data.value}}
350+
</a>
350351
</template>
351-
352352
</b-table>
353353
</div>
354354
</template>
@@ -358,29 +358,37 @@ export default {
358358
data: {
359359
fields: {
360360
name: {
361-
// A column that needs custom formatting
361+
// A column that needs custom formatting, calling formatter 'fullName' in this app
362362
label: 'Full Name',
363-
formatter:'fullName'
363+
formatter: 'fullName'
364364
},
365365
age: {
366366
// A regular column
367367
label: 'Sex'
368368
},
369369
sex: {
370-
// A regular column
371-
label: 'Sex'
370+
// A regular column with custom formatter
371+
label: 'Sex',
372+
formatter: (value) => { return value.charAt(0).toUpperCase(); }
372373
},
374+
birthYear: {
375+
// A virtual column with custom formatter
376+
label: 'Birth Year',
377+
formatter: (value, key, item) => {
378+
return (new Date()).getFullYear() - item.age;
379+
}
380+
}
373381
},
374382
items: [
375383
{ name: { first: 'John', last: 'Doe' }, sex: 'Male', age: 42 },
376384
{ name: { first: 'Jane', last: 'Doe' }, sex: 'Female', age: 36 },
377-
{ name: { first: 'Rubin', last: 'Kincade' }, sex: 'Male', age: 73 },
378-
{ name: { first: 'Shirley', last: 'Partridge' }, sex: 'Female', age: 62 }
385+
{ name: { first: 'Rubin', last: 'Kincade' }, sex: 'male', age: 73 },
386+
{ name: { first: 'Shirley', last: 'Partridge' }, sex: 'female', age: 62 }
379387
]
380388
},
381389
methods: {
382390
fullName(value){
383-
return `${value.first} ${value.last}`
391+
return `${value.first} ${value.last}`;
384392
}
385393
}
386394
}
@@ -421,7 +429,7 @@ The slot's scope variable (`data` in the above example) will have the following
421429
| -------- | ---- | -----------
422430
| `column` | String | The fields's `key` value
423431
| `field` | Object | the field's object (from the `fields` prop)
424-
| `label` | String | The fileds label value (also available as `data.field.label`)
432+
| `label` | String | The fields label value (also available as `data.field.label`)
425433

426434
When placing inputs, buttons, selects or links within a `HEAD_` or `FOOT_` slot,
427435
be sure to add a `@click.stop` handler (which can be empty) to prevent the
@@ -431,7 +439,7 @@ or a `head-clicked` event.
431439
```html
432440
<template slot="HEAD_actions" scope="foo">
433441
<!-- We use click.stop here to prevent 'sort-changed' or 'head-clicked' events -->
434-
<input tyep="checkbox" :value="foo.column" v-model="selected" @click.stop>
442+
<input type="checkbox" :value="foo.column" v-model="selected" @click.stop>
435443
</template>
436444
```
437445

@@ -447,10 +455,10 @@ the original `items` array (except when `items` is set to a provider function).
447455
Deleting a record from the v-model will **not** remove the record from the
448456
original items array.
449457

450-
>**Note:** *Do not bind any value directly to the `value` prop. Use the `v-model` binding.*
458+
**Note:** *Do not bind any value directly to the `value` prop. Use the `v-model` binding.*
451459

452460
## Filtering
453-
Filtering, when used, is aplied to the original items array data, and hence it is not
461+
Filtering, when used, is applied to the original items array data, and hence it is not
454462
possible to filter data based on custom rendering of virtual columns. The items row data
455463
is stringified and the filter searches that stringified data (excluding any properties
456464
that begin with an underscore (`_`) and the deprecated property `state`.
@@ -465,10 +473,10 @@ will emit the `filtered` event, passing a single argument which is the complete
465473
items passing the filter routine. Treat this argument as read-only.
466474

467475
## Sorting
468-
As mentioned above in the [**Fields**](#fields-column-definitions-) section above, you can make columns sortable. Clciking on
469-
sortable a column header will sort the column in ascending direction, while clicking
470-
on it again will switch the direction or sorting. Clicking on a non-sortable column
471-
will clear the sorting.
476+
As mentioned above in the [**Fields**](#fields-column-definitions-) section above,
477+
you can make columns sortable. Clicking on a sortable column header will sort the
478+
column in ascending direction, while clicking on it again will switch the direction
479+
of sorting. Clicking on a non-sortable column will clear the sorting.
472480

473481
You can control which column is pre-sorted and the order of sorting (ascending or
474482
descending). To pre-specify the column to be sorted, set the `sort-by` prop to
@@ -482,18 +490,21 @@ on the `.sync` prop modifier
482490

483491
### Sort-Compare routine
484492
The built-in default `sort-compare` function sorts the specified field `key` based
485-
on the data in the underlying record object. The field value is first stringified
486-
if it is an object, and then sorted.
493+
on the data in the underlying record object (not by the formatted value). The field
494+
value is first stringified if it is an object, and then sorted.
487495

488496
The default `sort-compare` routine **cannot** sort virtual columns, nor sort based
489-
on the custom rendering of the field data (which is used only for presentation).
490-
For this reason, you can provide your own custom sort compare routine by passing a
491-
function reference to the prop `sort-compare`.
497+
on the custom rendering of the field data (formatter functions and/or scoped slots
498+
are used only for presentation). For this reason, you can provide your own
499+
custom sort compare routine by passing a function reference to the prop `sort-compare`.
492500

493501
The `sort-compare` routine is passed three arguments. The first two arguments
494502
(`a` and `b`) are the record objects for the rows being compared, and the third
495-
argument is the field `key` being sorted on. The routine should return
503+
argument is the field `key` being sorted on (`sortBy`). The routine should return
496504
either `-1`, `0`, or `1` based on the result of the comparing of the two records.
505+
If the routine returns `null`, then the default sort-compare rouine will be used.
506+
You can use this feature (returning `null`) to have your custom sort-compare routine
507+
handle only certain fields (keys).
497508

498509
The default sort-compare routine works as follows:
499510

@@ -581,7 +592,7 @@ function myProvider(ctx) {
581592
a `busy` prop that can be used either to override inner `busy`state, or to monitor
582593
`<b-table>`'s current busy state in your application using the 2-way `.sync` modifier.
583594

584-
>**Note:** _in order to allow `<b-table>` fully track it's `busy` state, custom items
595+
**Note:** _in order to allow `<b-table>` fully track it's `busy` state, custom items
585596
provider function should handle errors from data sources and return an empty
586597
array to `<b-table>`._
587598

@@ -590,13 +601,13 @@ set to `true` just before your async fetch, and then set it to `false` once you
590601
your data, and just before you send it to the table for display. Example:
591602

592603
```html
593-
<b-table id="my-table" :busy.sync="isBusy" :items="myProvider" :fields="fields" ....>
594-
</b-table>
604+
<b-table id="my-table" :busy.sync="isBusy" :items="myProvider" :fields="fields" ...></b-table>
595605
```
606+
596607
```js
597608
data () {
598609
return {
599-
isBusy = false
610+
isBusy: false
600611
};
601612
}
602613
methods: {
@@ -620,10 +631,10 @@ methods: {
620631
}
621632
```
622633

623-
>**Notes:**
624-
>- _If you manually place the table in the `busy` state, the items provider will
634+
**Notes:**
635+
- _If you manually place the table in the `busy` state, the items provider will
625636
__not__ be called/refreshed until the `busy` state has been set to `false`._
626-
>- _All click related and hover events, and sort-changed events will __not__ be
637+
- _All click related and hover events, and sort-changed events will __not__ be
627638
emiited when in the `busy` state (either set automatically during provider update,
628639
or when manually set)._
629640

@@ -643,7 +654,7 @@ following `b-table` prop(s) to `true`:
643654
When `no-provider-paging` is `false` (default), you should only return at
644655
maximum, `perPage` number of records.
645656

646-
>**Note** _`<b-table>` needs reference to your pagination and filtering values in order to
657+
**Note** _`<b-table>` needs reference to your pagination and filtering values in order to
647658
trigger the calling of the provider function. So be sure to bind to the `per-page`,
648659
`current-page` and `filter` props on `b-table` to trigger the provider update function call
649660
(unless you have the respective `no-provider-*` prop set to `true`)._
@@ -659,9 +670,9 @@ You must have a unique ID on your table for this to work.
659670

660671
Or by calling the refresh method on the table reference
661672
```html
662-
<b-table ref="table" ... >
663-
</b-table>
673+
<b-table ref="table" ... ></b-table>
664674
```
675+
665676
```js
666677
this.$refs.table.refresh();
667678
```
@@ -673,8 +684,7 @@ By listening on `<b-table>` `sort-changed` event, you can detect when the sortin
673684
and direction have changed.
674685

675686
```html
676-
<b-table @sort-changed="sortingChanged" ...>
677-
</b-table>
687+
<b-table @sort-changed="sortingChanged" ...></b-table>
678688
```
679689

680690
The `sort-changed` event provides a single argument of the table's current state context object.
@@ -774,7 +784,7 @@ const items = [
774784
{ _cellVariants: { age: 'danger', isActive: 'warning' },
775785
isActive: true, age: 87, name: { first: 'Larsen', last: 'Shaw' } },
776786
{ isActive: false, age: 26, name: { first: 'Mitzi', last: 'Navarro' } },
777-
{ isActive: false, age: 22, name: { first: 'Genevive', last: 'Wilson' } },
787+
{ isActive: false, age: 22, name: { first: 'Genevieve', last: 'Wilson' } },
778788
{ isActive: true, age: 38, name: { first: 'John', last: 'Carney' } },
779789
{ isActive: false, age: 29, name: { first: 'Dick', last: 'Dunlap' } }
780790
];
@@ -818,6 +828,7 @@ export default {
818828

819829
<!-- table-complete-1.vue -->
820830
```
831+
821832
## Table options
822833
`<b-table>` provides several props to alter the style of the table:
823834

@@ -833,4 +844,3 @@ export default {
833844
| `head-variant` | Use `default` or `inverse` to make `<thead>` appear light or dark gray, respectively
834845
| `foot-variant` | Use `default` or `inverse` to make `<tfoot>` appear light or dark gray, respectively. Has no effect if `foot-clone` is not set
835846

836-

0 commit comments

Comments
 (0)