Wait until function is complete while Table is generating

Wait until function is complete while Table is generating

xKushGenexKushGene Posts: 2Questions: 2Answers: 0
edited December 2017 in Free community support

I made a Table with Ajax and setting the columns like this:

var table = $('#customerTable').DataTable({
        "ajax": {
            "url": "api.php?getUsers&type=customers",
            "type": "GET",
            "dataSrc": ""
        },
        "columns": [
            { "data" : "id" },  // Get User ID
            { "data" : "email" },   // Get User Email
            { 
                // Convert Usergroup
                "data" : "usergroup",
                render: function(data){
                    ConvertUserGroup(data).done(function(){
                    });
                    
                }
            },  

In my DataBase, usergroup is the id of a group which is in another Mysql table (for example -> 1, 2 or 3).

So I want to convert these numbers into a Bootstrap Badge, thats why i do this line:

ConvertUserGroup(data).done(function(){
});

And the function contains this:

// Convert Account Status
function ConvertUserGroup(usergroupID) {
    $.ajax({
        url: "api.php?getGroups&single="+usergroupID,
        type: "GET",
        dataType: "json",
        success: function(data){
            var returnString = '<span class="badge badge-'+data.groupcolor+'">'+data.groupname+'</span>';
            console.log(returnString);
            return returnString;
        }
    });
}

But this isn't working because i can't use two Ajax methods at once. I can't find a callback for render.

So my Question is, how can i tell to ajax in DataTables (while the columns are creating) that it has to wait until ConvertUserGroup() is finished?

«1

Answers

  • jvretamerojvretamero Posts: 26Questions: 0Answers: 3

    I think its not possible, because the columns.render does not have a callback parameter and it does not accept a Promise as return value.

    I would recomend you to provide both groupcolor and groupname at the api.php?getUsers&type=customers route and then use like this:

    {
        defaultContent: '',
        render: function(data, type, rowData) {
            if (type == 'display') {
                return '<span class="badge badge-'+rowData.groupcolor+'">'+rowData.groupname+'</span>';
            }
        }
    }
    
  • allanallan Posts: 61,667Questions: 1Answers: 10,096 Site admin

    Correct - the columns.render function is executed multiple times for every cell, so just like an event handler, you want it to execute as fast as possible. Using an async function there would be extremely bad news for performance.

    If you can include the data in the API JSON, then absolutely that is the way to do it.

    If you can't use cell().data() after the DataTable has initialised (initComplete) to update the data for the cells in the table you need to get async data for.

    Allan

  • Bain McKayBain McKay Posts: 48Questions: 1Answers: 1

    Suggestion: Small [edit] that makes the response above a little easier to follow.

    ...

    If you can [,] include the data in the API JSON[. T]hen absolutely that is the way to do it.

    If you can't[,] use cell().data() after the DataTable has initialised (initComplete) to update the data for the cells in the table you need to get async data for.

    Allan

  • Bain McKayBain McKay Posts: 48Questions: 1Answers: 1

    Datatables generates an empty nested DataTable from an Ajax which returns and packages the data

    Details

    I have a problem with DataTables getting the Javascript Object data to draw but not drawing it, when nested 3-deep in recursive Row-Detailed expansion in Firefox quanta - developer version.

    The top level DataTable has three columns, each having a click event which displays a different table view upon detail expansion. Only one expansion column is active at a time.

    When you click a column row in the top table view, one expansion field, called App Sources, generates a table that lists the source collections (one per row) mapped to the App row in the top table view. This works well. But the next drill down table view, which fetches and packages it's table fulfillment data in an Ajax call, does not.

    Third-level drill down:
    Click on a source collection row, and it expands to generate a detailed table view listing the files in the collection. Thus, when clicked, the Table structure is generated (via a format function) and an Ajax call is issued (via a loadData function) to the server to fetch the list of files and their properties. The Ajax call uses the Promise function to ensure the data is returned and processed (deserialized from Json to a Javascript Object Array) before it is passed back to the Datatables API to map the array to the table to fill the rows and columns. Using Firefox debug, if I stop at a breakpoint on the return statement of the Ajax call, and then continue, the DataTable Api generates the table and fills in the table rows and columns as designed. if I do not place a breakpoint at the return statement from the Ajax call, but instead, place the breakpoint in the DataTables Api call where its mapping the data for table generation, the table generates is empty - no rows and columns, and no exceptions. The same holds true if I do not use debug (eg. no breakpoints are used to pause processing).

    Should not the Ajax Promise call be sufficient, since it's generating the JavaScript object array that's being passed back to DataTables API for table generation? I do not see a DataTables feature (like Promise) to wait until the table is generated to display the contents.

    What am I missing?

    Bain

  • kthorngrenkthorngren Posts: 20,276Questions: 26Answers: 4,765

    I just tried a quick example using a Promises AJAX call and was not able to make it work. However the technique described in this blog does work:
    https://datatables.net/blog/2017-03-31

    Allan can answer whether he expects the row().child.show() to work with Promises.

    Kevin

  • Bain McKayBain McKay Posts: 48Questions: 1Answers: 1

    Thank you for your suggestion. I've built lots of tables. This one is different. It's a drill down detail table 3 levels down.

    I already have the fully-formed Javascript Object Array from an Ajax call (with Promise) made prior to the table build call, There are no nulls, it has the right number of columns, and the table was properly defined with <tbody> - no syntax errors.

    The Table HTML was added to the the clicked parent row-cell prior to the Ajax data call, using a format function, providing a table structure for the row-cell mapping, and .show() was chained to call.

    I make the Ajax call prior to the Table Build call, so there is nothing to wait for, other than building the table, which it is doing so nested 3 layers deep. This could be manifesting a timing issue.

    No Debug => Empty Table: If I do not step through the table build call with debugger, I get zero entries in the table. From that I assume it's empty - not hidden. Nothing binded.

    Debug => Fully Formed Table: But if I step trough the table build call with the debugger, the table is fully populated. So it does appear to be a timing issue. It could be DataTables, or could even be FireFox. As you noted, DataTables does not appear to have a Promise function. Wrapping the call with $(document).ready... doesn't help either. Ive run out of things to try.

    I have a sense that it's something simple I am getting wrong. But alas, I have run out of things to try.

  • kthorngrenkthorngren Posts: 20,276Questions: 26Answers: 4,765

    I figured out my issue with the Promises ajax call in my original child details test case. Its a simple case which is based on the blog note I pointed you to. If interested you can see it here:
    http://live.datatables.net/sutizodo/1/edit

    I think I follow what you are doing but not sure. I created a nested parent/child/child table based on my understanding of your description. It is working but I think I am handling the Promises ajax calls differently. It sounds like you are calling ajax then initializing Datatables. In my case I'm using the Datatables ajax option as a function to make the Promises ajax call. Here is the example:
    http://live.datatables.net/tabohayu/1/edit

    One thing to note is that I use the same function for the parent table load and the children (or is it childs :-). The ajax call retrieves all the data so, if its a child call, I loop through the data to grab the correct row. I don't have control over the server data.

    Maybe this will help. If not it might be best if you can provide a test case showing what you are doing.

    Kevin

  • allanallan Posts: 61,667Questions: 1Answers: 10,096 Site admin

    Allan can answer whether he expects the row().child.show() to work with Promises.

    Not directly. You'd need to add the Promise code around it. In fact, no part of DataTables core will explicitly check for a Promise. However, it is possible to use promises with DataTables by invoking suitable callbacks when needed - just as Kevin has done in the examples above.

    Beyond that, I think it would be useful to be able to see the page in question.

    Regards,
    Allan

  • Bain McKayBain McKay Posts: 48Questions: 1Answers: 1

    I think what you are saying, Kevin, is that you submitted the AJAX call within the DataTables definition API call envelope. I did the AJAX call prior to calling the DataTables setup, outside it. I called it with Promise, creating the JavaScript object in advance, and then injected that object into the Datatables call mapped to Data.

    Alan, if I call the AJAX with Promise and package the Javascript object ahead of time, but outside the Datatables call envelope, would not the subsequent Datatables API call wait until it receives all the input? I get that once it receives the input, an asynchronous call continues unless there is an asynch task wait/Promise. It sounds like it's in a thread-race condition at the input level?

  • kthorngrenkthorngren Posts: 20,276Questions: 26Answers: 4,765

    called it with Promise, creating the JavaScript object in advance, and then injected that object into the Datatables call mapped to Data.

    Guessing this means you are using data. Instead of that maybe you can use rows.add() in your callback to add the data to the table.

    Again its hard to say without seeing what you are doing. Any chance you can provide a working example?

    Kevin

  • Bain McKayBain McKay Posts: 48Questions: 1Answers: 1
    edited May 2018

    I moved the Ajax call inside the Datatables API call, and it works now, as far as raising the event, and processing continues. Why would this differ.. Before, I was passing an already-prepared result (a javascript object) and mapping it to a Datatables parm, it would be doing the same results processing.

    However, upon submission of the Ajax call, the DataTable call throws an exception before the parameters hits the server "DataTables warning: table id=sourcecollectionfilelist_1 - Invalid JSON response. For more information about this error, please see http://datatables.net/tn/1".
    * The return Json is well-formed. If I click down the error, processing continues to the server, and the server returns proper json results. The same Ajax call, when submitted before the Datatales call (ref previous response on this thrread) had no problems with the Json result turned.
    *** I checked the exception, and it says the Json is mal-formed. That Root cannot start with an object, It has to be an array. The Json returned is a List<string> using JsonConvert, and it's perfectly fine. I do a ton of json, so know it well. And the json works in a Json online editor.

    What could be causing this issue?

    For a 3-level drill down, the code first two layers are at root in the method, but the code for the 3rd layer is nested in the 2nd level drill-down code.

    Here's the Ajax code shown after I moved the Ajax call into the Datatables API

    ....

       sourceListTable = $('#sourcecollectionfilelist_' + sourceCollectionFilesCounter).DataTable({
                                        /* 
                                         * May 21 2018 dbm;: Replace results of Ajax call outside DataTable API call, with direct Ajax call inside using the DataTable Ajax parm. 
                                         *  The processing is now submitted to the server and results are now returned. Hower, an exception event is thown client-site complaint about well-formed
                                           // data: _sourcecollectionfilelist,//map JavaObject of table transform insertion of row-column data to mapped columns (FilePath,LastUpdated,Size)
                                        */
                                        ajax: 
                                            //get list of files for the source collection filepath
                                            $.ajax({
                                                type: "GET",
                                                url: signalRHubURLForImanageService + "/api/rest/sourcecollectionfilelist", //get destination setup data from server
                                                crossDomain: true,
                                                data: {
                                                    user: $window.user,
                                                    server: $window.server,
                                                    appname: rowData.appName,
                                                    collectionpath: physicalSourcePath
                                                }
                                            }).then(function (results) {
                                                var _results = JSON.parse(results.replace(/\\/g, "^"));
                                                for (var i = 0; i < _results.length; i++) {
                                                    var parts = _results[i].split('|');
                                                    var appName = parts[0].split(":")[1];
                                                    var filePath = parts[1].split(":")[1];
                                                    var lastUpdated = parts[2].split(":")[1];
                                                    var size = parts[3].split(':')[1];
                                                    var collectionFolder = {
                                                        "AppName": appName,
                                                        "FilePath": filePath == null ? 'na' : filePath.replace(/\^/g, "\\"),
                                                        "LastUpdated": lastUpdated == null ? 'na' : lastUpdated,
                                                        "Size": size == null ? 'na' : size,
                                                        "Exclude": false
                                                    }
                                                    fileListResults.push(collectionFolder);
                                                }
                                            }).fail(function (xhr) {
                                                console.log('error:' + xhr.statusText);
                                            })
                                        ,
                                        paging: false,
                                        searching: false,
                                        columns: [
                                            { data: "AppName" },
                                            { data: "FilePath" },
                                            { data: "LastUpdated" },
                                            { data: "Size" },
                                            {
                                                data: "Exclude",
                                                render: function (data, type, row) {
                                                    if (type === 'display') {
                                                        return '<input type="checkbox" class="editor-exclude">';
                                                    }
                                                    return data;
                                                },
                                                className: "dt-body-center"
                                            }
                                        ],
                                        order: [1, 'asc'],
                                        select: {
                                            style: 'os',
                                            selector: 'td:not(:last-child)' // no row selection on last column
                                        },
                                        rowCallback: function (row, data) {
                                            // Set the checked state of the checkbox in the table
                                            $('input.editor-excluse', row).prop('checked', data.active == 1);
                                        }
                                    });
    
                                    $('#sourcecollectionfilelist_' + sourceCollectionFilesCounter).on('change', 'input.editor-exclude', function () {
                                        editor
                                            .edit($(this).closest('tr'), false)
                                            .set('active', $(this).prop('checked') ? 1 : 0)
                                            .submit();
                                    });
                                }
                                sourceCollectionFilesCounter += 1; // you neend to bump the Table ID for each  parent Row, so it's a unique drill-down addressable DOM = insertion point
    
                            });
    

    EDIT: Formatted code using Markdown

  • Bain McKayBain McKay Posts: 48Questions: 1Answers: 1

    I should also add that a new behaviour has surfaced. Where level 2 table opened the first time before, and level 3 table opened the first time if I walked through it slowly in FireFox debug, now, level 2 table has to be opened twice before the rows are populated and the ADD button is inserted. It feels like there is still an event conflict going on.

  • kthorngrenkthorngren Posts: 20,276Questions: 26Answers: 4,765

    And the json works in a Json online editor.
    What could be causing this issue?

    Can you post the JSON response you received using the browser's dev tools?

    Any chance you can build a test case or post a link to your page?

    Please use Markdown to format your code.

    Kevin

  • Bain McKayBain McKay Posts: 48Questions: 1Answers: 1

    I'll need to re-read your documentation. Let me get this off to you and I'll read your site documentation to figure out how to use JSBin with your site as well.
    ...

    First, this only happens in the nested table. Else the call pattern works. In other places when I call ajax to populate the Datatables ajax parm, I pass back Json of a string from the SignalR Hub. All is well. On my test below, i am using a List<string>. I cast it to Array[], but still the same issue. And I cast it to string using string.Join(delimiter, array) and then serialized to JSon, That didn't work either.

    The Json is good, as long as it Does Not go through the [Ajax:] parm on the Datatables API call.

    I just ran some tests. If I put it through the [data:] parm, it works, but it gets into a thread race condition, which is why it's erratic and requires me to pace it with breakpoints. And the breakpoints have to be set at the front of the process - where Ajax is called. It will display the collection files list as intended. Note, in all cases, the Ajax call using a Promise return.

    But if I put it through the [ajax:] parm, it throws exceptions and it's impossible to get through it. irrespective of the Promise Call, consists with the [data:] parm, it's does not wait. That's why pacing it with break points, if you set them in advance of the call, you can manually hold it back until the server returns with the results., and the files populate the 3rd label drill down table.

    On the server-side call, use Http.Results.JsonResult(json data). This call packages an MCV jSon return, with Result.Content containing the Json.

    So at this stage, in the nested tables, it throws exception and DataTables does not wait for results to return from the server, irrespective of the Promise call.

    Also, I now need to open the 2nd level datable twice to populate the table and insert the add button.

    Another behaviour I picked up, If I wrap the code with $(document).ready(function () {..}, none of the events work.

    So with that, I think it's time to simplify to isolate, so that I can post to JSBin.

    If you can think of anything to try, in the mean time, please let me know

  • Bain McKayBain McKay Posts: 48Questions: 1Answers: 1

    Not quite sure how to do this. First time for JSBin.

    Here's the share url: http://live.datatables.net/jaminixe/1/edit?html,css,js,console,output

    I didn't complete it. Other views, like config, transactions, at al, are not useful in solving issue. So I provided Sources column drill-down to level 3.

    To make it run, I need to populate Ajax files to emulate server-side data, but I don't see where to add them. Hopefully, I have provided enough that you can see my error. In either case, knowing how to add custom Ajax files would be helpful for future issues isolation and resolution.

    My intent is to go n levels of arbitrary depth, of n columns of arbitrary width - each column a different view.

  • kthorngrenkthorngren Posts: 20,276Questions: 26Answers: 4,765

    Here is a tech note on using the Datatables JS Bin:
    https://datatables.net/manual/tech-notes/9

    There are some ajax based examples to start from. If you look at the example I provided you can see how I simulate getting one row of data even though all 57 are returned from the ajax call:
    http://live.datatables.net/tabohayu/1/edit

    Kevin

  • Bain McKayBain McKay Posts: 48Questions: 1Answers: 1

    I has read most the first link (had missed the embedded link) and just read the second link. But I am still not making the connection - how to assign the data to the filenames. Are they assumed by the data type if I create data objects in the Javascript?

  • Bain McKayBain McKay Posts: 48Questions: 1Answers: 1

    In reviewing the code in the JDBin, is there anything that stands out which I have done wrong such that Datatables might exhibit the behaviour I am experiencing?

  • kthorngrenkthorngren Posts: 20,276Questions: 26Answers: 4,765
    edited May 2018

    I looked at your example. I see some errors in the browser's console. A couple issues stand out:

    1. The JS and CSS includes are not in the correct order. For example datatable.js needs to be loaded before Editor and Buttons.
    2. You are loading jquery twice which will cause problems
    3. I don't see the main Datatable init code - the #application table
    4. You are using the variable signalRHubURLForImanageService as the prefix to you ajax urls but I don't see where you are assigning it a value.

    Is your ajax url accessible from the internet?

    For the "level 2" table it looks like you are doing this:

    var _appsourcelist = loadAppSourceListData(row.data());
    ....
    //LEVEL 2
    var termTable = $('#appsourcelist_' + appnameAsId).DataTable({
       dom: "Bfrtip",
       data: _appsourcelist,  //map the JavaObject to the table rows (sourcepath, numberfiles, and avgsize) in a matrix transform operation provided by the DataTables API
       paging: false,
    ....
    

    But for the "level 3" table you are doing this:

                                    sourceListTable = $('#sourcecollectionfilelist_' + sourceCollectionFilesCounter).DataTable({
                                        ajax: 
                                            //get list of files for the source collection filepath
                                            $.ajax({
                                                type: "GET",
                                                url: signalRHubURLForImanageService + "/api/rest/sourcecollectionfilelist", //get destination setup data from server
    

    You are loading the data into the tables differently which might explain the behavior differences.

    My recommendation is to fix the above issues, remove items you don't need for the test case like the Editor code. It would be best to just have the code needed to replicate the issue. If your ajax url is not accessible then you may need to adjust your code to use the ajax data provided by jsbin.

    Kevin

  • kthorngrenkthorngren Posts: 20,276Questions: 26Answers: 4,765
    edited May 2018

    I had a bit of time so I re-worked my example to look a bit more like yours.

    In your example you have your ajax like this:

    sourceListTable = $('#sourcecollectionfilelist_' + sourceCollectionFilesCounter).DataTable({
        ajax:
            //get list of files for the source collection filepath
            $.ajax({
                type: "GET",
     .....
    

    It wouldn't work for me like that so I put the ajax call inside a function which I know is supported by the ajax option. There are some examples in the doc plus I used it in my previous example. I have something more like this:

                ajax: function (data, callback, settings) {
                  $.ajax({
                    url: "/ajax/objects.txt",
                  }).then ( function(json) {
                ....
    

    I use the ajax function option for the main table, 2nd level and 3rd level tables. All works as expected. For your 3rd level try using the function option.

    Example:
    http://live.datatables.net/nokeduka/1/edit

    Kevin

  • Bain McKayBain McKay Posts: 48Questions: 1Answers: 1

    HI Kevin,

    Thank you for your work and support on this.

    I was cutting and piecing code, so not surprised I had out-of-order issues and duplicates. I got stuck on how to populate the ajax files, which you answered above, so was waiting for a response before I continued. But your code-rework to reflect what I have that is not working, is the enlightenment for me.

    Two items in your response were very helpful
    1. If I don't have direct internet access I can map on to JSBin, which I don't, then I need to rework my sample to use the ajax files you provided. That wasn't clear. It is now. Thank you for that.
    2. The adjustment to the Ajax call, per your example (wrap a function around it), made a different on my code here. I made the adjustment and it's now making the call to the server, which is returning the JSon. That seems to have been the issue on which I was getting stuck here.
    3. I still have the issue where I need to click level 2 twice to populate the table.
    4. The return from the Ajax call is skipping the promise, so I'll need to work through that.

    Thank you for all our help on this. I'll keep you up to date. I may need another round or two to complete this feature.

    Bain

  • Bain McKayBain McKay Posts: 48Questions: 1Answers: 1

    GOT IT!!!

    Here was the missing piece

    callback({ data: fileListResults });
    

    when the ajax call was wrapped in

    function (data, callback, settings) {

    }

    fileListResults is the array built from the JSon result from the SignalHub.

    I have one more item to work out. Somewhere only the journey, level 2 needed to be open twice for the table to populate.

    As you noted above, I already have the array - it's fed into the Anglar module form the identity server. Once I have that, i should have what I need to complete this feature.

  • Bain McKayBain McKay Posts: 48Questions: 1Answers: 1

    Hi Kevin,

    Level 2 table continues to need to be opened twice for the columns to populate. It's fed by a pre-formed complex object - not an Ajax call.. If I look at what solved the details display of level 3, Callback({data: fileListResults }), it assigns the value of fileListResults to data, where a Return does not work.

    If I look at level 2 as being at that stage in the table draw life cycle which assigns the value to data, then a table draw will not help - it's already at that stage. To confirm, it tired it. Assuming, of course, that I did the drawCallaback/callback right.

    So here is the issue as I understand it. I have a complex object that is assigned to the data parm, and when Datables draws the table, it does not insert the rows from that data until after the table is drawn. I tried to redraw the table with drawCallback, but it had no effect.

    In the example you reworked to fit the intent of my code, if you changed level 2 to an object complex, and mapped that to data, what would be the reasons it would take two opens to render the rows? And how can I force the table to wait until the rows are drawn before it renders it?

    Tx

    Bain

  • Bain McKayBain McKay Posts: 48Questions: 1Answers: 1

    I am completing the checkbox update function. The sample server side processing is in PHP. But I am writing in DOTNET C# WebAPI2. Where can I find a sample C# implementation? In the following code, how can I leverage .submit()? Does it make more sense to just use ajax rather than the submit?

    $('#example').on('change', 'input.editor-exclude', function () {
    editor
    .edit($(this).closest('tr'), false)
    .set('active', $(this).prop('checked') ? 1 : 0)
    .submit();
    });

  • Bain McKayBain McKay Posts: 48Questions: 1Answers: 1

    Let me try the code block again (I'm new to markdown)

    ``` $('#example').on('change', 'input.editor-exclude', function () {
    editor
    .edit($(this).closest('tr'), false)
    .set('active', $(this).prop('checked') ? 1 : 0)
    .submit();
    });

  • Bain McKayBain McKay Posts: 48Questions: 1Answers: 1

    ```js $('#example').on('change', 'input.editor-exclude', function () {
    editor
    .edit($(this).closest('tr'), false)
    .set('active', $(this).prop('checked') ? 1 : 0)
    .submit();
    });

  • Bain McKayBain McKay Posts: 48Questions: 1Answers: 1
    edited May 2018
     $('#example').on('change', 'input.editor-exclude', function () {
                    editor
                        .edit($(this).closest('tr'), false)
                        .set('active', $(this).prop('checked') ? 1 : 0)
                        .submit();
                });
    
  • Bain McKayBain McKay Posts: 48Questions: 1Answers: 1

    Enough of that....

  • Bain McKayBain McKay Posts: 48Questions: 1Answers: 1

    Just read that markdown is not available in forums posts yet. So that explains the above.

  • tangerinetangerine Posts: 3,348Questions: 36Answers: 394

    Just read that markdown is not available in forums posts yet.

    Where did you read that? It's incorrect (unless there is a current problem).

This discussion has been closed.