Clientside sorting of HTML TABLE in JavaScript

To save network bandwidth and server resources, it is often beneficial to to sorting of tabular data on the client. Here’s a workable solution that I’ve implemented several times.

Additionally, the need to make the solution ‘accessible’ to screen-reader technology and be backward compatible for users without JavaScript often become challenging.

There are some small “quirks” that you should be aware of…

  • The ‘sortTable’ method uses the ID of the ‘TBODY’. Working to remove this requirement through better use of the DOM.
  • Will likely rework this to use Prototype framework which should result in smaller code.
  • Future enhancement will add a class to the header indicating the sort order of the column(s).
  • Creating the ‘SPAN’ for dates to be sorted is best handled by a taglib.
  • Large TABLE’s can take a significant amount of time to sort on a client, so it’s sometimes better to use a server side solution. Developers should use their experience to make this judgment call.

Example code (XHTML logic removed as usual for brevity):

<html>
<head>
<title>Client Side TABLE sorting</title>
<style type=”text/css”>
/* SCROLL */
div.scroll {width:100%;overflow:scroll;}
html>body div.scroll {width:100%;overflow:scroll} /* fixes IE6 hack */
/*** TABULAR ***/
tr.even {background-color:#eee;}
th.first, td.first {border-width:0;}
th.memo {text-align:left;padding:0;}
td.sorted {background-color: #f0f0f0;}
th.sorted {background-color: #99f;}
tr.even td.sorted { background-color: #d0d0d0; }
table.sorted tr.error { background-color:red; }
table.sorted tr.scroll th { background-color:#99f; text-align:left;}
table.sorted tr.scroll th a.sorted { color:#fff; text-decoration:none;}
table.sorted tr.scroll th a.sorted:hover { color:#fff; text-decoration:underline;}
</style>
<script type=”text/javascript”>
//—————————————————————————–
// sortTable(id, col, rev,xcase)
//
// id – ID of the TABLE, TBODY, THEAD or TFOOT element to be sorted.
// col – Index of the column to sort, 0 = first column, 1 = second column, etc.
// rev – If true, the column is sorted in reverse (descending) order initially.
// xcase – makes sort NOT case sensitive.
//
// The following is an example of jsp code for setting up the reformatted copy of a date
// field to allow sorting by date:
//
// <fmt:formatDate var=”varSortDate” value=”${searchResult.dateOfBirth}” pattern=”${sortableDateTimePattern}” />
// <span title=”<c:out value=”${varSortDate}” />”><fmt:formatDate value=”${searchResult.dateOfBirth}” pattern=”${dateFormatPattern}” /></span> | etc.
//
// The above code creates html code that contains, for example, a line like the following:
//
// <span title=”19640101000000″>01/01/1964</span>
//
// This sort routing concatenates the title element and the text node
// content to sort on the following string:
//
// 1964010100000001/01/1964
//
// This effective ignores the date containing slashes and used the yyyyMMdd etc. value.
// Fields that are not dates should not use the span element and title attribute, unless
// it is desired to sort on something other than the text node content.
//—————————————————————————–
function sortTable(id, col, rev, xcase) {
// Get the table or table section to sort.
var tblEl = xgetHelper(id);
if(tblEl != null){
// The first time this function is called for a given table, set up an array of reverse sort flags.
if (tblEl.reverseSort == null) {
tblEl.reverseSort = new Array();
// Also, assume the column zero is initially sorted.
tblEl.lastColumn = 0; // was 1
}

// If this column has not been sorted before, set the initial sort direction.
if (tblEl.reverseSort[col] == null)
tblEl.reverseSort[col] = rev;

// If this column was the last one sorted, reverse its sort direction.
if (col == tblEl.lastColumn)
tblEl.reverseSort[col] = !tblEl.reverseSort[col];

// Remember this column as the last one sorted.
tblEl.lastColumn = col;
// Set the table display style to “none” – necessary for Netscape 6 browsers.
var oldDsply = tblEl.style.display;
tblEl.style.display = “none”;
// Sort the rows based on the content of the specified column using a selection sort.

var tmpEl;
var i, j;
var minVal, minIdx;
var testVal;
var cmp;
for (i = 0; i < tblEl.rows.length – 1; i++) {

// Assume the current row has the minimum value.
minIdx = i;
minVal = getTextValue(tblEl.rows[i].cells[col], xcase);

// Search the rows that follow the current one for a smaller value.
for (j = i + 1; j < tblEl.rows.length; j++) {
testVal = getTextValue(tblEl.rows[j].cells[col], xcase);
cmp = compareValues(minVal, testVal);
// Negate the comparison result if the reverse sort flag is set.
if (tblEl.reverseSort[col])
cmp = -cmp;
// If this row has a smaller value than the current minimum, remember its
// position and update the current minimum value.
if (cmp > 0) {
minIdx = j;
minVal = testVal;
}
}

// By now, we have the row with the smallest value. Remove it from the
// table and insert it before the current row.
if (minIdx > i) {
tmpEl = tblEl.removeChild(tblEl.rows[minIdx]);
tblEl.insertBefore(tmpEl, tblEl.rows[i]);
}
}

// Make it look pretty.
makePretty(tblEl, col);

// Restore the table’s display style.
tblEl.style.display = oldDsply;
}

return false;
}

//—————————————————————————–
// Functions to get and compare values during a sort.
//—————————————————————————–

// This code is necessary for browsers that don’t reflect the DOM constants
// (like IE).
if (document.ELEMENT_NODE == null) {
document.ELEMENT_NODE = 1;
document.TEXT_NODE = 3;
}

function getTextValue(el, xcase){
var i;
var s;
var spanTitleValue;

// Find and concatenate the values of all text nodes contained within the element.
s = “”;

for (i = 0; i < el.childNodes.length; i++) {
if (el.childNodes[i].nodeType == 1) {
if (el.childNodes[i].nodeName != null) {
if (el.childNodes[i].nodeName == “SPAN”) {
spanTitleValue = el.childNodes[i].getAttribute(“Title”);
s += spanTitleValue;
}
}
else {
}
// Use recursion to get text within sub-elements.
s += getTextValue(el.childNodes[i]);
}
else if (el.childNodes[i].nodeType == document.TEXT_NODE) {
s += el.childNodes[i].nodeValue;
}
else {
// Gets here when element is empty! <span title=””></span>
//alert(‘Error — Not element or text node’);
}
}
return normalizeString(s, xcase);
}

function compareValues(v1, v2) {

var f1, f2;
// If the values are numeric, convert them to floats.

f1 = parseFloat(v1);
f2 = parseFloat(v2);
if (!isNaN(f1) && !isNaN(f2)) {
v1 = f1;
v2 = f2;
}

// Compare the two values.
if (v1 == v2)
return 0;
if (v1 > v2)
return 1
return -1;
}

// Regular expressions for normalizing white space.
var whtSpEnds = new RegExp(“^\\s*|\\s*$”, “g”);
var whtSpMult = new RegExp(“\\s\\s+”, “g”);

function normalizeString(s, xcase) {

s = s.replace(whtSpMult, ” “); // Collapse any multiple whites space.
s = s.replace(whtSpEnds, “”); // Remove leading or trailing white space.

var rc = s;
if(xcase == true) {
rc = s.toUpperCase();
}
return rc;
}

//—————————————————————————–
// Functions to update the table appearance after a sort.
//—————————————————————————–

// Style class names.
var rowClsNm = “even”;
var colClsNm = “sorted”;

// Regular expressions for setting class names.
var rowTest = new RegExp(rowClsNm, “gi”);
var colTest = new RegExp(colClsNm, “gi”);

function makePretty(tblEl, col) {
var i, j;
var rowEl, cellEl;

// Set style classes on each row to alternate their appearance.
for (i = 0; i < tblEl.rows.length; i++) {
rowEl = tblEl.rows[i];
rowEl.className = rowEl.className.replace(rowTest, “”);
if (i % 2 != 0)
rowEl.className += ” ” + rowClsNm;
rowEl.className = normalizeString(rowEl.className);
// Set style classes on each column (other than the name column) to
// highlight the one that was sorted.
for (j = 0; j < tblEl.rows[i].cells.length; j++) { /* was j=2 */
cellEl = rowEl.cells[j];
cellEl.className = cellEl.className.replace(colTest, “”);
if (j == col)
cellEl.className += ” ” + colClsNm;
cellEl.className = normalizeString(cellEl.className);
}
}

// Find the table header and highlight the column that was sorted.
var el = tblEl.parentNode.tHead;
rowEl = el.rows[el.rows.length – 1];
// Set style classes for each column as above.
for (i = 2; i < rowEl.cells.length; i++) {
cellEl = rowEl.cells[i];
cellEl.className = cellEl.className.replace(colTest, “”);
// Highlight the header of the sorted column.
if (i == col)
cellEl.className += ” ” + colClsNm;
cellEl.className = normalizeString(cellEl.className);
}
}
function xgetHelper(id){
var obj = null;
try {
obj = document.getElementById(id);
} catch(z) {
//var dummy=alert(“Error:” + z);
}
return obj;
}
</script>
</head>
<body>
<fieldset>
<div id=”ex_div” class=”scroll”>
<table summary=”” id=”names” class=”sorted” cellspacing=”0″>
<colgroup>
<col style=”first labels” />
<col style=”form_fields” />
</colgroup>
<thead>
<tr class=”scroll”>
<th scope=”col” id=”ex_0″><a href=”javascript:void(0);” class=”sorted” onclick=”return sortTable(‘ex_sort’,0,true,true);”>Alpha</a></th>
<th scope=”col” id=”ex_1″><a href=”javascript:void(0);” class=”sorted” onclick=”return sortTable(‘ex_sort’,1,true,true);”>Date</a></th>
<th scope=”col” id=”ex_2″><a href=”javascript:void(0);” class=”sorted” onclick=”return sortTable(‘ex_sort’,2,true,true);”>Url</a></th>
</tr>
</thead>
<tbody class=”scroll” id=”ex_sort”>
<tr class=”even”>
<td headers=”ex_0″>Alpha</td>
<td headers=”ex_1″><span title=”20070701″>July 2, 2007</span></td>
<td headers=”ex_2″><a href=”javascript:void(0);” onclick=”alert(‘view.php?userid=6’);”>5</a></td>
</tr>
<tr class=”odd”>
<td headers=”ex_0″>Bravo</td>
<td headers=”ex_1″><span title=”20050903″>Sept 3, 2005</span></td>
<td headers=”ex_2″><a href=”javascript:void(0);” onclick=”alert(‘view.php?userid=8’);”>4</a></td>
</tr>
<tr class=”even”>
<td headers=”ex_0″>Charlie</td>
<td headers=”ex_1″><span title=”19700709″>July 9, 1970</span></td>
<td headers=”ex_2″><a href=”javascript:void(0);” onclick=”alert(‘view.php?userid=4’);”>2</a></td>
</tr>
<tr class=”odd”>
<td headers=”ex_0″>Delta</td>
<td headers=”ex_1″><span title=”20001213″>Dec. 13, 2000</span></td>
<td headers=”ex_2″><a href=”javascript:void(0);” onclick=”alert(‘view.php?userid=5’);”>3</a></td>
</tr>
<tr class=”even”>
<td headers=”ex_0″>Echo</td>
<td headers=”ex_1″><span title=”20010911″>Sept 11, 2001</span></td>
<td headers=”ex_2″><a href=”javascript:void(0);” onclick=”alert(‘view.php?userid=2’);”>1</a></td>
</tr>
</tbody>
</table>
</div>
</fieldset>
</body>
</html>

Cheers!

Detecting browser SSL capability with JavaScript

If you run a secured website using HTTPS (aka SSL) it’s often wise to stop or notify users that are using a browser or client that doesn’t support the proper encryption level required.

Here’s a short method to “sniff” the capabilities prior to forwarding users to the secure area. You could add logic to inform the user of the problem.

As usual I’ve stripped a lot of the XHTML markup for readability.

<html>
<head>
<!– set ‘sslok’ global variable for testing SSL capability –>
<script type=”text/javascript”>
<!–
var sslok = 0;
//–>
</script>
<!– try including source javascript from secure server, this will set “sslok” to 1 if it works –>
<!– note that the /secure directory is protected so that only 128+bit SSL is allowed –>
<script type=”text/javascript” src=”https://www.example.com/secure/ssl-test.js”></script>
<!– if ssl is 1, our javascript include worked, so SSL is successful – redirect to SSL –>
<script type=”text/javascript”>
<!–
if (sslok == ‘1’) {
window.location = ‘https://www.example.com/secure’;
}
//–>
</script>
</head>
<body>
</body>
</html>

Contents of the ‘ssl-test.js’ file:

<!– set sslok to 1, so we know this include succeeded –>
sslok = ‘1’;

NOTE: If you use the same ‘filesystem’ for HTTP & HTTPS you might want to use a server-side program (PHP or Java for example) to generate the JavaScript.  Benefit of that process would be that you could also interrogate and return other SSL attributes such as cypher strength.

Cheers!

Javascript ‘Response Time’ measurement (latency)

Here’s another ‘fun’ trick. When you build complex web applications, performance metrics start to become an issue. Unfortunately, web tools for this are not typically available within the browser itself and ‘testers’ often rely of such non-technical solutions as stop-watch timing. Not only is this time consuming, but it is nearly impossible to reproduce as the ‘time’ includes the hand-eye coordination of the person using the watch.

In defense of the software itself, there are several Mozilla add-ons to help with this issue, but my solution is in your application code and as such can be enhanced to provide notification or logging with some additional work.

If you’ve ever used a 3270 Emulator (Mainframe “Green Screen”) you’re probably familiar with seeing this data as most 3270 clients expose it somewhere on the screen.

The trouble here is that there is no good API to do this, and no javascript variables are persisted across page loads. (cookies would be overkill). What we’re left with is the browser window-name itself, while it’s normally used when naming ‘popup’ windows and is not visible directly to the user, it provides a ‘safe’ place to store this transient data between pages.
When leaving a page we store the current time, to be sure that we can find the correct data in the window name we use unique prefix and suffix values and add the current name so that it can be restored later.

When the new page is loaded we have to compare the current time with the stored time, then we have to be sure to restore the window ‘name’ as can’t be sure about what other reason it may be named, and we don’t want to interfere.

NOTE: If you also have access to the server code (PHP or Servlet for example) or can wrap calls to external systems like databases, you can make this much more granular by including those times. In that case you could report total time, extract database time, then extract servlet time, then ‘deduce’ network latency itself! This goes a long way when troubleshooting vague user experiences when they indicate that the “site is slow” as you can now pinpoint the problem area(s) to focus on.

WARNING: For Mozilla/Firefox the default security settings prevent JavaScript from modifying the ‘window status’ used in the example code. In a real implementation you might consider using DHTML to insert the output somewhere else. Otherwise changing Mozilla Firefox 2.x’s settings is found at Tools, Options, Content Tab, Advanced JavaScript settings.

<html>
<head>
<script>
var build=’GiantGeek Example’; // you could put build label information here 🙂
var PRE=”MMSpre”;
var SUF=”MMSsuf”;

function xload(){
var tim=grvGetDuration();
build=build + tim;
grvWindowStatus(build);
}
function xclose(){
grvWindowStatus(”);
}
function xlinkObj(obj,url){ // this is for all valid links
grvSetTimeclean();
window.location.replace(uniqueUrl(url)); // ‘replace’ has side-effect of ‘restricting’ back-button, or ‘location’
return false;
}
function grvMillis(){
return new Date().getTime();
}
function grvSetTimeclean(){
var dummy=grvGetTimeclean(); // get any remainder (dupes?)
var x=grvGetName() + grvMms();
grvSetName(x);
}
function grvMms(){
return PRE + grvMillis() + SUF;
}
function grvGetDuration(){
var rc=”;
var old=grvGetTimeclean();
if(old!=”){
rc=” [” + (grvMillis() – old)/1000 + “]”;
}
return rc;
}
function grvGetTimeclean(){
var x=grvGetName();
var pre = x.indexOf(PRE);
var suf = x.indexOf(SUF);
var rc=”;
if((pre >= 0) && (suf > pre)){
rc=x.substring(pre+PRE.length,suf);
// get the garbage in the name (around mms)
var p=x.substring(0,pre);
//var s = x.substr(suf+SUF.length);
//assemble new name (without mms)!
grvSetName(p);
}
if(pre==0){
grvSetName(”);
}
return rc;
}
function grvGetName(){
var rc = window.name;
if(rc==undefined){rc=”};
return rc;
}
function grvSetName(x){
window.name=x;
}
function grvWindowStatus(txt){
window.defaultStatus=txt;
}
function uniqueUrl(x){ // this adds a timestamp to URLs
var mms = grvMillis();
var delim = “?”;
if(x.indexOf(“?”) >=0) { delim = “&”; }
return x + delim + ‘time=’ + mms;
}
</script>
</head>
<body onunload=”xclose();” onload=”xload();”>
</body>
</html>

Adding Support for ‘disabled’ OPTION tags in MSIE

This is a very annoying bug/oversight in MSIE (including the recently released MSIE7!).

For some reason, Microsoft didn’t implement the ‘disabled’ attribute on <option> tags.
All other modern (even the old Netscape 4.x) browsers support this, why would they not do the same.
This is probably for the same reason that all versions of MSIE (prior to MSIE7) left the rendering of the SELECT tag to the operating system itself, above the browser HTML.
Thankfully, you can still access the attribute on the DOM element with javascript!

My solution has evolved over time, here’s the current release code.

To emulate this behavior in MSIE, you’ve got several different challenges to overcome.

  1. You must ‘persist’ the current value of the SELECT so that you can ‘restore’ it when the user chooses a disabled field, I do this during the onload event.
  2. When the ‘onchange’ event for the SELECT tag is invoked, the currently selected OPTION’s attributes must be read and checked.
  3. If the selected OPTION is ‘disabled’, then the previous value must be restored.
  • As this solution only stores one value per SELECT, the ‘multiple’ SELECT is not currently supported.
  • In the future I’ll probably do some ‘event injection’ so that the HTML itself is cleaner. Problem is that in more complex solutions like ‘dependent dropdowns’ this becomes tricky.
  • The [if IE] ‘comment’ is critical as it is conditional logic supported only in MSIE and simplifies what was previously done via various ‘browser-sniffing’ tricks.
  • The example code in this example excludes the CDATA escapes and several tags required for valid XHTML for brevity.
  • FYI, the example also contains the MSIE background-image cache fix discussed in a previous post.

<!DOCTYPE >
<html>
<head>
<script type=”text/javascript”>
var isMSIE=false;
<!–[if IE]>
<script type=”text/javascript”>
isMSIE=true;
</script>
<script src=”/js/grv-msie-hacks.js” type=”text/javascript”></script>
<![endif]–>

<script type=”text/javascript”>
function xload(){
if(isMSIE){
grvMsieInitHacks();
}
}
function xchange(obj){
// note: javascript emulation of <option disabled…> (for MSIE)
if(isMSIE){
grvMsieSelectFix_restore(obj);
}
}
</script>
</head>

<body onload=”xload();”>

<form action=”#” method=”GET”>
<select name=”s” id=”s” size=”1″ onchange=”xchange(this);”>
<option value=”n1″>Normal1</option>
<option value=”di” disabled=”disabled”>Dis</option>
<option value=”n1″>Normal2</option>
</select>
</form>
</body>
</html>

JavaScript file (grv-msie-hacks.js):

/*
* Code library to add several ‘broken’ features in MSIE 6 and 7
* @version $Id: $
*/
function grvMsieInitHacks(){
grvMsieCacheFix();
grvMsieSelectFix_init()
}
function grvMsieCacheFix(){
try{
document.execCommand(“BackgroundImageCache”,false,true);
}catch(e)
{}
}
/* Added new functions to support <option disabled…> emulation
*
* First part, necessary for <body onLoad…>
* builds array of all initial selections for <select>s on page (for LT or EQ MSIE7)
* WAS: disabledOptionEmulation();
*/
function grvMsieSelectFix_init() {
if (document.getElementsByTagName) {
var allSelects = document.getElementsByTagName(“select”); // build array of all <select> tags
if (allSelects.length > 0) { // if array has values…
window.allSelectsCurrentIndex = new Array(); // new array to hold initial selections
for (var i=0, individualSelect; individualSelect = allSelects[i]; i++) { // crawl through all <select> tags
individualSelect.onfocus = function(){ window.allSelectsCurrentIndex[this.id] = this.selectedIndex; } // fill array with selectedIndex values
}
}
}
}

/* companion code for grvMsieSelectFix_init()
* resets <option> selection if disabled to last good selection (for LT or EQ MSIE7)
* WAS: restoreSelection(inOptionChoice)
*/
function grvMsieSelectFix_restore(inOptionChoice) {
if (inOptionChoice.options[inOptionChoice.selectedIndex].disabled){
// if new choice is disabled…
inOptionChoice.selectedIndex = window.allSelectsCurrentIndex[inOptionChoice.id]; // deny selection, revert back to last ‘known good’ choice (typically, the initial selection at page load)
} else { // if new choice isn’t disabled…
window.allSelectsCurrentIndex[inOptionChoice.id] = inOptionChoice.selectedIndex; // update array so last ‘known good’ choice is now the latest user selection
}
}

Hopefully, Microsoft will get around to fixing this correctly some day… maybe for MSIE8!

Open Source Media Player

There’s a lot of free software out there, but just like with Instant Messaging software you have to install many of them to be flexible.

Examples:

Several years ago I stumbled upon the VideoLAN Client (aka VLC) when researching methods to stream video from my ReplayTV (Tivo predecessor/clone) to my laptop as the files were in MPEG4 format and my previous clients didn’t support it.

I urge you to check it out as it’s an all in one solution, so that you don’t need all of that other bloat-ware installed.

G’day!

P.S. – I should add that this plays most video and audio formats and is available on most platforms, not just Windows.

Private Caching/Relay DNS Server

Since I’ve run a few small websites (like this one) out of my home for years, I’ve found it useful to run a DNS server inside of my firewall. Not only does this make it easier to maintain the websites, but it allows me to lock down security and increase performance of many of my applications.

I run a the following services that use DNS:

  • Apache JAMES – mail server that does lookups to send email and filter inbound SPAM.
  • Analog – web server log analysis.
  • Apache HTTPD – web server, used to host websites, private domains used for internal purposes.

To make things more efficient, I currently have my DNS setup to forward all requests to OpenDNS, allowing for ‘adult’ website filters and analysis of DNS activity.

Some open-source/free DNS servers that I recommend:

Cheers!

The Oath of Enlistment

Let’s review this for just one moment… all ‘sworn’ government officials take a similar oath when they assume office. This is the wording used for enlisted members of the U.S. military.

I,­­­______________, do solemnly swear (or affirm) that I will support and defend the Constitution of the United States against all enemies, foreign and domestic; that I will bear true faith and allegiance to the same; and that I will obey the orders of the President of the United States and the orders of the officers appointed over me, according to regulations and the Uniform Code of Military Justice. So help me God.

Without delving into the details, I personally feel that many of our elected officials chose to ignore their oaths, instincts, and the people they represent. Let’s not forget these facts come election time as voting along “traditional” party lines doesn’t ever seem to fix matters, it’s time for a change!

Disturbed, especially by H1B and illegal immigration.

House for Sale

Well, after just over seven years of ongoing updates to the house, we’re sadly, but finally ready to move on to a new home. I posted several months ago about our new house in Plainfield, and as of last week we’ve listed our current place for sale.

If you’re interested in moving in to my old home in Carol Stream, please contact my realtor Sherry McGovern of J.W. Reedy Realty in Lombard, IL at +1 630-629-0016.

Here’s some more information on the home:

Make me an offer 🙂

MSIE Conditional code

This is not so much as a MSIE bug, but rather a non-standard “feature” (added in MSIE 5.5) that is often helpful when constructing websites that you try to build with valid XHTML and CSS. As all non-Microsoft browsers will see these as simple
HTML markup comments, it gives you the flexibility to deal with the inconsistencies and quicks in the various versions of MSIE. This is particularly important for the numerous changes introduced with MSIE7.

I’ve tested this with <style />, <script /> and <meta /> tags, but wouldn’t doubt that it works anywhere (but don’t see any typical situations for that!)

Some simple instructions:

  1. Start with an HTML comment
  2. Add the opening brackets, so it resembles <!–[if …]>
  3. Add the closing brackets too… <![endif]–> (don’t forget that extra less than and bang/exclamation point!)
  4. Add the versioning info, this should be pretty obvious, but i’ll list the most common ones.
    IE 7 = MSIE7
    IE 6 = MSIE6
    IE 5.5000 = MSIE 5.5
  5. Some modifiers, (should you want a range of versions):
    lt = Less Than
    gt = Greater Than
  6. Make sure that the body content of the tag (which now resembles an HTML comment) is coded.
  7. That’s it, now be sure to test for expected behaviors!

Some Examples:

<!–[if IE 6]>
<style type=”text/css”>
/* <![CDATA[ */
html {
overflow-y: scroll;
}
/* ]]> */
</style>
<![endif]–><!–[if lt IE 5.5000]><style type=”text/css”>@import “/css/IE50Fixes.css”;</style><![endif]–>
<!–[if IE 5.5000]><style type=”text/css”>@import “/css/IE55Fixes.css”;</style><![endif]–>
<!–[if IE 6]><style type=”text/css”>@import “/css/IE60Fixes.css”;</style><![endif]–>
<!–[if IE 7]><style type=”text/css”>@import “/css/IE70Fixes.css”;</style><![endif]–>
<!–[if lt IE 7]><script type=”text/javascript” src=”/js/IEFixes.js”></script>
<meta http-equiv=”imagetoolbar” content=”no” /><![endif]–>

Cheers!

Cars?

“They” say that you can ‘learn a lot about a man, by the car he drives’…. just what do these say about me ?

What’s next, I’m not even sure, just drive fast and stay outta my way!

🙂