Welcome to CrankyGoblin.Com Sign in | Join | Help

Public Class GeoffAppleby

Inherits Microsoft.VisualBasic.MVP : Implements IBrainFart
Dynamic Table Column Resizing in IE

I wrote about this very subject in the past. That old blog entry was showing the code from the first few drafts of getting this working right.

Since then, many people have read it, and many have asked questions, and I've been continually promising to give an update of the end result of how it came out. The credit doesn't go to me however, as much as I'd like to think so. Noonie (my boss) wrote the first drafts, and Rory refined it. However, since I posted Noonie's first draft, I get to post the update.

Note: Please remember, this only works in Internet Explorer. There's several good reasons why this doesn't work in Firefox (all to do with the client-side javascript) and these include window.attachEvent() and element.parentElement(). I haven't looked at making a Firefox version yet.

Extensibility options: This example works perfectly. On top of that, you can combine it with other tricks - like, say, TBODY scrolling :) The two don't interfere with each other.

So, there easiest way is to simply list all the code, and describe what's going on.

There's four important points to remember about it all.

  1. Based on how the code is written, the table that has resizable columns must be contained in some other element (in these examples I use a DIV) and that element must have a style set of "position: absolute;". You need this to size the black vertical bar that attaches to the mouse correctly when performing an actual resize.
  2. Based on how the code is written, you can only perform resize operations on table header cells (TH tags). This simply feels like the right way to do it - you could make it TD tags instead, and be able resize the column for the entire height of the table if you wanted to.
  3. The actual table must have a style set of "table-layout: fixed;" If you don't set this style, strange things happen (try it and see, it goes all loopy when you resize :)
  4. To save having to remember to put too many requirements in the implementation, the black vertical bar is created dynamically. It's important to leave the creation of this element until the page's onload event fires - if it runs before it finishes loading, you IE sometime throws an error trying to call document.body.appendChild() - it took us ages to debug this!

So long as you remember these three things, it should all work smoothly.

So, first lets start with some simple styles

.tablecontainer
{
    position: absolute;
}

.mytable
{
    table-layout: fixed;   
}

.mytable TD, .mytable TH
{
    border: solid 1px black;
    width: 120px;
}

.mytable TH
{
    background-color: #e0e0e0;   
}

The tablecontainer class  is set to absolute positioning (see point 1 above). The mytable class has a table-layout of fixed (see point 3 above). The rest is only there for prettiness.

Now, the HTML. The styles listed above are referenced in tabletest.css. Javascript functions are all contained in tableresize.js.

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>

<head>
    <title>Table Test</title>
    <link rel="stylesheet" type="text/css" href="tabletest.css" />
    <script type="text/javascript" src="tableresize.js"></script>
</head>

<body>
<div class="tablecontainer">
    <table border="0" cellspacing="0" cellpadding="0" class="mytable"
        onmousemove="TableResize_OnMouseMove(this);"
        onmouseup="TableResize_OnMouseUp(this);"
        onmousedown="TableResize_OnMouseDown(this);">
        <tr>
            <th>Column 1</th><th>Column 2</th><th>Column 3</th><th>Column 4</th><th>Column 5</th>
        </tr>
        <tr>
            <td>Some Data</td><td>Some Data</td><td>Some Data</td><td>Some Data</td><td>Some Data</td>
        </tr>       
        <tr>
            <td>Some Data</td><td>Some Data</td><td>Some Data</td><td>Some Data</td><td>Some Data</td>
        </tr>       
        <tr>
            <td>Some Data</td><td>Some Data</td><td>Some Data</td><td>Some Data</td><td>Some Data</td>
        </tr>       
        <tr>
            <td>Some Data</td><td>Some Data</td><td>Some Data</td><td>Some Data</td><td>Some Data</td>
        </tr>       
        <tr>
            <td>Some Data</td><td>Some Data</td><td>Some Data</td><td>Some Data</td><td>Some Data</td>
        </tr>       
        <tr>
            <td>Some Data</td><td>Some Data</td><td>Some Data</td><td>Some Data</td><td>Some Data</td>
        </tr>       
        <tr>
            <td>Some Data</td><td>Some Data</td><td>Some Data</td><td>Some Data</td><td>Some Data</td>
        </tr>       
        <tr>
            <td>Some Data</td><td>Some Data</td><td>Some Data</td><td>Some Data</td><td>Some Data</td>
        </tr>       
    </table>
</div>

</body>
</html>

As you can see, it's just a simple table contained in a DIV. It's really  nothing special. The trick is in the javascript that follows. It's fairly well commented, so there shouldn't be too much to say about it.

/*

Table Resizing code

*/

