Home Articles FAQs XREF Games Software Instant Books BBS About FOLDOC RFCs Feedback Sitemap
irt.Org

Related items

How to do the impossible with Google Gears

Out and About

JavaScript Bouncing Balls

Turning Tables Into Selection Lists

Drag and Drop with Microsoft Internet Explorer 5

Dynamic Floating Tool Tips

What is DHTML?

Image Manipulation Techniques

String Gradients the Fun Way!

"The Light Fantastic"

Multi-dialogue forms on one page

You are here: irt.org | Articles | Dynamic HTML (DHTML) | Multi-dialogue forms on one page [ previous next ]

Published on: Thursday 13th April 2000 By: Martin Webb

Multi-dialogue forms on one page

Introduction

Traditionally very large forms are split into separate forms and displayed on separate pages. Each form collects and validates the input before displaying the next one. For each form there is a client-server request for the next form. If the user needs to go back and update one of the previous forms, then they then have to go through each of the subsequent forms possibly filling in data again. This results in extra client-server trips and the possibility of losing data entered previously.

This article shows how to include a multi-dialogue form all on one page. The working example presented in this article will show how to split a form into several parts, but display each part form on the same page without requesting the next form from the server, and allow the user to go back and amend any of the previous forms without losing data already entered.

This article will also introduce techniques for coping with the W3C Document Object Model (DOM), which is supported by both Internet Explorer and the upcoming release of Netscape Navigator.

The Large Form Approach

Large unwieldy forms may not fit in the browser window. Faced with the attached form, your users may be discouraged from completing and submitting the form.

This long form also suffers from another problem. It is static, there is no way to alter subsequent questions based on the answers in the preceeding questions. We may, for example, want different information from people living in the United States than from those living elsewhere.

The Multiple Page Approach

Splitting the form over several pages has several advantages. Each form is concise and to the point. Each form can ask specific information, e.g. address or skills, and not a mixture of diverse, unrelated information.

If the technology is available, you can serve different pages based on the responses giving on the previous forms. Even changing the order that the forms are displayed. For example, once someone has logged on you can elect not to ask them for their address if you already know it, or, you could perhaps ask them to confirm their address based on data you already hold.

We could split the large form mentioned above into several smaller forms: Form 1, Form 2, Form 3, Form 4.

There are however a couple of disadvantages:

The Multi-Dialogue Form

Using Dynamic HTML (DHTML) we can combine the small separate pages all on to the same page - reminiscent of tab dialogue boxes in use in most GUI applications.

We can display an initial form, and once the user has complete their input, they click one of several tab like buttons to display one of the other forms. The user can elect to pick whatever forms they wish in whatever order suits them, and they can return to a previous form without loss of input data.

Using JavaScript, we write out the entire form to a SPAN tag, replacing the previously displayed dialogue form. Before we destroy the previous form, we need to capture the entered data, this is done using an onClick event handler, that loops through all the form elements, retrieving the form data for later use.

Once the forms have been completed, we need to be able to submit all of the form data in one go. This is achieved with the aid of a hidden form, that contains a hidden form field for each of the visible form fields on the individual dialogue forms.

Multiple Forms On One Page

The following SPAN tag will be the container for the forms. Its initial state is empty, but as it is a positioned SPAN tag we can use DHTML techniques to update its contents:

<span id="myTab" style="position:absolute"></span>

Next the hidden form field, which is used to hold all the entered form data for when the user submits the form:

<form name="hiddenForm" method="get">
<input type="hidden" value="" name="myName">
<input type="hidden" value="" name="myEmail">
<input type="hidden" value="" name="myUserid">
<input type="hidden" value="" name="myPassword">

<input type="hidden" value="" name="myAddress1">
<input type="hidden" value="" name="myAddress2">
<input type="hidden" value="" name="myAddress3">
<input type="hidden" value="0" name="myCountry">

<input type="hidden" value="" name="myAge">

<input type="hidden" value="" name="Java">
<input type="hidden" value="" name="JavaScript">
<input type="hidden" value="" name="VBScript">
<input type="hidden" value="" name="ASP">
<input type="hidden" value="" name="HTML">

