13

I have a table which has a header row, but also a header column and a total column with several columns in between.

Something like this:

Name Score 1 Score 2 ... Total -------------------------------------- John 5 6 86 Will 3 7 82 Nick 7 1 74 

The entire table is defined inside a fixed-width scrollable div because there are likely to be a large number of "Score" rows and I have a fixed-width page layout.

<div id="tableWrapper" style="overflow-x: auto; width: 500px;"> <table id="scoreTable"> ... </table> </div> 

What I would like is for the first (Name) and last (Total) columns to remain visible while the inner columns scroll.

Can anyone help me with this?

Edit: I mean horizontal scrolling only - changed to specify that.


Update: I've solved this problem for myself and have posted the answer below. Let me know if you need any more information - this was a bit of a pain to do and I'd hate for someone else to have to rewrite everything.

2
  • why have the tablewrapper don't overuse divs... Commented Apr 13, 2009 at 13:01
  • Correct me if I'm wrong, but I think I need it if I want the entire table to scroll. That may change if I can find a solution to the problem though. Commented Apr 13, 2009 at 13:05

7 Answers 7

5

Can I propose a somewhat unorthodox solution?

What would you think about placing the 'total' column after the 'name' column, rather than at the very end? Wouldn't this avoid the requirement for only a portion of the table to scroll?

