How do I freeze the first and last columns of an html table in a scrollable div?

前端 未结 7 1431
天涯浪人
天涯浪人 2020-12-28 08:57

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    Scor         


        
相关标签:
7条回答
  • 2020-12-28 09:14

    Take a look at this jQuery plugin:

    http://fixedheadertable.com/

    0 讨论(0)
  • 2020-12-28 09:16

    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.

    0 讨论(0)
  • 2020-12-28 09:18

    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 )
    0 讨论(0)
  • 2020-12-28 09:20

    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.

    0 讨论(0)
  • 2020-12-28 09:25

    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.

    0 讨论(0)
  • 2020-12-28 09:26

    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)

    0 讨论(0)
提交回复
热议问题