/*
Global constants to store elements that may be resized but we
could probably place these into custom table attributes instead.
*/
var sResizableElement = "TH";    // This MUST be upper case
var iResizeThreshold = 8;
var iEdgeThreshold = 8;
var iSizeThreshold = 20;
var sVBarID = "VBar";

/*
Global variables to store position and distance moved but we
could probably place these into custom table attributes instead.
*/
var oResizeTarget = null;
var iStartX = null;
var iEndX = null;
var iSizeX = null;

/*
Helper Functions
*/

/*
Creates the VBar on document load
*/
function TableResize_CreateVBar()
{
    // Returns a reference to the resizer VBar for the table
    var objItem = document.getElementById(sVBarID);

    // Check if the item doesn't yet exist
    if (!objItem)
    {       
        // and Create the item if necessary
        objItem = document.createElement("SPAN");

        // Setup the bar
        objItem.id = sVBarID;
        objItem.style.position = "absolute";
        objItem.style.top = "0px";
        objItem.style.left = "0px";
        objItem.style.height = "0px";
        objItem.style.width = "2px";
        objItem.style.background = "silver";
        objItem.style.borderLeft = "1px solid black";
        objItem.style.display = "none";

        // Add the bar to the document
        document.body.appendChild(objItem);
    }
}

window.attachEvent("onload", TableResize_CreateVBar);

/*
Returns a valid resizable element, even if it contains another element
which was actually clicked otherwise it returns the top body element.
*/
function TableResize_GetOwnerHeader(objReference)
{
    var oElement = objReference;

    while (oElement != null && oElement.tagName != null && oElement.tagName != "BODY")
    {
        if (oElement.tagName.toUpperCase() == sResizableElement)
        {
            return oElement;
        }

        oElement = oElement.parentElement;
    }

    // The TH wasn't found
    return null;
}

/*
Find cell at column iCellIndex in the first row of the table
needed because you can only resize a column from the first row.
by using this, we can resize from any cell in the table if we want to.
*/
function TableResize_GetFirstColumnCell(objTable, iCellIndex)
{
    var oHeaderCell = objTable.rows(0).cells(iCellIndex);

    return oHeaderCell;
}

/*
Clean up - clears out the tracking information if we're not resizing.
*/
function TableResize_CleanUp()
{
    // Void the Global variables and hide the resizer VBar.
    var oVBar = document.getElementById(sVBarID);

    if (oVBar)
    {
        oVBar.runtimeStyle.display = "none";
    }

    iEndX = null;
    iSizeX = null;
    iStartX = null;
    oResizeTarget = null;
    oAdjacentCell = null;

    return true;
}

/*
Main Functions
*/

/*
MouseMove event.
On resizable table This checks if you are in an allowable 'resize start' position.
It also puts the vertical bar (visual feedback) directly under the mouse cursor.
The vertical bar may NOT be currently visible, that depnds on if you're resizing.
*/
function TableResize_OnMouseMove(objTable)
{
    // Change cursor and store cursor position for resize indicator on column
    var objTH = TableResize_GetOwnerHeader(event.srcElement);

    if (!objTH)
        return;

    var oVBar = document.getElementById(sVBarID);

    if (!oVBar)
        return;

    var oAdjacentCell = objTH.nextSibling;

    // Show the resize cursor if we are within the edge threshold.
    if ((event.offsetX >= (objTH.offsetWidth - iEdgeThreshold)) && (oAdjacentCell != null))
    {
        objTH.runtimeStyle.cursor = "e-resize";
    }
    else
    {
        if(objTH.style.cursor)
        {
            objTH.runtimeStyle.cursor = objTH.style.cursor;
        }
        else
        {
            objTH.runtimeStyle.cursor = "";
        }
    }

    // We want to keep the right cursor if resizing and
    // don't want resizing to select any text elements...
    if (oVBar.runtimeStyle.display == "inline")
    {
        // We have to add the body.scrollLeft in case the table is wider than the view window
        // where the table is entirely within the screen this value should be zero...
        oVBar.runtimeStyle.left = window.event.clientX + document.body.scrollLeft;

        document.selection.empty();
    }

    return true;
}

