It’s been a long time since I played around with JavaScript. I was pretty much under the impression it was dead and basically made editing pages more difficult. When I would go back and try and update pages that I had written long ago with a smattering of script in the header, and some strewn throughout to make things happen, I basically found that it was just too much work for the benefit it provided. With CSS I could accomplish 90% of what I used to do with JavaScript and it was much easier to read and maintain.
I think I’m a born again JavaScript fiend. After recently discovering the great site Unobtrusive Javascript, I’m once again sold. This site makes it easy to separate the script from the CSS from the HTML, and all work harmoniously together.
After playing around with their method of separating CSS and JS from the HTML I was finally able to elegantly solve an issue I’ve been wanting a solution to for years. I love how gmail does the whole stack of cards thing with your email. But more than that I love how they show and hide parts of the page arbitrarily. When you are reading a message there is a nice little reply box down at the bottom that when you put your cursor in to write a reply gets bigger, but doesn’t refresh the page so you can continue to type. And when you want to look back through some old messages all you do is click on the stack of cards and viola there you go, your old messages are right there.
My dilemma was more simplistic. I need to make a large form that I wanted to display on a single page, but not force people to scroll down through the whole thing, and I didn’t want people to have to click through sections of it because I want people to go right where they need to go without having to navigate a menu if they don’t want to. And I wanted to allow people to check over the entire form before submitting it, so they could fill in the whole thing at once if they wanted to, or could submit just parts, and through the wonders of MYSQL they could come back later and finish up.
The solution I had in mind was to show the headings for the sections of the form, and then allow people to click on the header to expand or collapse the section of the form. In addition I wanted a way to expand and collapse the whole form so that if you wanted to jump around from one section to another you could with minimal scrolling.
JavaScript to the rescue.
Using the basic function provided by the fine folks over at Unobtrusive Javascript I was able to make a page where all h2 tags turn into buttons that expand and contract their sibling divs. Then after some trial and error as I got my mind wrapped around what they were doing, I was able to take h3 tags and make them expand and contract all of the divs that immediately proceed h2 tags. The beauty is that all the CSS and all of the JavaScript are in their own files, no body tag onload() functions, no extra classes or id’s even. When CSS is needed for a specific application it is called and generated by the javascript, and the javacript file itself tells the HTML page to load it when the window loads, so all you have to change in your HTML is to include a single line telling it where to find the JavaScript file.
Alright, enough talk here are the three files: (note this is part of a php file so the values for the form fields are self populating with php code, get rid of it if you don’t want this.)
First the HTML File:
<html>
<head>
</head>
<body>
<h3>Collapse All Sections</h3>
<form action="processupdates.php" method="post">
<h2>Personal Inforamtion</h2>
<div>
<label for="FirstName">First Name: </label>
<input type="text" id="FirstName" name="FirstName" value="First" size="30" maxlength="60" tabindex="1"/>
<label for="LastName">Last Name: </label>
<input type="text" id="LastName" name="LastName" value="last" size="30" maxlength="60" tabindex="2"/>
</div>
<h2>Other Inforamtion</h2>
<div>
<label for="FirstName">First Name: </label>
<input type="text" id="FirstName" name="FirstName" value="<? echo $line["FirstName"]; ?/>" size="30" maxlength="60" tabindex="1">
<label for="LastName">Last Name: </label>
<input type="text" id="LastName" name="LastName" value="<? echo $line["LastName"]; ?/>" size="30" maxlength="60" tabindex="2">
</div>
<h2>More Inforamtion</h2>
<div>
<label for="FirstName">First Name: </label>
<input type="text" id="FirstName" name="FirstName" value="<? echo $line["FirstName"]; ?/>" size="30" maxlength="60" tabindex="1">
<label for="LastName">Last Name: </label>
<input type="text" id="LastName" name="LastName" value="<? echo $line["LastName"]; ?/>" size="30" maxlength="60" tabindex="2">
</div>
<br />
<input type="submit" id="Save Application" value="submit"/>
</form>
<h3>Collapse All Sections</h3>
</body>
</html>
Next the CSS:
body{
background:#f8f8f8;
color:#333;
font-family:Arial, Helvetica, sans-serif;
}
h2{
font-size:110%;
font-weight:normal;
width: 40em;
}
h3{
font-size:110%;
font-weight:normal;
width: 40em;
}
.hidden{
display:none;
}
.shown{
display:block;
}
.trigger{
background:#ccf;
cursor:n-resize;
}
.open{
background:#66f;
cursor:s-resize;
}
.hover{
background:#99c;
}
Finally the JavaScript:
function collapse()
{
//Collapses and expands siblings of h2 tags in html file
if(!document.createTextNode){return;}
var heads=document.getElementsByTagName('h2');
for(var i=0;i<heads .length;i++)
{
//Figure out what should be opened and closed.
var tohide=heads[i].nextSibling;
while(tohide.nodeType!=1)
{
tohide=tohide.nextSibling;
}
//open all parts that should be open, and set their parents to be ready to close them
cssjs('add',tohide,'shown')
cssjs('add',heads[i],'trigger')
heads[i].tohide=tohide;
//Make them act more like links so the user gets a response
heads[i].onmouseover=function()
{
cssjs('add',this,'hover');
}
heads[i].onmouseout=function()
{
cssjs('remove',this,'hover');
}
heads[i].onclick=function()
{
if(cssjs('check',this.tohide,'hidden'))
{
cssjs('swap',this,'trigger','open');
cssjs('swap',this.tohide,'hidden','shown');
} else {
cssjs('swap',this,'open','trigger');
cssjs('swap',this.tohide,'shown','hidden');
}
}
}
var mainhead=document.getElementsByTagName('h3');
for(var i=0;i<mainhead.length;i++)
{
//run through and make h3's into close and open all links
cssjs('add',mainhead[i],'trigger');
this.innerHTML = 'Collapse all Sections';
mainhead[i].onmouseover=function()
{
cssjs('add',this,'hover');
}
mainhead[i].onmouseout=function()
{
cssjs('remove',this,'hover');
}
mainhead[i].onclick=function()
{
heads=document.getElementsByTagName('h2');
if(cssjs('check',this,'trigger'))
//This runs through and opens all sections cleaning up the h2's so that they are ready to close again
{
for(var j=0;j<heads.length;j++)
{
if(cssjs('check',heads[j],'trigger'))
{
cssjs('swap',heads[j],'trigger','open');
cssjs('swap',heads[j].tohide,'hidden','shown');
}
}
for(var l=0;l<mainhead.length;l++)
{
//Makes sure that all h3s are ready to close the sections
mainhead[l].innerHTML = 'Expand all Sections';
cssjs('swap',mainhead[l],'trigger','open');
}
} else {
//This runs through and closes all sections cleaning up the h2's so that they are ready to open again
for(var k=0;k<heads.length;k++)
{
if(cssjs('check',heads[k],'open'))
{
cssjs('swap',heads[k],'open','trigger');
cssjs('swap',heads[k].tohide,'shown','hidden');
}
}
for(var m=0;m<mainhead.length;m++)
{
//Makes sure that all h3s are ready to re-open the sections
cssjs('swap',mainhead[m],'open','trigger');
mainhead[m].innerHTML = 'Collapse all Sections';
}
}
}
}
}
function cssjs(a,o,c1,c2)
{
//Applies action a to object o by adding, removing, swapping, or checking for classes c1 or c2.
switch (a){
case 'swap':
o.className=!cssjs('check',o,c1)?o.className.replace(c2,c1):o.className.replace(c1,c2);
break;
case 'add':
if(!cssjs('check',o,c1)){o.className+=o.className?' '+c1:c1;}
break;
case 'remove':
var rep=o.className.match(' '+c1)?' '+c1:c1;
o.className=o.className.replace(rep,'');
break;
case 'check':
return new RegExp('b'+c1+'b').test(o.className)
break;
}
}
//make it all happen when the page loads
window.onload=collapse;