Today's Episode:
Forms A La Mode
I first met Todd when I was interviewing for a job several years ago. That interview was memorable because we discovered right in the middle of it that we both attended the same church. In spite of that, he hired me. We have a wonderful symbiotic relationship: he'll program his way into an impossible situation, he comes to my cubicle for help, I administer a cluestick to him. We both come away happier, each for our own reasons.
I mention Todd because the web-enabling project that for me has served as both paycheck and public inspiration the past two years has given him a chance to spread his wings and take flight. While I was in combat with both the server code and Base Networking about a proxy that insisted on caching dynamically-generated data, Todd was given the task of creating forms that could be toggled between view-only or edit modes. Even I was startled when he peeked over the cubicle wall one afternoon to proudly announce he was done. I looked at his handiwork, put it through its paces, and had to admit being impressed by what he was able to achieve.
Then I viewed the page source.
The nicest thing that could be said about the source was that the <SCRIPT> block in the page header was longer than the page body. It seemed to be composed of lines that looked an awful lot like this:
var dbTableInfo = new Array();
dbTableInfo["namef.length"] = 80;
dbTableInfo["namef.size"] = 87;
dbTableInfo["namef.type"] = "T";
dbTableInfo["namef.req"] = "Y";
dbTableInfo["statusf.type"] = "D";
dbTableInfo["statusf.req"] = "Y";
dbTableInfo["class_wordf.type"] = "D";
dbTableInfo["class_wordf.req"] = "Y";
dbTableInfo["security_classf.type"] = "D";
dbTableInfo["security_classf.req"] = "Y";
...and so on for nearly 30 more lines. That was just the beginning, for he then passed this monstrosity into a routine from an external Javascript library he custom-wrote for this problem. Not just any Javascript routine, mind you. No, this one reached deep into the fractional dimensions of the HTML 4 spec where the Document Object Model (DOM) resides and used the array in various and arcane ways to toggle the target nodes between simple text and input fields. Trust me when I say this was code that would have made Cthulhu run screaming in fright back to R'lyeh with his tentacles between his legs. Nonetheless, the team was on a schedule, it worked, and there weren't any better alternatives out there. For this version of the project, it would have to be our production code.
SOLUTION
One of the nicest perks about my job is a Dell Inspiron 8100 named SuperMooey (it's a long story involving my daughter Rachel and a previous laptop. Don't ask), which was issued to me earlier this year. Once I successfully configured it to dual-boot between Windows 2000 and Linux, I no longer had to sacrifice time at work to the exigencies of being the family soccer mom. Three-hour wait to see Devonna's eye surgeon in Cincinnati? Stuck at the Irish Dance school with both daughters? Luke tying up the home computer seeing what new games are on the Big Idea website? NOOO PROBLEM! Just whip SuperMooey out, boot him up in Linux mode, and start slinging code... Well anyway, I had to take Devonna down to Cincinnati the other day. I figured this was as good a time as any to see if there were any simpler ways of toggling a form between view-only and editable. Between the lack of patients that afternoon and the raptuous attention various members of the medical profession paid to SuperMooey, I didn't have anywhere near a three-hour wait to experiment. The time I did have, however, was productive enough.
CODE
Long-time readers will have deduced that, as an unspoken rule, I try to keep the Javascript to a bare minimum around here. This stems from my years of experience with both Netscape's and Microsoft's interpretation of the phrase "standards compliance," combined with the irrational belief that even now in the beginning years of the 21st century Javascript isn't quite stable yet. If there's a way to do something that can be done almost as well with stylesheets or HTML tags, I will prefer that way over doing it with Javascript. This is the philosophy with which I approached the rewrite of the forms toggling code.
"So what does this mean, Ges?" I hear the audience ask. It means that we first need to ask the question, "What ways other than DOM manipulation can we toggle HTML data between view-only and editable?" Glad you asked, for the first technique that springs to mind is a handy property posessed by every form element called disabled. This is a read/write property that influences not only whether a form element can be acted on by a user but whether that form element gets sent to the server on submission. All our Javascript then needs to do is loop over all our form elements and set the disabled property of each depending on which mode we are in.
Let's go to the code:
01 function dbFormToggle (n, mode) {
02 var f = document.forms[n];
03 var l = f.elements.length;
The function takes two variables
- An integer to serve as an index into the
document.forms[]collection, and - A string containing the mode we're about to switch into
Line 2 caches a reference to our target form so we don't have to reach into the document.forms[] collection all the time we're running this script. Similarly, line 3 caches the number of form elements into a single, easy-to-use variable. If I try accessing the length property of the elements[] collection directly in the for()-loop we'll see later, I'm spending time each and every iteration reaching down the object hierarchy to access it. Since this isn't going to change over the lifetime of this function's invocation, it makes much more sense to do it once and store the result for later use. It's quicker, too.
04
05 // -- set style and disabled toggled based on current mode
06 if (mode == 'view') {
07 disable = true;
08 f_class = 'disabled';
09 f.elements[l-2].disabled = true;
10 f.elements[l-1].disabled = false;
11
12 } else if (mode == 'edit') {
13 disable = false;
14 f_class = 'enabled';
15 f.elements[l-2].disabled = false;
16 f.elements[l-1].disabled = true;
17 }
In this block, we check the contents of the mode argument passed into the function when it was called. The contents of two variables and the status of two buttons depend on its value. disable should be self-explanatory but I'll do so anyway -- this is a boolean variable which will be used later to set the disabled property of all our form elements. The variable f_class is used to determine which CSS class we will use to set properties for all text or textarea elements. If we're view-only, default browser behavior is to gray out the font color and leave the field background the same color. I prefer the opposite behavior to indicate the field is disabled, because graying the background out is a lot more noticeable. Conversely, if we're going into edit mode, we need a way to restore some semblance of a default appearance.
The two form elements whose disabled property is being manipulated are what I use in this example to control the form mode. They're not important right now, for in a real live application you'll probably have a different means of letting the user choose a form mode.
18
19 // -- loop over all elements in this form
20 for(i=0; i<l-2; i++) {
21 with(f.elements[i]) {
22 if (type.match(/text/i))
23 className = f_class;
24 disabled = disable;
25 }
26 }
27 }
This block is the meat of the function. We loop over all but the last two entries in the form's element[] collection (for reasons to be discussed in a later section). Line 21 is there both for code readability and because it makes what I'm doing less cumbersome to type. Line 22 applies a regular expression to the type property of the current form element, returning true only if it's of type text or textarea. If it is, I set the background color of that element appropriately in line 23, setting the className property to the appropriate CSS class determined in the previous block of code. Line 24 sets the disabled property based on what we determined previously. After all that's done, all that's left is to close all our blocks and bail out.
USAGE
We will set this function (with appropriate choice of arguments) in three places:
- the
onloadhandler of the <BODY> tag - the
onclickhandler of the View button - the
onclickhandler of the Edit button
As usual, I've included a simple example form below which you can toggle between view-only and edit modes of operation. Once you get tired of fiddling with that, feel free to View Source on this page to get a better feel for how I intend to do things for the next release of that pesky web application.
BUGS FEATURES
I used the document.forms[] collection as documented above solely because I was in a hurry. A more robust version of this code would use document.getElementById() or pass the form object by reference as one of the function's arguments.
The for()-loop is hardwired to skip over the last two elements in the form. This is because the buttons I use to control the form's mode just happen to be the last two elements, and I have code controlling their status earlier in the script. If your page puts mode-toggling controls somewhere else, you'll need to put some conditionals within the loop to skip over those elements.
A more robust version of this script would replace the if-then-elseif construct in lines 5-17 with a switch statement that could test for other possible operating modes. This would also give you the flexibility to code a fallback option that you could use to capture illegal data in the mode variable and bomb out. Proof of concept is left to the reader.