It's not exactly what you're asking for, but perhaps it is a sufficient solution, given that the alternative would be pretty messy. (Placing the 'total' and 'name' columns outside of the table, for instance, would create alignment problems when not all rows are of equal height. You could correct this with javascript but then you'd be entering a whole new world of pain).

Also from a UI perspective, it may be that 'name' and 'total' are the most important data, in which case it would make sense to put them together, followed by a sort of 'breakdown' of the total. Of course, we seem to have an intuition that a 'total' should come after its constituent parts, but I don't think it would cause too much confusion to the user if the order were reversed like this (though this is a question for you, based on your product and your users).

Anyway, something to consider.

EDIT:

Here are some more unorthodox solutions, now that I think I understand your intentions a bit better:

  1. Paginate the scores. Give the most recent ten, say, and the total, and a link to older scores, which are provided 10 at a time
  2. Only give names, totals, and some other meaningful measures such as mean and sd, then provide a link for each name that shows all results corresponding to that name. You could then also provide a link showing all results for a given score set, so that comparisons between different users can be made. The point is that you'd only have to give 1 dimension of data for each view, rather than having an unwieldy 2D data set
  3. Make the rows sortable (easy with jQuery UI) so that if I want to compare Mary to Jane, I can drag and place one after the other, so I wont need to keep scrolling left and right to see which scores correspond to which names
  4. Highlight a row when it is clicked, by changing the background color or similar, again so I don't need to keep scrolling left and right.

Anyway you get the idea. Perhaps it is better to look for a UI solution than a contorted markup solution. Ultimately I would be questioning how important it is to present so much data to the user at once, that a portion of it needs to scroll in a particular fashion for the data to be readable. Perhaps you're building a spreadsheet app, and you really do need to display a 100x100 matrix in a single view. If not, you could surely come up with more creative ways than I have to split up the results.

Sign up to request clarification or add additional context in comments.

4 Comments

True - the total could be put at the start and it would make sense. It really comes down to what I want the users to see - while they browse through the other columns, it'd be good for them to be able to see the name and the total. I'd still want to only scroll the rest.
I guess if you want to scroll the rest anyway it doesn't make all that much of a difference. I thought your main concern was that a user might not realise that the 'total' column exists if they are required to scroll to it.
No, more that if they want to compare people's scores, they'd need to keep scrolling back and forth between the name and the later score columns or total.
This is a good answer, to look at it from a different perspective +1
2

It's not complete right now (I just mocked it up really quick), but here's a way of doing it using straight HTML. You want three tables ... two outside the <div> and one inside the <div>. All three are floated to the left, so that they're all on the same line.

Table Test

and the code itself:

 <table style="float: left;"> <tr> <td>Name</td> </tr> <tr> <td>John</td> </tr> <tr> <td>Will</td> </tr> </table> <div id="tableWrapper" style="overflow-x: auto; width: 500px;float:left;padding-bottom: 10px;"> <table> <tr> <td>Score1</td> <td>Score2</td> ... </tr> <tr> <td>5</td> <td>6</td> ... </tr> <tr> <td>3</td> <td>7</td> ... </tr> <tr> <td>7</td> <td>1</td> ... </tr> </table> </div> <table style="float: left;"> <tr> <td>Total</td> </tr> <tr> <td>86</td> </tr> <tr> <td>82</td> </tr> </table> 

Note: The padding-bottom on the div is so that the scrollbar does not cover up the table in IE. Also, the bug I have to work out is how to specify the width of the header elements inside the middle table. For some reason, specifying the width="" attribute does not work. Thus, if the text of the header element is too wide (and breaks onto another line), then the layout is broken (off by one row)

2 Comments

as I said, that's impossible to achieve over all major browsers without JavaScript. You may want to synchronize all height values of all rows in a loop, and you may want to do that again over events or in an interval if things change. another option would be position absolute and fixed with in div.
True - good answer though and something to consider if I can control the header height somehow...
2

I assume you want to scroll it horizontally only. Otherwise it could be confusing with static columns.

You could put the overflow: auto onto a containing element of the inner table cells only... I'm not sure how browsers would handle this, but you may be able to put the elements you want fixed inside thead and tfoot elements, and put the scrolling portion inside a tbody element, and set it's overflow to auto.

Failing that, you may need to drop semantics and code the left column and right column outside the table.

Personally, I'd try code it as semantic as possible, and then use JavaScript to position the left and right columns. Without JavaScript, you should make it fail gracefully to just a wide table (not sure how difficult this would be, as you say you have a fixed width)

You could try this (rough) jQuery example to put the first column's values outside.

var nameTable = '<table id="outside-name-col"> <thead> <tr> <th>Name</th> </tr> </thead> <tbody>'; // start making a table for name column only $('#scoreTable tbody tr').each(function() { // iterate through existing table var nameCol = $(this).find(':first'); // get column of index 0, i.e. first var cellHeight = nameCol.height(); // get the height of this cell $(this).find('td').height(cellHeight); // equalise the height across the row so removing this element will not collapse the height if it is taller than the scores and total nameTable += '<tr><td style="height: ' + cellHeight + 'px">' + nameCol.html() + '</td></tr>'; // append the next row with new height and data nameCol.remove(); // remove this cell from the table now it's been placed outside }); nameTable += '</tbody></table>'; // finish table string $('#scoreTable').before(nameTable); // insert just before the score table 

Use CSS to position them to align correctly. This is untested, but it should give you some idea.

17 Comments

Sorry, yes - scrolling horizontally only, but it expands vertically at the moment anyway :)
As with my comment for phoenix's answer, I don't think I can do it with html. The <td> tags aren't defined consecutively in the html so I can't really wrap them with a div.
I would leverage jQuery to do it... I'd cut out the first and last columns and position them outside the table and then set the table to overflow-x: auto
Cool, I think that's what I'll look at doing. Any suggestions on how to do that? :)
That will not work in many browsers. There are better but harder solutions.
|
2

I've experimented with a few methods (thanks to everyone who helped) and here's what I've come up with using jQuery. It seems to work well in all browsers I tested. Feel free to take it and use it however you wish. Next step for me will be turning it into a reusable jQuery plugin.

Summary:

I started with a normal table with everything in it (Id="ladderTable"), and I wrote Three methods - one to strip the first column, one to strip the last column, and one to fix the row heights.

The stripFirstColumn method creates a new table (Id="nameTable"), traverses the original table and takes out the first column, and adds those cells to the nameTable.

