Wednesday, September 19th, 2007

Freeze Pane Functionality

Category: JavaScript

<>p>Markku Uttula was tasked with Excel-like freeze pane functionality, which seems like it would be as simple as a few fixed positions.

He ended up with a piece of code that allows you to take almost any table, choose a cell to use as a pivot, and be done with it via:

javascript
< view plain text >
  1. fpInitFreezePanes('fp','fpdiv','pivot', true);

Freeze Panes

Related Content:

19 Comments »

Comments feed TrackBack URI

damn slow

Comment by Anonymous — September 19, 2007

I like the functionality, but having 4 copies of the same table sitting on top of each other is overkill and very slow.

Why not just clone the cells that make up that section of the top or bottom, or clone the entire thing and then remove cropped items. I realize that with your implementation that this can be used on non-tables as well.

perhaps setting style.crop may perform better then overflow:hidden

I created something similar called scrollTable with three divs and three separate tables where the cell width/height synced, but I like your idea of placing it on top with position:static.

Also for the ie bug of position:fixed, try
fpTableContainerDivElement.style.setExpression(“width”, “functionName()”);

With setExpression, IE will fire off the function when any DOM event happens and it will allow you to re-position the items while scrolling rather then jumping after

Comment by Steven Moberg — September 19, 2007

The design considerations for this were not targeted towards speed, but rather uniformity across “recent” browsers and the relative “ease of use” (after all, it is just a technology demo, and as such, it does have its limitations). The code is less than 100 lines (of which about 10 lines are used just for styling – though I must say that the demo still looks awful:), requires no libraries (if libraries were to be used, it could be made even smaller than it is, because libraries provide much of the functionality needed).

I’m interested in comments and suggestions on improving the code. Also, bug reports are welcome (I’m sure there are a plethora of them hiding in there). My eMail address, in case you wish to contact me directly, is my_first_name dot my_last_name at disconova.com

Comment by Markku Uttula — September 19, 2007

@Steven: I actually have some code that attempts to remove the items that will never be visible, but I never really got it to work as I wanted (removing the non-existent cells changes the table’s layout and the row heights and column widths varies between the fixed and non-fixed portions). A great idea on using the setExpression, though.

Comment by Markku Uttula — September 19, 2007

I don’t really understand why there is four cloned tables used, seems inefficient. I implemented the same thing a couple years ago..the technique I used was to move any fixed cells to new tables that I would generate, and then align the tables against each other so that it was seamless. That way you don’t get any duplicates and you don’t get the cosmetic bugs.

Comment by Andy Kant — September 19, 2007

I know this is only for IE, but it might help some people …
http://web.tampabay.rr.com/bmerkey/examples/locked-column-csv.html

Comment by AC — September 19, 2007

In firefox, the top pane goes completely over the scrollbar!

Comment by James Murray — September 19, 2007

@Steven: About the four copies; I’ve been playing with the idea of extracting the necessary TR-elements into display:block -items and thus separating them from the table. For the frozen columns, I propably could do something similar by generating COL- or COLGROUP-elements dynamically. Haven’t tested any of it yet, though. I think however, that doing this would break the row/column sizing. I did implement your suggestion on the style.setExpression, which made a rather impressive speed improvement on IE (too bad I can’t think of how to do something similar with Mozilla/Opera; on those browsers, the “jerkiness” of the frozen panes is because just before the onScroll-event fires, the document is reflowed, only after which, I can reset the margins of the frozen elements, causing yet another reflow).

@Andy: The original reasoning was that doing it that way, even when the layout of the table were to change, the row and column positioning would still be seamless. I now realise that there is a rather obvious shortcoming on the approach, since the panes are frozen, no matter what size the client screen is, the layout of the table will not change (unless the content in the cells changes, which is another problem with the chosen approach alltogether).

@AC: Yes, that is more or less exactly the functionality the people who wanted this were looking for. Unfortunately for me, only supporting the worst of today’s browsers simply was not acceptable solution :)

@James: Yes, that was rather freaky when I first noticed it myself. The same happens on Opera (note that the scrollbars are not that of the page, but that of the containing DIV-element).

Comment by Markku Uttula — September 19, 2007

Seems like this isn’t limited to freezing panes of a table element like steven noted. The code requires a container, element to freeze, and a pivot point for freezing. It doesn’t really even seem require the pivot point to be within the container, but I don’t know if that would make any sense to use it that way?

Comment by Marco — September 19, 2007

Markku,
The example posted by AC (IE only) uses the setExpression for the left and top pane though a CSS style “expression” and position:relative. You should be able to produce the same effect for mozilla with a event Handler for the onscroll.


/* css */
td.fixed, th.fixed { position:relative; z-index:10; }

/* javascript */
pivot_init = true;

function setTop(){
for each cell in top row {
if(pivot_init) cell.className += " fixed";
cell.style.marginTop = (pivot_ie)?(container.scrollTop-2) + 'px';
}
for each cell in left column {
if(pivot_init) cell.className += " fixed";
cell.style.marginLeft = container.scrollLeft + 'px';
}
pivot_init = false;
return 0; // for IE setExpression top
}

if(div.attachEvent){
div.style.setExpression("top", "setTop()")
} else if(div.addEventListener){
div.addEventListener("scroll", setTop, false);
window.addEventListener("resize", setTop, false);
}

