Qlik Community

Qlik Design Blog

All about product and Qlik solutions: scripting, data modeling, visual design, extensions, best practices, etc.

Employee
Employee

I recently rebuilt our custom table in the qdt-components library that we use on the demo team here at Qlik. You can check out an example at https://qdt-apps.qlik.com/qdt-components/react/#/table-engine. Perhaps the trickiest part to get just right was sorting. When a user clicks on a column header, the sorting needed to update to sort by that column, and that part is pretty straightforward, just patch the objects qInterColumnSortOrder and move that column's index to the front of the array. But how do I know to display to the user in the column header whether that column is sorting by descending or ascending? And what if the user clicks on that same column header again to reverse the sort order of that column? Then I learned about the qSortIndicator and qReverseSort sort properties.

You can find the values of the qSortIndicator and qReverseSort properties in the qDimensionInfo and qMeasureInfo structs in qLayout. Basically, qSortIndicator just returns either 'N' for no sort, 'A' for ascending sort, or 'D' for descending sort, and qReverseSort indicates with true or false whether the sort order is actually reversed. And you can set the qReverseSort to reverse the sort order inside the qDef of the dimension or measure. With these properties in hand, I was able to correctly display in the column header of the active sorted column whether the sort was ascending or descending, as well as handle reverse sorting of a column with relative ease. Let's take a look at it.

First thing I do is grab all the information I need for each column. That looks like this.

const columns = useMemo(() => (
    qLayout
      ? [
        ...qLayout.qHyperCube.qDimensionInfo.map((col, index) => ({
          Header: col.qFallbackTitle,
          accessor: (d) => d[index].qText,
          defaultSortDesc: col.qSortIndicator === 'D',
          id: col.qFallbackTitle,
          qInterColumnIndex: index,
          qPath: `/qHyperCubeDef/qDimensions/${index}`,
          qSortIndicator: col.qSortIndicator,
          qReverseSort: col.qReverseSort,
        })),
        ...qLayout.qHyperCube.qMeasureInfo.map((col, index) => ({
          Header: col.qFallbackTitle,
          accessor: (d) => d[index + qLayout.qHyperCube.qDimensionInfo.length].qText,
          defaultSortDesc: col.qSortIndicator === 'D',
          id: col.qFallbackTitle,
          qInterColumnIndex: index + qLayout.qHyperCube.qDimensionInfo.length,
          qPath: `/qHyperCubeDef/qMeasures/${index}`,
          qSortIndicator: col.qSortIndicator,
          qReverseSort: col.qReverseSort,
        })),
      ]
      : []
  ), [qLayout]);

I'm just looping through all of the qDimensionInfos and qMeasureInfos, grabbing the header name, and storing information such as the column header text in addition to the qSortIndicator and qReverseSort. I also set a property called defaultSortDesc which is true if qSortIndicator is 'D', and false otherwise. This property is what is used by the react-table library, which I used to build the table, to display whether the column header displays whether the sort is descending or ascending.

Next, I need a function to handle sorting change. The callback for clicking on a column header in react-table provides a newSorted prop, which includes the new direction of the sort, and a column prop, which includes the column data for the header clicked. I apply two patches when a user clicks on a header, one which sets the qReverseSort correctly if needed, and one that updates the qInterColumnSortOrder to move that columns qInterColumnIndex to the first position in the array. That looks like this.

const handleSortedChange = useCallback(async (newSorted, column) => {
    setLoading(true);
    await applyPatches([
      {
        qOp: 'replace',
        qPath: `${column.qPath}/qDef/qReverseSort`,
        qValue: JSON.stringify((newSorted[0].desc !== column.defaultSortDesc) !== !!column.qReverseSort),
      },
      {
        qOp: 'replace',
        qPath: '/qHyperCubeDef/qInterColumnSortOrder',
        qValue: JSON.stringify([...qLayout.qHyperCube.qEffectiveInterColumnSortOrder].sort((a, b) => (a === column.qInterColumnIndex ? -1 : b === column.qInterColumnIndex ? 1 : 0))), //eslint-disable-line
      },
    ]);
    setPage(0);
  }, [applyPatches, qLayout]);

 Now i can simply display the table using react-table like so.

<ReactTable
        manual
        data={qData ? qData.qMatrix : []}
        columns={columns}
        onSortedChange={handleSortedChange}
        multiSort={false}
      />

And that's it. You can check out the example at https://qdt-apps.qlik.com/qdt-components/react/#/table-engine and the full code for the table at  https://github.com/qlik-demo-team/qdt-components/blob/master/src/components/QdtTable/QdtTable.jsx