/*
MouseDown event.
This fills the globals with tracking information, and displays the
vertical bar. This is only done if you are allowed to start resizing.
*/
function TableResize_OnMouseDown(objTable)
{
    // Record start point and show vertical bar resize indicator
    var oTargetCell = event.srcElement;

    if (!oTargetCell)
        return;

    var oVBar = document.getElementById(sVBarID);

    if (!oVBar)
        return;

    if (oTargetCell.parentElement.tagName.toUpperCase() == sResizableElement)
    {
        oTargetCell = oTargetCell.parentElement;
    }

    var oHeaderCell = TableResize_GetFirstColumnCell(objTable, oTargetCell.cellIndex);

    if ((oHeaderCell.tagName.toUpperCase() == sResizableElement) && (oTargetCell.runtimeStyle.cursor == "e-resize"))
    {       
        iStartX = event.screenX;
        oResizeTarget = oHeaderCell;

        // Mark the table with the resize attribute and show the resizer VBar.
        // We also capture all events on the table we are resizing because Internet
        // Explorer sometimes forgets to bubble some events up.
        // Now all events will be fired on the table we are resizing.
        objTable.setAttribute("Resizing", "true");
        objTable.setCapture();

        // Set up the VBar for display

        // We have to add the body.scrollLeft in case the table is wider than the view window
        // where the table is entriely within the screen this value should be zero...
        oVBar.runtimeStyle.left = window.event.clientX + document.body.scrollLeft;

        oVBar.runtimeStyle.top = objTable.parentElement.offsetTop + objTable.offsetTop;;
        oVBar.runtimeStyle.height = objTable.parentElement.clientHeight;
        oVBar.runtimeStyle.display = "inline";
    }

    return true;
}

/*
MouseUp event.
This finishes the resize.
*/
function TableResize_OnMouseUp(objTable)
{
    // Resize the column and its adjacent sibling if position and size are within threshold values
    var oAdjacentCell = null;
    var iAdjCellOldWidth = 0;
    var iResizeOldWidth = 0;

    if (iStartX != null && oResizeTarget != null)
    {
        iEndX = event.screenX;
        iSizeX = iEndX - iStartX;

        // Mark the table with the resize attribute for not resizing
        objTable.setAttribute("Resizing", "false");

        if ((oResizeTarget.offsetWidth + iSizeX) >= iSizeThreshold)
        {
            if (Math.abs(iSizeX) >= iResizeThreshold)
            {
                if (oResizeTarget.nextSibling != null)
                {
                    oAdjacentCell = oResizeTarget.nextSibling;
                    iAdjCellOldWidth = (oAdjacentCell.offsetWidth);
                }
                else
                {
                    oAdjacentCell = null;
                }

                iResizeOldWidth = (oResizeTarget.offsetWidth);
                oResizeTarget.style.width = iResizeOldWidth + iSizeX;

                if ((oAdjacentCell != null) && (oAdjacentCell.tagName.toUpperCase() == sResizableElement))
                {
                    oAdjacentCell.style.width = (((iAdjCellOldWidth - iSizeX) >= iSizeThreshold)?(iAdjCellOldWidth - iSizeX):(oAdjacentCell.style.width = iSizeThreshold))
                }
            }
        }
        else
        {
            oResizeTarget.style.width = iSizeThreshold;
        }
    }

    // Clean up the VBar and release event capture.
    TableResize_CleanUp();
    objTable.releaseCapture();

    return true;
}

And that's all there is to it. There's on function called on mouse down, one on mouse move, and one on mouse up. The resizing happens in the mouse up event.

In my first post about doing this, I had resizable attributes marking which columns could and couldn't be resized. While you could still do this, we realised it was pretty silly - either you've attached the resize code to the table onmouse* events, or you haven't - surely all columns would be resizable. However, there's nothing to stop you putting extra checks in yourself. I also previously had double-click functionality to 'resize to fit' in there before - this is still pretty nasty, as we didn't bother getting it perfect, since we didn't need it. I'll leave that as an exercise for you to do :)

Posted: Wednesday, February 09, 2005 8:30 PM by Geoff Appleby
Filed under:

Comments

TrackBack said:

# February 9, 2005 5:58 AM

TrackBack said:

# February 9, 2005 6:01 AM

Antonio said:

Hello Jeoff,

Thanks for your awesome example.

Could it be possible that you offered a link to a working example (I know I'm asking to much)

Thanks,
Antonio

# February 10, 2005 7:05 PM

Geoff Appleby said:

If i get a chance, I might - but it should be quite easy for you to just copy and paste the three code boxes - them in themselves are a working example right there :)
# February 10, 2005 7:51 PM

William Liu said:

Hello Geoff,
Thanks for the great code.
Is it possible to make it work in Netscape?

Thanks,
William
# June 29, 2005 8:03 PM

Geoff Appleby said:

Hi William,

By netscape, do you mean firefox? Either way, I'm sorry, as it stands no mozilla based browser can do it.

I'm not a firefox expert, and certainly haven't had the time to look at working out a port. Maybe there's some people who know it better than I do that can work on a port?

