Flexible, Dynamically-Expanding Columns with Absolute Positioning

April 22, 2010

April 22, 2010
 
To use this tutorial, you will need Prototype.js (v1.6) and a good working knowledge of javascript, CSS, and XHTML. This tutorial assumes you are an intermediate-to-advanced javascript programmer and understand the basics of programming and code design. I will keep the technical jargon to a minimum, but some concepts are just too basic to be covered in great detail here.
 
All of the files you will need are in this self-contained ZIP archive:
 Download this article’s source code here.

 

 
Question… 
How many of you out there have wanted multiple columns of content on web pages without all of the required housekeeping drudgery to make sure all the content gets displayed properly and nothing gets clipped, overlaps or gets shoved around by other elements? And without the fear of getting a call from your client, telling you that the site does not look good in browser X? From the search results I came up with, it would seem that there are a LOT of others like me out there who are looking for a simple solution that does not required executing acres of processor-intensive code. If your page is torturously-slow, what good is pretty content?
 
It has been extremely frustrating finding something that is flexible with a minimum of customization and that works on all browsers. Of course, I am not talking about supporting legacy browser versions, like Internet Explorer 6 and older… that is a COMPLETELY different beast with razor teeth and slathering jaws waiting to clamp down on your unsuspecting ankles. So, for the purposes of clarity and simplicity, I am going to focus only on Safari 4 (mac) / 4 (pc), Opera 9 (mac) / 10 (pc), Chrome beta (mac) / 4 (pc), Firefox 3 (mac) / 3(pc), and Internet Explorer 7 (pc) / 8 (pc).
 
So, Why Bother? 
Good question! Couldn’t this be done using pure CSS? Theoretically it is possible to create a wrapper DIV, and then simply insert column DIV’s into the wrapper. Then, you would use the “display:inline-block” style to stack them left-to-right, or you could use the style “float:left” to take it out of the layout-stream. You could also assign the style “height:auto” to each column to have them automatically expand or contract the column DIV height. Well, theoretically, if you did not want any padding, or margins, or extra spacing around your columns this would seem like the obvious path to success… however, this is not an ideal world. Because of browser differences and flat-out incompatibilities, it will not work this easily. For example, if you used the float method, then because it is not a part of the document-flow, it will not adjust the height f the parent DIV that it is contained in: content will begin to spill out of the bottom of your wrapper DIV, ruining you nice, organized layout. As for the display method, it is lacking since the tops of the column DIV’s will tend to shift up or down depending on the height of its neighboring DIV… so many issues!
 
Trust me, I have tried just about every combination of CSS styles imaginable. Sometimes they worked great in 2-out-of-3 browsers… but then almost is never going to be good enough when it comes to websites, is it? That is where Javascript comes in. With it, we can create a robust solution that ALWAYS works and gives us predictable results every time.
 
Also, most of the current solutions out there only resize things at the time of the page load. This solution updates all the time, since there are times when you want a more expandable-contractable page design.
 

What This Tutorial IS NOT… 
This tutorial is meant to be a starting point only. All code has been commented inside the Javascript and XHTML, and CSS. You may copy-paste this code and use it for any purpose commercial, or private, as long as you are not selling the code itself and it is an integrated part of an actual webpage project not intended to solicit sales of this code.
 

Let’s Get Started 
To begin with, let’s introduce the technologies we will be using to achieve these automated, absolutely positioned columns of content. I decided to do everything in as few technologies as possible and using things that would be updated and open-source for a long time to come. I settled on Prototype.js for my cross-browser support, and CSS/XHTML for everything else. Prototype.js is a javascript-expanding system which is widely used, fully supported, and is ALWAYS being extended by others and updated. So, there is a pretty good chance that it will be around for years. The other nice thing about Prototype.js is that the code you need for your pages will reside on the same webspace as your site, and is not retrieved remotely, so if your page works now, it should work until your webhosting provider makes enough changes in their webserver configuration that it breaks your site. Besides, the average life-span of a website is typically 3-5 years. Anything beyond that is an amazing, unexpected bonus.
 

Prototype.js extends Javascript quite well, and it allows me to write smaller, faster code that is rather cross-browser compatible. So, I created my functions in an external javascript file and simply import them at load-time for use in my page.
 

Our Logic Flow 
What I needed was a container (wrapper) DIV that would hold multiple columns (DIV’s). Each wrapper-enclosed DIV would become a vertical column of content that would dynamically expand or contract its height according to how much content was enclosed. As the columns approached or receded from the bottom of the wrapper DIV, it would enlarge or reduce the height of the wrapper DIV so that our longest column maintained the same distance from the actual bottom of the wrapper DIV. Also, the page needs to be able to update itself upon load and anytime that a column changes height. Oh, yeah, and everything needs to be positioned absolutely so I could control the layout exactly the way I wanted it. I also wanted header and footer elements that could act as either visual spacers, or as holders for page-cap graphics. A lot of times, when I create a “page” of content within a web page, I like to give it a visual top and bottom so that it stands out more from the rest of the content on the page.
 

