fnDraw in revised fnDataUpdate

fnDraw in revised fnDataUpdate

dmcleandmclean Posts: 55Questions: 2Answers: 0
edited June 2011 in DataTables 1.8
I was using fnDataUpdate (API plug-in) and the table was not showing any updates so I modified it (as shown below). The table still isn't showing any updates. Both the enter and end messages are coming out in the log. I am at a loss as to what to try next and would greatly appreciate some suggestions.

Thank you,

Donald

[code]
$.fn.dataTableExt.oApi.fnDataUpdate = function (oSettings, nRowObject, iRowIndex) {
try {
console.log("[fnDataUpdate] enter.");
var dataRow = oSettings.aoData[iRowIndex]._aData;
$(nRowObject).find("td").each(function(i) {
dataRow[i] = $(this).html();
});

// var iPos = oSettings._iDisplayStart;
this.oApi._fnDraw(oSettings, true);
console.log("[fnDataUpdate] end.");
}
catch(e) {
console.log("[fnDataUpdate] threw exception.")
}
};
[/code]

Replies

  • allanallan Posts: 63,494Questions: 1Answers: 10,470 Site admin
    Hi Donald,

    How are you using DataTables with regard to what the data source is? Are you using mDataProp for example - because if so this will not work (the function needs to be updated for that... - added to the to-do list). If you are just using a DOM data source or a plain array then it looks okay... One thing to note - you say "the table still isn't showing any updates" - it wouldn't do when you run this function - it is designed for updating the internal cache of DataTables based on the DOM (i.e. you might have changed the DOM for this row). Generally speaking you would want to use fnUpdate ( http://datatables.net/api#fnUpdate ).

    Regards,
    Allan
  • dmcleandmclean Posts: 55Questions: 2Answers: 0
    I'm using a DOM data source but I'm updating it in real time using Comet. Two of the columns contain check boxes, which didn't appear to be handled by fnUpdate - that's why I tried using the fnDataUpdate function, but apparently I'm missing something here.

    After looking at fnUpdate more carefully, I tried the code shown below. That didn't work - I kept getting an error:

    Uncaught TypeError: Object # has no method 'fnFindCellRowIndexes'

    There should be a way to make this work - another suggestion would be lovely.

    Thank you,

    Donald

    [code]
    $.fn.dataTableExt.oApi.fnDataUpdate = function (oSettings, nRowObject, iRowIndex) {
    try {
    console.log("[fnDataUpdate] enter.");
    var dataRow = oSettings.aoData[iRowIndex]._aData;

    var data[] = $(nRowObject).find("td");
    this.oApi._fnUpdate(oSettings, data, iRowIndex)

    console.log("[fnDataUpdate] end.");
    }
    catch(e) {
    console.log("[fnDataUpdate] threw exception.")
    }
    };
    [/code]
  • allanallan Posts: 63,494Questions: 1Answers: 10,470 Site admin
    There will certainly be a way of making it work! Are you updating the DOM in the table manually and then using fnDataUpdate to pull in that new data? As I noted there won't be any visible difference when you do this since it's an internal data cache update. Or are you looking to have DataTables update the DOM for you - if that is the case then you are correct about the checkbox - fnUpdate would replace the DOM of the old checkbox - so the state would need to be saved and then restored.

    I've no idea what fnFindCellRowIndexes is I'm afraid it doesn't appear to be in DataTables or any of the 'extras'.

    Allan
  • dmcleandmclean Posts: 55Questions: 2Answers: 0
    fnFindCellRowIndexes is one of your plug-in API functions.

    Ok, so what I tried next is:

    1. Update the DOM directly (trivially easy).

    2. Get the row index (from the plug-in API):
    var oTableIndex = oTable.fnFindCellRowIndexes()[0];

    3. Get the DOM for the TR (from the plug-in API):
    var oTableRow = oTable.fnFindCellRowNodes()[0];

    4. Call fnDataUpdate (modified from the plug-in API - see below):
    oTable.fnDataUpdate(oTableRow, oTableIndex);

    What happens is that the entire row is ending up in the first column of the table. DOM looks like this:




    ....



    Something is obviously going wrong in either fnFindCellRowNodes or fnDataUpdate, but I'm not sure what.

    Thank you,

    Donald

    [code]
    $.fn.dataTableExt.oApi.fnDataUpdate = function (oSettings, nRowObject, iRowIndex) {
    try {
    console.log(nRowObject);

    var dataRow = oSettings.aoData[iRowIndex]._aData;
    $(nRowObject).find("td").each(function(i) {
    dataRow[i] = $(this).html();
    });

    this.oApi._fnDraw(oSettings, true);
    }
    catch(e) {
    console.log("[fnDataUpdate] threw exception: " + e);
    }
    };
    [/code]
  • allanallan Posts: 63,494Questions: 1Answers: 10,470 Site admin
    heh - too many function to keep a track of these days...!

    I have to say I'm not massively convinced by the fnDataUpdate function looking at it more closely - it seems that nRowObject is a bit redundant... You could do something like this:

    [code]
    $.fn.dataTableExt.oApi.fnDataUpdate = function (oSettings, iRowIndex) {
    try {
    console.log(nRowObject);

    var nRow = Settings.aoData[iRowIndex].nTr;
    var dataRow = oSettings.aoData[iRowIndex]._aData;
    $('td', nRow).each(function(i) {
    dataRow[i] = $(this).html();
    });

    this.oApi._fnDraw(oSettings, true);
    }
    catch(e) {
    console.log("[fnDataUpdate] threw exception: " + e);
    }
    };
    [/code]
    With this you don't need the call to fnFindCellRowNodes (which sounds like it would be quite a slow operation with fnFindCellRowIndexes as well).

    Given that you've updated the DOM you must have a reference to the TR node already? In which case you could use fnGetPosition ( http://datatables.net/api#fnGetPosition ) which will be faster and less complex.

    Regards,
    Allan
  • dmcleandmclean Posts: 55Questions: 2Answers: 0
    That didn't make any difference. All of the data ends up in the first column - a tr within a tr.

    And as a result sorting doesn't work.
  • allanallan Posts: 63,494Questions: 1Answers: 10,470 Site admin
    Can you give me a link to this please so I can see it in action and track down what might be happening. I don't see anything in the above code which would result in a cell being written to or appended to.

    Allan
  • dmcleandmclean Posts: 55Questions: 2Answers: 0
    Unfortunately, I can't at the moment - the real app is hidden behind our firewall, and all of the resources that it depends on are also hidden behind the firewall. I have not yet had a chance to create decent mocks. Also, we're still working out how to pay for support.

    In the meantime, I'll have to continue trying things based on your suggestions. I've included the specific HTML from the page before and after an update. If you have another idea that I can try I'm all ears.

    Thank you,

    Donald

    The original HTML for one row:

    [code]


    2011-03-14T08:49:57
    11031412495570753gaffney
    gaffney70753
    NORMAL
    2264:18
    Staging

    moved to trouble

    1
    35
    0/32


    [/code]

    The HTML for the same row after it has been updated and fnDraw has been called:

    [code]



    2011-03-14T08:49:57
    11031412495570753gaffney
    gaffney70753
    NORMAL
    2264:32
    Staging

    moved to trouble

    1
    35
    0/32



    [/code]
  • allanallan Posts: 63,494Questions: 1Answers: 10,470 Site admin
    Heh - yup that's going to mess up the rendering :-). So it looks like something has actually cloned the row and then inserted it into the current one - that or the innerHTML of the row has been set to the same as the old one. Are you using any plug-ins for DataTables which do cloning, such as FixedColumns? Also, could you show me the bit of code which is doing the update of the data in the table from the comet input?

    Thanks,
    Allan
  • dmcleandmclean Posts: 55Questions: 2Answers: 0
    Your last response led me to either a fix or a work around. I'm not sure which it is yet but I'm going to investigate.

    One of the complicating factors here is that I'm using an advanced web framework (Lift - http://liftweb.net/) and some things are very easy (Comet updates). However, the documentation is not very mature and so it's very easy to confuse similar functionality - such as replacing an entire or replacing just the contents.

    Thank you for your help.
  • dmcleandmclean Posts: 55Questions: 2Answers: 0
    I'm fairly sure that is a workaround. Below I have included the JavaScript that works.

    [code]
    try { destroy_F822621353229GKMKJ1(); } catch (e) {}

    try {
    var parent1 = document.getElementById("row.11061516535370819gaffney");
    parent1.innerHTML = "2011-06-15T12:53:5311061516535370819gaffneygaffney70819NORMAL44:32Local complete-succeeded 21010/10";
    for (var i = 0; i < parent1.childNodes.length; i++) {
    var node = parent1.childNodes[i];
    parent1.parentNode.insertBefore(node.cloneNode(true), parent1);
    }
    parent1.parentNode.removeChild(parent1);
    } catch (e) {
    // if the node doesn't exist or something else bad happens
    }

    var oTableIndex = oTable.fnFindCellRowIndexes("11061516535370819gaffney")[0];
    oTable.fnDataUpdate(oTableIndex);
    console.log("Done for: row.11061516535370819gaffney.");

    try { destroy_F822621353229GKMKJ1 = function() {}; } catch (e) {}
    if (lift_toWatch['F822621353229GKMKJ1'] !== undefined) lift_toWatch['F822621353229GKMKJ1'] = '822621353709';
    [/code]
  • dmcleandmclean Posts: 55Questions: 2Answers: 0
    After studying the code more, I think that the problem is in DataTables. This code shown below should have worked, but DataTables is undoing the part that moves the new node to the parent and then deletes the old node.

    This means that I'm going to have to code around the problem because that means that I can't easily update properties of the TR as part of a single call. Blech.

    Any idea how much work it would take to fix this problem?

    [code]
    try { destroy_F400000897468E4QTBO(); } catch (e) {}

    try {
    var parent1 = document.getElementById("row.11031413100570758gaffney");
    parent1.innerHTML = "\u000a 2011-03-14T09:10:0611031413100570758gaffneygaffney70758NORMAL2282:02Staging KILLED 170/6\u000a ";
    for (var i = 0; i < parent1.childNodes.length; i++) {
    var node = parent1.childNodes[i];
    parent1.parentNode.insertBefore(node.cloneNode(true), parent1);
    }
    parent1.parentNode.removeChild(parent1);
    } catch (e) {
    // if the node doesn't exist or something else bad happens
    }

    var oTableIndex = oTable.fnFindCellRowIndexes("11031413100570758gaffney")[0];
    oTable.fnDataUpdate(oTableIndex);
    console.log("Done for: row.11031413100570758gaffney.");

    try { destroy_F400000897468E4QTBO = function() {}; } catch (e) {}
    if (lift_toWatch['F400000897468E4QTBO'] !== undefined) lift_toWatch['F400000897468E4QTBO'] = '400000897534';
    [/code]
  • allanallan Posts: 63,494Questions: 1Answers: 10,470 Site admin
    Yup - that's the bit of code that is triggering the issue - thanks for posting that. So what you want to do is not replace the TR element (you can actually - but what would the advantage be? It would just require the browser to create a new node and slow things down).

    So given that you have complete control over the insertion of the new data from the Comet library, why not just use fnUpdate ( http://datatables.net/api#fnUpdate ). DataTables will take care of updating the internal cache and the DOM for you, all you need to do is tell it what cell or row you want to update and feed it the data.

    An alternative would be to use fnDelete to remove the old row and then fnAddData to insert the new row. Note that for the checkbox you would need to store the state of it and then restore it after the add (since again it's a new DOM element).

    Basically DataTables can't know when you modify the table's DOM so it's best to try and use the API functions for updating information in the table - and I suspect that it might make things a bit simpler for you here.

    Allan
  • dmcleandmclean Posts: 55Questions: 2Answers: 0
    Ok, I changed it to use fnUpdate. Three problems:

    1. At the moment, it's kind of ugly. The Lift framework makes it dead easy to compose a TR and throw it to the client. I'll probably end up writing a Lift widget if I get it working properly.

    2. The update shown below doesn't work - the check boxes disappear. If I wrap them it a they're ok, but I shouldn't have to do that.

    3. Scroll position is lost. My customers will never accept that. Is there a way to preserve scroll position?

    [code]
    oTable.fnUpdate([,'2011-03-14T08:49:57','11031412495570753gaffney','gaffney70753','NORMAL','2284:54','Staging','moved to trouble','1','35','0/32',], oTableIndex);
    [/code]
  • allanallan Posts: 63,494Questions: 1Answers: 10,470 Site admin
    1. One possible option is to use the TR from Lift, but don't insert it into the table - just pull the information out of it and put that info into the table. For example $('td', liftRow)[0].innerHTML would give you the HTML for the first cell. Might tidy it up nicely.

    2. and 3. I think the answer for these lie together. What you want is the 'no-redraw' option of fnUpdate (param 4: http://datatables.net/api#fnUpdate ). That will stop a full sort and filter occurring. DataTables will do a full sort and filter when information is updated by default - this will stop that and stop the full redraw (which must take the table back to row 0). If you do want to do the full redraw, then you could just save the scroll position and then restore it after the fnUpdate call.

    So with the checkbox, as I mentioned you would need to store it's state, but there is a different way of approaching this - doing cell by cell updates:

    [code]
    var liftCells = $('td', liftRow);
    oTable.fnUpdate( lifeCells[0].innerHTML, rowIndex, 0, false );
    oTable.fnUpdate( lifeCells[1].innerHTML, rowIndex, 1, false );
    oTable.fnUpdate( lifeCells[2].innerHTML, rowIndex, 2, false );
    ...
    [/code]
    and just don't update any cells which don't need to be updated with new information from the server (including the checkbox).

    Does that sound workable?

    Regards,
    Allan
  • dmcleandmclean Posts: 55Questions: 2Answers: 0
    The state of the check boxes is stored on the server - that isn't an issue. I'm only mildly annoyed with the ugliness - it would be easier to live with it at this point (since it works).

    I do need to do the redraw since some updates could affect sort order, so I looked at the plug-in API to try and create a function that would save the position, do the update and then restore the position. What I tried didn't work, perhaps you can tell me what I did wrong?

    [code]
    $.fn.dataTableExt.oApi.fnRevisedUpdate = function (oSettings, data, rPos) {
    if (typeof bRedraw == 'undefined') {
    bRedraw = true;
    }

    var pos = oSettings._iDisplayStart;

    this.oApi._fnUpdate(oSettings, data, rPos);

    oSettings._iDisplayStart = pos;
    oSettings.oApi._fnCalculateEnd(oSettings);

    this.oApi._fnDraw(oSettings, true);
    };
    [/code]
  • allanallan Posts: 63,494Questions: 1Answers: 10,470 Site admin
    That looks like it should do a standing redraw with paging, but you mentioned scrolling in a previous post - it won't save the scrolling position since that is (sort of) independent from the paging (it depends on how exactly you've initialised the table). What you'll need to do if you have scrolling enabled is also save the scrollTop value of the DataTables scrolling container (div.dataTables_scrollBody). How have you got the scrolling / paging set up in the table?

    Allan
  • dmcleandmclean Posts: 55Questions: 2Answers: 0
    Is this what you are asking for:

    [code]
    "sScrollY": "600px",
    "bPaginate": false,
    [/code]
  • allanallan Posts: 63,494Questions: 1Answers: 10,470 Site admin
    Okay - so the pagination saving that you have in the above code isn't going to help since _iDisplayStart will always be 0 as you having paging disabled. So scroll position saving is what you want. Same idea as what you have above - different variable :-). $('div.dataTables_scrollBody').scrollTop should give you what you need.

    Allan
  • dmcleandmclean Posts: 55Questions: 2Answers: 0
    edited June 2011
    The code below works. Please feel free to use it, add it to list of plug-in API functions or whatever. I have permission from my employer to contribute to open source projects.

    One note about updates with check boxes (and probably other HTML elements) - you may want to add this to the documentation for fnUpdate: the whole thing must be wrapped in tick marks (as shown in the example below). Firefox can and will parse it without the tick marks, but Chrome and Safari will not.

    It may be that this seems obvious to most people, but it wasn't obvious to me and since I'm not quite a complete idiot (yet), perhaps a little clarification will save somebody some pain and aggravation.

    And just so we're clear: Allan, you ROCK, dude!

    [code]
    oTable.fnUpdateWOScroll([
    '',
    '2011-03-22T07:10:19',
    '11032211101870764kyp',
    'kyp70764','NORMAL','2163:39',
    'SFTP','moved to trouble',
    '1',
    '24',
    '0/23',
    '',
    'movedtotrouble'],
    oTableIndex);
    [/code]

    [code]
    $.fn.dataTableExt.oApi.fnUpdateWOScroll = function (oSettings, data, rPos) {
    var pos = $('div.dataTables_scrollBody').scrollTop();

    this.fnUpdate(data, rPos);

    $('div.dataTables_scrollBody').scrollTop(pos);
    };

    $.fn.dataTableExt.oApi.fnAddWOScroll = function (oSettings, data) {
    var pos = $('div.dataTables_scrollBody').scrollTop();

    this.fnAddData(data);

    $('div.dataTables_scrollBody').scrollTop(pos);
    };
    [/code]
  • dmcleandmclean Posts: 55Questions: 2Answers: 0
    edited June 2011
    I almost forgot to add the delete version.

    Though I did notice that calling delete with an undefined "rPos" leads to an ugly error that is not easy to trace back to the real cause. Thus the check that I added.

    [code]
    $.fn.dataTableExt.oApi.fnDeleteWOScroll = function (oSettings, rPos) {
    if (typeof rPos == 'undefined') {
    console.log("[fnDeleteWOScroll] invalid delete, do nothing.");
    return;
    }

    try {
    var pos = $('div.dataTables_scrollBody').scrollTop();

    this.fnDeleteRow(rPos);

    $('div.dataTables_scrollBody').scrollTop(pos);
    }
    catch(e) {
    console.log("Exception: ");
    console.log(e);
    }
    };
    [/code]
  • allanallan Posts: 63,494Questions: 1Answers: 10,470 Site admin
    That's awesome - thanks very much for posting your code! I'm sure others will find it useful.

    > It may be that this seems obvious to most people, but it wasn't obvious to me and since I'm not quite a complete idiot (yet)

    I think this is an entirely fair point. I'll look at improving the documentation there. Might be suitable for a future blog post as well...

    Regards,
    Allan
  • SantoshRSantoshR Posts: 1Questions: 0Answers: 0
    Hey!

    I know this is old news, but I have to say, DataTables is amazing!

    Also, I wanted to use fnDataUpdate. However I faced issues with the code. So I made a couple of tweaks and it worked just fine. Hope it is useful for some one. Feel free to clean up the code.

    [code]
    $.fn.dataTableExt.oApi.fnDataUpdate = function (oSettings, nTr, iRowIndex) {


    var nTds = nTr.getElementsByTagName('td');

    if ( nTds.length != oSettings.aoColumns.length )
    {
    alert( 'Warning: not adding new TR - columns and TD elements must match' );
    return;
    }

    var dataRow = oSettings.aoData[iRowIndex]._aData;
    $('td', nTr).each(function(i) {
    dataRow[i] = $(this).html();
    });

    for ( iLen=oSettings.aoColumns.length, i=iLen-1 ; i>-1 ; i-- )
    {
    if ( !oSettings.aoColumns[i].bVisible)
    {
    nTr.removeChild(nTds[i]);
    }
    }
    oSettings.aoData[iRowIndex].nTr=nTr;
    oSettings.aiDisplay = oSettings.aiDisplayMaster.slice();
    this.oApi._fnDraw(oSettings, true);
    }
    };
    [/code]
This discussion has been closed.