diff --git a/src/components/datatable/DataTable.d.ts b/src/components/datatable/DataTable.d.ts index 59925dcf4..86ad3fbe9 100644 --- a/src/components/datatable/DataTable.d.ts +++ b/src/components/datatable/DataTable.d.ts @@ -35,6 +35,9 @@ export declare class DataTable extends Vue { resizableColumns?: boolean; columnResizeMode?: string; reorderableColumns?: boolean; + expandedRows?: any[]; + expandedRowIcon?: string; + collapsedRowIcon?: string; $emit(eventName: 'page', event: Event): this; $emit(eventName: 'sort', event: Event): this; $emit(eventName: 'filter', event: Event): this; @@ -43,6 +46,8 @@ export declare class DataTable extends Vue { $emit(eventName: 'column-resize-end', event: Event): this; $emit(eventName: 'column-reorder', event: Event): this; $emit(eventName: 'row-reorder', event: Event): this; + $emit(eventName: 'row-expand', event: Event): this; + $emit(eventName: 'row-collapse', event: Event): this; $slots: { header: VNode[]; paginatorLeft: VNode[]; diff --git a/src/components/datatable/DataTable.vue b/src/components/datatable/DataTable.vue index 59c52d41e..544f23b3a 100644 --- a/src/components/datatable/DataTable.vue +++ b/src/components/datatable/DataTable.vue @@ -51,21 +51,34 @@ @@ -274,6 +287,18 @@ export default { reorderableColumns: { type: Boolean, default: false + }, + expandedRows: { + type: Array, + default: null + }, + expandedRowIcon: { + type: String, + default: 'pi-chevron-down' + }, + collapsedRowIcon: { + type: String, + default: 'pi-chevron-right' } }, data() { @@ -285,6 +310,7 @@ export default { d_sortOrder: this.sortOrder, d_multiSortMeta: this.multiSortMeta ? [...this.multiSortMeta] : [], d_selectionKeys: null, + d_expandedRowKeys: null, columnOrder: null }; }, @@ -322,6 +348,11 @@ export default { if (this.dataKey) { this.updateSelectionKeys(newValue); } + }, + expandedRows(newValue) { + if (this.dataKey) { + this.updateExpandedRowKeys(newValue); + } } }, mounted() { @@ -758,10 +789,13 @@ export default { return false; }, findIndexInSelection(rowData) { + return this.findIndex(rowData, this.selection); + }, + findIndex(rowData, collection) { let index = -1; - if (this.selection && this.selection.length) { - for (let i = 0; i < this.selection.length; i++) { - if (this.equals(rowData, this.selection[i])) { + if (collection && collection.length) { + for (let i = 0; i < collection.length; i++) { + if (this.equals(rowData, collection[i])) { index = i; break; } @@ -781,9 +815,30 @@ export default { this.d_selectionKeys[String(ObjectUtils.resolveFieldData(selection, this.dataKey))] = 1; } }, + updateExpandedRowKeys(expandedRows) { + if (expandedRows && expandedRows.length) { + this.d_expandedRowKeys = {}; + for (let data of expandedRows) { + this.d_expandedRowKeys[String(ObjectUtils.resolveFieldData(data, this.dataKey))] = 1; + } + } + else { + this.d_expandedRowKeys = null; + } + }, equals(data1, data2) { return this.compareSelectionBy === 'equals' ? (data1 === data2) : ObjectUtils.equals(data1, data2, this.dataKey); }, + isRowExpanded(rowData) { + if (rowData && this.expandedRows) { + if (this.dataKey) + return this.d_expandedRowKeys ? this.d_expandedRowKeys[ObjectUtils.resolveFieldData(rowData, this.dataKey)] !== undefined : false; + else + return this.findIndex(rowData, this.expandedRows) > -1; + } + + return false; + }, getRowKey(rowData, index) { return this.dataKey ? ObjectUtils.resolveFieldData(rowData, this.dataKey): index; }, @@ -1172,7 +1227,38 @@ export default { this.onRowDragLeave(event); this.onRowDragEnd(event); event.preventDefault(); - } + }, + toggleRow(event, rowData) { + let expanded; + let expandedRowIndex; + let _expandedRows = this.expandedRows ? [...this.expandedRows] : []; + + if (this.dataKey) { + expanded = this.d_expandedRowKeys ? this.d_expandedRowKeys[ObjectUtils.resolveFieldData(rowData, this.dataKey)] !== undefined : false; + } + else { + expandedRowIndex = this.findIndex(rowData, this.expandedRows); + expanded = expandedRowIndex > -1; + } + + if (expanded) { + if (expandedRowIndex == null) { + expandedRowIndex = this.findIndex(rowData, this.expandedRows); + } + _expandedRows.splice(expandedRowIndex, 1); + this.$emit('update:expandedRows', _expandedRows); + this.$emit('row-collapse', {originalEvent: event,data: rowData}); + } + else { + _expandedRows.push(rowData); + this.$emit('update:expandedRows', _expandedRows); + this.$emit('row-expand', {originalEvent: event,data: rowData}); + } + }, + rowTogglerIcon(rowData) { + const icon = this.isRowExpanded(rowData) ? this.expandedRowIcon : this.collapsedRowIcon; + return ['p-row-toggler-icon pi pi-fw p-clickable', icon]; + }, }, computed: { containerClass() { diff --git a/src/router.js b/src/router.js index 9a6943e6d..6426eb441 100644 --- a/src/router.js +++ b/src/router.js @@ -170,6 +170,11 @@ export default new Router({ path: '/datatable/colresize', name: 'datatablecolresize', component: () => import('./views/datatable/DataTableColResizeDemo.vue') + }, + { + path: '/datatable/rowexpand', + name: 'datatablerowexpand', + component: () => import('./views/datatable/DataTableRowExpandDemo.vue') }, { path: '/datatable/crud', diff --git a/src/views/datatable/DataTableDoc.vue b/src/views/datatable/DataTableDoc.vue index f8ea5af21..be1e68f4a 100644 --- a/src/views/datatable/DataTableDoc.vue +++ b/src/views/datatable/DataTableDoc.vue @@ -220,6 +220,12 @@ export default { null Defines column based selection mode, options are "single" and "multiple". + + expander + boolean + false + Displays an icon to toggle row expansion. + colspan number @@ -377,70 +383,6 @@ export default { <Column field="color" header="Color"></Column> </DataTable> - - -

Lazy Loading

-

Lazy mode is handy to deal with large datasets, instead of loading the entire data, small chunks of data is loaded by invoking corresponding callbacks everytime paging, sorting and filtering happens. Sample belows imitates lazy paging by using an in memory list. - It is also important to assign the logical number of rows to totalRecords by doing a projection query for paginator configuration so that paginator displays the UI - assuming there are actually records of totalRecords size although in reality they aren't as in lazy mode, only the records that are displayed on the current page exist.

- -

Lazy loading is implemented by handling pagination and sorting using page and sort events by making a remote query using the information - passed to the events such as first offset, number of rows and sort field for ordering. Filtering is handled differently as filter elements are defined using templates, use - the event you prefer on your form elements such as input, change, blur to make a remote call by passing the filters property to update the displayed data. Note that, - in lazy filtering, totalRecords should also be updated to align the data with the paginator.

- -

Here is a sample paging implementation with in memory data, a more enhanced example with a backend is being worked on and will be available at a github repository.

- - - - - -import CarService from '../../service/CarService'; - -export default { - data() { - return { - loading: false, - totalRecords: 0, - cars: null - } - }, - datasource: null, - carService: null, - created() { - this.carService = new CarService(); - }, - mounted() { - this.loading = true; - - setTimeout(() => { - this.carService.getCarsLarge().then(data => { - this.datasource = data; - this.totalRecords = data.length, - this.cars = this.datasource.slice(0, 10); - this.loading = false; - }); - }, 1000); - }, - methods: { - onPage(event) { - this.loading = true; - - setTimeout(() => { - this.cars = this.datasource.slice(event.first, event.first + event.rows); - this.loading = false; - }, 1000); - } - } -}

Sorting

@@ -512,6 +454,7 @@ data() { } } +

Filtering

Filtering is enabled by defining a filter template per column to populate the filters property of the DataTable. The filters property should be an key-value object where keys are the field name and the value is the filter value. The filter template receives the column properties @@ -601,6 +544,143 @@ data() { <Column field="color" header="Color"></Column> </DataTable> + + +

Lazy Loading

+

Lazy mode is handy to deal with large datasets, instead of loading the entire data, small chunks of data is loaded by invoking corresponding callbacks everytime paging, sorting and filtering happens. Sample belows imitates lazy paging by using an in memory list. + It is also important to assign the logical number of rows to totalRecords by doing a projection query for paginator configuration so that paginator displays the UI + assuming there are actually records of totalRecords size although in reality they aren't as in lazy mode, only the records that are displayed on the current page exist.

+ +

Lazy loading is implemented by handling pagination and sorting using page and sort events by making a remote query using the information + passed to the events such as first offset, number of rows and sort field for ordering. Filtering is handled differently as filter elements are defined using templates, use + the event you prefer on your form elements such as input, change, blur to make a remote call by passing the filters property to update the displayed data. Note that, + in lazy filtering, totalRecords should also be updated to align the data with the paginator.

+ +

Here is a sample paging implementation with in memory data, a more enhanced example with a backend is being worked on and will be available at a github repository.

+ + + + + +import CarService from '../../service/CarService'; + +export default { + data() { + return { + loading: false, + totalRecords: 0, + cars: null + } + }, + datasource: null, + carService: null, + created() { + this.carService = new CarService(); + }, + mounted() { + this.loading = true; + + setTimeout(() => { + this.carService.getCarsLarge().then(data => { + this.datasource = data; + this.totalRecords = data.length, + this.cars = this.datasource.slice(0, 10); + this.loading = false; + }); + }, 1000); + }, + methods: { + onPage(event) { + this.loading = true; + + setTimeout(() => { + this.cars = this.datasource.slice(event.first, event.first + event.rows); + this.loading = false; + }, 1000); + } + } +} + + +

Row Expansion

+

Rows can be expanded to display additional content using the expandedRows property with the sync operator accompanied by a template named "expansion". row-expand and row-collapse are optional callbacks that are invoked when a row is expanded or toggled.

+ +

The dataKey property identifies a unique value of a row in the dataset, it is not mandatory in row expansion functionality however being able to define it increases the performance of the table signifantly.

+ + + + + +import CarService from '../../service/CarService'; + +export default { + data() { + return { + cars: null, + expandedRows: [] + } + }, + carService: null, + created() { + this.carService = new CarService(); + }, + mounted() { + this.carService.getCarsSmall().then(data => this.cars = data); + }, + methods: { + onRowExpand(event) { + this.$toast.add({severity: 'info', summary: 'Row Expanded', detail: 'Vin: ' + event.data.vin, life: 3000}); + }, + onRowCollapse(event) { + this.$toast.add({severity: 'success', summary: 'Row Collapsed', detail: 'Vin: ' + event.data.vin, life: 3000}); + }, + expandAll() { + this.expandedRows = this.cars.filter(car => car.vin); + this.$toast.add({severity: 'success', summary: 'All Rows Expanded', life: 3000}); + }, + collapseAll() { + this.expandedRows = null; + this.$toast.add({severity: 'success', summary: 'All Rows Collapsed', life: 3000}); + } + } +}

Column Resize

@@ -1081,7 +1161,25 @@ export default { reorderableColumns boolean false - When enabled, columns can be reordered using drag and drop.. + When enabled, columns can be reordered using drag and drop. + + + expandedRows + array + null + A collection of row data display as expanded. + + + expandedRowIcon + string + pi-chevron-down + Icon of the row toggler to display the row as expanded. + + + collapsedRowIcon + string + pi-chevron-right + Icon of the row toggler to display the row as collapsed. @@ -1162,6 +1260,18 @@ export default { value: Reordered value Callback to invoke when a row is reordered. + + row-expand + event.originalEvent: Browser event
+ event.data: Expanded row data. + Callback to invoke when a row is expanded. + + + row-collapse + event.originalEvent: Browser event
+ event.data: Collapsed row data. + Callback to invoke when a row is collapsed. + diff --git a/src/views/datatable/DataTableRowExpandDemo.vue b/src/views/datatable/DataTableRowExpandDemo.vue new file mode 100644 index 000000000..2017fe5dd --- /dev/null +++ b/src/views/datatable/DataTableRowExpandDemo.vue @@ -0,0 +1,196 @@ + + + + + \ No newline at end of file diff --git a/src/views/datatable/DataTableSubMenu.vue b/src/views/datatable/DataTableSubMenu.vue index 3eb1e4434..98b92f71c 100644 --- a/src/views/datatable/DataTableSubMenu.vue +++ b/src/views/datatable/DataTableSubMenu.vue @@ -3,16 +3,17 @@