How We’ll do It 
We will accomplish self-updating columns by using an event listener for any click on the document. This will execute a function called “pageSnap” which will adjust the page_wrapper DIV height in relation to its contained column DIV’s. Also, instead of calling each column of content by name, we will simply create an array that holds every element of the class “page_column”. This will come in very handy later when you want to make your layout a 3, 4, 5… column layout. Since the columns are called by their class and not by their id, you can name the actual column DIV elements anything you like. They just need to be assigned to the class “page_column” to be included in the self-updating process of the wrapper DIV.
 

Our Page Structure 
Below is a simple diagram of how our DIV elements are structured. A copy of this diagram is included in the source code for this project. As you can see, all of the individual DIV elements that make up our page of content are contained in a single wrapper called “outer_wrapper”. While this wrapper is not necessary, it does gives us the flexibility to take the entire chunk of elements and simply drop them into a new page design. By enclosing all the elements together, it keeps them from spilling out into other page elements and interacting in an undesirable way.
 
The names of each element also correspond to that element’s CSS class definition. Since class and id do not share the same namespace, your browser should handle these just fine. If you do run into some future, unforeseen conflict, you can always change the class names in the CSS, just make sure you change them in the imported CSS file as well. I have been using duplicate class and id names for a while and have yet to have them conflict… but that does not mean it will stay that way forever.
 

Let’s Dissect Our Code 

Click here to see a working version of this example code.
 
Click here to download the source code.
 
Okay, so now you have the basic logic, the “why should I bother”, and the overall element structure down, so let’s move on to dissect our code block-by-block. But, first, let me tell you that I am only going to go over the relevant code blocks. I refrain from describing all the code in the Javascript, CSS, and XHTML files since they have all been thoroughly commented.
 


document.observe("dom:loaded", startup);

 1. This block of code assigns an event listener for the DOM event “loaded”. Since this fires before the onLoad event on the body element, it makes a great “pre-startup” startup handler. Just be sure to practice good-housekeeping and remove the listener after you no longer need it.
 

var pageWrapper, footer;
 
2. Here we add a couple of variables to the DOM for later use. We do not want to assign them to their respective page elements yet, because those have not been registered with the DOM yet and will simply return an undefined element, which will break the page and nothing will resize properly, or at all. Now, we do not have to create an instance of these variable names here, but it is good practice to do it this way, in case we want to add other functions that might use these variables. We could avoid globals altogether by placing our variables as properties on the element itself, but that is another article altogether. For now, we will keep things as simple as possible.
 

function startup()
{
// turn OFF the event listener?s
document.stopObserving("dom:loaded", startup);

// [ OPTIONAL CODE BLOCK ]
// these are OPTIONAL and should be removed when you create your own layout
// they are only here to allow you to shrink and grow the test columns to see how the page functions.
// assign some listeners for our shrink/grow buttons
$('clickable_1').observe( 'click', function(myEvent) { stretchElement("20",myEvent) } );
$('clickable_2').observe( 'click', function(myEvent) { stretchElement("-20",myEvent) } );
$('clickable_3').observe( 'click', function(myEvent) { stretchElement("20",myEvent) } );
$('clickable_4').observe( 'click', function(myEvent) { stretchElement("-20",myEvent) } );
// [ /OPTIONAL CODE BLOCK ]

// get a handle for the page_wrapper main div holding your columns
// you could name your page_wrapper anything, just make sure you change the name here as well
pageWrapper = $('page_wrapper');

// assign a height offset value for out footer element. If it does not exist, it will return a zero height
try {
footer = $('page_footer').getHeight();
} catch(e) { footer = 0 }

// set up a listener for a click on the document since we do not know which elements will expand or contract out layout height
// we need to check every time for changes in the height of the pageWrapper DIV
document.observe( 'click', pageSnap );

// DON'T FORGET! You can update the page_wrapper height any time simply by calling this function.
pageSnap();
}

 
3. This is the function that will run AFTER the page has loaded and registered all the page elements with the DOM. Now, we can assign our elements to their respective variables and thereby get a valid handle for later use. You will notice that there is a stopObserve method called on the document object. This is where we stop listening for the “DOM:loaded” event.
 

pageWrapper = $('page_wrapper');
 
4. Here, we assign our “page_wrapper” element to the global variable “pageWrapper”. This will give us access to that element without having to change the occurance of the element name more than once.
 

