Chapter 13. Form Widgets

This chapter provides systematic coverage of the various dijits that enable you to create fantastic-looking forms with minimal effort. Like everything in Dijit, the controls you'll learn about in this chapter can be defined entirely in markup, require very little JavaScript, and were designed with accessibility considerations in mind. With that said, realize that you're about to embark upon reading a very hefty chapter. The functionality offered by dijit.form is quite intense, packing tons of breadth and depth; the form dijits are by far the most object-oriented widgets in the toolkit, so you'll see deeper inheritance hierarchies via dojo.declare in this chapter than anywhere else in the book.

Drive-By Form Review

While the HTML 4.01 specification (http://www.w3.org/TR/html401/) provides the authoritative specification on forms and is quite worthy of careful reading on its own, this section attempts to summarize some of the most useful content about forms that will help you to get the most out of this chapter. If you're reading this book, a working assumption is that you've designed a form or two, so it is not necessary to belabor fact that a form is a collection of one or more controls that capture information and send it back to a server for processing.

However, it is noteworthy to highlight that the AJAX revolution has really skewed the paradigm of passing data to a server for processing. Previously, data would be submitted to a server handler that broke it into convenient key/value pairs, used these hashes as part of a processing routine, and then returned a new page that somehow reflected these form choices. To make the whole process more elegant, the form might have even been included in an iframe so that the effect of the page reload would be minimized. Now, however, the XMLHttpRequest (XHR) object makes it easy to asynchronously send small chunks of data to a server without an explicit form submission or a page reload of any kind.

Of course, the XHR object, AJAX, and slicker ways of interacting with the user certainly don't make forms obsolete. Forms are still a battle-tested standard; they work even when JavaScript is disabled, and they are important for accessible implementations. In general, it's almost always a good idea to make sure that any fancy way of passing information back to the server is degradable and accessible. In other words, it isn't a matter of "either forms or AJAX"; it's a matter of "both forms and AJAX."

For example, consider Example 13-1, an enhanced version of the plain vanilla form from Chapter 1.

Example 13-1. Simple form
<html>
    <head>
        <title>Register for Spam</title>
        <script type="text/javascript">
            function help(  ) {
                var msg="Basically, we want to sell your info to a 3rd party.";
                alert(msg);
                return false;
            }

            //simple validation
            function validate(  ) {
                var f = document.getElementById("registration_form");

                if (f.first.value == "" || f.last.value == "" || f.email.value
== "") {
                        alert("All fields are required.");
                        return false;
                }

                return true;
            }
        </script>
    <head>
    <body>
        <p>Just Use the form below to sign-up for our great offers:</p>
        <form id="registration_form"
            method="POST"
            onsubmit="javascript:return validate(  )"
            action="http://localhost:8080/register/">

            First Name: <input type="text"  name="first"/><br>
            Last Name: <input type="text" name="last"/><br>
            Your Email: <input type="text" name="email"/><br>
            <button type="submit">Sign Up!</button>
            <button type="reset">Reset</button>
            <button type="button" onclick="javascript:help(  )">Help</button>

        </form>
    </body>
</html>

While as bland as it can possibly get, this form is quite functional, and would behave properly on virtually any browser; the incorporation of a nice CSS stylesheet could make it look quite nice. There's even a Help button to tell the user why the form really exists. On the server side, a simple script would process the form, probably after a web server has already distilled the named fields in the form out of their raw format. A functional CherryPy script might process the form, as in Example 13-2.

Example 13-2. CherryPy script to process a form
import cherrypy
class Content:
    """
    A routine for processing a form submission.
    Named form fields are easily accessible.
    """
    @cherrypy.expose
    def register(self, first=None, last=None, email=None):

        #add user information to evil database here...

        #send back this customized html page
        return """
        <html>
            <head><title>You're now on our spam list!</title></head>
            <body>
                <p>Congratulations %s %s, you're gonna get spammed!</p>
            </body>
        </html>
        """ % (first, last) #substitute in variables

cherrypy.quickstart(Content(  ))

While extremely simple, the previous example did touch on several fundamentals regarding forms:

  • Forms controls should be enclosed in a FORM tag.

  • The FORM tag almost always includes name, method, onsubmit, enctype, and action attributes that provide pertinent information about how the form should be processed.

  • The onsubmit attribute is the standard way of performing client-side validation. Returning false from a validation routine prevents the form from being submitted to the server.

  • The action attribute provides the URL for submitting the form.

  • Form fields that represent meaningful state should include a name attribute, which is what most server-side frameworks will collect into key/value pairs and pass to the specific routine that handles the form submission.

  • Forms are innately accessible with the keyboard; tabs move between fields[24] and the Enter key submits the form. Although not demonstrated, the tabindex attribute can change the default tab order.

  • In general, there are multiple kinds of controls, as specified by the type attribute. This particular example illustrated three different kinds of buttons: one for triggering the onsubmit event, one for resetting the form, and one for handling a custom action.

  • Submitting a form necessarily reloads the page with whatever the server returns if an action attribute is provided in the form. If no action attribute is provided, custom JavaScript or DHTML actions could be taken by attaching scripts to DOM events such as onclick.

Throughout this chapter, the term "attribute" is frequently used to describe both form attributes and object attributes. The intended usage should be apparent from context and is not anything to get hung up over.

While nowhere near exhaustive, hopefully this brief review sets the stage for a discussion of the various form dijits. For a great desktop reference on HTML forms, consider picking up HTML & XHTML: The Definitive Guide by Chuck Musciano and Bill Kennedy (O'Reilly).

Form Dijits

Form dijits that are explicitly declared suitable for use in bona fide HTML forms, as defined with the FORM tag, are a part of the dijit.form namespace. This section walks through all of the dijits included in this namespace, providing example code and sample screenshots that use Dijit's built-in tundra theme. But first, it's worth reiterating that all form dijits are designed to be fully degradable and accessible; in other words, they remain fully functional even if JavaScript, CSS, and images aren't available, and if a keyboard is the only input device available. Accessibility attributes on Windows environments also support high-contrast mode and screen readers.

Figure 13-1 shows the general inheritance structure of the dijit.form module. The diagram does not show every single mixin class along the way, but does convey the general relationships amongst the widgets. The hope is that you'll be able to use it to get a better idea of how the source code is laid out when it comes time to cut your teeth on it.

The inheritance-rich dijit.form module
Figure 13-1. The inheritance-rich dijit.form module

In addition to the standard dijit attributes inherited from _Widget, such as domNode, et al., and ordinary HTML attributes included in the HTML 4.01 spec, such as disabled and tabIndex, form dijits all inherit from a base class that explicitly supports the attributes, methods, and extension points listed in Table 13-1.

Table 13-1. Supported attributes, methods, and extension points for form dijits via _FormWidget

Name

Data type

Category

Comment

value

String

Attribute

The current value of the dijit; works just like its pure-HTML equivalent.

name

String

Attribute

The named value for the dijit; works just like its pure-HTML equivalent; useful for form submissions to a server handler.

alt

String

Attribute

Alternate text that should appear should the browser not be able to display—a somewhat uncommon event for forms, although still common enough for images; works just like its pure-HTML equivalent.

type

String

Attribute

Specifies the type of the element when more than one kind is possible. For example, a button might have type="submit" to trigger the form's onsubmit action; works just like its pure-HTML equivalent. By default, this attribute is "text".

tabIndex

Integer

Attribute

Used to provide an explicit tab index for keyboard navigation; works just like its HTML equivalent. By default, this attribute is "0".

disabled

Boolean

Attribute

Disables a control so that it cannot receive focus and is skipped in tabbing navigation; do not attempt to use this attribute on an element that does not support it, which per the HTML 4.01 spec include button, input, optgroup, option, select, and textarea. Controls that are disabled are not included in form submissions. This attribute is false by default.

readOnly

Boolean

Attribute

Disables a control so that its value cannot be changed; however, it can still receive focus, is included in tabbing navigation, and is included in form submissions. Do not attempt to use this attribute on an element that does not support it, which per the HTML 4.01 spec include input and textarea. This attribute is false by default.

intermediateChanges

Boolean

Attribute

Whether to fire the onChange extension point for each value change. This attribute is false by default.

setAttribute

(/* String */ attr,

/* Any */ value)

Function

Method

The proper way to set an attribute value for a dijit. For example, setting a dijit's value attribute to "foo" would be accomplished via

<dijit name>.setAttribute

("value", "foo").

focus( )

Function

Method

Sets the focus on the control.

isFocusable( )

Function

Method

Returns information about whether the control can receive focus.

forWaiValuenow( )

Function

Extension point

By default, returns the current state of the widget to be used for the WAI-ARIA valuenow state, which may be set via dijit.removeState and dijit.setWaiState.

onChange(/* Any */ val)

Function

Extension point

Override to provide a custom callback function that fires each time the value changes.

For great online documentation on HTML 4.01 forms, see http://www.w3.org/TR/html401/interact/forms.html.

TextBox Variations

Ordinary text input via the HTML input element is by far the most commonly used form field. Countless hours have been spent formatting and validating what are generally small snippets of text, and the auxiliary scripts that have supported input boxes may single-handedly account for the most collective boilerplate that's even been written to support web pages. If any one of those comments resonates deep within your soul, the Dijit TextBox family will seem like a godsend.

Let's take a look at each member of the TextBox family and improve our example form from earlier in this chapter. The most basic member is the ordinary TextBox itself, which comes packed with several custom formatting operations as well as the ability to create your own using the format and parse extension points. The following listing summarizes TextBox 's attributes and extension points. A TextBox is technically a kind of input element, so remember that the standard HTML attributes, if not listed here, still apply.

TextBox 's attributes and extension points are inherited by all other dijits in this family; they are especially important to be aware of because they are widely used.

TextBox

Table 13-2 provides a listing of pertinent features to the most basic TextBox dijit.

Table 13-2. TextBox attributes and extension points

Name

Category

Comment

trim

Attribute

Removes leading and trailing whitespace. This attribute is false by default.

uppercase

Attribute

Converts all characters to uppercase. This attribute is false by default.

lowercase

Attribute

Converts all characters to lowercase. This attribute is false by default.

propercase

Attribute

Converts the first character of each word to uppercase. This attribute is false by default.

maxLength

Attribute

Used for passing through the standard HTML input tag's maxlength attribute. This attribute is "" by default.

format(/* String */ value, /*Object*/constraints)

Extension point

A replaceable function to convert a value to a properly formatted String value. The default implementation returns the result of a value's toString method if it has one; otherwise, it returns the raw value as a last resort. Returns an empty string for null or undefined values.

parse(/* String */ value)

Extension point

May be used to provide a custom parsing function to convert a formatted String to a value, a function that is common to all form dijits, before returning the value. The default implementation returns the raw String value.

setValue(/*String*/value)

Method

Used to set the String value for a TextBox and any subclass of TextBox. Do not use the _FormWidget's setAttribute('value', /*...*/) function for this subclass hierarchy.

getValue( )

Method

Used to fetch the String value for a TextBox and any subclass of TextBox. Do not access the value property directly and sidestep this method.

As of version 1.1, _FormWidget 's setValue and getValue methods were deprecated in favor of using the setAttribute('value', /*...*/) function for setting values and getting values via the .value property where appropriate. TextBox, its subclasses, and a few other dijits, however, override the setValue and getValue methods for legitimate use. The rule of thumb is that setValue and getValue are used for widget values. For example, a TextBox has an obvious value (hence, the use of setValue and getValue ), whereas you would use the setAttribute method for something like a Button because it does not have a widget value even though a value is submitted with the form.

To illustrate the most basic usage possible, Example 13-3 plugs some text boxes into our earlier form example, and switches on the propercase and trim attributes for the first and last fields in the form.

Example 13-3. Updated form with TextBox and theming
<html>
    <head>
        <title>Register for Spam</title>

        <link rel="stylesheet" type="text/css"
          href="http://o.aolcdn.com/dojo/1.1/dojo/resources/dojo.css" />
        <link rel="stylesheet" type="text/css"
          href="http://o.aolcdn.com/dojo/1.1/dijit/themes/tundra/tundra.css" />

        <script
            djConfig="parseOnLoad:true",
            type="text/javascript"
            src="http://o.aolcdn.com/dojo/1.1/dojo/dojo.xd.js">
        </script>

        <script type="text/javascript">
            dojo.require("dojo.parser");
            dojo.require("dijit.form.TextBox");

            function help(  ) {
                var msg="Basically, we want to sell your info to a 3rd party.";
                alert(msg);
                return false;
            }

            //simple validation
            function validate(  ) {
                var f = document.getElementById("registration_form");

                if (f.first.value == "" ||
                    f.last.value == "" ||
                    f.email.value == "") {
                        alert("All fields are required.");
                        return false;
                }

                return true;
            }
        </script>
    <head>
    <body class="tundra">
        <p>Just Use the form below to sign-up for our great offers:</p>
        <form id="registration_form"
            method="POST"
            onsubmit="javascript:return validate(  )"
            action="http://localhost:8080/register/">

            First Name:
              <input dojoType="dijit.form.TextBox" propercase=true
               trim=true name="first"><br>
            Last Name:
              <input dojoType="dijit.form.TextBox" propercase=true
               trim=true name="last"><br>
            Your Email:
          <input dojoType="dijit.form.TextBox" length=25 name="email"><br>
            <button type="submit">Sign Up!</button>
            <button type="reset">Reset</button>
            <button type="button" onclick="javascript:help(  )">Help</button>
        </form>
    </body>
</html>

If you try to use dijits without properly including the dojo.css file and the relevant theme, your dijits may still be accessible—but they'll also look horrible. A common frustration with beginners to Dijit is either forgetting to load the CSS or forgetting to set the appropriate class attribute in the BODY tag.

In addition to the TextBox dijit improving the appearance of the control, it also saves you the work of implementing a dozen or so lines of custom scripting. Of course, you could override the format extension point to implement your own custom formatting by simply defining a JavaScript function and passing it into format. For example, the following formatting function would take a string and turn it into MiXeD CaPiTaLiZaTiOn like so:

function mixedCapitalization(value) {
    var newValue = "";
    var upper = true;

    dojo.forEach(value.toLowerCase(  ), function(x) {
        if (upper)
            newValue += x.toUpperCase(  );
        else
            newValue += x;

        upper = !upper;
    });

    return newValue;

}

Using the function in the TextBox dijit is just as easy as it should be:

<input dojoType="dijit.form.TextBox" format="mixedCapitalization"
    trim=true name="first">

If you interact with the form and cause a blur event by moving the cursor out of it, you'll see the conversion take place. The parse function may be overridden in the very same manner as format to standardize values when they are returned. Common operations include converting numeric types into Number values, or standardizing String values.

The custom format and parse extension points are invoked every time a setValue or getValue operation is called—not just in response to explicit user interaction with the form.

ValidationTextBox

One thing that's probably on your mind is that pesky validation function that ensures the fields are not empty—and the fact that it wasn't all that great in the first place since it didn't validate an email address properly. ValidationTextBox to the rescue!

Table 13-3 includes a complete listing of additional functionality that ValidationTextBox offers.

Table 13-3. Attributes of ValidationTextBox

Name

Type

Comment

required

Boolean

Attribute that determines whether the field is required. If left empty when this attribute is set, the field cannot be valid. false by default.

promptMessage

String

Attribute used to define a hint for the field when the field has the cursor.

invalidMessage

String

Attribute that provides the message to display if the field is invalid.

constraints

Object

Attribute that provides a user-defined object that can be defined to (dynamically, if necessary) feed constraints to the regExpGen attribute. This object is used extensively for other dijits such as DateTextBox to provide custom formats for display.

regExp

String

Attribute that provides a regular expression to be used for validation. Do not define this attribute if regExpGen is defined. By default this attribute is ".*" (a regular expression that allows anything/everything).

regExpGen

Function

Attribute that denotes a user-replaceable function that may be used to generate a custom regular expression that is dependent upon the key/value pairs in the constraints attribute; useful for dynamic situations. Do not define this attribute if regExp is defined. By default, this attribute is a function that returns ".*" (a regular expression that allows anything/everything).

tooltipPosition

Array

Attribute used to define whether the tooltip should appear above, below, to the left, or to the right of the control. By default, this attribute returns the value of dijit.Tooltip.defaultPosition, which is defined internally to the dijit.Tooltip widget.

isValid( )

Function

Method that calls the validator extension point to perform validation, returning a Boolean value.

validator(/* String */ value,

/* Object */ constraints)

Function

Extension point that is called by onblur, oninit, and onkeypress DOM events.

displayMessage(/* String */ message)

Function

Extension point that may be overridden to customize the display of validation errors or hints. By default uses a dijit.Tooltip.

The dijit.Tooltip widget is covered in Chapter 15.

Drop-in usage for a ValiationTextBox in our example is as straightforward as adding required attributes to the various controls and tacking on an additional regex to validate the email address. The change in Example 13-4 incorporates a ValidationTextBox and eliminates the need for all of the JavaScript that was previously written; the Help button was also removed now that a tooltip more elegantly accomplishes that purpose.

Example 13-4. Updated form to use ValidationTextBox
<html>
    <head>
        <title>Register for Spam</title>

        <link rel="stylesheet" type="text/css"
          href="http://o.aolcdn.com/dojo/1.1/dojo/resources/dojo.css" />
        <link rel="stylesheet" type="text/css"
          href="http://o.aolcdn.com/dojo/1.1/dijit/themes/tundra/tundra.css" />

        <script
            djConfig="parseOnLoad:true",
            type="text/javascript"
            src="http://o.aolcdn.com/dojo/1.1/dojo/dojo.xd.js">
        </script>

        <script type="text/javascript">
            dojo.require("dojo.parser");
            dojo.require("dijit.form.ValidationTextBox");
        </script>

        <!-- lots of ugly JavaScript was removed -->

    <head>
    <body class="tundra">
        <p>Just Use the form below to sign-up for our great offers:</p>
        <form  id="registration_form"
            method="POST"
            action="http://localhost:8080/register/">

            First Name:
            <input dojoType="dijit.form.ValidationTextBox"
              properCase="true" trim=true required="true"
              invalidMessage="Required." name="first"><br>

            Last Name:
            <input dojoType="dijit.form.ValidationTextBox"
              properCase="true" trim=true required="true"
              invalidMessage="Required." name="last"><br>

            Your Email:
            <input dojoType="dijit.form.ValidationTextBox"
              promptMessage="Basically, we want to sell your info to a 3rd party."
              regExp="[a-z0-9._%+-]+@[a-z0-9-]+\.[a-z]{2,4}"  required
              name="email"><br>

            <button type="submit">Sign Up!</button>
            <button type="reset">Reset</button>
            <!-- tooltip message replaced need for help button -->

        </form>
    </body>
</html>

And with very little effort, you suddenly have richer, better-looking functionality, with less code to maintain.

We still need to do some work to those buttons in the next section, but first, let's work through the remaining members of the TextBox family.

MappedTextBox and RangeBoundTextBox

Two well-defined form dijit classes that are not covered in this chapter include MappedTextBox and RangeBoundTextBox. Basically, MappedTextBox provides some methods for serializing its data into a String value via a custom toString method, and RangeBoundTextBox facilitates ensuring that a value is within a specified range by allowing you to pass in max and min values to the constraints object. Although it might intuitively seem like the "validation" in ValidationTextBox should be handling tasks like range checking, keep in mind that ValidationCheckBox uses regular expressions to validate String values. RangeBoundTextBox explicitly deals with numeric types.

In short, these two classes provide some intermediate machinery that is used to enable the remaining form dijits in this chapter and are in place largely to facilitate the internal design. While you may want to be aware of these two classes if you plan on creating a highly custom form dijit, they are not really intended for general-purpose consumption.

TimeTextBox and DateTextBox

Custom validation routines for validating dates and times are another implementation detail that just about any web developer who has been around a while has had to produce at some point or another. Although dates and times have well-defined formats that are quite universal, the ultra-generic HTML INPUT element offers no support, and the load is pushed off to JavaScript for validation and custom formatting. Fortunately, Dijit makes picking dates and times just as easy as it should be. These dijits are also preconfigured to work with the most common locales, and extending them beyond the stock locale collection is straightforward.

The DateTextBox and TimeTextBox dijits use the Gregorian calendar, which is the default for the dojo.date facilities.

Let's suppose that instead of spamming you, an organization would instead like to bother you over the telephone once you get home from a long, hard day of work. Naturally, they would like to collect information from you ahead of time so as to avoid any unnecessary overhead of their own. Assuming they're smart enough to be using Dojo to minimize costs on the programming budget, they might produce some form fields like so:

<!-- Remember to dojo.require these dijits before using them! -->

Best Day to call:
<input dojoType="dijit.form.DateTextBox"><br>

Best Time to call:
<input dojoType="dijit.form.TimeTextBox"><br>

That's it! No additional effort is required. The DateTextBox in Figure 13-2 automatically pops up a beautiful little calendar for picking a date when the cursor enters the INPUT element, and a scrolling list containing times, broken into 15-minute increments, appears for the TimeTextBox in Figure 13-3.

The DateTextBox pop up that appears
Figure 13-2. The DateTextBox pop up that appears
The TimeTextBox popup that appears
Figure 13-3. The TimeTextBox popup that appears

As a reminder, programmatic creation is just as simple:

var t = new dijit.form.TimeTextBox( );
var d = new dijit.form.DateTextBox( );

/* now place them in the page via their domNode attribute*/

In addition to ease of use, these dijits allow for customized formatting of their displayed values—allowing you to do anything that you could do via dojo.date, which they use internally. Specifically, the formatLength, timePattern, and datePattern attributes may be specified within the constraints object to produce the corresponding effect.

Table 13-4 and Table 13-5 summarize the various options available. In general, either the format length or one of the time or date patterns are specified, depending on the degree of granularity desired.

Table 13-4. Attributes for DateTextBox

Attribute

Comment

formatLength

Used to format a value for the default locale. Valid values are full, long, medium, or short. Custom values for specific locales are honored. Examples for the en-us locale include:

full

Thursday, January 10, 2008

long

January 10, 2008

medium

Jan 10, 2008

short (default)

1/16/2008

datePattern

Used to provide a custom format for all locales. Accepts a string formatted according to Java-like conventions. See http://www.w3.org/TR/NOTE-datetime. Common values with examples include:

yyyy

2008

yyyy-MM

2008-01

MMM dd, yyyy

Jan 08, 2008

strict

When true, allows for slight relaxations of some abbreviations and whitespace. This attribute is false by default.

locale

Allows for overriding the default locale for this specific widget only. Be sure to configure the extra local via djConfig.extraLocale or you may receive an error or unexpected results.

selector

When submitting a form, the value of selector determines whether the date, the time, or both get passed with the submission, even though only a date or time is visible as a displayed value. By default, both are passed, specifying either date or time accordingly.

Table 13-5. Attributes for TimeTextBox

Attribute

Comment

clickableIncrement

A String representing the amount every clickable element in the time picker should increase. This value should be set in non-Zulu time without a time zone and divide visibleIncrement evenly. For example, the default value of "T00:15:00" would denote a 15-minute increment.

visibleIncrement

A String representing the increment that should visibly provide a text value indicating a time increment. The default value of "T01::00:00" creates text in one-hour increments. This value should be set in non-Zulu time without a time zone.

visibleRange

A String representing the time range to display. This default value is "T05:00:00", which is five hours of time. This value should be set in non-Zulu time without a time zone.

formatLength

A String value used to format a value for the default locale. Valid values are long and short. Custom values for specific locales are honored. Examples for the en-us locale include:

long

10:00:00PM CST

short

10:00 PM

timePattern

Used to provide a custom format for all locales. Accepts a string formatted according to Java-like conventions. See http://www.w3.org/TR/NOTE-datetime. Common values with examples include:

hh:mm

08:00

h:mm

8:00

h:mm a

8:00 PM

HH:mm

22:00

hh:mm:ss

08:00:00

hh:mm:ss.SSS

08:00:00.000

strict

When true, allows for slight relaxations of some abbreviations (am versus a.m., etc.) and whitespace. This attribute is false by default.

locale

Allows for overriding the default locale for this specific widget only. Be sure to configure the extra local via djConfig.extraLocale or you may receive an error or unexpected results.

selector

When submitting a form, the value of selector determines whether the date, the time, or both get passed with the submission, even though only a date or time is visible as a displayed value. By default, both are passed, specifying either date or time accordingly.

In markup, the constraints object is provided like any other attribute:

<input constraints="{datePattern:'MMM dd, yyyy'}" dojoType="dijit.form.DateTextBox">

Just like always, the programmatic approach is a direct translation:

var d = new dijit.form.DateTextBox({datePattern:'MMM dd, yyyy'});

Commonalities between DateTextBox and TimeTextBox

Two additional methods that are available for TimeTextBox and DateTextBox are getDisplayedValue and setDisplayedValue. The difference between these methods and the ordinary getValue and setValue approaches involves the difference in what is actually displayed in the dijit versus what data type is used internally by the dijit. Both TimeTextBox and DateTextBox use JavaScript Date objects internally, and getting this Date object is just one method call away.

Recall that the machinery inherited from RangeBoundTextBox also allows for min and max values to be provided, which is highly useful for preventing a user from ever selecting an invalid value from the pop up. For example, to constrain a date from December 1, 2007 through June 30, 2008:

<input constraints="{min:'2007-12', max:'2008-06', datePattern:'MMM dd, yyyy'}"
dojoType="dijit.form.DateTextBox">

Additionally, MappedTextBox wires in facilities for serialization via the toString method; you can also get an ISO-8601 compliant string if you should need one, which can be quite useful for sending back to the server.

It's important to understand the duality between datePattern, timePattern, and the ISO-8601 specification: basically, there isn't a connection. The datePattern and timePattern values are used for opaquely manipulating user-visible formatting for widgets, while the ISO-8601 formatting is what the parser accepts and sends to the server for processing.

Two additional methods provided by these two dijits include getDisplayedValue and setDisplayedValue. While setDisplayedValue produces the same results as setAttribute('value', /*...*/), getDisplayedValue returns the values you see in the dijit, while resolving the dijit's .value property to return a JavaScript Date object.

Table 13-6 provides a quick synopsis of these additional features that both DateTextBox and TimeTextBox provide.

Table 13-6. DateTextBox and TimeTextBox commonalities

Name

Comment

getDisplayedValue( )

Retrieves the formatted value that is actually displayed in the form element, whereas getValue retrieves an actual Date object.

setDisplayedValue(/*Date*/ date)

Sets both the displayed as well as the internal value for the dijit. (Calling setValue accomplishes exactly the same thing.)

toString( )

Returns an ISO-8601-compliant date or time value.

min and max values for the constraints object

Provided to constrain the values that are available via the pop ups.

serialize( )

An extension point that can be used to specify a custom implementation for the toString method. This extension point manipulates the value that is presented to the server when a form is submitted.

Serializing data to the server

As it turns out, the serialize extension point can be especially useful when transferring data to and from a server-side component that is expecting a date to be formatted in a special way. For example, you might use the code in Example 13-5 to extend the DateTextBox and provide a custom format when the toString method is used. Example 13-5 illustrates using a custom DateTextBox to submit a custom value that is different from what is displayed.

Example 13-5. Custom serialization of data to the server with a DateTextBox
<html>
    <head>
        <title>Custom DateTextBox</title>

        <link rel="stylesheet" type="text/css"
          href="http://o.aolcdn.com/dojo/1.1/dojo/resources/dojo.css" />
        <link rel="stylesheet" type="text/css"
          href="http://o.aolcdn.com/dojo/1.1/dijit/themes/tundra/tundra.css" />

        <script
            djConfig="parseOnLoad:false",
            type="text/javascript"
            src="http://o.aolcdn.com/dojo/1.1/dojo/dojo.xd.js">
        </script>
        <script type="text/javascript">
            dojo.require("dojo.parser");
            dojo.require("dijit.form.DateTextBox");

            dojo.addOnLoad(function(  ) {
                dojo.declare("dtdg.CustomDateTextBox",[dijit.form.DateTextBox], {
                    serialize: function(d, options) {
                        return dojo.date.locale.format(d,
                        {
                            selector:'date',
                            datePattern:'dd-MMM-yyyy'}).toUpperCase(  );
                        }
                });
                dojo.parser.parse(dojo.body(  ));
            });
        </script>
    </head>
    <body class="tundra">
        <form action="http://localhost:8080" type="POST">
            <input dojoType="dtdg.CustomDateTextBox" name="customDate"/>
            <input type="submit" value="Submit"/>
        </form>
    </body>
</html>

A minimal CherryPy class can accept the form submission and display it for you:

import cherrypy

class Content:
    @cherrypy.expose
    def index(self, **kwargs):
        return str(kwargs)

cherrypy.quickstart(Content(  ))

Don't forget about inherited properties

Although the inheritance hierarchy is getting a little bit deep by this point, recall that all of the methods inherited from TextBox and ValidationTextBox are also available to use and are essential for many common use cases. A review of dojo.date, as presented in Chapter 6, is also helpful for brushing up on some of the finer details associated with these dijits.

NumberTextBox

NumberTextBox inherits all of the great features you've grown to love from RangeBoundTextBox and its ancestors and expands upon them with customization for numeric types via the dojo.number facilities. In a nutshell, numeric value formatting defaults to the current locale and allows you to provide the constraints listed in Table 13-7.

Table 13-7. NumberTextBox constraints

Name

Comment

min and max constraints

Used to check the bounds of the input, just like any other RangeBoundTextBox descendant.

pattern

Used to provide the number of digits to require after the decimal, along with any additional formatting, such as a percent sign that follows.

type

Used to designate that the value should be a decimal or percentage.

places

Used to designate the number of places to require after the decimal (providing this value in addition to a custom pattern overrides the pattern).

For example, to require a value to have exactly two places after the decimal and a percent sign, the following does the trick:

<input constraints="{pattern: '#.##%'}" dojoType="dijit.form.NumberTextBox">

Although there is only a single hash before the decimal place, note that you can have multiple digits. Should you not want any dijits before the decimal, however, you can provide a pattern without a leading hash, such as {pattern:'.##%'}. Also note that when editing begins, the displayed values automatically convert to a pure numeric value; when editing ends, the value converts back to a formatted number.

Recall that dojo.number as presented in Chapter 6 is your one-stop shop for tons of custom facilities for number formatting and related operations. NumberTextBox directly builds upon these facilities.

NumberSpinner

The NumberSpinner was introduced in Chapter 11, and you can think of the NumberSpinner and a fancier NumberTextBox with small buttons on the edge that allow for incrementally increasing the value. The buttons are typematic in that you can hold them down and they will repeatedly affect the value. The NumberSpinner also has slightly different min and max constraints in that if min and max constraints are provided, the NumberSpinner 's buttons will not allow you to move outside of those boundaries.

NumberSpinner offers the attributes listed in Table 13-8.

Table 13-8. NumberSpinner attributes

Name

Comment

defaultTimeout

The number of seconds a key or button is held down before it becomes typematic. This attribute is 500 by default.

timeoutChangeRate

The fraction of time that is used to change the typematic timer between events. A value of 1.0 means that each typematic event fires at defaultTimeout intervals. A value of less than 1.0 means that each typematic event fires an increasingly faster rate proportional to this value. This attribute is 0.90 by default.

smallDelta

The value to adjust the spinner by when using arrow keys or buttons. This attribute is 1 by default.

largeDelta

The value to adjust the spinner by when using the Page Up or Page Down keys. This attribute is 10 by default.

Creating a NumberSpinner is just like creating any other dijit:

<input dojoType="dijit.form.NumberSpinner" smallDelta="2" largeDelta="4"
constraints="{min:100,max:120}" value="100">

CurrencyTextBox

The CurrencyTextBox is the farthest dijit from the common ancestor, inheriting from NumberTextBox, and utilizes dojo.currency for much of its formatting handiwork.

This dijit, however, provides only one additional attribute, currency, which is formatted according to its specific locale. Values for currency must be one of the three letter sequences specified in the ISO4217 currency code standard, available from http://en.wikipedia.org/wiki/ISO_4217.

Anytime international characters such as currency symbols are used, you'll want to be especially aware of the encoding that your browser is using so that all symbols are rendered properly. There is always the possibility that the web server may not include this information in the header.

In HTML pages, the standard way of specifying an encoding is by placing a special META tag in the head of the page, and the Dijit project encourages this technique as a best practice. The following example is a META tag for the UTF-8 character set, which is almost always a safe bet:

<META http-equiv="Content-Type"
content="text/html; charset=UTF-8"/>

Note that as of version 1.1, you will need to use this tag if serving up Dojo from AOL's CDN because the server currently does not include encoding information in the headers, which is another means of achieving the same result. (Otherwise, currency and certain unicode symbols may not display properly.)

The following snippet illustrates a currency dijit for U.S. dollars that requires a value for the cents to be explicitly provided after the decimal point via the fractional constraint, which is the only additional constraint of interest that this dijit provides besides those that have already been inherited:

<input dojoType="dijit.form.CurrencyTextBox"
constraints="{min:1,max:100,fractional:true}" currency="USD"/>

Like NumberTextBox, the values for this dijit change to vanilla numeric values when editing begins, and format back to currency values once editing ends via a blur event.

ComboBox

ComboBox provides a drop-down list of values much like an HTML SELECT element; however, a ComboBox is based on an ordinary input element, so if an acceptable value is not identified by the list of possibilities, you may opt to type in any value you'd like. ComboBox inherits from ValidationTextBox, so you have the full gamut of features for validation available to you; some additional enhancements are that it also provides a filtered list of possible values based on the prefix you've entered. The list of values can be a static list that is established a priori or a dynamic list from a dojo.data store that may be fetched from a server.

In its simplest manifestation, you might use a ComboBox simply to provide a static list of common options, with the ability for the user to type in a custom option. The following code listing illustrates static data with the auto-complete feature enabled.

<select name="coffee" dojoType="dijit.form.ComboBox" autoComplete="true">
  <option>Verona</option>
  <option>French Roast</option>
  <option>Breakfast Blend</option>
  <option selected>Sumatra</option>

  <script type="dojo/method" event="onChange" args="newValue">
    console.log("value changed to ", newValue);
  </script>
</select>

Hooking a ComboBox to an ItemFileReadStore is quite simple and involves little more than pointing the ComboBox to the data source. For example, consider a data source that contains coffee roasts and their descriptions in the following form:

{identifier : "name",
    items : [
        {name : "Light Cinnamon", description : "Very light brown, dry, tastes
like toasted grain with distinct sour tones, baked, bready"},
        {name : "Cinnamon", description : "Light brown and dry, still toasted
grain with distinct sour acidy tones"},

        ...lots more...
    ]
}

Assume that you'd like to populate the ComboBox with a name field, and when a change occurs, use the description in some other meaningful way. You might accomplish this task as shown in Example 13-6.

Example 13-6. ComboBox at work
<html>
    <head>
        <title>Pick a coffee roast, any coffee roast</title>

        <link rel="stylesheet" type="text/css"
          href="http://o.aolcdn.com/dojo/1.1/dojo/resources/dojo.css" />
        <link rel="stylesheet" type="text/css"
          href="http://o.aolcdn.com/dojo/1.1/dijit/themes/tundra/tundra.css" />

        <script
            djConfig="parseOnLoad:true",
            type="text/javascript"
            src="http://o.aolcdn.com/dojo/1.1/dojo/dojo.xd.js">
        </script>

        <script type="text/javascript">
            dojo.require("dojo.parser");
            dojo.require("dojo.data.ItemFileReadStore");
            dojo.require("dijit.form.ComboBox");
            dojo.require("dijit.form.Button");
            dojo.require("dijit.form.Form");
        </script>
    <head>
    <body class="tundra">

        <div dojoType="dojo.data.ItemFileReadStore"
            jsId="coffeeStore" url="./coffee.json"></div>

        <form action="localhost" dojoType="dijit.form.Form">
            <select name="coffee" dojoType="dijit.form.ComboBox"
                store="coffeeStore" searchAttr="name">

                <script type="dojo/method" event="onChange" args="newValue">
                    console.log("value changed to ", newValue);
                    var f = function(item) {
                        console.log("new description is ",
                          coffeeStore.getValue(item, "description")
                        );
                    };
                    coffeeStore.fetchItemByIdentity(
                        {identity : newValue, onItem : f}
                    );
                </script>
            </select>
        <button dojoTyype="dijit.form.Button">Submit</button>
    </form>
    </body>
</html>

To recap, all that takes place is that you hook up the ComboBox to the ItemFileReadStore via the store attribute, and tell the ComboBox which field to display via the searchAttr attribute. Then, when a change occurs, the ComboBox 's onChange method detects and uses the new value to look up the description from the store.

Internally, the ComboBox only implements a specialized subset of the dojo.data.Read/Notification API that is necessary for it to work. Specifically, it implements the following methods:

  • getValue

  • isItemLoaded

  • fetch

  • close

  • getLabel

  • getIdentity

  • fetchItemByIdentity

  • fetchSelectedItem

For completeness, the specific attributes shown in Table 13-9 are also available for ComboBox.

Table 13-9. ComboBox attributes

Name

Type

Comment

item

Object

The currency selected item. null by default.

pageSize

Integer

Specifies the number of results per page (via the count key in an ItemFileReadStore 's fetch method). Useful when querying large stores. Infinity by default.

store

Object

A reference to the data provider such as an ItemFileReadStore. null by default.

query

Object

A query that can be passed to the store to initially filter the items before doing any further filtering based on searchAttr and the key that is currently typed in. {} by default.

autoComplete

Boolean

Whether to display a list of options for the key that is currently typed in (using the queryExpr as a search criteria). true by default.

searchDelay

Integer

How many milliseconds to wait between when a key is pressed and when to start search for that value. 100 by default.

searchAttr

String

The search pattern to match against for the values that should be displayed. name by default.

queryExpr

String

The dojo.data query expression pattern to use. (The default expression searches for any value that is a prefix of the current key that is typed in.) "${0}*" by default.

ignoreCase

Boolean

Whether queries should be case-sensitive. true by default.

hasDownArrow

Boolean

Whether to display the down arrow for the drop-down indicator. true by default.

FilteringSelect

A FilteringSelect is an enhanced version of the ordinary HTML select element in that provides a drop-down list of mandatory values and submits the hidden values and the displayed values. While FilteringSelect looks like and shares a lot of features with ComboBox, including the ability to filter a drop-down list as text is typed and the ability to fetch data from a serve via a store, it is built upon an HTML SELECT element.

Three particularly important distinctions between a FilteringSelect and a ComboBox are worth noting:

  • ComboBox is built on an ordinary select element in which the value that is submitted to the server on a submit event is the control's hidden value, not the visible value in the control. This distinction is an important feature because FilteringSelect can be degradable and behave as much like an ordinary SELECT as possible.

  • The FilteringSelect inherits from MappedTextBox (a serializable TextBox ) instead of ValidationTextBox because validation is a nonissue because users cannot type free text into the control.

  • FilteringSelect can display HTML as its label, not just text. Thus, you can include customizable markup such as images in labels.

In addition to common dijit.form operations such as getValue, setValue, getDisplayedValue, setDisplayedValue, and the various ComboBox options, FilteringSelect provides two additional attributes and one additional function, listed in Table 13-10.

Table 13-10. FilteringSelect additions

Name

Comment

labelAttr

The text to display in the control. If no value is specified, then searchAttr is used.

labelType

Whether to treat the text label as markup or ordinary text. Valid values include 'text' or 'html'.

labelFunc (/*Object*/ item, /*dojo.data.store*/ store)

The event handler that is called when the label changes; returns the label that should be displayed.

MultiSelect

MultiSelect is a simple wrapper (with the attributes listed in Table 13-11) around a native SELECT element (with the attribute multi=true ) that inherits from _FormWidget. The primary reason that it is included in Dijit is because it facilitates interaction with the dijit.Form wrapper (coming up later in this chapter) and streamlines the task of otherwise having to style the SELECT element yourself.

Table 13-11. MultiSelect

Name

Comment

size

The number of elements to display on a page. 7 by default.

addSelected(/*dijit.form.MultiSelect*/select)

Moves the selected nodes from another MultiSelect into this MultiSelect.

getSelected( )

Returns the selected nodes in the widget.

setValue(/*Array*/values)

Sets the value of each node in the widget according to the sequential values provided in the values Array.

invertSelection(/*Boolean*/fireOnChange)

Inverts the selection. If fireOnChange is true, then an onChange event is fired.

Because MultiSelect is just a lightweight wrapper around the HTML equivalent, there is little to say about that is specific to Dojo. You can define a MultiSelect in markup, as shown in Example 13-7.

Example 13-7. Typical MultiSelect in markup
<select multiple="true" name="foo" dojoType="dijit.form.MultiSelect"
 style="height:100px; width:100px; border:3px solid black;">

   <option value="TN" selected="true">Tennessee</option>
   <option value="VA">Virginia</option>
   <option value="WV">West Virginia</option>
   <option value="OH">Ohio</option>

</select>

Textarea Variations

A bane of traditional web development has often been the dreaded TEXTAREA element that takes up a fixed amount of space on the screen, requiring somewhat of a black art to determine just how much space to allocate to it so as to maximize the amount of viewable area while minimizing wastage on valuable screen real estate.

Textarea

The Textarea dijit inherits from _FormWidget and gives the best of both worlds in that it supports the standard HTML attributes for an ordinary textarea, yet its appearance is a fixed width element that grows vertically as needed. The API for the Textarea dijit simple in that you'll normally only need to use the standard setValue and getValue methods. onChange is a valuable extension point that you can use as a callback when a change occurs:

<textarea dojoType="dijit.form.Textarea" style="width:300px">
    One fish, two fish...
</textarea>

SimpleTextarea

Although Textarea 's ability to expand is convenient in a lot of cases, it doesn't lend itself well to situations in which an enclosing container (such as the layout dijits you'll learn about in Chapter 14) needs to dictate its overall size. For this reason, the SimpleTextarea dijit was introduced. For all practical purposes, the SimpleTextarea behaves just like an ordinary TEXTAREA element except that it can expand and contract in size. You populate it with the same attributes as an ordinary TEXTAREA element such as rows and cols and, like Textarea, you can use setValue and getValue to manipulate the text in it.

Button Variations

Dijit provides drop-in, degradable replacements for standard push buttons and checkboxes, yet it also gives you a lot of sophisticated options, such as the kinds of buttons that you normally find in toolbars. Let's start out with an ordinary Button and work our way up through more sophisticated options.

Button

Figure 13-4 shows a button, and Table 13-12 gives the rundown on the most basic kind of button dijit, a Button, which inherits _FormWidget.

A typical Button
Figure 13-4. A typical Button
Table 13-12. Button properties

Name

Comment

label

Used to provide the label for the button in markup or via programmatic creation.

showLabel

A Boolean value designating whether to display the text label in the Button. true by default.

iconClass

A class specifying an image that can make a button appear like an icon.

onClick(/* DOM Event*/ evt)

An extension point that is called in response to a click. This is a very common method to override.

setLabel(/* String */ label)

A method accepting an HTML string that can change a Button 's label.

Unlike TextBox and its descendants, the Button widgets require you to use the setAttribute('value', /*...*/) function, inherited from _FormWidget, to set value because Buttons don't have a widget value so much as they have a form value that is relayed to the server.

Let's dust off the code from Example 13-4 and provide some final polish by replacing those ugly buttons, as shown in Example 13-8. Remembering to add an obligatory dojo.require("dojo.form.Button") to the head of the page, the replacement is straightforward. Note how convenient providing the onClick handler in markup is for this situation.

Example 13-8. Typical Button usage
<button dojoTye="dijit.form.Button" type="submit">Sign Up!
    <script type="dojo/method" event="onClick" args="evt">
        alert("You just messed up...but it's too late now! Mwahahaha");
    </script>
</button>
<button dojoTye="dijit.form.Button" type="reset">Reset</button>

The Button 's iconClass is especially snazzy in that it doesn't just replace the entire button with an icon. Instead, it embeds the icon into the button alongside an optional label if one is specified and showLabel is true. For example, if you had a small 20 × 20px thumbnail image of some spam that you wanted to embed into the "Sign Up!" button, you could do it by including iconClass="spamIcon" in the button tag and ensuring that the following class appeared in your page:

.spamIcon {
    background-image:url('spam.gif');
    background-repeat:no-repeat;
    height:20px;
    width:20px;
}

Of course, you can provide any customized styles you'd like for buttons to make them look any way that you'd like by applying an inline style or a custom class.

ToggleButton

Because form dijits leverage inheritance so heavily, they often have common ancestors that provide common functionality for descendant classes. ToggleButton is one such class; it inherits from Button and adds in functionality for a button that has an on/off state, like a RadioButton or a CheckBox. The only notable attribute it adds is checked, which can be toggled with setAttribute.

Although you would probably use a more conventional control like CheckBox to designate on/off states, you could choose to use ToggleButton directly, or subclass it and implement your own custom ToggleButton. The onChange extension point (common to all form dijits) is one particularly useful feature:

<button dojoType="dijit.form.ToggleButton">
  <script type="dojo/method" event="onChange" args="newValue">
     console.log(newValue);
  </script>
</button>

Most of the buttons that appear in a toolbar such formatting a text with italics, bold, underline, etc., use the ToggleButton. The Menu and MenuItem dijits are introduced in Chapter 15.

Several button dijits are not included in their own designated resource file. In particular, you should dojo.require("dijit.form.Button") for Button, ToggleButton, DropDownButton, and ComboButton. While it may seem odd to require one thing when you actually want another, the rationale is that the (inheritance-driven) implementations for the various buttons are so similar that they are included in the same physical file to minimize overhead in acquiring resources from the server. Additionally, recall that the mapping between classes simulated via dojo.declare and resource files is not designed to be a one-to-one mapping (although traditional object-oriented programming philosophy often deems it so).

This technique remains a source of consternation amongst Dojo circles, as the overhead from a synchronous request to the server would be a moot point in a production setting that uses the facilities from Util to optimize layers for each page of an application.

These kinds of nuances result from so many (well-intentioned) competing interests in the Dojo community.

CheckBox

CheckBox descends directly from ToggleButton and is a standard drop-in replacement for an ordinary <input type="checkbox"> element. Using it is as simple as requiring it into the page and then using the dojoType tag. We might introduce it into Example 13-4 page by disabling the "Sign Up!" button until after user click the CheckBox to confirm that they're aware of our covert intentions to spam them:

<div name="confirmation" dojoType="dijit.form.CheckBox">
  <script type="dojo/method" event="onClick" args="evt">
    if (this.checked)
      dijit.byId("signup").setAttribute('disabled', false);
    else
      dijit.byId("signup").setAttribute('disabled', true);
  </script>
</div> I understand that you intend to spam me.<br>

<button id="signup" disabled dojoType="dijit.form.Button" type="submit">
  Sign Up!
</button>

Figure 13-5 shows a series of CheckBox dijits.

A series of CheckBox dijits
Figure 13-5. A series of CheckBox dijits

The reason that DIV tags are being used instead of INPUT tags is because you cannot embed SCRIPT tags inside of INPUT tags, and if you try, it is almost a certainty that the browser will strip them out. Thus, if you want to use SCRIPT markup inside of dijits, you should be especially cognizant that you can't use INPUT tags. If degradability is so important that this isn't acceptable for your application, simply write the methods in pure JavaScript instead of markup.

Thus, to programmatically check the CheckBox, you might use the setValue(true) method, which would check the box as well as set its checked attribute to true and its value attribute to true.

If it is really important to ensure every page is as degradable as possible, you can go the extra mile to explicitly include ordinary HTML attributes in tags. For example, instead of just specifying <input dojoType="dijit.form.CheckBox"/>, you could also include the extra type attribute, resulting in <input dojoType="dijit.form.CheckBox type="checkbox"/>.

Like ordinary HTML checkbox elements, however, there is a difference in the state of the checkbox versus the value of the checkbox. The state of the checkbox is either that it is or is not checked, and you can detect the state via the standard checked attribute. The value attribute, however, may take on non-Boolean values to pass special values to the server if the box is checked when the form is submitted. For example, a tag like <input name="pleaseSpamMe" value="yes"/> would append pleaseSpamMe=yes to the query string if the form was submitted via GET. (The default for value is "on".)

The confusion comes in, however, when you find out that the getValue method and the value attribute do not always return the same thing. The way it works is that getValue returns whether the box is checked regardless of what the actual value attribute happens to be. The rationale for this design is that the most common use case for a getValue function would be to determine a visible on/off state—not getting the actual value, which may not reflect the on/off state.

Because it is possible to get yourself tangled up in the differences between some of the different possibilities, consider some of the common cases for a CheckBox dijit:

<input id="foo" dojoType="dijit.form.CheckBox"></input>

Example 13-9 shows a series of calls to manipulate the dijit along with extensive commenting to show the effects.

Example 13-9. Typical CheckBox usage
/* Check the initial state */
dijit.byId("foo").checked // false
dijit.byId("foo").getValue(  ) // "on"

/* Use setValue with true */
dijit.byId("foo").setValue(true) // check the box and set the value to true
dijit.byId("foo").checked // true
dijit.byId("foo").getValue(  ) // true

/* Use setValue with false */
dijit.byId("foo").setValue(false) //uncheck the box and set the value to false
dijit.byId("foo").checked // false
dijit.byId("foo").getValue(  ) // false


/* Use setValue with a String */
dijit.byId("foo").setValue("bar") //check the box and set the value to "bar"
dijit.byId("foo").checked //true
dijit.byId("foo").getValue(  ) // "bar"

These most common use cases for the CheckBox are using getValue and setValue with Boolean values as parameters, so the chances are reasonably good that you won't need to wade through the potentially esoteric effects that can arise when you start mixing state and values.

Here's one particularly unintuitive combination that accentuates some of the issues involved in mixing state and value that you should be especially aware of:

dijit.byId("foo").setAttribute("value", "foo")
// changes the value attribute but does not check the box
dijit.byId("foo").value // "foo"
dijit.byId("foo").getValue(  )
//false, because the box is not checked

The unintuitive part is that after setting a value you wouldn't expect a call to getValue( ) to return false because common idioms in JavaScript involve the ability to test a string value, and if it's not "", null, or undefined, then it evaluates as true. However, the thing to remember is that getValue( ) consistently returns whether the box is checked or not—regardless of what is actual value attribute is set to be. In this case, the box is not checked, so getValue( ) returns false.

Likewise, the dijit's onChange event will not fire for a dijit.byId("foo").setAttribute("value", "foo") method call since the checked state of the box did not visibly change.

RadioButton

A RadioButton is a drop-in replacement for the ordinary HTML equivalent descending from CheckBox, and like its HTML equivalent, is conceptually a group of checkboxes in which only one can be selected at any given time. Recall that each button in a radio group has the same value for name but distinct values for value. Figure 13-6 shows a RadioButton group.

A RadioButton group
Figure 13-6. A RadioButton group

We might even further refine our working example (Example 13-4) by asking users how many times a day they'd like us to bother them. We could use radio buttons as shown in Example 13-10 to achieve this purpose quite easily, having first required dijit.form.CheckBox in the page.

Although you'd think that last sentence was a typo, it's not. Recalling that you dojo.require resources, not individual widgets, it turns out that the dijit.form.CheckBox resource provides dijit.formCheckBox and dijit.form.RadioButton.

This warning is along the same lines as the previous warning about how the dijit.form.Button resource provides multiple dijit implementations.

Example 13-10. Typical RadioButton usage
<input name="spamFrequency" value="1 per day" dojoType="dijit.form.RadioButton">
  1 per day<br>
<input name="spamFrequency" value="2 per day" dojoType="dijit.form.RadioButton">
  2 per day<br>
<input name="spamFrequency" value="3+ per day" dojoType="dijit.form.RadioButton">
  3+ per day<br>

ComboButton

A ComboButton inherits from DropDownButton, but with a twist: it provides a reserved area that produces a drop-down when it is clicked, whereas if you click on the "other" part of the button that is initially visible, it invokes a default action. For example, you might have a "Save" button that triggers an ordinary save action when clicked, while clicking the drop-down portion of the button produces a menu with options such as "Save", "Save as . . . ", "Save to FTP site", and so on. Figure 13-8 shows a ComboButton before and after clicking on the expander.

Left: a ComboButton before clicking on the expander; right: the ComboButton after clicking on the expander
Figure 13-8. Left: a ComboButton before clicking on the expander; right: the ComboButton after clicking on the expander

Example 13-12 illustrates using a ComboButton.

Example 13-12. Typical ComboButton usage
<button dojoType="dijit.form.ComboButton">
    <span>Save</span>

    <script type="dojo/method" event="onClick" args="evt">
        console.log("you clicked the button itself");
    </script>

    <div name="foo" dojoType="dijit.Menu">
        <div dojoType="dijit.MenuItem" label="Save">
            <script type="dojo/method" event="onClick" args="evt">
                console.log("you clicked", this.label);
            </script>
        </div>
        <div dojoType="dijit.MenuItem" label="Save As...">
            <script type="dojo/method" event="onClick" args="evt">
                console.log("you clicked", this.label);
            </script>
        </div>
        <div dojoType="dijit.MenuItem" label="Save to FTP...">
            <script type="dojo/method" event="onClick" args="evt">
                console.log("you clicked", this.label);
            </script>
        </div>
    </div>
</button>

Notice that the label for the ComboButton is still provided via the first child element, <span>Save</span> in this case, and the options that are provided via the drop-down are just the same as with a DropDownButton.

Slider

While a slider may not be a native HTML form control, there can be little dispute about how useful sliders can be for highly visual interfaces. Whether your goal is to adjust the transparency for an image, adjust the amount of a particular color in a custom color combination, or resize some other control on the screen, a slider can help you do it in a very intuitive manner. Dijit offers both horizontal and vertical sliders.

The Slider dijit is an especially slick piece of engineering. Like some of the other dijits, it keeps track of the current value via a hidden form value so that when you submit a form, the value is passed over to the server just like any other form field.

To get all of the various Slider machinery into your page, simply do a dojo.require("dijit.form.Slider"). In addition to VerticalSlider and HorizontalSlider, you also get the supporting classes for rules and labels. Let's start with something simple and gradually add some complexity so that you get a better feel for exactly how customizable this fantastic little widget really is.

HorizontalSlider

Suppose that as a caffeine junkie, you want to create a horizontal slider that denotes caffeine levels for various beverages. Your first stab at getting a plain vanilla slider into the page might be something like the Example 13-13, remembering to first require dijit.form.Slider into the page.

Example 13-13. HorizontalSlider (Take 1)
<div dojoType="dijit.form.HorizontalSlider" name="caffeine"
    value="100"
    maximum="175"
    minimum="2"
    style="margin: 5px;width:300px; height: 20px;">

    <script type="dojo/method" event="onChange" args="newValue">
        console.log(newValue);
    </script>
</div>

To summarize, the code created a slider without any kinds of labels whatsoever; the slider displays values ranging from 2 through 175 with the dimensions provided by the inline style. The default value is 100, and whenever a change occurs, the onChange method picks it up and displays it to the console. Note that clicking on the slider causes its value to move to the click point. So far, so good.

To further refine the slider, let's remove the buttons that are on each end of it by adding showButtons="false" as an attribute and adding a HorizontalRule and some HorizontalRuleLabels to the top of the slider. Everything you need was already slurped into the page, so no additional resources are required; we pull in the dojo.number module, however, to facilitate formatting to the console.

Just add some more markup into the body of the existing slider, as shown in Example 13-14.

Example 13-14. HorizontalSlider (Take 2)
<div dojoType="dijit.form.HorizontalSlider" name="caffeine"
    value="100"
    maximum="175"
    minimum="2"
    showButtons="false"
    style="margin: 5px;width:300px; height: 20px;">

    <script type="dojo/method" event="onChange" args="newValue">
        console.log(dojo.number.format(newValue,{places:1,pattern:'#mg'}));
    </script>

    <ol dojoType="dijit.form.HorizontalRuleLabels" container="topDecoration"
        style="height:10px;font-size:75%;color:gray;" count="6">
    </ol>

    <div dojoType="dijit.form.HorizontalRule" container="topDecoration"
        count=6 style="height:5px;">
    </div>
</div>

Presto! The slider is already looking much sharper with the addition of some ticks to break up the space and some percentage labels. Note that it is not necessary to have a one-to-one correspondence between the rules and the rule labels, but in this case, it works out quite nicely. Additionally, the attribute container used an enumerated value, topDecoration, defined by the slider to place the rules and labels.

Although the slider contains a percentage rating, it would be nice to bring in some domain specific data for the bottom of the slider. The basic pattern is the same as before, except that we'll use the slider's bottomContainer instead of its topContainer. However, instead of relying on the dijit to produce some bland numeric values, we provide the contents of the list ourselves in Example 13-15, including explicit <br> tags in multiword beverages to keep the display looking sharp. Figure 13-9 shows the result.

Example 13-15. HorizontalSlider (Take 3)
<div dojoType="dijit.form.HorizontalSlider" name="caffeine"
    value="100"
    maximum="175"
    minimum="2"
    showButtons="false"
    style="margin: 5px;width:300px; height: 20px;">

    <script type="dojo/method" event="onChange" args="newValue">
        console.log(newValue);
    </script>
    <ol dojoType="dijit.form.HorizontalRuleLabels" container="topDecoration"
        style="height:10px;font-size:75%;color:gray;" count="6">
    </ol>

    <div dojoType="dijit.form.HorizontalRule" container="topDecoration"
        count=6 style="height:5px;">
    </div>

    <div dojoType="dijit.form.HorizontalRule" container="bottomDecoration"
        count=5 style="height:5px;">
    </div>

    <ol dojoType="dijit.form.HorizontalRuleLabels" container="bottomDecoration"
        style="height:10px;font-size:75%;color:gray;">
        <li>green<br>tea</li>
        <li>coffee</li>
        <li>red<br>bull</li>
    </ol>
</div>
A HorizontalSlider
Figure 13-9. A HorizontalSlider

VerticalSlider

VerticalSlider works just like HorizontalSlider except that it renders along the y -axis, and you'll use leftDecoration and rightDecoration instead of topDecoration and bottomDecoration to specify container values for the rules and rule labels, as well as adjust your style to space elements out horizontally instead of vertically. Example 13-16 is the same slider, but adjusted for the vertical axis. Figure 13-10 depicts the result.

A VerticalSlider
Figure 13-10. A VerticalSlider
Example 13-16. VerticalSlider
<div dojoType="dijit.form.VerticalSlider" name="caffeine"
    value="100"
    maximum="175"
    minimum="2"
    showButtons="false"
    style="margin: 5px;width:75px; height: 300px;">

    <script type="dojo/method" event="onChange" args="newValue">
        console.log(newValue);
    </script>
    <ol dojoType="dijit.form.VerticalRuleLabels" container="leftDecoration"
        style="height:300px;width:25px;font-size:75%;color:gray;" count="6">
    </ol>

    <div dojoType="dijit.form.VerticalRule" container="leftDecoration"
        count=6 style="height:300px;width:5px;">
    </div>

    <div dojoType="dijit.form.VerticalRule" container="rightDecoration"
        count=5 style="height:300px;width:5px;">
    </div>

    <ol dojoType="dijit.form.VerticalRuleLabels" container="rightDecoration"
        style="height:300px;width:25px;font-size:75%;color:gray;">
        <li>green&nbsp;tea</li>
        <li>coffee</li>
        <li>red&nbsp;bull</li>
    </ol>
</div>

Table 13-13, Table 13-14, and Table 13-15 illustrate the important facets of the dijit.form.Slider ; namely, the sliders themselves, the rules, and the labels. Remember that all of the ordinary form machinery, such as setValue, et al., is inherited and works as usual.

Table 13-13. Horizontal Slider and VerticalSlider

Name

Type

Comment

showButtons

Boolean

Whether to show increment/decrement buttons on each end of the slider. true by default.

minimum

Integer

The minimum value allowed. 0 by default.

maximum

Integer

The maximum value allowed. 100 by default.

discreteValues

Integer

The number of discrete value between the minimum and maximum (inclusive). Infinity is a continuous scale. Values greater than 1 produce a discrete effect. Infinity by default.

pageIncrement

Integer

The amount of adjustment to nudge the slider via the page up and page down keys. 2 by default.

clickSelect

Boolean

Whether clicking the progress bar causes the value to change to the clicked location. true by default.

slideDuration

Number

The time in milliseconds to take to slide the handle from 0% to 100%. Useful for programmatically changing slider values. 1000 by default.

increment( )

Function

Increments the slider by one unit.

decrement( )

Function

Decrements the slider by one unit.

Table 13-14. HorizontalRule and VerticalRule

Name

Type (default)

Comment

ruleStyle

String

The CSS class to apply to individual hash marks.

count

Integer

The number of hash marks to generate. 3 by default.

container

DOM Node

Where to apply the label in relation to the slider: topDecoration or bottomDecoration for HorizontalSlider. leftDecoration or rightDecoration for VerticalSlider.

HorizontalRuleLabel and VerticalRuleLabel inherit from HorizontalRule and VerticalRule, respectively.

Table 13-15. HorizontalRuleLabel and VerticalRuleLabel

Name

Type (default)

Comment

labelStyle

String

The CSS class to apply to text labels.

labels

Array

Array of text labels to render, evenly spaced from left-to-right or top-to-bottom. [] by default.

numericMargin

Integer

The number of numeric labels that should be omitted from display on each end of the slider. (Useful for omitting obvious start and end values such as 0, the default, and 100.)

minimum

Integer

When the labels array is not specified, this value provides the leftmost label to include as a label. 0 by default.

maximum

Integer

When the labels array is not specified, this value provides the right-most label to include. 1 by default.

constraints

Object

The pattern to use (from dojo.number ) to use for generated numeric labels when the labels array is not specified. {pattern:"#%"} by default.

getLabels

Function

Returns the labels array.

Form

Although form dijits can be wrapped in an HTML form tag, the dijit.form.Form dijit provides some additional conveniences that are quite useful. This section rounds off the chapter by reviewing ordinary HTML forms and then reviews the specific features provided by dijit.form.Form. A common source of confusion to many Dijit newcomers is that they expect Dijit to do something directly that already works just fine via ordinary HTML. Recall that a significant part of Dojo's design philosophy is to not reinvent aspects of web technologies that already work; rather, Dojo supplements and augments as needed where web technology is lacking or not standardized.

HTML Form Tag Synopsis

dijit.form.Form respects the standard form attributes as defined in the HTML 4.01 specification. All attribute values are assumed to be wrapped in quotes as string values, although DOM events such as onclick entail explicitly denoting that a script action is expected, like onclick="javascript:someScriptAction( )" or onclick="javascript:return someValidationAction( )". For mouse events, a "left-click" action is assumed.

Form

The Form dijit itself supplements the standard HTML form attribute by providing several methods that may be called to manipulate it directly, and one extension point that is called internally in response to a user action. Table 13-16 lists the key aspects of Form.

Table 13-16. Form methods and extension points

Name

Category

Comment

getValues( )

Method

Returns a JSON structure providing named key/value pairs for the form.

isValid( )

Method

Returns true if each enabled value in the form returns true for its isValid method.

setValues

(/*Object*/values)

Method

Provides a concise way of setting all values in the form at one time via a JSON structure where each key in the structure is a named form field.

submit( )

Method

Used to programmatically submit the form.

reset()

Method

Systematically calls reset( ) on each contained dijit in the form to reset its value.

onSubmit( )

Extension point

Called internally when the submit( ) method is executed. This extension point is intended to provide a way of canceling the form submission if it returns false. By default, it returns the value from isValid( ).

validate( )

Method

Returns true if the form is valid, which is the same as isValid, but also highlights any form dijits that are valid and calls focus( ) on the first invalid dijit that is contained in the form.

Wrapping up the entire form into a dijit.form.Form is just like replacing any other element with the corresponding dijit, as shown in Example 13-17.

Example 13-17. Typical Form usage
<form id="registration_form" dojoType="dijit.form.Form">

  <!-- form elements go here -->

  <!-- override extension points as usual...-->

  <script type="dojo/method" event="onSubmit" args="evt">
    //return false if form should not be submitted. By default
    //onSubmit returns isValid(  ) for the dijit.form.Form
  </script>
</form>

Summary

This chapter has covered some serious ground. After working through it, you should:

  • Understand how ordinary HTML forms work

  • Understand how to use drop-in form dijit replacements for standard form elements

  • Be familiar with the general taxonomy of form dijits, understanding the broad strokes of the inheritance relationships

  • Be able to create form dijits both programmatically and in markup

  • Understand the difference between methods, attributes, and extension points

  • Understand what is meant by a degradable form and be able to weigh the various factors involved in producing a degradable design

It's time to move on to layout widgets.



[24] Mac OS X Firefox 2.0+ users may need to download the Configuration Mania add-on at https://addons.mozilla.org/en-US/firefox/addon/4420 to enable tabbing into buttons.