8

I have an ASP.NET MVC site and I have a HTML table in a view. As per the example html code I sometimes have nested tables inside of cells but that can be ignored until you get to the end of the question.

<table id="mainTable">
      <thead>
      <tr>
        <th>Col 1</th>
        <th>Col 2</th>
        <th>Col 3</th>
      </tr>
      </thead>
      <tbody>
      <tr>
          <td>data</td>
          <td>data</td>
          <td>data</td>
      </tr>
      <tr>
          <td>data</td>
          <td>
               <table class="nestedTable">
                   <tr><th>Col 1</th><th>Col 3</th></tr>
                   <tr>
                       <td>nested data</td><td>nested data 1</td>
                    </tr>
               </table>
          </td>
          <td>data</td>
      </tr>
    </tbody>
</table>

I now want to to use this jQuery multiselect plugin to allow the user to select the ordering of columns they want as well as show / hide certain columns So I am creating a multiselect that looks like this:

<select id="cols" class="multiselect" multiple="multiple" name="cols[]">
    <option value="Col1">Col1</option>
    <option value="Col2">Col2</option>
    <option value="Col3">Col3</option>
</select>

to allow the user to pick the ordering and then I am going to store this data in local storage for the time being. So all I am storing is an array of strings that represent the "visible order of columns" which is used to setup the multiselect picker. This works fine and I am able to persist this array or "ordered column names".

My question is what is the best way to take this string array and update the HTML table to reflect this column ordering and column visibility?

I know there are larger html table grid frameworks that have this feature but in this case I need to stick with a hand coded html table.

Update:

@Rick Hitchcock's answer below got me 90% of the way there but there is one open issue that I realize complicates my question a bit. In certain cases, I have a nested table inside the main table. I have updated the question to include this situation. I do NOT want the column chooser code to affect the nested table so I am looking for a way to just have the code affect the main table.

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
leora
  • 188,729
  • 360
  • 878
  • 1,366

1 Answers1

6

[original answer removed based on updated question]

Given an array (arr) like this:

['Col 2', 'Col 3', 'Col 1']

… or this:

['Col 1', 'Col 3']

… this will order the main table's columns to match, and it will hide unused columns:

var tr= $('#mainTable > tbody > tr, #mainTable > thead > tr');

$('> th, > td', tr).hide();

$.each(arr, function(_, val) {
  var col=  $('> th', tr)
              .filter(function() {
                return $(this).text()===val;
              })
              .index();

  $(tr).append(function() {
    return $('> td, > th', this).eq(col).show();
  });
});

Fiddle

Click a button to reorder the table.


How it works

This will limit subsequent selectors to the main table's rows:

var tr= $('#mainTable > tbody > tr, #mainTable > thead > tr');

Even if you exclude tbody, it will be added for you. For this reason, this is always an empty collection: $('#mainTable > tr').


This hides only direct children of the tr collection (from the previous line):
$('> th, > td', tr).hide();

As a side effect, all descendants will also be hidden.

It's the equivalent of:

$(tr).children('th, td').hide();

jQuery's children and find methods are often unnecessary (and costly), when you can accomplish the same thing using CSS selectors – especially given the advantages of the optional context parameter:

$( selector [, context ] )

This looks at each element of the array:

$.each(arr, function(_, val) {
  ...
});


This gets the index() of the th element (that is a direct child of the tr collection), in which its text() matches the array value:
var col=  $('> th', tr)
              .filter(function() {
                return $(this).text()===val;
              })
              .index();

That is the column we want to show.


Finally, this appends to each member of the tr collection all of its children having that column number, and it shows them:

$(tr).append(function() {
  return $('> td, > th', this).eq(col).show();
});

jQuery's append() method (like JavaScript's appendChild() method) move an element – either into the DOM or somewhere else within the DOM.

Knowing this, we can often avoid using methods such as detach().


Community
  • 1
  • 1
Rick Hitchcock
  • 35,202
  • 5
  • 48
  • 79
  • this works great except for one issue. In one column I have a nested table and the code above seemed to be messing up that nested table . is there anyway to avoid the above affecting nested tables inside my main table? – leora May 23 '15 at 13:43
  • thanks for the update but it seems like my example is not working with your code. I think its due to the fact I have a thead section around my TH (looking at your fiddle, it looks like you don't have that) so when this line runs: $('> th, > td', tr).hide(), my TH header rows are not hidden as they are not inside tbody .. any suggesitons? – leora May 23 '15 at 23:24
  • Your example HTML doesn't have a `thead` section, but I've updated my second fiddle to take that into account. This does it: `var tr= $('#mainTable > tbody > tr, #mainTable > thead > tr');` – Rick Hitchcock May 24 '15 at 00:10
  • thanks Rick . . I have updated the question as I realized i left that out of the question .. i will give your latest update a shot – leora May 24 '15 at 00:25