<input type="button" value="Name"     onClick="update(tab);show(tab=1)">
<input type="button" value="Address"  onClick="update(tab);show(tab=2)">
<input type="button" value="Age"      onClick="update(tab);show(tab=3)">
<input type="button" value="Skills"   onClick="update(tab);show(tab=4)">
<input type="submit" value="Submit">

The form includes several visible elements: four visible form buttons which when clicked invoke the update() and show() functions, and one visible submit button. The remainder are all hidden form fields - one each for the visible form fields on the dialogue forms.

The update() function is passed the existing value of tab - more about these update() later. The show() function is used to first update the value of tab to either 1, 2, 3 or 4 - corresponding to the numbered form to be displayed, and then passes the value of tab to the show() function.

Next we need to build up the HTML for the individual forms. Before we do, we'll simply the generation of the HTML by holding data for the drop down selections and radio boxes in arrays:

var theCountries = ['United States','United Kingdom','Canada'];
var theAges      = ['0 - 17','16 - 21','22 - 30','30 - 45','45 - 60','60+'];
var theSkills    = ['Java','JavaScript','VBScript','ASP','HTML'];

Not only does this make the JavaScript code to generate the forms shorter, it also enables us to add further entries by making additional entries to the arrays. The JavaScript code to generate the individual forms, uses the above arrays to build up the drop down selections and radio boxes within loops.

To generate the individual forms, we use the following show() function, which accepts the passed value for tab to control which form is to be built for display, using the switch statement:

function show(n) {
    var output = '<form name="myForm">';
    switch(n) {
        case 1:
            // This demonstrates text and password fields
            output += '\n<p>Name:<br>';
            output += '\n<input type="text" name="myName" value="' + document.hiddenForm.myName.value + '" size="30">';
            output += '\n<p>Email Address:<br>';
            output += '\n<input type="text" name="myEmail" value="' + document.hiddenForm.myEmail.value + '" size="30">';
            output += '\n<p>Userid:<br>';
            output += '\n<input type="text" name="myUserid" value="' + document.hiddenForm.myUserid.value + '" size="30">';
            output += '\n<p>Password:<br>';
            output += '\n<input type="password" name="myPassword" value="' + document.hiddenForm.myPassword.value + '" size="30">';
            break;
        case 2:
            // This demonstrates more text fields and a select form field
            output += '\n<p>Address1:<br>';
            output += '\n<input type="text" name="myAddress1" value="' + document.hiddenForm.myAddress1.value + '" size="30">';
            output += '\n<p>Address2:<br>';
            output += '\n<input type="text" name="myAddress2" value="' + document.hiddenForm.myAddress2.value + '" size="30">';
            output += '\n<p>Address3:<br>';
            output += '\n<input type="text" name="myAddress3" value="' + document.hiddenForm.myAddress3.value + '" size="30">';
            output += '\n<p>Country:';
            output += '\n<br><select name="myCountry">';
            for (var i=0; i<theCountries.length; i++) {
                if (document.hiddenForm.myCountry.value == theCountries[i])
                    output += '\n<option selected>' + theCountries[i];
                else
                    output += '\n<option>' + theCountries[i];
            }
            output += '\n<\/select>';
            break;
        case 3:
            // This demonstrates radio form fields
            output += '\n<p>Age:';
            for (var i=0; i<theAges.length; i++) {
                if (document.hiddenForm.myAge.value == theAges[i])
                    output += '\n<br><input checked type="radio" name="myAge" value="' + theAges[i] + '"> ' + theAges[i];
                else
                    output += '\n<br><input type="radio" name="myAge" value="' + theAges[i] + '"> ' + theAges[i];
            }
            break;
        case 4:
            // This demonstrates check box form fields
            output += '\n<p>Skills:'
            for (var i=0; i<theSkills.length; i++) {
                if (document.hiddenForm.elements[theSkills[i]].value == 'on')
                    output += '\n<br><input checked type="checkbox" name="' + theSkills[i] + '"> - ' + theSkills[i];
                else
                    output += '\n<br><input type="checkbox" name="' + theSkills[i] + '"> - ' + theSkills[i];
            }
            break;
    }
    output += '\n<\/form>';

The value of each form field is retrieved from the relevant hidden form field. Depending on whether the form field is a text box, a select list or radio buttons, either the value property or selected or checked attributes are set.

We need the ability to update the contents of the SPAN tag. This is dependent on the browser being used - for now we'll just describe the techniques for Netscape Navigator 4 (NN4) and Internet Explorer 4 and 5 (IE). If IE is being used we can update the SPAN tag by amending its innerHTML property. If NN4 is being used we can use the document.layers object to write new content into the SPAN tag:

    if (document.all)
        document.all('myTab').innerHTML = output;
    else if (document.layers) {
        document.layers['myTab'].document.open();
        document.layers['myTab'].document.writeln(output);
        document.layers['myTab'].document.close();
    }
}

