Hiding rows that don't have data

Hiding rows that don't have data

dmolavidmolavi Posts: 65Questions: 0Answers: 0
edited February 2011 in General
Example here: http://www.gallerymodules.com/

At the top of the table are 3 checkboxes that allow a user to filter by source. The checkbox has a JS onclick that calls this function:
[code]function fnShowHide( iCol ) {
var oTable = $('#module_table').dataTable();
var bVis = oTable.fnSettings().aoColumns[iCol].bVisible;
oTable.fnSetColumnVis( iCol, bVis ? false : true );
}[/code]

However, in certain cases (for example, uncheck the "Codex" box, and look at the "about" module), the hiding of a row will result in an entry that has no source to download. What would be the best way to hide that row, and subsequently show it again if the checkbox is re-checked?

Replies

  • GregPGregP Posts: 500Questions: 10Answers: 0
    edited February 2011
    This must be possible, but without prototyping I can only make a guess. Here's how I would approach it in general terms (sorry for lack of sample code):

    If the row is being hidden without a new table draw, it's just a jQuery function. You would have to iterate through all your rows to find matching candidates for hiding, which will produce an array of TR objects. Then all you have to do is call jQuery's hide() on it.

    If your click event (unchecking the box, for example) causes the table to draw, then in the fnRowCallback, check the relevant TD elements for content. So, if aData[4]="" or other relevant check. If the conditions are met for hiding the row (no content in the TD elements being checked), you can use jQuery to traverse up a level (.parent() or .closest() should do the trick) to get to the TR. Then just call a hide() on that row.

    Come to think of it, there's a hybrid approach:

    You could generate a series of "special case" utility classes to apply to the TR for later use. So during the draw, you would add a class "no_download" to rows in which no download is available. Then in your click function, you simply have to $('.no_download').hide();

    If you get stuck, I could try to generate some sample code. More likely, Allan will know a better way of doing it. LOL!
  • dmolavidmolavi Posts: 65Questions: 0Answers: 0
    I was actually just looking at that part of the API...I think I can cobble something together and will post when I do so, in the hopes that someone might be able to find a more efficient mechanism.
  • GregPGregP Posts: 500Questions: 10Answers: 0
    Just replying back so that you get the email notification... I added a third thought to my reply: the hybrid approach might be the ticket.
  • dmolavidmolavi Posts: 65Questions: 0Answers: 0
    ooh, i like that one. thanks :)
  • GregPGregP Posts: 500Questions: 10Answers: 0
    edited February 2011
    Looking at the jQuery Tools (Flowplayer) Tooltip page here: http://flowplayer.org/tools/demos/tooltip/table.html , I actually noticed this which was neat:

    [code]
    $("#mytable img").click(function() {

    // get handle to the current image (trashcan)
    var img = $(this);

    // gradually hide the parent row
    img.parents("tr").fadeOut(function() {

    // after the row is deleted, hide our tooltip using the tooltip API
    img.data("tooltip").hide();

    });
    });
    [/code]

    I like that he faded out the row instead of hiding it. Just a nice visual touch that works well since jQuery's slideUp doesn't work consistently or smoothly with TR elements.

    Not a drop-in piece of code but the basics are there. It even uses parents() in the way I suggested; you would just need to put something similar into your own custom function that first builds your array of TR elements and binds to your other click event (instead of adding click to the images).
  • dmolavidmolavi Posts: 65Questions: 0Answers: 0
    bah, the clicking of the checkbox doesn't cause a redraw, thus the row callback is never called after initialization. i might have to go w/ the flowplayer method, which will be testing my JS and JQuery coding skills...
  • GregPGregP Posts: 500Questions: 10Answers: 0
    You could do either of two things:

    1. If you really only need to load your data once per pageload, just load it ALL, and use any checkboxes to selectively filter out contents. You would use the 'hybrid' approach and apply special classes to rows that match certain descriptions, in order to facilitate hiding/showing. So like I mentioned earlier, instead of firing the hide from the callback, you just use it to add classes.

    2. You can have the checkbox click force a redraw. ;-)
  • dmolavidmolavi Posts: 65Questions: 0Answers: 0
    edited February 2011
    Hmmm...using hide/show doesn't work out too well. Check out http://www.gallerymodules.com/sandbox/ and uncheck the 'codex' box. then re-check it and select to show 100 rows.

    it appears that when calling 'show', jquery simple tags the style as 'display:block' which messes up the table display. Here is the code I have now:

    [code]
    function fnShowHide( iCol, table ) {
    var oTable = (table == 'm') ? $('#module_table').dataTable() : $('#theme_table').dataTable();
    var bVis = oTable.fnSettings().aoColumns[iCol].bVisible;
    oTable.fnSetColumnVis( iCol, bVis ? false : true );
    fnToggleRows(iCol, oTable, bVis);
    }
    function fnToggleRows(iCol, oTable, bVis) {
    var numrows = oTable.fnGetData().length;
    for(i=0;i
  • GregPGregP Posts: 500Questions: 10Answers: 0
    edited February 2011
    Hm, I can see the problem. An additional problem: the table doesn't get re-striped. show() should work on inline elements as well as block elements, but it didn't occur to me that table cells actually have thier own display mode...which is table-row. Since the default for show() is block, and the row is NOT inline, it's just adding block back, as you say.

    So show() probably won't ever work, which is silly... not sure why they don't just have the function query for display type and then toggle as need be.

    To get the row to display again properly, swap out the show() in favour of this:

    [code]
    $(node).css('display', 'table-row');
    [/code]

    To fix the striping I think would mean dropping the DataTables/jQuery UI way, which is to add the classes "odd" and "even" to the row. I'm not sure how to disable setting a class (passing "" to the sStripOdd and sStripEven parameters?), but once that was done you would instead style with tr:nth-child(even) and tr:nth-child(odd), which as far as I know will respect even and odd even when rows are removed.
  • dmolavidmolavi Posts: 65Questions: 0Answers: 0
    [code]$(node).css('display', 'table-row')[/code] worked for the display issue, thank you :)

    supposedly, fnDraw will redraw the table, but it looks like it doesn't take the row visibility into account to re-stripe.

    i actually had even/odd selectors added during the creation of the table (all done via php); i suppose that i could just start at the top and re-class each row to a new style. that seems inefficient to me though, as i'm already looping through the table once to figure out what rows to show/hide, then i'd have to do it again to get the style correct...though i suppose with a counter and some mod math, i could figure out whether the row's style needs to be updated...
  • GregPGregP Posts: 500Questions: 10Answers: 0
    edited February 2011
    Bah,

    I actually was wrong. Removing a row will NOT allow the nth-child selector to respect the striping, because (of course!) those rows are still in the DOM. Back to the drawing board!
  • dmolavidmolavi Posts: 65Questions: 0Answers: 0
    hmm, and the mod method won't work either, since there is no way of knowing how many rows are visible and what their locations are in order to appropriately assign the even/odd classes...hrmmm....
  • dmolavidmolavi Posts: 65Questions: 0Answers: 0
    i might have this figured out. i changed the show/hide to just toggle(), which adds a "display:none" to the row's style. i could have a counter in place that increments only if that style is *not* present, and modify classes accordingly based on mod math...time to test.
  • dmolavidmolavi Posts: 65Questions: 0Answers: 0
    edited February 2011
    ick, just realized that the counter at the bottom of the table (showing X of Y) and the pagination would need to be redone, as well.
  • allanallan Posts: 63,542Questions: 1Answers: 10,476 Site admin
    DataTables doesn't really expect rows to be hidden using display:none - indeed it doesn't do it itself and has absolutely no handling of that, so there will certainly be quirks. If you don't want certain rows be shown you can either 1. alter the data source to not have those rows (i.e. clear the table and feed in the rows you do want), or 2. use custom filtering to simply remove the rows you don't want based on whatever logic: http://datatables.net/development/filtering / http://datatables.net/examples/plug-ins/range_filtering.html .

    Allan
  • dmolavidmolavi Posts: 65Questions: 0Answers: 0
    edited February 2011
    allan-
    is there a way to call $.fn.dataTableExt.afnFiltering from within the showhide function I have in place to handle the checkbox toggles? (sorry if that's a dumb JS question, but this is my first major journey into JS in years).

    Edit to add:
    here's the function:
    [code]function fnShowHide( iCol, table ) {
    var oTable = (table == 'm') ? $('#module_table').dataTable() : $('#theme_table').dataTable();
    var bVis = oTable.fnSettings().aoColumns[iCol].bVisible;
    oTable.fnSetColumnVis( iCol, bVis ? false : true );
    } [/code]

    The checkboxes call this function via onClick...

    Edit to add #2:
    The added wrinkle here is that I need to know which columns I have to look at, based on which checkbox is clicked, in order to determine how to filter the rows...my head is spinning now :)
  • allanallan Posts: 63,542Questions: 1Answers: 10,476 Site admin
    If you call oTable.fnDraw() that will cause the filtering to be applied (fnDraw without any arguments will basically re-establish the table's state - sorting, filtering etc). I would say, looking at your page, that using an afnFiltering plug-in is the way to go here. The parameters for the function are:

    1. object - DataTables settings object
    2. array - data for the row in question. Array is indexed by column
    3. int - index of row in aoData

    So you know which columns (data columns) you want to look at, and you know which columns are needed based on the checkbox values. So there will need to be some logic there to combine the two :-)

    Allan
  • dmolavidmolavi Posts: 65Questions: 0Answers: 0
    edited February 2011
    Pardon a potentially stupid question, but with those three parameters that get passed in, how can I determine which of the checkboxes has been modified? I have that as a parameter in my initial function (shown above), but can't figure out how to pass it on to a function that isn't expecting it...

    Edited to delete the question re: multiple tables...got that figured out, now just need to know which checkbox was toggled...
  • dmolavidmolavi Posts: 65Questions: 0Answers: 0
    edited February 2011
    ok...very close here...got it to work for one of the checkboxes, but it seems that the others aren't...sooooo close. when i get it working, i'll post the final code here.
  • dmolavidmolavi Posts: 65Questions: 0Answers: 0
    ok, so i'm stumped...and i know it's in my logic. The example is at http://www.gallerymodules.com/sandbox/index.php and here's the relevant code:
    [code]function fnShowHide( iCol, table ) {
    var oTable = (table == 'm') ? $('#module_table').dataTable() : $('#theme_table').dataTable();
    var bVis = oTable.fnSettings().aoColumns[iCol].bVisible;
    oTable.fnSetColumnVis( iCol, bVis ? false : true );
    oTable.fnDraw();
    }

    $.fn.dataTableExt.afnFiltering.push(
    function( oSettings, aData, iDataIndex ) {
    var codex = (document.getElementById('cbx_codex').checked) ? true : false;
    var g30 = (document.getElementById('cbx_git30').checked) ? true : false;
    var g31 = (document.getElementById('cbx_git31').checked) ? true : false;
    if(oSettings.sTableId == "module_table") {
    if(codex == false && aData[3] == '' && aData[4] == '') {
    return false;
    }
    if(g30 == false && aData[2] == '' && aData[4] == '') {
    return false;
    }
    if(g31 == false && aData[2] == '' && aData[3] == '') {
    return false;
    }
    return true;
    }
    if(oSettings.sTableId == "theme_table") {
    return true;
    }
    }
    );[/code]

    if you uncheck two boxes, a handful of the resulting list will still appear even if there is no download available. I must be missing a few checks in the "if tree" in the filter, but my brain is fried....any pointers would be appreciated.
  • dmolavidmolavi Posts: 65Questions: 0Answers: 0
    Got it!!!
    [code]$.fn.dataTableExt.afnFiltering.push(
    function( oSettings, aData, iDataIndex ) {
    var codex = (document.getElementById('cbx_codex').checked) ? true : false;
    var g30 = (document.getElementById('cbx_git30').checked) ? true : false;
    var g31 = (document.getElementById('cbx_git31').checked) ? true : false;
    if(oSettings.sTableId == "module_table") {
    if(codex == false) {
    if(aData[3] == '' && aData[4] == '') { return false; }
    if(g30 == false && aData[4] == '') { return false; }
    if(g31 == false && aData[3] == '') { return false; }
    }
    if(g30 == false) {
    if(aData[2] == '' && aData[4] == '') { return false; }
    if(codex == false && aData[4] == '') { return false; }
    if(g31 == false && aData[2] == '') { return false; }

    }
    if(g31 == false) {
    if(aData[2] == '' && aData[3] == '') { return false; }
    if(codex == false && aData[3] == '') { return false; }
    if(g30 == false && aData[2] == '') { return false; }
    }
    return true;
    }
    if(oSettings.sTableId == "theme_table") {
    return true;
    }
    }
    );[/code]

    It may not be the most elegant way to do it, but it does what I want it to do. Props to Allan and Greg for their help on this endeavor.
  • allanallan Posts: 63,542Questions: 1Answers: 10,476 Site admin
    edited February 2011
    Hi dmolavi,

    Nice one - good to hear you got it going :-). The logic looks fine to me - often these things can be a little messy, but I think that's plenty understandable.

    The one thing I would say is that there is an optimisation which can be made with regard to getting the filtering values from the DOM - which will be by far the slowest operation in your code, and will happen on every single draw. What you could do is cache the values and read them only once per draw:

    [code]
    var iDraw = -1;
    var filters = {
    codex: null,
    g30: null,
    g31: null
    };

    $.fn.dataTableExt.afnFiltering.push(
    function( oSettings, aData, iDataIndex ) {
    var codex, g30, g31;
    if ( iDraw != oSettings.iDraw ) {
    filters.codex = (document.getElementById('cbx_codex').checked) ? true : false;
    filters.g30 = (document.getElementById('cbx_git30').checked) ? true : false;
    filters.g31 = (document.getElementById('cbx_git31').checked) ? true : false;
    }

    if(oSettings.sTableId == "module_table") {
    if(filters.codex == false) {
    if(aData[3] == '' && aData[4] == '') { return false; }
    if(filters.g30 == false && aData[4] == '') { return false; }
    if(filters.g31 == false && aData[3] == '') { return false; }
    }
    if(filters.g30 == false) {
    if(aData[2] == '' && aData[4] == '') { return false; }
    if(filters.codex == false && aData[4] == '') { return false; }
    if(filters.g31 == false && aData[2] == '') { return false; }

    }
    if(filters.g31 == false) {
    if(aData[2] == '' && aData[3] == '') { return false; }
    if(filters.codex == false && aData[3] == '') { return false; }
    if(filters.g30 == false && aData[2] == '') { return false; }
    }
    return true;
    }
    if(oSettings.sTableId == "theme_table") {
    return true;
    }
    }
    );
    [/code]
    It uses a global variable - although you could scope limit that if you want, or even attach it to the DataTables object.

    Regards,
    Allan
  • dmolavidmolavi Posts: 65Questions: 0Answers: 0
    hrmm...something in there breaks the jQuery tabs. I'll have to take a look at it later today.
  • allanallan Posts: 63,542Questions: 1Answers: 10,476 Site admin
    My mistake - sorry! I missed out a brace from the if statement I added, which would have resulted in a Javascript error.

    Allan
  • dmolavidmolavi Posts: 65Questions: 0Answers: 0
    That'd do it :) I probably would have found it out quickly enough w/ Firebug, but my kids just got up from their nap. Thanks again. I love DataTables, and will be using it in a re-architecting of another site I run.
This discussion has been closed.