deferRender compatibility with columns.render?

deferRender compatibility with columns.render?

Loren MaxwellLoren Maxwell Posts: 439Questions: 103Answers: 10
edited April 16 in DataTables 2

My understanding is that deferRender holds off on rendering rows until they are displayed in the table and that it is default to true in DataTables 2.0.


I have the following table with 4,237 rows:

var dt_coaches_index = $('#dt-coaches-index').DataTable({

    ... blah blah blah ...

    columns: [
        {
            title: 'Coach',
            data: 'person_link',
            render: function ( data, type, row, meta ) {
                return hbs('hbs-site-link', data)  // Handlebars template
            }
        }
    ]
})

This takes anywhere from between 10 and 12 seconds, which seemed excessive so I started to troubleshoot my query, but it only takes about 0.15 seconds to generate and seems to be optimized in itself.

Then I tried without the Handlebars template:

        {
            title: 'Coach',
            data: 'person_link'
        }

And the table renders quickly as expected.

So it seemed to be an issue with my Handlebars template or when using columns.render.

I added a troubleshooting line to write to the console whenever the row is rendered:

var dt_coaches_index = $('#dt-coaches-index').DataTable({

    ... blah blah blah ...

    columns: [
        {
            title: 'Coach',
            data: 'person_link',
            render: function ( data, type, row, meta ) {
                console.log('row render called')  // Troubleshooting
            }
        }
    ]
})

And was surprised to see that the render is called three times for each cell even though deferRender is set to true:

Am I misunderstanding deferRender? Or it's compatibility with columns.render?

Separately, when I do this workaround using the createdRow callback to achieve what I thought deferRender was meant to do, it's probably less than a half second total for 25 rows, which I would expect, so I don't think it's the Handlebars template:

var dt_coaches_index = $('#dt-coaches-index').DataTable({

    ... blah blah blah ...

    columns: [
        {
            title: 'Coach',
            data: 'person_link',
            className: 'person-link'  // 'person-link'
        }
    ],
    createdRow: function (row, data, dataIndex, cells) {
        
        var cellElement = $('.person-link', row)  // Find 'person-link'
        
        if (cellElement.length) {
            cellElement.html(hbs('hbs-site-link', data.person_link))  // Update
        }
    }
})

This question has an accepted answers - jump to answer

Answers

  • kthorngrenkthorngren Posts: 21,913Questions: 26Answers: 5,063
    Answer ✓

    What you see is expected behavior. You are seeing columns.render called for the different orthogonal data types. Update the console log statement like this to see the types:

    console.log('row render called', type) 
    

    The output with 25 is the display operation. See if this helps the timing:

                render: function ( data, type, row, meta ) {
                    if ( type === 'display' ) {
                      return hbs('hbs-site-link', data)  // Handlebars template
                    }
                    return data;
                }
    

    This will call the handlebars template only when the cell data is displayed. This will have the side affect of not having the handlebars data as part of sorting and searching.

    Kevin

  • Loren MaxwellLoren Maxwell Posts: 439Questions: 103Answers: 10

    Ahhh -- I knew I had to be overlooking something!

    Many thanks for the explanation, @kthorngren!

  • Loren MaxwellLoren Maxwell Posts: 439Questions: 103Answers: 10

    I'm adding this small note for anyone interested --

    I've updated my render as per @kthorngren's post and while it has significantly reduced the time from 10 to 12 seconds to around a second or so, it is still not nearly as quick as the createdRow workaround, which is nearly instantaneous.

    There may be other issues with that approach, but I thought noting the speed difference might still be helpful.

  • allanallan Posts: 64,301Questions: 1Answers: 10,618 Site admin

    Yeah, there are trade-offs to the two approaches. The primary disadvantage of createdRow is that DataTables won't "see" the changes that you make to the display data. So if your end user were to search on that data, it wouldn't match the modified rendering - it would still be searching the original data. The same with ordering.

    That might or might not be an issue.

    If it isn't then you could consider using render and checking for type === 'display' and only perform the handlebars templating under that condition.

    If it is, the another other with render would be to cache the result. Use the row and cell index from the meta object to save the result into an object. Then have the rendering function check if there is already a cached result, and if so, use it, if not, create and cache it.

    Of course, if the data changes, then cache brings its own complications (two problems in computer science...), but that would be one of the most optimal approaches while allowing the data to be searched and sorted.

    Allan

  • Loren MaxwellLoren Maxwell Posts: 439Questions: 103Answers: 10

    Just wanted to show my solution for anyone interested (and willing to critique) . . .

    My site is full of tables with links using a custom Handlebars 'site-link' template:

        hbs('hbs-site-link', data)  // Handlebars template
    

    For those columns I set the columns.type to a custom type, site-link:

        {
            title: 'Coach',
            data: 'person_link',
            type: 'site-link'
        },
    

    Then using a global DataTables draw I find those columns and update those cells:

    // Update site links
    $(document).on('draw.dt', function ( e, settings ) {
    
        const api = new $.fn.dataTable.Api( settings )
        
        // Find all visible columns where type === 'site-link'
        const siteLinkColumns = api.columns(':visible').indexes().filter(colIdx =>
            api.column(colIdx).type() === 'site-link'
        )
    
        // For each site-link column
        siteLinkColumns.each(colIdx => {
            
            // Get all cell nodes for the current page
            const nodes = api.column(colIdx, { page: 'current' }).nodes();
    
            // And update those cell nodes
            nodes.each(function (cellNode, rowIdx) {
                const cellData = api.cell(cellNode).data()
                cellNode.innerHTML = hbs('hbs-site-link', cellData);
            })
        })
        
    })
    

    If you needed the row data for some reason you could use do this:

        // For each site-link column
        siteLinkColumns.each(colIdx => {
                    
            // Loop through each row on the current page
            api.rows({ page: 'current' }).every(function(rowIdx) {
                        
                const rowData = this.data()
                    
                // Get the cell nodes
                const cell = api.cell(rowIdx, colIdx)
                const cellData = cell.data()
                const cellNode = cell.node()
                        
                // And update those cell nodes with the cell and the row data
                cellNode.innerHTML = hbs('hbs-site-link', cellData, rowData)
                        
            })
        })
    

    This seems to work well for me because of:
    1) the (much) increased speed
    2) the cleaner code (only have to specify the column is columns.type as site-link and only have to maintain the one global draw for all tables on the entire site
    3) avoiding a potential conflict with createdRow when overriding DataTables defaults

    That being said -- I'm not worried about the potential filtering or ordering issues that @allan mentioned in the post above, so I think it would work for most folks but might not for all.

    There's also a risk that @allan could introduce a future columns.type of site-link that would cause a conflict, but I'm thinking that's unlikely :smile:

  • allanallan Posts: 64,301Questions: 1Answers: 10,618 Site admin

    Yup, very unlikely I'd say :).

    Many thanks for the write up - it looks like a good solution (taking account of the issues you mention).

    I do like handlebars - its nice to see this integration :)

    Allan

  • Loren MaxwellLoren Maxwell Posts: 439Questions: 103Answers: 10

    Yeah, Handlebars has been almost as impactful as DataTables and Editor for me.

    It has greatly simplified things and made my site much more consistent looking.

    I can't see doing anything without it (or some templating library) in the future.

Sign In or Register to comment.