# ta-good-table

TA Good Table is forked from [Vue Good Table](https://github.com/xaksis/vue-good-table). We've made some updates to it to fit our Traction needs, and have been trying to capture the major changes below.

### Installation
- Set up [Satisfaction](https://github.com/TechnologyAdvice/satisfaction) locally
- Install package: `npm install @technologyadvice/ta-good-table`

import Component:
```javascript
import VueGoodTable from '@technologyadvice/ta-good-table'
```
import Styles:
```javascript
@import '~ta-good-table/src/styles/style.scss';
```


### Local development
* Clone the repository / pull latest: `rr repo pull -fib ta-good-table`
* Link the package to one or more applications: `rr app package link ta-good-table accounts-portal -b`

### Publish NPM package
Publishing of NPM packages to the [Satisfaction](https://github.com/TechnologyAdvice/satisfaction) registry happens automatically via the `publish` GitHub Actions workflow.

----------------

### Filtering
To enable filtering on a column, pass a `filterOptions` property on the column object with the enabled flag. This will take all column field values and format it into an options dropdown.

```javascript
filterOptions: {
    enabled: true,
},
```

### Initial filtering
You can set an initial filter value by passing in an object to `VueGoodTable`. When setting the filter options, ta-good-table will set the field's active property to true.

```javascript
:initialFilter="{
    field: plan_name, // name of the column field
    value: Free, // value to filter by
    }"
```

#### Multicolumn filters
Use this option if you have multiple columns that may share values and want only one filter dropdown to filter on both columns. Enable the `filterOptions` on both columns as shown above, and pass the following as a prop. For each set, the first field in the array will have the filter icon/dropdown.

```javascript
:multi-column-filter="{
    enabled: true,
    columnSets: [
        [ 'field_one', 'field_two' ],
    ],
}"
```

#### Static filters
Use this if you have a static list of filter options.
```javascript
filterOptions: {
    enabled: true,
    type: 'remote',
    staticFilterOptions: [
        { id: 1, value: 'testing' },
        { id: 2, value: 'testing123' },
    ],
    promoteActiveOptions: true, //orders the selected options before the the others when the filter is opened- defaults to false for static filters
    outputFilterIdentifier: 'test_options_selected', //the key of the selected options in the request payload
},
```

### Multifield columns
Use this option if you have multiple fields in a single column. You will need to add a `subFields` property to the column object:

```javascript
subFields: [ { 
    field: 'cool-field', 
    label: 'Cool Field', 
    filterActive: false, //necessary for reactivity, but not needed if not using filter
    filterFn: method, // optional
    type: 'date' // optional - will default to string
} ],
```

To enable the filter, just pass the field name into the `enableSubFields` array (you also need the `filterActive` property shown above):

```javascript
filterOptions: {
    enableSubFields: [ 'client' ]
},
```

### Sorting
Sorting is enabled by default. To disable, add `sortable: false` to the column object. Be sure to pass to pass the correct `type` for proper sorting.

```javascript
:sort-options="{
    enabled: true,
    initialSortBy: {
        field: 'field_name',
        type: 'asc', // or 'desc'
    }
}"
```

### Types
Used for sorting (and possibly filtering in the future):
* boolean
* date
* decimal
* number
* percentage

### Pagination
Pagination is enabled by default and has the following options. Note: currently the TA pagination is only setup for `position: bottom`

```javascript
:pagination-options="{
    enabled: true,
    perPage: 10,
    position: 'bottom',
    setCurrentPage: 1,
}"
```

Remote filtering is also possible - check out [this confluence document](https://technologyadvice.atlassian.net/wiki/spaces/EN/pages/2083127326/Pagination+in+Relay) for more information.

### Clickable rows
For clickable rows, you may either listen for the `on-row-click` event (which will have the row in the payload), or you can use the `clickable-row-options` property for routing. `rowProperty` is the value the route will use in the query params.

```javascript
:clickable-row-options="{
    url: '/',
    rowProperty: 'uuid',
    useVueRouter: true,
}"
```

### Class name
pass the `class-name` prop to the `VueGoodTable` to dynamically generate classes on the table, rows, and headers using the field property on the column object.

`class-name: "cool-table"` will generate:
* class `cool-table` on the table root element
* class `cool-table__cool-field` on the header and cells (if the field is `cool-field`)
* class `cool-table__cool-field--header` on the column's header element
* class `cool-table__cool-field--cell` on the column's cell element
    * when two field names are the same, a tdClass can be passed in to differentiate the columns
    * it will still follow the format of the other class names `cool-table__customname-cell`


### Zero state message
Pass the `zero-state-message` prop to the `VueGoodTable` if you want a custom zero state for that implementation of the table. The default message is "No results found" but good UX practices dictate selecting a zero message that is relevant to the content of your table.

### Custom headers/rows
* full-row: use `v-slot:table-full-row="props"`
* row: use `v-slot:table-row="props"`
* column: use `v-slot:table-column="props"`
(see implementation below)

### Selection with remote pagination
When `mode="remote"` and you have `selectOptions.selectAllByPage=false`, ta-good-tables uses prop `selectedRowsRemote` for its selected rows, `selectOptions.selectionIdentifier` for the row identifier to use when selecting, `selectOptions.getAllSelectionItems` function to get all selected rows and `on-selected-rows-changed` to keep the `selectedRowsRemote` updated.

`selectOptions.selectionIdentifier` is the unique field for a row, most likely `uuid`.

`selectedRowsRemote` is a prop for ta-good-tables that should be a list of your `selectOptions.selectionIdentifier` for each selected row.

`selectOptions.getAllSelectionItems` should return a list of all selectable row identifiers.  This should take into account the same filtering and searching that the table is using.  So if your current table only shows 3 rows and your `selectOptions.selectionIdentifier` is `"uuid"` then this function should return a list of the 3 UUIDs.

`on-selected-rows-changed` returns the current list of selected rows and should replace your `selectedRowsRemote` prop value.

### Example implementation
Table: 
```html
<VueGoodTable
    :cols="columns"
    :rows="rows"
    class-name="cool-table"
    :clickable-row-options="{
        url: '/cool',
        rowProperty: 'uuid',
        useVueRouter: true,
    }"
    :sort-options="{
        enabled: true,
        initialSortBy: {
            field: 'date',
            type: 'asc'/'desc',
        }
    }"
    :multi-column-filter="{
        enabled: true,
        columnSets: [
            [ 'field_one', 'field_two' ],
        ],
    }"
    :pagination-options="{
        enabled: true,
        mode: 'records',
        perPage: type === 'unresolved' ? 10 : 5,
        position: 'bottom',
    }"
    zero-state-message="Looks like you don't have any rows">
    <template v-slot:table-full-row="props">
	// use a custom component for the full row instead of just a single column
	<td :colspan="columns.length">
            <CustomRowComponent :item="props.row"></CustomRowComponent>
	</td>
    </template>

    <template v-slot:table-row="props">
        <span v-if="props.column.field == 'field_name'">
            // Custom Row 
        </span>
    </template>

    <template v-slot:table-column="props">
        <span v-if="props.column.field == 'field_name'">
            // Custom Header
        </span>
    </template>
</VueGoodTable>
```

Columns
```javascript
const columns = [
    {
        label: 'Name',
        field: 'name',
        subFields: [ { field: 'client', label: 'Client', filterActive: false } ],
        filterOptions: {
            enableSubField: [ 'client' ]
        },
    },
    {
        label: 'Brand',
        field: 'brand',
        filterOptions: {
            enabled: true,
        },
    },
    {
        label: 'Date',
        field: 'date',
        type: 'date',
        dateInputFormat: 'yyyy-MM-dd',
        dateOutputFormat: 'E, MMM dd y',
    },
    {
        label: 'Count',
        field: 'count',
        type: 'number',
    },
]
```

### Troubleshooting
#### Columns are blank
A common issue with blank columns happens when there are custom columns on the page

you may see something like this in the `table-row` slot:
```html
<template v-if="props.column.field === 'name'">...</template>
<template v-if="props.column.field === 'date'">...</template>
```
  
The above code could cause blank columns. The solution would be every conditional `<template>` after the initial `v-if`, should be a `v-else-if`

```html
<span v-if="props.column.field === 'name'"></span>
<span v-else-if="props.column.field === 'date'"></span>
```

---

Copyright &copy; 2025 TechnologyAdvice