--Geoff
# June 30, 2005 2:47 AM

Shardul Kulkarni said:

Hi Geoff,
Thanks for this nifty piece of code. :) It's just what I had been looking for. I just wanted to know if there are any restrictions on its use. Can I use this in my project?

Thanks.
Shardul.
# July 5, 2005 4:48 AM

Geoff Appleby said:

Hi Shardul.

Go for it! If, perchance, you decided this was extremely valuable and you wanted to send a donation my way, I''m sure i'd not complain, it is neither required nor expected.

I put code snippets up for people to take and enjoy :)

--Geoff
# July 5, 2005 4:37 PM

Mike said:

Thanks Geoff, great code.

Can you think of a way NOT to decrease the next sibling?

I.E. Increase the total width of the table and not eat into the other table columns

Mike
# August 9, 2005 6:09 AM

Geoff Appleby said:

Hey Mike,

Actually, yes, I think this is doable. I'm pretty sure I had something like it going at one stage.

Mess around with the table styles. It might be as simple as removing the table-layout: fixed; style. You most likely won't be able to do it if you specify a width for the table at the same time - but give it a shot.

But I don't remember. Let me know if you get it - i'll post back here if I have time to figure it out myself :)
# August 9, 2005 7:09 AM

Mike said:

Hi Geoff,

I had a poke around in the JavaScript:

Commenting out these lines from the TableResize_OnMouseUp Event Handler gave me the dssired result...

//if ((oAdjacentCell != null) && (oAdjacentCell.tagName.toUpperCase() == sResizableElement))
//{
// oAdjacentCell.style.width = (((iAdjCellOldWidth - iSizeX) >= iSizeThreshold)?(iAdjCellOldWidth - iSizeX):(oAdjacentCell.style.width = iSizeThreshold))
//}


Many Thanks, Mike
# August 10, 2005 10:42 AM

Rob said:

This is really cool. A colleague grabbed an early version which I've adapted for firefox and IE. Thank god I didn't have to figure it out from scratch!

Cheers.
# October 10, 2005 1:21 PM

DeKin said:

Thanks for a good piece of code! It really works well even for a thousands of rows.
# November 6, 2005 9:41 AM

spaceman said:

I noticed that Rob mentioned that he created a version which works in FF. I would be very, very interetsed in such a version.

I'd be willing to give a generous donation to whomever can:

1) make it compatible with FireFox
2) make the column headers sortable

P.S. - I've also created a very cool web page which I'd be willing to share for free. It uses a whole ton of custom JS to build a dashboard with movable boxes, resizable windows, scrollable objects, drag and drop, etc.

:-) Spencer
spencers@msoft.com
# December 6, 2005 7:13 PM

Raman said:

Thanks, It is really very good stuff.
# March 25, 2006 9:30 AM

Mahesh said:

Does any one have working code of this for Firefix too.
# April 3, 2006 8:32 PM

Griff said:

I'd appreciate it if someone did have it working for Firefox and wouldn't mind sharing ;-)

Griff

the_judge_g4@hotmail.com

# March 30, 2007 6:33 AM

David said:

I would really appreciate a Firefox version.

calmze@yahoo.com

# June 27, 2007 5:26 AM

Ram said:

Can anyone please convert this into the firefox version.  I got stuck on the TableResize_OnMouseMove where the event object is good for IE but not for Mozilla.

# January 15, 2008 3:05 PM

Work from home. said:

Legitimate work at home jobs. Home work at home http. Free work at home. Work home time tracking. Work at home. At home work http. Work from home mlm business opportunity.

# June 11, 2008 11:25 PM

url « Its Mohan here said:

# September 5, 2008 3:21 AM

url « Its Mohan here said:

# September 5, 2008 3:22 AM
Leave a Comment

(required) 

(required) 

(optional)

(required) 

To submit your comment, click on these pictures:
  • Geoff's bald spot
  • Sleepy Geoff
  • Searching Geoff
Gaptcha Image - No Peeking! Gaptcha Image - No Peeking! Gaptcha Image - No Peeking!
Gaptcha Image - No Peeking! Gaptcha Image - No Peeking! Gaptcha Image - No Peeking!
Gaptcha Image - No Peeking! Gaptcha Image - No Peeking! Gaptcha Image - No Peeking!
Can't recognise the people in these pictures? Look here for a quick introduction.
There's a time limit for you to get your comment submitted before this set of pictures expires. If you think it's been longer than 10 minutes, get some new pictures first (you won't lose what you've typed so far).
Get some new pictures 

Comment Notification

If you would like to receive an email when updates are made to this post, please register here

Subscribe to this post's comments using RSS