QTable with less clutter on hierarchical data
-
I have data that has multiple hierarchy levels; imagine each row being an entry with 3 hierarchy levels on top:
Subject Date Sample Property1 Property2 Patient1 2018-04-12 Sample1 42g blue Patient1 2018-04-12 Sample2 10g blue Patient1 2019-02-24 Sample1 45g blue Patient2 2018-07-01 Sample1 22g red
Now, I would like to suppress rendering cell bodies with repeated values from the previous row, at least for the first columns, similar to this:
Subject Date Sample Property1 Property2 Patient1 2018-04-12 Sample1 42g blue Sample2 10g blue 2019-02-24 Sample1 45g blue Patient2 2018-07-01 Sample1 22g red
While I could do this via the
:data
array, I would prefer to do that in thebody-cell
slot, in order to allow for filtering and reordering of rows (sorting is enabled). I think I could do it with therowIndex
introduced in version 1.10, by looking up the previous row and comparing with it.But I have two questions:
- Would you see an obvious problem with this approach, e.g. in terms of performance? It also feels strange to get the current cell neatly unwrapped and presented by QTable, but to then add lengthy code querying the cell value from above.
- Also, it could be a feature request for quasar / QTable itself, to suppress repeated values?
-
If you really don’t want to use body slots, you could potentially accomplish this with the
format
function in the column definition. It might be slower than a body slot because I couldn’t find a reference torowIndex
in that function (it would be a nice feature if it was there) but it would be a cleaner template. I probably wouldn’t worry about speed unless it is actually slow. Note that I’m assuming each row has a unique id, say from a mysql db.columns: [ { label: 'Patient', name: 'patient', field: 'patient', // ... format: (val, row) => { const rowIndex = this.data.findIndex(item => item.id === row.id) if(rowIndex === -1 || rowIndex === 0) { // this row wasn't found (shouldn't happen) or it's the first row return val } const prevRow = this.data[rowIndex - 1] if(prevRow.patient === val) { return '' } return val }, } ]
Edit: I’m also not positive how it will play with filtering. Another option is to modify the
:data
array as you said, but set two object properties on each row:patient
andpatientLabel
. Then use theformat
function like this:columns: [ { label: 'Patient', name: 'patient', field: 'patient', // ... format: (val, row) => row.patientLabel, } ]
That will preserve sorting, but override the display.
-
@beets Thanks – it’s good to know yet another option. However, I fear that an O(N²) approach (with
findIndex()
) is really too slow for me. To give you an impression of the size of the table – it is already kind of slow when scrolling and has a 1-2 seconds delay on sorting. (And one would also have to ensure thatformat()
is called again when sorting / filtering, I guess?!)Also, it is not that I “don’t want to use body slots” – I am just wondering whether people find this use of
rowIndex
reasonable. -
I just noticed that my plan does not work: Apparently, the slot only has access to the
rowIndex
in the sorted and filtered table data, but not to the sorted and filtered data array itself! -
Thanks to @rstoenescu himself (who replied on GitHub, pointing me to the
filteredSortedRows
computed property), I found a solution that works fine.Simplified code:
<template> <div class="... q-pa-md"> <q-scroll-area class="col row"> <q-table ref="myTable" :data="tableData" :columns="columns" :pagination.sync="pagination" > <template v-slot:body-cell="props"> <q-td :props="props"> {{ deduplicatedCellValue(props) }} </q-td> </template> </q-table> </q-scroll-area> </div> </template> <script> export default { data() { return { tableData: [ ... ], columns: [ { name: "subject", label: "Subject", field: "subject", sortable: true, deduplicate: true, }, { name: "date", label: "Date", field: "date", sortable: true, deduplicate: true, }, { name: "sample", label: "Sample", field: "sample", sortable: true, deduplicate: true, }, ... ], pagination: { rowsPerPage: 0, sortBy: "subject", }, }; }, methods: { deduplicatedCellValue(props) { if (props.rowIndex > 0 && props.col.deduplicate) { let table = this.$refs.myTable.filteredSortedRows; if (props.value == table[props.rowIndex - 1][props.col.field]) { return ""; } } return props.value; }, }, }; </script>
It does not seem to come with a big performance penalty, although the previous row has to be computed for every cell anew. However, I had introduced a “deduplicate” column flag anyhow, because I only wanted to do this on the header columns, but not remove identical property values from the later columns. Therefore, the dereferencing of
this.$refs.myTable.filteredSortedRows[props.rowIndex - 1]
has to be done only for three cells per row, which is acceptable.Still, I wonder if QTable should possibly introduce a
props.previousRow
next toprops.row
. To me, that would mean much less code on my side, and a possibly slightly more performant solution in the end. But I understand that every feature in Quasar is something that needs to be documented and maintained as well, so I won’t push for it.