fnRowCallback slow when used in bulk

fnRowCallback slow when used in bulk

doncullendoncullen Posts: 32Questions: 2Answers: 0
edited October 2010 in General
Code works fine, but it's causing the browser to lock up for a second or so until it finishes executing. Trying to figure out a way to optimize fnRowCallback so it doesn't affect browser as much. Feedback would be appreciated.

Keep in mind; it's fine when executed on 5 rows or less- it's when it goes into bulk that it lags the browser considerably. By bulk, I mean 30 or more.

Code:
[code] function processThis(row) {
addClassToStringContainer('away', 'qsAway', row, 1, 6);
addClassToStringContainer('available', 'qsAvailable', row, 1, 6);
addClassToStringContainer('in chat', 'qsInChat', row, 1, 6);
addClassToStringContainer('lobby', 'qsLobby', row, 1, 6);
addClassToStringContainer('teaming', 'qsTeaming', row, 1, 6);
addClassToStringContainer('in game', 'qsInGame', row, 1, 6);
addClassToStringContainer('after game', 'qsAfterGame', row, 1, 6);
}

function addClassToStringContainer(stringID, classID, row, whichCol, maxCol){
var l = 0;
var hascenter = false;
$(row).children().each(function(index, td){
if(l == whichCol){
if ($(td).html().indexOf(stringID) === 0) {
hascenter = $(td).hasClass('center');
$(td).removeClass();
$(td).addClass(classID);
if(hascenter) $(td).addClass('center');
}
}
l++;
if(l == maxCol) l == 0;
});
}

