datatables.net-react 1.0.0 slots function does not consider all columns

datatables.net-react 1.0.0 slots function does not consider all columns

chocchoc Posts: 91Questions: 11Answers: 7
edited October 26 in Free community support

Description of problem: The use of columnDefs with the string _all - all columns (i.e. assign a default) doesn't work for datatables.net-react slots, in the source code, the check for string _all is missing.

It seems that the datatables.net-react repo: https://github.com/DataTables/React is not visible, so I'll post my fix in here.

in the source code:

function applySlots(cache: SlotCache, options: DTConfig, slots: DataTableSlots) {
    if (!options.columnDefs) {
        options.columnDefs = [];
    }

    Object.keys(slots).forEach((name) => {
        let slot = slots[name];

        if (!slot) {
            return;
        }

        // Simple column index
        if (name.match(/^\d+$/)) {
            // Note that unshift is used to make sure that this property is
            // applied in DataTables _after_ the end user's own options, if
            // they've provided any.
            options.columnDefs!.unshift({
                target: parseInt(name),
                render: slotRenderer(cache, slot)
            });
        }
        else {
            // Column name
            options.columnDefs!.unshift({
                target: name + ':name',
                render: slotRenderer(cache, slot)
            });
        }
    });
}

the fix:

function applySlots(cache: SlotCache, options: DTConfig, slots: DataTableSlots) {
    if (!options.columnDefs) {
        options.columnDefs = [];
    }

    Object.keys(slots).forEach((name) => {
        let slot = slots[name];

        if (!slot) {
            return;
        }

        // Apply to all columns
        if (name === '_all') {
            options.columnDefs!.unshift({
                targets: '_all',
                render: slotRenderer(cache, slot)
            });
        }
        // Simple column index
        else if (name.match(/^\d+$/)) {
            // Note that unshift is used to make sure that this property is
            // applied in DataTables _after_ the end user's own options, if
            // they've provided any.
            options.columnDefs!.unshift({
                target: parseInt(name),
                render: slotRenderer(cache, slot)
            });
        }
        else {
            // Column name
            options.columnDefs!.unshift({
                target: name + ':name',
                render: slotRenderer(cache, slot)
            });
        }
    });
}

with this fix, slots like below can work.

            slots={{
                _all: (data, type, row) => {
                    return "<div class='whitespace-normal max-w-60'>" + data || "-" + "</div>";
                }
            }}

which now applies to all columns

Hope it helps!

Edit: Oh! This is not perfect and the _all will overwrite other columns!