try {
footer = $('page_footer').getHeight();
} catch(e) { footer = 0 }

 
5. There are times that you may not want a footer element. You cold simply assign the element a height of zero, but for the sake of robust code, I wanted something that would allow me to remove the “page_footer” DIV altogether without breaking my page. So, if the element does not exist, it simply ignores the error and assign a height value of 0 to the variable “footer”.
 

document.observe( 'click', pageSnap );
 
6. Here is where we now add an event listener to the document element. This allows us to automatically check our “page_wrapper” height without having to call it from every function that might resize our “page_column” DIV elements due to a user click on some size-changeing UI element. You could just as easily assign this listener to the “page_wrapper” element instead of the “document” element. But, for the vast majority of purposes this works best.
 

pageSnap();
 
7. Once the page loads, our “page_column” DIV’s may have already changed size due to font-size differences among browsers and font families. So, it is always a good idea to execute this function to make sure everything is adjusted right away.
 

function pageSnap()
{

// get a the object that was acted upon to get it to this function
var target = pageWrapper;

// calculate a starting prevHeight (previous height) of the page_wrapper DIV
var prevHeight = 0;//target.getHeight() - footer

// this creates an array of all child elements in the target parent (page_wrapper) that are of the class "page_column"
var targetKids = $( target ).select('[class="page_column"]');

// go through all page_column elements and adjust the page_wrapper DIV height
for(var index=0;index prevHeight )
{
target.setStyle({ height:String( (height + footer) + "px" ) });
}
// save our current height as the prevHeight variable for reference on the next "page_content" column
prevHeight = height;
}
}

 
8. This is the function that will make the actual column adjustments. For the sake of simplicity, I have assigned the “pageWrapper” handle to a new variable called “target”. Of course, you do not need to do this, but I like to reassign our global to a local variable so that there is only a single instance of my global in my function definition. Also, if I wanted to have this function work on multiple “page_wrapper” element in a single webpage, I would just have to assign our “click” listener to each “page_wrapper” instead of the document, and then assign the “wrapper” variable to the “this” variable which would give us a handle to whatever element called the function without having to track all the different names of a the different “page_wrapper” DIV elements.
 

var prevHeight = 0;
 
9. Here, we want to create a variable to hold the previous height of the last “page_content” column whose height we compared to the height of the “page_wrapper” element. I assign it to zero, simply because of we try to add it to another integer and then assign it the height style property of an element, it will break our page in some browsers. By setting its inital value to 0, it helps to guarantee that we do not get some erroneous starting value.
 

var targetKids = $( target ).select('[class="page_column"]');
 
10. Here is probably the trickiest part of our code. What the select method allows us to do is collect all the occurances of the “page_content” class into a single array that we can iterate through and compare each element individually to the height of our “page_wrapper” DIV.
 

for(var index=0;index prevHeight )
{
target.setStyle({ height:String( (height + footer) + "px" ) });
}
// save our current height as the prevHeight variable for reference on the next "page_content" column
prevHeight = height;
}

 
11. Here we create a loop that will iterate through all of the elements in our “targetKids” array. Now, we could have done this with an each method and assigned our function to each of the elements themselves in the array, but for the sake of simplicity, I decide to just use the tried-and-true for-loop. Unless you have many, many elements to be compared, you should not notice any really appreciable speed difference.
 

var kid = $( targetKids[index] );
 
12. Here we assign our first element in the array to a shorter variable name. There is just too much room for error to have to type, retype, or even copy-paste the long name of “$( targetKids[index] )” every time we need to reference it. The shorter your variable names the less chance of typos.
 
 

var top = parseFloat( kid.getStyle( 'top' ) );
var height = kid.getHeight();

 
13. Now, let’s get a floating-point version of the values stored in our style property “top” and “height” on the “kid” element.
 

if( height + top > prevHeight )
{
target.setStyle({ height:String( (height + footer) + "px" ) });
}

 
14. Now, let’s add the height of our “page_content” column to its “top” style value to get its actual height inside the “page_wrapper”. If our content is now taller than the holding DIV element, we adjust the height of the holding DIV to the same as the height of our “kid” element PLUS the height of our “page_footer” element. This will make sure our holding DIV stays taller than or “page_content” DIV.
 

prevHeight = height;
 
15. So, all we need to do now, is save the current height value for comparison against the height of the next element. Using this value gives us a simple way to make sure that if the next column is taller than the last, we need to increase our “page_wrapper” height. Otherwise, if the next column is shorter, we can simply ignore it and keep the “page_wrapper” height unchanged. This way, we do not need to compare every column to every other column every time we look at each column height.
 

16. Also, make sure that if you want to move your columns, or add columns, you duplicate the “page_column” DIV element, give it a unique instance “id” and override any “left” or “top” properties using an inline style definition. The default position for all columns in the CSS class definition for page columns is at top:0, left:0.
 


 