All we need to do now is capture the dialogue form data before the form is replaced by another. This is done by a call to the update() from within the hidden forms visible buttons onClick event handlers.

The update() function is passed the current value of tab. The function then uses this to interrogate the type and value of all the form elements within the myTab SPAN tag, using either the document.all or document.layers object (for IE and NN4 respectively.)

function update(n) {
    var what;
    if (document.all)           what = document.all['myTab'].myForm.elements;
    else if (document.layers)    what = document.layers['myTab'].document.myForm.elements;

    for (var i=0, j=what.length; itype).toLowerCase(), myName = what[i].name;
        if (myType == 'radio' && what[i].checked)
            document.hiddenForm.elements[myName].value = what[i].value;
        if (myType == 'checkbox')
            document.hiddenForm.elements[myName].value = ((what[i].checked) ? what[i].value : '');
        if (myType == 'password' || myType == 'text' || myType == 'textarea')
            document.hiddenForm.elements[myName].value = what[i].value;
        if (myType == 'select-one')
            document.hiddenForm.elements[myName].value = what[i].options[what[i].selectedIndex].text;
    }
}

The hidden form is updated using the names of the visible form fields to dictate which hidden form elements should be populated.

We just now need the ability to display the initial form. This is done using an onLoad event handler in the BODY tag:

<body onLoad="if (document.all || document.layers) show(tab=1)">

And that is it. Or it would be if we did not have to worry about...

The W3C Document Object Model

Netscape and Microsoft both went their separate ways with the DOM in the version 4 browsers. Microsoft with the document.all object, and Netscape with the document.layers object. Since then developers have had to code, in effect, to two seperate DOMs. The models do provide commonality, which assists the developer in providing cross browsers solutions, but sometimes there is no commonality between the two models, leaving the developer no option but to either limit the functionality (coding to the lowest common denominator), or restricting the functionality for one browser, or at worst, excluding one or other of the browsers from the site.

This problem was forseen quite early on, in fact, as far back as the introduction of Netscape Navigator 3 and Internet Explorer 2. Netscape Navigator 3 provided support for accessing the image object via JavaScript, but Internet Explorer 2 did not - Microsoft's implementation of JScript was based on the Netscape Navigator 2 and JavaScript 1.0 - which didn't support the Image object. At least the days of people asking how to do image roll-overs in Internet Explorer are long gone, but now we are faced with questions on how to code solutions for browser specific extensions.

The sooner the differences between the browsers disappear the better. We can then concentrate on developing code that we know will work on all browsers.

The World Wide Web Consortium chaired by Tim Berners-Lee has been championing browser standards since the release of the HTML 2.0 Recommendation.

The W3C DOM Working Group has spent the last couple of years looking at how to bring the two diverging DOMs in the two main vendors' browsers together. The DOM Level 1 became a W3C Recommendation in October 1998, specified Core and HTML specific objects, methods and properties, that DOM Level 1 conforming browsers should support.

Internet Explorer 5 stole an early lead by providing support for most of DOM Level 1. Netscape Navigator has taken a good deal longer to catch up. The decision to open the source code up to Netscape Navigator through the Mozilla organization, led to the declaration that the browser would be 100% standards compatible.

Mozilla have spent the last two years completely rewriting the browser, to an extent where the next version of Netscape Navigator is expected to be version 6. Version 5 was the open source code version given to Mozilla.

Mozilla, at the time of writing is just about to be prepared for a beta release. One major change for web developers is the decision to not support the Netscape specific layers object. This decision, not to be taken lightly, will have the potential to render hundreds if not thousands of dynamic web sites as static, i.e. the dynamic capabilities of JavaScript code crafted with the use of the document.layers object will no longer function. This does not mean that the document.layers object has been removed, it will still be possible to test for the document.layers object, and interact with it, but the interaction will not have any effect.