Replies

  • chocchoc Posts: 91Questions: 11Answers: 7

    Also another performance issue is about the render of React component using slots. It seems that the deferRender is not applied for the slots.
    If I have more than 30 rows (with a button (React component) in column 0, rendered with slots), I can see a slightly delay. And noticeable delay when over 80 rows. And my Chrome crashes when I have 20000 rows. But I do need to render those buttons for some interactions.

    Any advice on how to solve this performance issue? I have deferRender and pagination enabled. But it seems that the slots are making React render all the rows, causing a significant performance issue.

  • chocchoc Posts: 91Questions: 11Answers: 7

    https://stackblitz.com/edit/datatables-net-react-simple-pm4amf?file=src%2FApp.tsx
    This is the demo case using slots to render button.

    Try to feel the delay from 20, 200, 2000, and 20000 rows. it took me ~5 seconds to see the result for 2000 rows.

  • allanallan Posts: 63,368Questions: 1Answers: 10,449 Site admin

    It seems that the datatables.net-react repo: https://github.com/DataTables/React is not visible

    Doh - sorry about that. I had it private during the initial development of the component and then must have forgotten to update it. Don enow.

    Good point about the _all - thank you - I'll get that included.

    Performance-wise - try only to return JSX for the display type (second parameter to the rendering function). If you return JSX for all, you are forcing it to render that JSX and then extract the data needed. For type detection, sorting and filtering that normally is not needed and you can just return the underlying data.

    The display type will only be requested when DataTables needs to draw the cell on the page, so with pagination enabled, you should hopefully see a decent difference in performance.

    Allan

  • chocchoc Posts: 91Questions: 11Answers: 7

    Thank you Allan for pointing out that! I just tried to wrap the JSX for only display type.
    it will just run for only the first page indeed.

    But then, when I resize the page. It continues to render the rest on init! And then the whole page freezes for a moment.

    See the demo case below.
    https://stackblitz.com/edit/datatables-net-react-simple-e5jf2a?file=src%2FApp.tsx

    Open the dev tool first, and then reload the preview. See that only 10 rows are rendered and print display in console. Resize the page (or click the column to sort), see that all the rest rows are being rendered and print display in console.

    In my case since I have sidebar so I must call columns.adjust() right after init, which means the all JSX components are being rendered actually. And that causes the performance issue.

    Is it possible to stop rendering all rows when sorting or resizing? That is, when resizing, only the current rows on the first page are taken into account for the draw. And when sorting, sort the orthogonal data first and then render the JSX in the cells for the first page.

  • allanallan Posts: 63,368Questions: 1Answers: 10,449 Site admin

    Is it possible to stop rendering all rows when sorting or resizing?

    There isn't an option for that. What I think must be happening is that it is trying to find the longest string to optimise the column widths. I'll need to have a look into finding a way to either limit that to the current page, or some other option.

    Allan

  • chocchoc Posts: 91Questions: 11Answers: 7
    edited October 28

    That's it!
    Disable the autoWidth works! But then the columns are not automatically adjusted when the window size is changed.
    I use draw and order to adjust the column instead.

    See here:
    https://stackblitz.com/edit/datatables-net-react-simple-ijtxhg?file=src%2FApp.tsx

    I think this would be fine for me to disable autoWidth as I must call the columns.adjust() anyway later on.

    This one listens to the window resize event:
    https://stackblitz.com/edit/datatables-net-react-simple-trcv79?file=src%2FApp.tsx

    It would be nice if there were an option (or built-in default) for optimization, as you mentioned.

  • allanallan Posts: 63,368Questions: 1Answers: 10,449 Site admin

    Cunning! That hadn't crossed my mind, but good to hear you found a workaround for now. Yes, it would definitely be good to improve this aspect of the software.

    Allan

  • chocchoc Posts: 91Questions: 11Answers: 7

    Hi Allan, I just realized that the slots doesn't have meta argument.

    From https://datatables.net/reference/option/columns.render
    There is 4 arguments

    render( data, type, row, meta )

    But for the slots described in https://datatables.net/manual/react#React-Components:

    If a slot function expects two parameters they are:

    Cell data
    Row data
    DataTables will then automatically attempt to extract the ordering and search data from the React element that is returned.

    If a slot function expects three parameters they are:

    Cell data
    Data type (sort, type, filter, display)
    Row data

    I tried to get the row index while rendering but the meta doesn't exist for the slots.

  • allanallan Posts: 63,368Questions: 1Answers: 10,449 Site admin

    I wasn't sure about adding the meta property - the main use of it was to get the rendered cell node, but that is redundant in React. I can certainly add it in though. What's the use case?

    Allan

  • chocchoc Posts: 91Questions: 11Answers: 7

    For example I use Scroller plug-in and slots to render button in cells.

    When I click the button, I want to add its row index in the onClick event so that I can replace the classes of the button to change its color to indicate that it is selected. However, since it does not update itself when the state/class changes, I have to use cell().data() and cell().invalidate() to manually update the button when I click it I guess.

    And another use case would be restoring:
    since I create the table in a Dialog (Modal), I would open it again later and use row().scrollTo() to scroll the table to the selected button, so that the selected button is visible/findable when it opens. Especially if the table is unmounted, we can restore the state later using the row index.

    I'm not sure if I'm overthinking whether or not they need a row index to do that.

  • allanallan Posts: 63,368Questions: 1Answers: 10,449 Site admin

    Would:

    table.row( this.closest('tr') ).index()
    

    do the job for you? Where this is the button (modify as needed). I generally try to steer people away from the meta property. I don't have a good solid reason, but it just always felt like a bit of a workaround.

    Allan

  • chocchoc Posts: 91Questions: 11Answers: 7
    edited November 1

    Hi Allan, it works for the onClick event. Thank you!

    I use:

    onClick={(e) => {
        setSelectedIndex(table.current?.dt().row( e.target.closest('tr') ).index())
    }}
    

    But I'm a bit stuck again. How can I do to change the variant prop value of the Button based on the selectedIndex?

                const [selectedIndex, setSelectedIndex] = useState(null);
    
                slots={{
                    0: (data, type, row) => {
                        if (type === 'filter' || type === 'sort' || type === 'type') {
                            return data
                        }
                        if (type === 'display') {
    
                            return (
                                <Button variant="outline"
                                        onClick={(e) => {
                                            setSelectedIndex(table.current?.dt().row( e.target.closest('tr') ).index()) //  get the row index
                                        }}
                                >
                                    {data}
                                </Button>
                            )
                        }
                    }
                }}
    

    I want to change the variant value from outline to default (which will render to another button style) if the button's index euqals to the selected index

    I will then update it with useEffect:

    useEffect(() => {
        if (selectedIndex !== null) {
            console.log("invalidate cell")
            table.current?.dt().cell(selectedIndex, 0).invalidate().draw('full-hold');
        }
    }, [selectedIndex]);
    

    any workarounds without using the meta property?

    BTW, I noticed that the row().scrollTo() does not scroll exactly the row index position. I suspect it is due to the rendered button that causes the changing in the height that I mentioned in another discussion's comment in here. I will try to provide a test case if needed.

    In general, I guess it would be the scroller.rowHeight that is not fixed so the height is changing

  • chocchoc Posts: 91Questions: 11Answers: 7
    edited November 1

    Here's the test case about the Scroller row().scrollTo() with slots render issue:
    https://stackblitz.com/edit/datatables-net-react-simple-fvmt9f?file=src%2FApp.tsx

    It will have slightly shift after using row().scrollTo(). And even more shift when I don't provide a fixed height using:

    table.dataTable > tbody > tr {
        height: 58.81px;
    }
    
  • allanallan Posts: 63,368Questions: 1Answers: 10,449 Site admin

    any workarounds without using the meta property?

    I don't think so I'm afraid. Not in this case since it is happening inside the renderer rather than externally in an event handler. I'll see about getting that change in next week.

    Regarding the scrolling issue - you might need to call scroller.measure() when the table is displayed.

    I do have a plan to have resizing calculations happen automatically - probably DataTables 2.2 when that happens.

    Allan

  • chocchoc Posts: 91Questions: 11Answers: 7

    It seems that it cannot scroll to the last cell correctly.

    For the clicked cell in the middle, the scroll position seems to be different each time the table is opened, even if the scroller.measure() is used.

    Here's the test case: https://stackblitz.com/edit/datatables-net-react-simple-yq8xph?file=src%2FApp.tsx

  • allanallan Posts: 63,368Questions: 1Answers: 10,449 Site admin

    Thank you - I'll take a look at it when I'm back in the office.

    Allan

Sign In or Register to comment.