This is not intended as a complete tutorial to using forms in HTML,
indeed it is assumed here that the programmer already has a basic knowledge of them. However,
there are a number of subtle aspects to their use ranging from the genuinely misleading and
confusing to the merely cosmetic, and these in turn lead to a common set of programming problems
which the script associated with this page, and other major forms on this site, attempts to
overcome. To download the script to your PC, <Right-Click>
Forms.js
and choose Save As.
Common Problems Programming Forms
This is not an exhaustive list, but I think most of the ones that I've encountered are here:
Using the built-in GET as the submit functionality automatically encodes into
the URL the values of <input> elements, but there is no corresponding functionality to
read values from such a URL and set them back into the inputs. The script function
setURLPars() fills this need.
The GET function ignores elements which do not have a name
attribute.
The GET function ignores Disabled inputs but processes
ReadOnly inputs. This is unfortunate, because in many browsers disabled
inputs do not show tooltips (titles) &/or cannot be selected and copied, while read only
ones do and can, but read only values are likely to be calculated and therefore not actually
required as parameters in a URL, so their being processed by the GET function
somewhat hampers practical use.
For any non-trivial application data as stored in a form may be inadequate. This
might be because:
<select> elements behave differently in different browsers, most
importantly the onchange event - different browsers fire
it under different circumstances, some even firing it differently depending on whether
the keyboard or mouse is being used: if the keyboard, FF
fires it on exiting the element if the final selection is different to that on entry,
while, illogically in my opinion, FF using the mouse, IE
and Opera fire it every time the user chooses a new selection, before the choice is
finalised by leaving the element. Thus it is almost impossible to program
consistent cross-browser behaviour for this event.
The options in <select> elements may need to be read from a file
or otherwise set programmatically, which may change the width of the element. In
some versions of IE, such DHTML
can leave garbage on the page Consequently, if the options are to be set only at
page load time, then it's best to interleave the form HTML with the corresponding
JavaScript (JS) definitions (see the coding of
this page), so that following content is created after disturbance to the layout;
otherwise setting the element &/or its wider container to have sufficient width to
cope with the widest expected layout sometimes may cure the problem.
Form data may need to be stored in one format, but displayed in another. For
example:
Regardless of how they are entered, strings may be required in either upper or
lower case.
Integers may need to be displayed in binary or some other radix, but for
practical purposes such as calculation need to be stored in native JS form.
FP numbers may not need to be
displayed to the full accuracy required for calculating with them.
The display and parsing of dates and times depend on user's locale and browser.
FP numbers and dates and times may not be suitable for submitting via a URL
parameter, either because the number of displayed decimal places will lead to a
URL parameter that is too short to be sufficiently accurate, or over many such
fields to a URL that is too long to be safe, or because locale dependence may
lead to ambiguity when they are read back in. The script function
submitURLPars() overcomes these problems.
Calculations may require SI
units such as kilometres, but a user's locale may use different units such as
miles.
Calculations involving angles such as latitude and longitude use decimal radians,
but these may mean nothing to the average user, who would be more used to either
decimal degrees, or degrees:minutes:seconds.
For all these reasons, the script uses a system where the underlying data value is stored in a
seperate object behind, as it were, the corresponding form element. When the user changes
the value in the form, the corresponding data object is updated with the new value.
It is often required to display different sets of inputs depending on some initial choice by
the user, but built-in functionality provides no easy way to do this. Again, the script
supplies this functionality, by supplying the possibility both to run functions whenever
particular form values change, and to hide or display and give focus to particular form elements.
See the addListen() and update() functions.
Depending on browser, particularly legacy IE, various page elements, including, of particular
relevance here, form elements, may not style in the manner expected. For example:
Default font-sizes differ between browsers.
Colours in forms and form elements may not inherit from containing elements. Note
particularly that <option> colours may not even inherit from the
containing <select>! In most browsers, it is not possible
to choose the foreground colour of disabled form elements.
Margin and alignment behaviour, particularly around floating elements, differs between
browsers, legacy IE being particularly idiosyncratic.
Documentation For "Forms.js"
Besides one or two global variables and functions, the script defines a number of types of
'background' data object each corresponding to a particular type of form element. All the
objects and functions intended for normal public use are documented here. Others exist for
the use of which you'll have to read and understand the code, but this is only likely to be
required if you wish to extend the Object hierarchy.
Form Styling with CSS
Form styling is largely a matter of choice but here are some tips:
It's probably best to style both <html> and <body>
elements, and by the same rules.
NEVER set explicitly just one of the foreground or background colours and let
the other default, either set both explicitly, or let them both default. This is far too
common a fault: Windows & Web Colour Setting Bugs.
If you want form elements to harmonise with some broader site colour scheme, it's probably best
to set them explicitly rather than relying on inheritance. Set both <select>
and <option> colours, and by the same rules.
Using styles, "Forms.js" can
implement DHTML if it is required to adapt form layout to user choices by hiding and showing
fields.
Satellite TV Dish & Rotor Alignment Settings Calculator
on my website is a complex example of what can be done. Here are some tips to achieving this:
The trick is accomplished by adding to or removing from an element the following style
class … .invis { display:none; margin:0; padding:0; line-height:0; }
… which respectively hides or shows it. This requires two of the
three external functions from another script on my site listed in the
Appendix, which were not included in earlier versions of
"Forms.js", but now are.
When implementing DHTML in this way, it's probably best to have such fields and the
surrounding ones styled with the float rule so that the layout adjusts
automatically to fields appearing and disappearing.
The showing and hiding is triggered via listeners, usually on a SelValue,
but any instance of StrValue, including any descendant, should do.
Here's a code clip adapted from the above page's coding which shows the post code field only
when the appropriate option is selected: <div id="fWhereHow" class="field" title="Choose …"> <div id="lWhereHow"><label for="iWhereHow">Choose:</label></div> <select id="iWhereHow" tabindex="0" onkeydown="logKey(this,event);" onchange="this.oData.setValue();" onblur="setNext(this);" > <option value="O" selected="selected">Other</option> <option value="C">UK Post Code</option> </select> </div> <div id="gUKPost" class="group invis"> <div class="horspacer"> </div> <div id="fUKPost" class="field" title="A UK post code"> <div id="lUKPost"><label for="iUKPost">UK Post Code:</label></div> <input type="text" id="iUKPost" value="" size="10" tabindex="0" onblur="this.oData.setValue();" /> </div> </div> <div id="gOther" class="group"> <div class="horspacer"> </div> <div id="fOther" class="field" title="The next field"> <div id="lOther"><label for="iOther">Next:</label></div> <input type="text" id="iOther" value="" size="10" /> </div> </div> … <script> <!-- rxWhereHow = new SelValue( "O", "fWhereHow" ); rxUKPost = new StrValue( "", "gUKPost", "U" ); rxWhereHow.addListen( new Function( "srcVal", "rxUKPost.update(srcVal,'C');" ) ); //--> </script>
Sometimes, for example if they are naturally paired like latitude and longitude, it is
required to show or hide a group of fields together. This is achieved by placing
them in an outer container, and defining that as the element for the first field in the
group, while the remainder have the usual arrangement. In this example the
selector to choose a rotor and the field for displaying its crank angle are hidden or
shown together by a single listener (defined on a triggering element not shown), which
by running update() on the former, toggles them both. Note also
that the group is initially hidden by default: <div id="gRotorSel" class="group invis"> <div id="fRotorSel" class="field" title="Choose a rotor model, or enter the rotor crank angle …"> <div id="lRotorSel"><label for="iRotorSel">Rotor:</label></div> <select id="iRotorSel" name="iRotorSel" onkeydown="logKey(this,event)" onchange="this.oData.setValue();" onblur="setNext(this);" > [Options] </select> </div> <div class="horspacer"> </div> <div id="fCrank" class="field" title="Rotor crank angle in decimal degrees"> <div id="lCrank"><label for="iCrank">Crank:</label></div> <input type="text" id="iCrank" name="iCrank" value="" size="5" maxlength="5" onblur="this.oData.setValue();" /> /> </div> </div> … // Rotor selector and crank rotorSel = new SelValue( "U", "gRotorSel", … ); rotorCrank = new Angle( 0, "fCrank", 2 ); system.addListen( new Function( "srcVal", "rotorSel.update(srcVal,'R');" ) );
The above should be enough to start with. If you study both the source code of the SatCalc
page (it's crunched though), and the parameters of update(), you should be able
to devise anything required to handle more complicated situations.
Appendix - Required Code From Other Scripts
The following functions, from other scripts on my site, are required for
"Forms.js"
to run (all four for this version, first two only for earlier versions), but were not included or
documented in earlier versions of this page. Apologies for any inconvenience caused by this
oversight. They are: // Add or remove a class to or from an element function setClass( anElement, aClass, set ) { var theElem = typeof anElement == "string" ? document.getElementById( anElement ) : anElement; var classStr = null; try { var regExp = new RegExp( "\\b" + aClass + "\\b", "g" ); classStr = theElem.className.replace(regExp,""); if( set ) classStr += " " + aClass; theElem.className = classStr; } catch(e){} return classStr } // Show or hide an element function setShowHide( anElement, show ) { return setClass( anElement, "invis", !show ); } // Walk a DOM subtree, looking for descendants of certain types function TreeWalk( aNode, theTypes ) { var node = typeof aNode == "string" ? document.getElementById(aNode) : aNode; var types = theTypes instanceof Array ? theTypes : [theTypes]; var result = []; var nodes; // Note: for( var x in y ) on an array bugs in some browsers, // especially if Array.prototype has been altered if( node ) { if( node.tagName ) for( var i = 0, I = types.length; i < I; i++ ) if( node.tagName.match(new RegExp(types[i],"i") ) ) result.push( node ); nodes = node.childNodes; for( var i = 0, I = nodes.length; i < I; i++ ) result = result.concat( TreeWalk(nodes[i], types) ); } return result; } // Extend String to have trim function String.prototype.trim = strTrim; function strTrim() { return this.replace(/(^\s+)|(\s+$)/g, ""); }
Updated
Description
13/06/2010
v1.1 - Safer Array for() loops if the Array.prototype is altered.
05/12/2009
Updated introduction with an IE bug concerning <select>s, and interleaved this page's
associated HTML & JS coding to avoid it. Altered documentation and coding to specify
calls from elements using this.oData.setValue(). Added trim parameter. Added checkbox
and radio-button functionality. Generalised setURLValue() functionality. Changed
update(), submitURLPars(), setURLPars(), and associated code to use read only rather than disabled
non-active inputs, and removed requirement for a form element name to be the same as its id.
Added onchange debouncing for SelValue. Changed setNext() to allow an id for next element.
Added use of TreeWalk() to find elements of particular types. Added CSS section. Added
missing functions from other scripts to make standalone. Moved from test area to website proper.
27/10/2009
Documented expected field layout. Further documented Internet Explorer layour bug. Fixed problem with dates and times in
URL parameters being potentially ambiguous in some combinations of browser and locale. Improved floating-point URL parameters.