The solution is to use the objects, methods and properties described in the DOM Level 1. As Internet Explorer also supports the DOM Level 1, this should in theory make it easier to develop web sites.

One impact of this will be that it will no longer be possible to write HTML content into a layer using document.write(). It will be possible to add nodes, e.g. HTML elements and text elements, to an existing document, but this requires a more extensive knowledge of the DOM than will be covered here. We will cover the DOM Level 1 Recommendation in further detail at a later date.

For now, we will simply use a Mozilla specific extension to enable us to adapt the Multi-Form example so that it will work when used on Mozilla, or when Netscape Navigator 6 becomes available.

The following code has the effect of replacing the HTML content of the SPAN tag:

spanNode = document.getElementById('myTab');
while (spanNode.hasChildNodes()) spanNode.removeChild(spanNode.lastChild);
var range = document.createRange();
range.selectNodeContents(spanNode);
spanNode.appendChild(range.createContextualFragment(output));

It uses the W3C DOM Level 1 HTML method getElementById() to retrieve a reference to the DOM tree node that corresponds to our SPAN tag. Using this node, we use the nodes hasChildNodes() method and remove all the child nodes using the removeChild() method, removing all the child nodes using the lastChild property to return a reference to the last child node of the SPAN tag, until there are no longer any child nodes attached.

This has the effect of deleting any exisiting form being held within the SPAN tag.

