1

I have the following table that is generated from Sharepoint:

<table class="ms-listviewtable">
   <tr>
      <th></th>
   </tr>
   <tr>
      <td></td>
   </tr>
</table>

how can I use Jquery to on document load to change the layout of this table to look like

  <table class="ms-listviewtable">
  <thead>
   <tr>
      <th></th>
   </tr>
  </thead>
  <tbody>
   <tr>
      <td></td>
   </tr>
 </tbody>
</table>
BenMorel
  • 34,448
  • 50
  • 182
  • 322
Andrew Burns
  • 346
  • 4
  • 15
  • Is this table actually completely empty, and containing exactly the HTML you posted, no content and nothing added or removed, with two rows and one column? – doppelgreener May 29 '13 at 02:49
  • sorry no. this table is full of information but I posted the basical layout for ease of purpose. – Andrew Burns May 29 '13 at 02:50

5 Answers5

4

It seems like all modern browsers automatically wrap the contents of a table in a <tbody> if you happen to omit the tag, so you'd have to do something like this:

$('table').each(function() {
    var $this = $(this);

    $this.children('tbody').children().unwrap();
    $this.children('tr:has(th)').wrapAll('<thead>');
    $this.children('tr:has(td)').wrapAll('<tbody>');
});

Test it with older browsers if you want. It seems like it'd work.

Blender
  • 289,723
  • 53
  • 439
  • 496
  • In Google Chrome, all `` will be child of `` automatically (if we don't set). So your code seems not working, check the [jsFiddle](http://jsfiddle.net/hXvss/1/) - note that `` must be in orange. –  May 29 '13 at 03:06
  • **+1** for creativeness `;)`. –  May 29 '13 at 03:26
  • Removing tbody isn't necessary, since it's not actually there. Putting it in the desired place doesn't result in an extra set of tbody tags. –  May 29 '13 at 03:39
  • Why base it on whether or not the `tr` has `th` children? It's perfectly valid for a table to have a row of `th`s to serve as column headers in the `thead`, then have a `th` in each row to serve as row headers. –  May 29 '13 at 03:43
  • @Ghodmode: That's not valid HTML. – Blender May 29 '13 at 04:05
  • 1
    @AndrewBurns: You'll have to be more specific than that. What isn't working? Is the structure turning out the right way? – Blender May 29 '13 at 04:06
  • I'm wrong. As @Blender pointed out in my answer below, you do have to remove the `tbody` in order to avoid an extra set of `tbody` tags. –  May 29 '13 at 04:46
  • @Blender ... but I'm right about `th`s being allowed in rows other than the header row. [The spec](http://developers.whatwg.org/tabular-data.html#the-th-element) even provides examples showing that. –  May 29 '13 at 04:53
  • @blender thanks for your comment. I have now found the solution. Which I have included below. – Andrew Burns May 29 '13 at 07:38
  • @Ghodmode: *Contexts in which this element can be used: As a child of a tr element.*? – Blender May 29 '13 at 12:59
3
var $t = $("table.ms-listviewtable");
if ($t.has("tbody")) $t.find("tbody").children().unwrap();
$t.find("tr:first").wrap("<thead />");
$t.find("tr").not(":first").wrapAll("<tbody />");

http://jsfiddle.net/ghodmode/p4pvT/

Update: +1 for Blender for pointing out my error. My original version did in fact create an extra set of tbody tags. The jsFiddle has been updated, too.

1

Step 1: Store the header row temporarily

First you need to isolate the header and body rows from the DOM, before we rearrange the table's internal layout.

We'll do this using jQuery's contextual selector, which you may have seen before. It works by selecting elements within a context you specify: if that context is a DOM node - the table, in this case - it selects elements within that DOM node.

Note: I use the $ prefix in JavaScript variable names to indicate variables that I'm using to store a jQuery object - which is all of the variables in this exercise. It's a personal convention that you don't have to use yourself.

var $table = $('#your-table-id');

// This gets the first <tr> within the table, and remembers it here.
var $headRow = $('tr', $table).first();
$headRow.remove();

Step 2: Create the tbody, if necessary

At this point, our work is either easier or harder. Some browsers, like Firefox, will already have interpreted the the table as having an implicit <tbody> element in which all the other rows have already been stored - if that's the case, our job is already done! Otherwise, we have a bit more work to do and need to create that <tbody> to store the current rows (all of which are not the header row).

if (!$table.has('tbody')) {
    var $otherRows = $('tr', $table);
    $otherRows.remove();

    var $tbody = $('<tbody>');
    $table.append($tbody);
    $tbody.append($otherRows);
}

Step 3: Create the thead

Now we'll insert the thead element at the start of the table, and add our table's header row to it.

var $thead = $('<thead>');
$table.prepend($thead);
$thead.append($headRow);

Now all is well.

The code in action, and minus my comments

First: JSFiddle demo

And now the code without my talking breaking it up:

var $table = $('#your-table-id');

// This gets the first <tr> within the table, and remembers it here.
var $headRow = $('tr', $table).first();
$headRow.remove();

if (!$table.has('tbody')) {
    var $otherRows = $('tr', $table);
    $otherRows.remove();

    var $tbody = $('<tbody>');
    $table.append($tbody);
    $tbody.append($otherRows);
}

var $thead = $('<thead>');
$table.prepend($thead);
$thead.append($headRow);

An iterative alternative

This will cover each table with the ms-listviewtable class in the document, rather than targeting exactly one table by ID.

$('table.ms-listviewtable').each(function() {

    var $table = $(this);

    // The rest of the code as above goes within the function here,
    // except, of course, for the line that sets the $table variable.

});
doppelgreener
  • 4,809
  • 10
  • 46
  • 63
  • 1
    This is much too easy. Do you have a version written in assembly? :P –  May 29 '13 at 03:35
  • very detailed solution thank you it helped me.. see below for my final version and my answer thanks! – Andrew Burns May 29 '13 at 07:37
  • @andrewBurns I'm glad it helped you. I just added to the end an alternative method which would iterate over every **ms-listviewtable** (as other answers do, since it's easier). That might work better for your purposes (or it might not). – doppelgreener May 29 '13 at 09:41