The stripLastColumn method does basically the same thing, except it takes out the last column and adds the cells to a new table called totalTable.

The fixHeights method looks at each row in each table, calculates the maximum height, and applies it to the related tables.

In the document ready event, I called all three methods in order. Note that all three tables float left so they'll just stack horizontally.

The HTML Structure:

<h1>Current Ladder</h1> <div id="nameTableSpan" style="float:left;width:100px;border-right:2px solid gray;"></div> <div id="ladderDiv" style="float:left;width:423px;overflow:auto;border:1px solid gray;margin-top:-1px;"> <table id="ladderTable" class="ladderTable"> <thead> <tr><td>Name</td><td>Round 1</td> ... <td>Round 50</td><td class="scoreTotal">Total</td></tr> </thead> <tr><td>Bob</td><td>11</td> ... <td>75</td><td>421</td></tr> ... (more scores) </table> </div> <div id="totalTableSpan" style="float:left;width:70px;border-left:2px solid gray;"></div> 

The jQuery:

function stripFirstColumn() { // pull out first column: var nt = $('<table id="nameTable" cellpadding="3" cellspacing="0" style="width:100px;"></table>'); $('#ladderTable tr').each(function(i) { nt.append('<tr><td style="color:'+$(this).children('td:first').css('color')+'">'+$(this).children('td:first').html()+'</td></tr>'); }); nt.appendTo('#nameTableSpan'); // remove original first column $('#ladderTable tr').each(function(i) { $(this).children('td:first').remove(); }); $('#nameTable td:first').css('background-color','#8DB4B7'); } function stripLastColumn() { // pull out last column: var nt = $('<table id="totalTable" cellpadding="3" cellspacing="0" style="width:70px;"></table>'); $('#ladderTable tr').each(function(i) { nt.append('<tr><td style="color:'+$(this).children('td:last').css('color')+'">'+$(this).children('td:last').html()+'</td></tr>'); }); nt.appendTo('#totalTableSpan'); // remove original last column $('#ladderTable tr').each(function(i) { $(this).children('td:last').remove(); }); $('#totalTable td:first').css('background-color','#8DB4B7'); } function fixHeights() { // change heights: var curRow = 1; $('#ladderTable tr').each(function(i){ // get heights var c1 = $('#nameTable tr:nth-child('+curRow+')').height(); // column 1 var c2 = $(this).height(); // column 2 var c3 = $('#totalTable tr:nth-child('+curRow+')').height(); // column 3 var maxHeight = Math.max(c1, Math.max(c2, c3)); //$('#log').append('Row '+curRow+' c1=' + c1 +' c2=' + c2 +' c3=' + c3 +' max height = '+maxHeight+'<br/>'); // set heights //$('#nameTable tr:nth-child('+curRow+')').height(maxHeight); $('#nameTable tr:nth-child('+curRow+') td:first').height(maxHeight); //$('#log').append('NameTable: '+$('#nameTable tr:nth-child('+curRow+')').height()+'<br/>'); //$(this).height(maxHeight); $(this).children('td:first').height(maxHeight); //$('#log').append('MainTable: '+$(this).height()+'<br/>'); //$('#totalTable tr:nth-child('+curRow+')').height(maxHeight); $('#totalTable tr:nth-child('+curRow+') td:first').height(maxHeight); //$('#log').append('TotalTable: '+$('#totalTable tr:nth-child('+curRow+')').height()+'<br/>'); curRow++; }); if ($.browser.msie) $('#ladderDiv').height($('#ladderDiv').height()+18); } $(document).ready(function() { stripFirstColumn(); stripLastColumn(); fixHeights(); $("#ladderDiv").attr('scrollLeft', $("#ladderDiv").attr('scrollWidth')); // scroll to the last round }); 

If you have any questions or if there's anything that wasn't clear, I'm more than happy to help.

It took me quite a while to work out that there was nothing that I could really reuse and it took a bit longer to write this. I'd hate for someone to go to the same trouble.

1 Comment

very cool i found a simpler way round this ... stackoverflow.com/questions/6827288/…
2

Take a look at this jQuery plugin:

http://fixedheadertable.com/

Comments

1

Did u mean horizontal scrolling??

If you want to achieve horizontal scrolling then you can use 3 containers.

  1. For the first column ( Name )
  2. For the columns that you want to scroll. Set the overflow-x style of this container to auto
  3. For the last column ( Total )

4 Comments

I think what he means is that he wants the Name and Total columns to stay in place, while Score1, Score2, etc, all scroll. This is possible in MS Excel, and I see no reason why it would be confusing to the user. However, I have no clue as to how to accomplish it.
Yes, that's what I'm after, but I'm not sure this'll accomplish it. The difficulty with putting containers around the sections (apart from invalid html) is that columns aren't consecutively defined. The <td> tag repeats for each row.
It could be better to stuck with your initial design itself. Because if you alter your middle columns then you have to take into consideration the height of the scroll bars too, so that every item in a single row aligns to the same line.
The div has no fixed height though - I'm happy for the height to expand as it does.
0

Scrolling the content of tables is an problematic issue, since there is no simple and browser-safe solution to achieve that.

The best and most secure way to do it requires JavaScript. You would achieve even horizontal Scrolling easily (it's included for free) with this technique I used. You can transform that to your problem easily:

1) I made 3 Tables

2) Placed the middle Table that holds the Data inside a DIV container