Next we create a DOM Level 2 (currently in progress) Range object, which we use to select the whole contents (now empty) of the SPAN tag. We then append a new child node using the appendChild() method. The actual child node appended is generated from the range objects createContextualFragment() method (a Mozilla specific method - not supported by the W3C DOM. The createContextualFragment() accepts a string of HTML and parses it into a document fragment, converting all the HTML into DOM nodes and child nodes.

These five lines of code have a similar effect to the Internet Explorer specific innerHTML property.

We can now adapt our code to make use of this, so that are completed working example will work on NN4, NN6 (i.e Mozilla), IE4 and IE5.

The complete source code, with the additions for NN6 emphasized, is shown below.

Source Code

<head>
<script language="JavaScript"><!--
function update(n) {
    var what;
    if (document.getElementById) what = document.getElementById('myForm').elements;
    else if (document.all)       what = document.all['myTab'].myForm.elements;
    else if (document.layers)    what = document.layers['myTab'].document.myForm.elements;

    for (var i=0, j=what.length; i<j; i++) {
        var myType = (what[i].type).toLowerCase(), myName = what[i].name;
        if (myType == 'radio' && what[i].checked)
            document.hiddenForm.elements[myName].value = what[i].value;
        if (myType == 'checkbox')
            document.hiddenForm.elements[myName].value = ((what[i].checked) ? what[i].value : '');
        if (myType == 'password' || myType == 'text' || myType == 'textarea')
            document.hiddenForm.elements[myName].value = what[i].value;
        if (myType == 'select-one')
            document.hiddenForm.elements[myName].value = what[i].options[what[i].selectedIndex].text;
    }
}

var theCountries = ['United States','United Kingdom','Canada'];
var theAges      = ['0 - 17','16 - 21','22 - 30','30 - 45','45 - 60','60+'];
var theSkills    = ['Java','JavaScript','VBScript','ASP','HTML'];

function show(n) {
    var output = '<form name="myForm">';
    switch(n) {
        case 1:
            // This demonstrates text and password fields
            output += '\n<p>Name:<br>';
            output += '\n<input type="text" name="myName" value="' + document.hiddenForm.myName.value + '" size="30">';
            output += '\n<p>Email Address:<br>';
            output += '\n<input type="text" name="myEmail" value="' + document.hiddenForm.myEmail.value + '" size="30">';
            output += '\n<p>Userid:<br>';
            output += '\n<input type="text" name="myUserid" value="' + document.hiddenForm.myUserid.value + '" size="30">';
            output += '\n<p>Password:<br>';
            output += '\n<input type="password" name="myPassword" value="' + document.hiddenForm.myPassword.value + '" size="30">';
            break;
        case 2:
            // This demonstrates more text fields and a select form field
            output += '\n<p>Address1:<br>';
            output += '\n<input type="text" name="myAddress1" value="' + document.hiddenForm.myAddress1.value + '" size="30">';
            output += '\n<p>Address2:<br>';
            output += '\n<input type="text" name="myAddress2" value="' + document.hiddenForm.myAddress2.value + '" size="30">';
            output += '\n<p>Address3:<br>';
            output += '\n<input type="text" name="myAddress3" value="' + document.hiddenForm.myAddress3.value + '" size="30">';
            output += '\n<p>Country:';
            output += '\n<br><select name="myCountry">';
            for (var i=0; i<theCountries.length; i++) {
                if (document.hiddenForm.myCountry.value == theCountries[i])
                    output += '\n<option selected>' + theCountries[i];
                else
                    output += '\n<option>' + theCountries[i];
            }
            output += '\n<\/select>';
            break;
        case 3:
            // This demonstrates radio form fields
            output += '\n<p>Age:';
            for (var i=0; i<theAges.length; i++) {
                if (document.hiddenForm.myAge.value == theAges[i])
                    output += '\n<br><input checked type="radio" name="myAge" value="' + theAges[i] + '"> ' + theAges[i];
                else
                    output += '\n<br><input type="radio" name="myAge" value="' + theAges[i] + '"> ' + theAges[i];
            }
            break;
        case 4:
            // This demonstrates check box form fields
            output += '\n<p>Skills:'
            for (var i=0; i<theSkills.length; i++) {
                if (document.hiddenForm.elements[theSkills[i]].value == 'on')
                    output += '\n<br><input checked type="checkbox" name="' + theSkills[i] + '"> - ' + theSkills[i];
                else
                    output += '\n<br><input type="checkbox" name="' + theSkills[i] + '"> - ' + theSkills[i];
            }
            break;
    }
    output += '\n<\/form>';

    if (document.getElementById) {
        if (window.HTMLElement) {
            spanNode = document.getElementById('myTab');
            while (spanNode.hasChildNodes()) spanNode.removeChild(spanNode.lastChild);
            var range = document.createRange();
            range.selectNodeContents(spanNode);
            spanNode.appendChild(range.createContextualFragment(output));
        }
        else {
            document.all('myTab').innerHTML = output;
        }
    }
    else if (document.all)
        document.all('myTab').innerHTML = output;
    else if (document.layers) {
        document.layers['myTab'].document.open();
        document.layers['myTab'].document.writeln(output);
        document.layers['myTab'].document.close();
    }
}
//--></script>
</head>

<body onLoad="if (document.getElementById || document.all || document.layers) show(tab=1)">

<form name="hiddenForm" method="get">
<input type="hidden" value="" name="myName">
<input type="hidden" value="" name="myEmail">
<input type="hidden" value="" name="myUserid">
<input type="hidden" value="" name="myPassword">

<input type="hidden" value="" name="myAddress1">
<input type="hidden" value="" name="myAddress2">
<input type="hidden" value="" name="myAddress3">
<input type="hidden" value="0" name="myCountry">

<input type="hidden" value="" name="myAge">

<input type="hidden" value="" name="Java">
<input type="hidden" value="" name="JavaScript">
<input type="hidden" value="" name="VBScript">
<input type="hidden" value="" name="ASP">
<input type="hidden" value="" name="HTML">

<input type="button" value="Name"     onClick="update(tab);show(tab=1)">
<input type="button" value="Address"  onClick="update(tab);show(tab=2)">
<input type="button" value="Age"      onClick="update(tab);show(tab=3)">
<input type="button" value="Skills"   onClick="update(tab);show(tab=4)">
<input type="submit" value="Submit">
</form>

<span id="myTab" style="position:absolute"></span>

</body>
</html>

Working Example

Why not try out the working example - the form submission just requests the current page.

References

Related items

How to do the impossible with Google Gears

Out and About

JavaScript Bouncing Balls

Turning Tables Into Selection Lists

Drag and Drop with Microsoft Internet Explorer 5

Dynamic Floating Tool Tips

What is DHTML?

Image Manipulation Techniques

String Gradients the Fun Way!

"The Light Fantastic"

©2018 Martin Webb