1

Working jsFiddle Demo

I've used this algorithm, it works for multiple tables, with multiple rows for thead and tbody.

$(function () {
    // loop through all tables with specific class
    $('.ms-listviewtable').each(function () {
        var $table = $(this);

        // find all thead rows and save the index
        var thead = [];
        $table.find('tr > th').each(function () {
            var index = $(this).closest('tr').index();
            if ($.inArray(index, thead) == -1) {
                thead.push(index);
            }
        });

        // find all tbody rows and save the index
        var tbody = [];
        $table.find('tr > td').each(function () {
            var index = $(this).closest('tr').index();
            if ($.inArray(index, tbody) == -1) {
                tbody.push(index);
            }
        });

        // make tbody and thead tags
        var $tbody = $('<tbody></tbody>');
        var $thead = $('<thead></thead>');

        // insert thead rows from table to new thead element
        $.each(thead, function (i, v) {
            $table.find('tr').eq(v).clone(true).appendTo($thead);
        });     

        // insert tbody rows from table to new tbody element
        $.each(tbody, function (i, v) {
            $table.find('tr').eq(v).clone(true).appendTo($tbody);
        });

        // make table fresh
        $table.html('');
        $thead.appendTo($table);
        $tbody.appendTo($table);
    });
});
  • Why do you populate the `thead`/`tbody` arrays? – Blender May 29 '13 at 03:20
  • @Blender Yes, you are right, it's possible to insert the rows inside the `$table.find()` and there is no need to use `thead`/`tbody` arrays, but my first analysis, was: first find the index, then work with them, so, I wrote this code `;)`. –  May 29 '13 at 03:23
  • @Blender However it would be more difficult to write, because it's possible we have multiple columns and rows (as you see, I push the values to the arrays with a condition). –  May 29 '13 at 03:25
  • How would your condition ever be not true? – Blender May 29 '13 at 03:26
  • @Blender As you see in the code, I loop through the `td` or `th` (not `tr` as I don't want it), so, for each cell I get index of it's parent and push it to the array, for the second and third column the if condition will be false. –  May 29 '13 at 03:28
0

Hi All thanks for everyones contribution to this question. I have managed to get it to work and the throw the tablesorter method around the table.

This is a solution if you ever need to make tablesorter work without the generated table have a THEAD or TBODY tags:

$(document).ready(function() {

    var $table = $('#ctl00_m_g_fb789c4e_7fc7_47b5_9be4_dfa65d4650e0_ctl00_resultsGrid');

    // This gets the first <tr> within the table, and remembers it here.
    var $headRow = $('tr', $table).first();
    $headRow.remove();

    if (!$table.has('tbody')) {
        var $otherRows = $('tr', $table);
        $otherRows.remove();

        var $tbody = $('<tbody>');
        $table.append($tbody);
        $tbody.append($otherRows);
    }

    var $thead = $('<thead>');
    $table.prepend($thead);
    $thead.append($headRow);

    $("#ctl00_m_g_fb789c4e_7fc7_47b5_9be4_dfa65d4650e0_ctl00_resultsGrid").tablesorter();
});
doppelgreener
  • 4,809
  • 10
  • 46
  • 63
Andrew Burns
  • 346
  • 4
  • 15