3) set the DIV style to overflow:auto

4) gave the header and footer table and the div each a width of 100%

5) surrounded everything by another div to control the overall table width

6) made very sure that the quantity of columns match in all three tables, and that all three have the same styles regarding paddings, margins and borders

now the interesting part:

6) below the last element of that table construction, write a javascript block (or include it from a file)

7) write a function that successively looks at each column (use a for loop), starting with the first one. At every iteration of that loop, check the td cell with of the first two tables in the current column. Check which with is the bigger one and assign that to the smaller td cell's style. You may want to apply that value to the footer table, too.

8) call that function. You may want to do that in an interval or when special events occur, such as new data in the cells (if using ajax for example).

Note: If you do scroll horizontally, your columns sizes are synchronized. But you will have to synchronize the scrolling position of your header and footer table with the content table. Alternatively you just let the overall div scroll the whole thing horizontally, but not vertically. That would be the job of the DIV that contains the data table.

This technique worked very well in all major browsers in an very coplex gantt diagram project.

Note: You can think of that concept easily in a 90 degrees rotated way, so that you fix the first and last header column when scrolling.

Edit: Here is another example from my code base that might help you to get the solution:

var left_td = document.getElementById("left_td"); var right_td = document.getElementById("right_td"); var contentDiv = document.getElementById("contentDiv"); window.setInterval(function(){ var left_td_width = Math.round((parseInt(contentDIV.offsetWidth) - 120) / 2); var right_td_width = (parseInt(contentDIV.offsetWidth) - left_td_width - 120; left_td.style.width = left_td_width + "px"; right_td.style.width = right_td_width + "px"; }, 25); 

This code does the following: It makes sure, that an Table with 3 columns always looks like that: The first and last column have the same size, and the center column has a width of 120 pixels. Sure that does not apply directly to your special problem, but It might give you a starting point in dynamic table manipulation through JavaScript.

4 Comments

Correct me if I'm wrong, but this seems to be a solution for a freezing a header row and footer row rather than a header column?
This is precisely the 'whole new world of pain' that I was referring to in my answer.
Ok... I don't want to freeze rows though, only columns. I'm starting to see that there's a world of pain involved :)
sorry I've got confused by rows and columns, but I've updated the description a little bit. Well, it's not really painful. It's hard to start, but it's fun doing it.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.