No context to columns.render function?

No context to columns.render function?

arobthearabarobthearab Posts: 6Questions: 1Answers: 0

Specifying a column rendering function, the function receives expected parameters ( cell data, type, row data, metadata ) but "this" is undefined. Is there some reason this is not or should not be the API for the affected table?

This question has an accepted answers - jump to answer

Answers

  • allanallan Posts: 63,455Questions: 1Answers: 10,465 Site admin

    A couple of reasons combined which mean that it isn't yet:

    1. Legacy - the rendering function existed before the new API did
    2. Performance - creating a new API instance for each cell (presumably it should be in the context for the cell being renderer) would be very expensive
    3. Partial data - The table is only partly initialised when the rendering function is called initially, which could lead to confusion
    4. Dangerous - doing something like table.cell().data(...) in the renderer would lead to an infinite loop.

    I'm open to the idea, I want to expose the API as much as possible, but I'm not sure I see enough benefits to do so here. What is the use case?

    Allan

  • arobthearabarobthearab Posts: 6Questions: 1Answers: 0

    It's probably naivete on my part, but I attach my own object to the table element, a la:

    $(selector).data("table", tableObject)

    Note this is NOT a DataTable object, it is my own class Table. Based on information in my tableObject, my rendering function generates different HTML to go into the table in response to a "display" render type.

    The issue is finding my Table object when the rendering function is called. One way to do this would be

    $($(this).table().node()).data("table")

    But because this is not defined, I can't do that. The way I am doing it now is:

    $(metaData.settings.nTable).data("table")

    But this is an ugly, internals-specific kludge.

    And note this object must persist across JS scripts that do not share the same context--so a global variable, which would have to be known to the internals of my code, would be even more of a kludge.

    As a neophyte, I welcome a solution that makes me look like a fool for not seeing it myself.

  • allanallan Posts: 63,455Questions: 1Answers: 10,465 Site admin

    Do you need to access the full tableObject in the rendering function or could you just assign a function to the column as needed. Can you show me an example of your rendering function - it sounds like it might be doing a lot of work which would hurt performance (a rendering function should ideally be like an event handler - get in and out as quickly as possible).

    Allan

  • arobthearabarobthearab Posts: 6Questions: 1Answers: 0
    edited January 2018

    It's definitely hurting performance. Loading a ~350 x 20 column table takes 8-10 seconds (about 7,100 calls to the rendering function, or around 1.125-1.408ms per call).

    This is the rendering class code. The handlers include things that return input fields, selects, etc. (essentially, I may be reinvented the Editor wheel). I make no claims to be idiomatic in JS, so if there are obvious performance-killers here I don't know what they are:

    class ColumnHandler
    {
        constructor ( initializer )
        {
            var permitted = 
            {
                save : true ,
                change : true ,
                htmlContent : true ,
                rawValue : true ,
                sortValue : true ,
                filterValue : true
            };
            
            this.save = null;
            this.change = null;
            this.htmlContent = this.generic;
            this.rawValue = this.generic;
            this.sortValue = this.generic;
            this.filterValue = this.generic;
            
            if (initializer.render instanceof Function)
            {
                this.htmlContent = initializer.render;
                this.rawValue = initializer.render;
                this.sortValue = initializer.render;
                this.filterValue = initializer.render ;
            }
            
            for (var property in initializer)
            {
                if (property in permitted)
                {
                    this[property] = initializer[property];
                }
            }
        }
        
        //---------------------------------------------------------------------
        // Default rendering action
        //---------------------------------------------------------------------
        static renderHtml ( tag , metaData , classes, attributes , value , postProcessor )
        {
            var columnNumber, element, markup;
            
            columnNumber = metaData.col;
            attributes["data-column"] = columnNumber;
            attributes["class"] = classes.concat("column" + columnNumber).join(" ");
            element = $(tag, attributes);
            
            if (postProcessor instanceof Function)
            {
                element = postProcessor.call(element);
            }
            
            markup = element[0].outerHTML;
        
            return markup;
        }
        
        //---------------------------------------------------------------------
        // Default rendering action
        //---------------------------------------------------------------------
        generic ( cellData , type , rowData , metaData , table , dataTable )
        {
            var element = $(table.dataTable.cell(metaData.row, metaData.col).node());
            var answer;
            
            if (element.is("input, textarea"))
            {
                answer = element.val();
            }
            else
            {
                answer = cellData;
            }
            
            return answer;
        }
        
        //---------------------------------------------------------------------
        // DataTable "columns.render" action -- "this" is undefined
        //---------------------------------------------------------------------
        render ( cellData , type , rowData , metaData )
        {
            // KLUDGE because there appears to be no "this" context
            var table = this ? this.table.node().data("table") : $(metaData.settings.nTable).data("table");
            var self = table.columnMap.get(metaData.col).columnType.render;
            var action = null;
            var value = cellData;
            
            switch (type)
            {
                case "filter":
                    action = "filterValue";
                    break;
                case "sort":
                case "type":
                    action = "sortValue";
                    break;
                case "display":
                    action = "htmlContent";
                    break;
                default:
                    type = (type !== null) ? type : "raw";
                    action = "rawValue";
            }
                            
            if (self[action] instanceof Function) 
            {
                value = self[action].call(self, cellData, type, rowData, metaData, table, table.dataTable);
            }
            
            return value;
        }
    }
    
  • allanallan Posts: 63,455Questions: 1Answers: 10,465 Site admin

    The performance killer here is that you are working out what rendering function to use every time render is called. That seems redundant unless you are expecting it to change for cells during the life time of the server? My suggestion would be to build the columns array with columns.render function already determined. Then it wouldn't need to either get the data from the main table node, or use the columnMap to get the rendering type - both of which will take time.

    Allan

  • arobthearabarobthearab Posts: 6Questions: 1Answers: 0
    edited January 2018

    Thank you Allan, that is above and beyond on your part, and something I started working on last night for the same reasons. The plan was not so much that the column renderers would change for any given application, but that this is a generic interface for many applications which might change the rendering--but even that can be dealt with beforehand and not at every render call.

    But that leaves open the issue of "this" providing context in a rendering function, which is still valuable regardless of how I implement this. The <table> element already exists, and presumably so do the <tr> and <td> by the time the render function is called. Other DataTables calls do provide this context, and this is not the first time I've run into this issue to much head scratching in other more mundane contexts.

    Two suggestions:

    1. In DataTables documentation make clear the "this" context, or lack thereof, for every callback or function.

    2a. Consistently set "this" to the DataTables object, whatever state it is in, and the buyer beware calling things like .data().

    2b. Provide a means to test the state of the DataTables object (this is probably uglier than how I breezily present it),

    2c. Allow the user to store a small amount of arbitrary data in the DataTables object, such as a userContext option/API call that could return arbitrary data the user wants, and that DataTables doesn't use or care about (this is common in my other guise as an IBM mainframe/midrange developer). I understand there are many ways to do this in modern languages, but most of them are butt-ugly to my (admittedly esoteric) sensibilities.

    Thank you again.

  • allanallan Posts: 63,455Questions: 1Answers: 10,465 Site admin
    Answer ✓

    Thanks for the feedback - all sensible suggestions. What I want to do with v2 is to make the API the context of every callback - in reality I don't know if I will do that because it will break backwards compatibility (for all that it is a v2 release).

    Making the render function have the scope of the table element similar to other callbacks is a good fallback, although it could also be argued that it should be the cell in question (although we run into issues if that doesn't exist yet).

    The documentation absolutely does need to state the context of the callbacks - good point.

    Allan

  • arobthearabarobthearab Posts: 6Questions: 1Answers: 0

    Excellent support, above and beyond the normal and reasonable call of duty in this case. Will make me strive harder to obtain the pittance necessary to license the editor so I don't have to develop it from scratch.

This discussion has been closed.