$(document).ready(function() {
$('#activequeue').html( '' );
$('#theactivequeue').dataTable( {
"fnRowCallback": function( nRow, aData, iDisplayIndex){ processThis(nRow); return nRow; },
"bPaginate": false,
"bJQueryUI": true,
"aaSorting": [[ 1, "desc" ]],
"aoColumns": [
{ "sTitle": "Gamer" },
{ "sTitle": "Status", "sClass": "center" },
{ "sTitle": "In State", "sClass": "center"},
{ "sTitle": "Active", "sClass": "center" },
{ "sTitle": "Away", "sClass": "center" },
{ "sTitle": "Offline", "sClass": "center" }
]
} );
}[/code]

Replies

  • allanallan Posts: 63,113Questions: 1Answers: 10,395 Site admin
    Not surprised that takes a fairly long time to execute, there is a lot of DOM manipulation going on there. Although surprised that the difference is noticeable between 5 rows and 10 (since fnRowCallback is called only when a row is displayed). Could you explain what you are trying to achieve here? Can this not be done only once, or must be be done every time the row is displayed?

    Allan
  • doncullendoncullen Posts: 32Questions: 2Answers: 0
    edited October 2010
    The status of a player is updated every five seconds. The status of the players are constantly changing, so the status needs to be polled often. As soon as incoming data is obtained, it then does the following:

    Query server for JSON data
    Check to see if a player in the JSON data exists in the table
    --if so, update row (this fires fnRowCallback, and is needed especially since status will be changing with the update)
    --if not, add player to table (this fires fnRowCallback, and is needed to modify class based on their status)
    Check to see if a player in the table exists in the JSON data
    --if so, skip and continue (already handled the player in previous update)
    --if not, remove row containing player (doesn't fire fnRowCallback, I think-- correct me if I'm wrong.)

    Right now, I'm staring at the processThis() function and the addClassToStringContainer() function. There has to be a better way to handle those two functions.

    Think about it. Look at the addClassToStringContainer function-- it basically crawls through the rows. Let's say we have 40 rows. That's 40 loops right there. But wait, we're forgetting that the loop also checks TD by TD. There's 6 columns. That comes out to 240 loops right there already!

    Now take a look at the processThis() function; it's executing the addClassToStringContainer() function 7 times.

    Which means every time JSON data is obtained (every 5 seconds), it slams the browser with 1680 loops!

    The end result? The browser locks up during those loops.

    There has to be a way to optimize the code so it doesn't need to run the addClassToStringContainer() seven times, but still retain reusability and flexibility... I can merge the two functions, it'd reduce it so it runs once instead of 7 times for each fnRowCallback. But if I do that, it kills flexibility (can't check a different column, can't check for different type, etc with minimal code).

    Got an idea on how to reduce intensivity of fnRowCallback?

    Edit: is there a way to have:

    $(row).children().each(function(index, td)

    work with a specific column? I tried:

    $(row).children(1).each(function(index, td)

    to select the second column to work with, but it didn't work. If I can have it select a specific column, it'll cut down alot on looping.
  • RoVeRTRoVeRT Posts: 1Questions: 0Answers: 0
    Either of these should help but i find using a drawCallback is usually faster

    [code]var td = $(row).children().eq(whichCol)
    // manipulate td here[/code]
    or using a drawCallback
    [code]// filter added to only process current page of data
    // nth-child starts from 1
    $(oTable.fnGetNodes()).filter(':visible').find('td:nth-child(' + (whichCol + 1) + ')').each(function(index, td){
    // manipulate td's here
    })[/code]
  • doncullendoncullen Posts: 32Questions: 2Answers: 0
    edited October 2010
    Since I wasn't sure how I'd implement the second method (as much as I wanted to), I went ahead with the first approach. I managed to consolidate both functions into a single function, and reduce the amount of loops considerably. New code is now:

    [code] var classArray = {
    'string2class': [
    {
    'theString': 'away',
    'theClass': 'qsAway'
    },
    {
    'theString': 'available',
    'theClass': 'qsAvailable'
    },
    {
    'theString': 'in chat',
    'theClass': 'qsInChat'
    },
    {
    'theString': 'reserved',
    'theClass': 'qsReserved'
    },
    {
    'theString': 'teaming',
    'theClass': 'qsTeaming'
    },
    {
    'theString': 'in game',
    'theClass': 'qsInGame'
    },
    {
    'theString': 'after call',
    'theClass': 'qsAfterCall'
    }
    ]
    };

    function processThis(row) {
    var hascenter = false, td, cellString, i;

    td = $(row).children().eq(1);
    // manipulate td here
    hascenter = $(td).hasClass('center');
    $(td).removeClass();
    cellString = $(td).text();
    classArray.string2class.length;
    for(i = 0; i < classArray.string2class.length; i++){
    if (cellString == classArray.string2class[i].theString) { $(td).addClass(classArray.string2class[i].theClass); }
    }
    if(hascenter){ $(td).addClass('center'); }

    }[/code]

    The page is working much better; the lockup is considerably slower-- it only locks up for a second. But the lockup still exists when loading large amounts of data.

    So I'd like to convert the above code to use drawcallback instead. Suggestion?

    Edit: Is there a way to track bottlenecks in the code? I'm going to hit Google for this, but would like to hear your solution for tracking down bottlenecks.

    By the way, thanks for your assistance!
  • doncullendoncullen Posts: 32Questions: 2Answers: 0
    I ran firebug Javascript profiler, and found the auto-resize was taking a huge toll, especially with it executing on every row update. So I disabled auto-resizing, and set an timeout function to execute after 1000 milliseconds which would execute oTable.fnAdjustColumnSizing();.

    The end result? Well formatted table, and the lock up pushed down to less than a second. The lockup's still there, but it's barely noticeable-- only way to actually catch it is if you're scrolling and you'll see it lock up for less than a second.

    I ran the profiler again, and this time DataTables was no longer what was locking it up. It's now:

    addClass
    removeClass

    So now it's jQuery that is causing it to lag alot. That's an issue that doesn't belong in this forum, so I'd like to thank everyone for your assistance. Of course, if you have an idea how to optimize the above code so those two commands are not as intensive, I'm open to it!
  • allanallan Posts: 63,113Questions: 1Answers: 10,395 Site admin
    Very nice - thanks for the update. The only optimisation I'd suggest from a quick scan of the code above would be that you call $() on the variable 'td' a lot ($(td)) - that will add a little bit of time, since you are just initialising jQuery with the same thing many times. Doing var jqTd = $(td); might help a touch.

    Allan
This discussion has been closed.