I think allot of the jittering is the lag in the browser re-rendering the CSS changes (4 clones). Kind of like running an animation script while adding items to the DOM. I had a similar thing happen in IE with the following style


td.vertical {
writing-mode: tb-rl;
filter: flipv fliph;
}

The writing-mode renders fine in IE, but when the filter:flipv fliph is applied it takes additional processing to render and produces a jittery lag in my onscroll method to synchronize the panels. — By the way. have you tested CSS CROP versus OVERFLOW:HIDDEN to see if it makes a difference in rendering. — just a thought.

Comment by Steven Moberg — September 19, 2007

@Steven: As far as I’ve understood, using “overflow:hidden” actually does nothing else than sets the element’s “clip” (there is no “crop”, I think?) to be equal to the defined client area. Haven’t tested it, but I don’t see how this could affect the performance at any noticeable rate. However, as we’ve seen how strange things affect JS performance, I think I’ll need to do a camp test on this eventually :)

And using relatively positioned elements… it took me a while to realise how usefull this could be; horizontally fixed elements would only need to have their vertical movement “fixed” (and vertically fixed of course need the horizontal movement “fixed”) during the onscroll, but since this is normally *not* the direction the scrolling is going to at that moment, the jitter “should” not be noticeable.

Comment by Markku Uttula — September 20, 2007

Hmm… what I meant to say is “element’s client dimensions”, not “client area”.

Comment by Markku Uttula — September 20, 2007


fpElement.onmousemove = fpElement.onscroll = function() {
this.repositioning = true;

//--------------

var curLeft, curTop;
container.onscroll = function(){
if(container.scrollLeft != curLeft){
curLeft = container.scrollLeft;
leftPanel.style.marginLeft = curLeft + "px";
}
if(container.scrollTop != curTop){
curTop = container.scrollTop;
topPanel.style.marginTop = curTop + "px";
}
}

this will stop the unnecessary style changes which should reduce flicker.

Comment by Steven Moberg — September 20, 2007

Sorry


fpElement.onmousemove = fpElement.onscroll = function() {

the onmousemove is unnecessary and over kill.

Comment by Steven Moberg — September 20, 2007

Markku,

Try this for giggles, place the clones outside of the container with “position:absolute” and place them directly above the container. Then all you have to do is set the appropriate scrollTop, scrollLeft



fpElement.style.position = "relative";
topPanel = fpElement.cloneNode(true);
topPanel.style.position = "absolute";
topPanel.style.left = fpElement.offsetLeft + "px";
topPanel.style.top = fpElement.offsetTop + "px";
topPanel.style.width = fpElement.offsetWidth + "px";
topPanel.style.height = fpElement.fpPT + "px"; // y of pivot point
// same for leftPanel
// if fpElement.scrollHeight > fpElement.offsetHeight
// subtract 17px for width so scroll bar is visible
fpElement.parentNode.insertBefore(topPanel, fpElement);
fpElement.onscroll = function(){
topPanel.style.scrollLeft = fpElement.scrollLeft;
leftPanel.style.scrollTop = fpElement.scrollTop;
}

The browser renders this pretty fast since there is no absolute position or margins to recalculate…. just a nice scroll.

Good in both IE and Mozilla.

This is similar to my original scrollGrid, but I would calculate the numbers of column/rows in the panels, clone what was visible and then set the height and width of each cell to that of the original table. This would ensure the cells lined up and you don’t have to worry about having 3 huge clones… If your grid data is dynamic, you never really know how much content will exists or if the inner content is scattered with Event Listeners.

Comment by Steven Moberg — September 20, 2007

Hi Steven Moberg ,
I have been working similar issue in Firefox:)
I am thinking that this could solve my problem.
so can you paste the whole source of this or mail to:
sudduch@aim.com
waiting for your reply.

Comment by Suddu — October 29, 2007

Hi,
Welcome to 2010 :)

This script works really well, in all browsers except IE8! Can someone help in getting rid of the “Not Implemented” error that is displayed in IE8?

Thanks alot.

Comment by koiseth — October 7, 2010

Welcome to 2011….i was wondering…if the number of columns are less(say about 7 columns) then there is no need for the horizontal scroll bar, so if I have dynamic data which create the columns…a situation can come where I have only 7 or 8 columns, in this case the horizontal scroll bar should not be there.

Comment by rodrigues — August 6, 2011

Welcome to 2013; interestingly, this single article is *still* the one that brings most new traffic to my site :)

@koiseth: unfortunately I have not been using a system that can run Windows in a while, so I’ve never had the time to revisit the issue you’re telling about :( I need to reiterate that this was only meant as a proof of concept – back in 2007… I haven’t actually used *this* since. Bbut I have made different versions of it that are still in use and that continue to work on more modern browsers. Unfortunately, those are part of commercial projects, and thus I am not allowed to release the code :((

@rodrigues: Both horizontal and/or vertical scroll bars can be turned off independently of each other by setting element.style.overflowX / element.style.overflowY to “hidden”. Setting it to “auto” might be a better idea though, because then you don’t need to check if the element actually needs a scrollbar or not.

Comment by DiscoNova — March 9, 2013

Leave a comment

You must be logged in to post a comment.