Thanks 
That is about it. Pretty simple, actually. But not necessarily obvious. So, I hope this tutorial has helped.
 
Download this article’s source code here.


How to Create Bookmarkable Hash/Anchor (#) Links in IE 5.5/6/7/8

January 17, 2010

Okay, so recently I had a client project that sounded pretty simple: all I had to do was to take their existing Flash website and make it so that they could deep link their samples and different site sections, on-the-fly (programatically) without hand-coding each new link.

The project.

The customer had coded it so that the flash used an XML document and sample names to identify and pull them up. I won’t go into great detail about the Flash, let’s just say that all I did was to call an external function using the ExternalInterface method “window.location.href.toString” and it dutifully retrieved the current URL from the browser of the page that was currently being displayed. 
 
Then, I simply compared the current URL to the one that Flash thought was being displayed. If they matched, I added the URL to the browser’s history. If they did not match, then I loaded the appropriate sample and updated the browser page URL so they both matched. 
 
For those who are interested, I can write an article about the solution above. However, for the purposes of this article I want to talk about a really simple and forward-compatible way to add items to Internet Explorer’s history without having to resort to using someone else’s fancy external javascript, Ajax, Visual Basic, or any other trick code that might break on the next version of Internet Explorer. 
 

Success.

Okay. So I got my solutions to work flawlessly in Flock, Netscape, Safari, Opera, and Firefox. I was thrilled that it only took me about 3-4 hours from start-to-finish. That did not take any longer than I thought it would. Hooray! Then…. I turned around to my craptastic-PC and started testing on all of the browsers under Windows XP. 
 
It simply didn’t work. Not at all. The page just kept-reloading in an infinite loop and NOTHING was being added to the history… After an hour of sobbing, I picked myself up and settled in for the long-stuggle I knew I had ahead of me. 
 
Why doesn’t Microsoft simply add history items like everyone else? Who knows! I am willing to bet that there was a very long and boring meeting about it in Redmond, Washington that sort of went like this:
 
“So, what about adding items to the user’s history?”
 
“Sure. Let’s add whatever they are viewing as a new entry into the history unless it is a duplicate entry.”
 
“Okay. Sounds good. How about when the user visits an HTML anchor link inside the same page?”
 
“Well, let’s not bookmark those. They are not really links.”
 
“Sure they are. They send you to another location and you might want to hit the back button to backup-through them…”
 
“No. That’s dumb. Why would any want to do that? I can’t imagine any reason that people would ever want to do that so, rather than just leave it as a possibility, or make it so people can change a preference, let’s just make it the standard that no hash links (#) indicating anchors will be saved in the history….”
 
The rest is… yes, I have to say it: history.
 

Aaaaaaaarg!

That is all I can say right now.
 
I can think of at least ONE really good reason. And I am sure there are many other people out there who have their own good reason.
 
Hey, Microsoft! Stop deciding what is best for the user and eliminating possibilities that you cannot imagine a use for! Sheesh. It’s like having your parents over for dinner and they continually edit your conversation. Your way is not EVERYONES’ way.
 

The Hash (#) Maneuver.

Now that that’s over, let me tell you how I finally overcame this dreaded “feature” in Internet Explorer:
 
1. I generate my dynamic links as I would in any other browser.
 
2. However, this time, instead of my link looking something like: ” http://www.mysite.com/#sampleID “, it ended up only working when it was formatted like: ” http://www.mysite.com/?sampleID/#sampleID “.
 
3. So, I had to check to see if the current browser was IE (of any flavor) and then format the new URL according to the above sample, starting my new sample URL with a query string (?) instead of simply adding a hash mark (#).
 
What did I learn from this? I learned that IE, for some odd reason, does not think that hash-links (anything starting with a hash mark “#”) are worth noting in the user’s history. The only way for me to easily add the entry to the history was to start my sample URL portion of the page URL with a string query (?). Luckily, IE accepts that any URL with a query in it might be important enough to save. Also, other browsers treat query links in the same way. So, at least we have a sort of pass-through channel for our hash-link.
 
Since the query string gets ignored by the page, unless you capture the variable and use it for something. It was easy to then pass the entire URL to Flash and using the String.split(“#”), create an array that contained the prefix of the URL and suffix.
 
The suffix contained my actual hash-link and so I was able to pull the sample name from it and then tell Flash to retrieve and display the proper sample.
 

Postmortem.

Why… why… why… does Microsoft continually do this to us? Hey, Guys! How about you actually talk to people who use your software and not just listen to the ones who are coding it? Make technology conform to the way people function. Don’t make people conform to the way technology functions.
 
For support of hash-links, I would have to give Microsoft an F-.