Chapter 6

Color Chooser


CONTENTS

In this chapter, you learn how to create a color chooser, a tool that lets you try combinations of colors on a Web page without having to edit and re-edit the page.

You can specify five global colors in a Web page's <BODY> tag. The five colors stipulate which colors the browser will use when it displays the Web page, and you specify them in attributes of the Web page's <BODY> tag. The BGCOLOR attribute specifies the color of the page's background. The TEXT attribute specifies the default color of the text in the page. The LINK attribute specifies the color of the text and border of link elements the user has not yet visited. The ALINK attribute specifies the color of the active link, which is the link element that the user has just clicked on. Finally, the VLINK attribute specifies the color of the text of link elements that the user has visited.

All colors are specified as 24-bit values-8 bits for the red component, 8 bits for the green component, and 8 bits for the blue component. The three colors are always specified in that order-red, green, blue-and the overall color is an RGB (red, green, blue) value. Not all video systems can directly support 24-bit colors; for such systems, applications such as Netscape must either use approximations of the 24-bit value or must dither the color. Dithering involves mixing pixels of different colors to fool the eye into seeing the illusion of a 24-bit color. Either way, two colors that are technically different may appear to be identical on some systems.

You can specify the color values you use in your Web page by using RGB values. An RGB value is a string consisting of a pound sign (#) followed by a two-digit hexadecimal (base 16) value for the red component, a two-digit hexadecimal value for the green component, and a two-digit hexadecimal value for the blue component. For example, a value of #1E90FFdescribes a color whose red component is 1E, whose green component is 90, and whose blue component is FF. It is very blue, with a good bit of green to it, and a little bit of red. Netscape calls this color "dodgerblue."

Including dodgerblue, Netscape has named 140 colors. Instead of using RGB values, you can use one of the named colors, and you can use a named color anywhere you can use an RGB value.

NOTE
Hexadecimal numbers are number written in base 16. They use the ten digits you're familiar with, plus the first six letters (A through F) to represent 10 through 15. By convention, the letters are usually written as capitals. In the two-digit color values, the digit on the left is multiplied by 16 and the digit on the right is added to that product to get a decimal number. Thus, 1E is 1 multiplied by 16, plus 14 (E = 14), for a value of 30; 90 is 9 multiplied by 16, plus 0, for a value of 144; and FF is 15 multiplied by 16 (F = 15), plus 15, for a value of 255. To translate larger numbers from hexadecimal to decimal, start with the leftmost number. If it's a letter, translate it to its decimal value (A = 10, B = 11, and so on). This starts your running total. For each digit to the right, multiply the running total by 16, translate the digit to decimal, and add the digit's value to the running total.

The scenario

A color chooser allows the user to specify colors for the five <BODY> color attributes. Such a tool lets you try out combinations of colors to see what they look like together. Perhaps two colors are too similar and you can't tell one from the other, or maybe one of the colors cannot be distinguished from the background color. You'll be able to pick out these problems when you use a color chooser. The color chooser lets you experiment without showing your failures to the world.

The requirements

What should a color chooser do? For starters, it should let you use Netscape's named colors, such as dodgerblue or floralwhite. It should also let you use RGB values-after all, the 140 named colors only represent a tiny fraction of the nearly seventeen million colors that you can define with RGB values. The color chooser should let you fine-tune the colors, making a given color just a little more red, or maybe a little less green. It must let you specify colors for each of the five <BODY> color attributes. It has to let you see a sample page with the colors you've specified. And it should show you what the <BODY> tag looks like with the colors you've selected, so you can use the tag in a Web page.

The solution (without JavaScript)

Because you need to be able to specify colors for each of the five <BODY> color attributes, you need a form with five input fields. These fields should be text fields, so you can enter either an RGB value or a named field. Each field needs to be large enough to hold the longest named color (20 characters, for lightgoldenrodyellow). The form needs a submit button; it should also have a reset button-that's just good form etiquette. Listing 6.1 shows the HTML for the color chooser Web page and Figure 6.1 shows you the form that you'll use.

Figure 6.1 : A simple color chooser.


Listing 6.1: A simple color chooser
<HTML>
    <HEAD>
        <TITLE>Color Chooser</TITLE>
    </HEAD>
    <BODY>
        <CENTER><H1>Color Selector</H1></CENTER>
        <FORM ACTION="http://cgi.server " METHOD=POST>
            <INPUT TYPE="text" NAME="fgcolor" SIZE="2Ø"> Foreground Color
            <BR>
            <INPUT TYPE="text" NAME="text" SIZE="2Ø"> Test Color
            <BR>
            <INPUT TYPE="text" NAME="link" SIZE="2Ø"> Link Color
            <BR>
            <INPUT TYPE="text" NAME="alink" SIZE="2Ø"> Active Link Color
            <BR>
            <INPUT TYPE="text" NAME="vlink" SIZE="2Ø"> Visited Link Color
            <HR>
            <INPUT TYPE="submit" VALUE="See Result">
            <INPUT TYPE="reset" VALUE="Ugh! Start Over!"> 
        </FORM>
    </BODY>
</HTML>

How good a solution is this? Let's measure it against the requirements. Can you use named colors? Yes, although you run the risk of making typos. Can you use RGB values? Again, yes, with the risk of mistyping them. Can you fine-tune colors? Yes, if they're RGB values. To fine-tune named colors, you need the documentation in front of you to get the RGB value of the named color. Can you set each of the <BODY> tag's five color attributes? Yes, you can. Can you see what the colors look like together?

Whoops.

No, not with just the code in Listing 6.1. You also need a CGI (Common Gateway Interface) program back on the server. The CGI program has to get the string generated when you press the submit button, parse it into the colors for each of the attributes, construct a valid HTML page that uses the five attributes, and send that page back as a response. It should also handle the last requirement, by including the <BODY> tag attributes in the displayed text.

You can't solve this problem with HTML alone, without JavaScript. You must have the CGI program as well. Unfortunately, some Web servers won't let you write your own CGI programs and run them from their system. The language you use is determined by what your Web server supports, and if you decide to change Web servers, you may have to start over from scratch-the new server may not support your CGI program.

The solution (with JavaScript)

In addition to the problems intrinsic to writing CGI (the server may not allow CGI programs, CGI programs are not necessarily portable from one server to another, a CGI program on a slow, bogged-down server may be very slow), there are bandwidth considerations. Every time you press the See Result button, your selections are passed through the network back to the server. Then the server sends back a new page. This is a waste of bandwidth, and makes you vulnerable to the rising amount of traffic on the Internet: The browser may well time out before it gets the result from the server.

With JavaScript, you can do much better.

You can eliminate the need for the CGI program by breaking the window into two frames. You place the form in one frame and use the form's field contents to draw a new document in the second frame. This eliminates the repeated, and unnecessary, traffic between the users' browsers and the server.

You need three documents now. The first document is a frame document that splits the window into frames, as shown here:

<HTML>
    <HEAD>
        <TITLE>Color Chooser</TITLE>
    </HEAD>
    <FRAMESET ROWS="*,15%">
        <FRAME SRC="selector.htm" NAME="Selector">
        <FRAME SRC="blank.htm" NAME="ViewScreen">
    </FRAMESET>
</HTML>

You also need a blank document to load into the ViewScreen frame. Here is such a document:

<HTML>
    <HEAD>
    </HEAD>
    <BODY>
    </BODY>
</HTML>

Finally, you need the document that creates the form and processes the data in the form. This document is described in the next section.

The Form

Using JavaScript, you can make the form much easier to use and make it impossible for the user to enter an illegal color value.

First, let's divide the top frame into three parts. In the first part, the user specifies a color. In the second part, the user applies the selected color to one or more of the <BODY> tag's five color attributes. In the third part are the See Result and Ugh! Start Over! buttons. Figure 6.2 shows how the three parts of the frame will be laid out.

Figure 6.2 : Layout of the color chooser form.

Selecting a color

In the top part of the page, let's use a table to lay out the fields for selecting a color. The table is divided into separate sections: one for the red component, one for the blue component, and one for the green component. There's also one more section for entering a named color. Figure 6.3 shows the high-level layout of the color selection table.

Figure 6.3 : Color selection table (high-level).

Within each of the "Define Color Component" blocks in Figure 6.3, you can add fields. A text field for setting the color level makes sense, and you can add buttons to increment and decrement the color level-that should suffice for fine-tuning. The text field should be used for entering a hexadecimal value for the color's component of the RGB color value, but let's add another text field. Some computer systems express colors in RGB format but do so by using decimal values separated by commas, as in 255,0,255 (equivalent to #FF00FF). Let's use the other text field to handle decimal values. You'll also need some text to let the user know which set of fields are for which color, and you'll need some table headings to let the user know what to do with the text fields and buttons. Figure 6.4 shows what you need.

Figure 6.4 : Color selection table (detailed).

To create the table shown in Figure 6.4, you need to create a TABLE element using the HTML code in Listing 6.2.


Listing 6.2: Creating the color selection table
<TABLE BORDER="2" CELLSPACING="2">
    <TR>
        <TH>Color</TH>
        <TH>Decimal (Ø-255)</TH>
        <TH>Hex (ØØ-FF)</TH>
        <TH>More/Less</TH>
        <TH>Choose Named Color</TH>
    </TR>
    <TR>
        <TD>RED</TD>
        <TD><INPUT TYPE=TEXT SIZE=3 VALUE="Ø"></TD>
        <TD><INPUT TYPE=TEXT SIZE=2 VALUE="ØØ"></TD>
        <TD>
            <INPUT TYPE=BUTTON VALUE=">>">
            <INPUT TYPE=BUTTON VALUE="<<">
        </TD>
        <TD ROWSPAN=3>
            <SELECT SIZE=8>
                <OPTION VALUE="FØF8FF">aliceblue
                <OPTION VALUE="FAEBD7">antiquewhite
                <OPTION VALUE="ØØFFFF">aqua
                <OPTION VALUE="7FFFD4">aquamarine
                <OPTION VALUE="FØFFFF">azure
                <OPTION VALUE="F5F5DC">beige
                <OPTION VALUE="FFE4C4">bisque
                <OPTION VALUE="ØØØØØØ" SELECTED>black
                <OPTION VALUE="FFEBCD">blanchedalmond
                <OPTION VALUE="ØØØØFF">blue
                <OPTION VALUE="8A2BE2">blueviolet
                <OPTION VALUE="A52A2A">brown
                <OPTION VALUE="DEB887">burlywood
                <OPTION VALUE="5F9EAØ">cadetblue
                <OPTION VALUE="7FFFØØ">chartreuse
                <OPTION VALUE="D2691E">chocolate
                <OPTION VALUE="FF7F5Ø">coral
                <OPTION VALUE="6495ED">cornflowerblue
                <OPTION VALUE="FFF8DC">cornsilk
                <OPTION VALUE="DC143C">crimson
                <OPTION VALUE="ØØFFFF">cyan
                <OPTION VALUE="ØØØØ8B">darkblue
                <OPTION VALUE="ØØ8B8B">darkcyan
                <OPTION VALUE="B886ØB">darkgoldenrod
                <OPTION VALUE="A9A9A9">darkgray
                <OPTION VALUE="ØØ64ØØ">darkgreen
                <OPTION VALUE="BDB76B">darkkhaki
                <OPTION VALUE="8BØØ8B">darkmagenta
                <OPTION VALUE="556B2F">darkolivegreen
                <OPTION VALUE="FF8CØØ">darkorange
                <OPTION VALUE="9932CC">darkorchid
                <OPTION VALUE="8BØØØØ">darkred
                <OPTION VALUE="E9967A">darksalmon
                <OPTION VALUE="8FBC8F">darkseagreen
                <OPTION VALUE="483D8B">darkslateblue
                <OPTION VALUE="2F4F4F">darkslategray
                <OPTION VALUE="ØØCED1">darkturquoise
                <OPTION VALUE="94ØØD3">darkviolet
                <OPTION VALUE="FF1493">deeppink
                <OPTION VALUE="ØØBFFF">deepskyblue
                <OPTION VALUE="696969">dimgray
                <OPTION VALUE="1E9ØFF">dodgerblue
                <OPTION VALUE="B22222">firebrick
                <OPTION VALUE="FFFAFØ">floralwhite
                <OPTION VALUE="228B22">forestgreen
                <OPTION VALUE="FFØØFF">fuchsia
                <OPTION VALUE="DCDCDC">gainsboro
                <OPTION VALUE="F8F8FF">ghostwhite
                <OPTION VALUE="FFD7ØØ">gold
                <OPTION VALUE="DAA52Ø">goldenrod
                <OPTION VALUE="8Ø8Ø8Ø">gray
                <OPTION VALUE="ØØ8ØØØ">green
                <OPTION VALUE="ADFF2F">greenyellow
                <OPTION VALUE="FØFFFØ">honeydew
                <OPTION VALUE="FF69B4">hotpink
                <OPTION VALUE="CD5C5C">indianred
                <OPTION VALUE="4BØØ82">indigo
                <OPTION VALUE="FFFFFØ">ivory
                <OPTION VALUE="FØE68C">khaki
                <OPTION VALUE="E6E6FA">lavender
                <OPTION VALUE="FFFØF5">lavenderblush
                <OPTION VALUE="7CFCØØ">lawngreen
                <OPTION VALUE="FFFACD">lemonchiffon
                <OPTION VALUE="ADD8E6">lightblue
                <OPTION VALUE="FØ8Ø8Ø">lightcoral
                <OPTION VALUE="EØFFFF">lightcyan
                <OPTION VALUE="FAFAD2">lightgoldenrodyellow
                <OPTION VALUE="9ØEE9Ø">lightgreen
                <OPTION VALUE="D3D3D3">lightgray
                <OPTION VALUE="FFB6C1">lightpink
                <OPTION VALUE="FFAØ7A">lightsalmon
                <OPTION VALUE="2ØB2AA">lightseagreen
                <OPTION VALUE="87CEFA">lightskyblue
                <OPTION VALUE="778899">lightslategray
                <OPTION VALUE="BØC4DE">lightsteelblue
                <OPTION VALUE="FFFFEØ">lightyellow
                <OPTION VALUE="ØØFFØØ">lime
                <OPTION VALUE="32CD32">limegreen
                <OPTION VALUE="FAFØE6">linen
                <OPTION VALUE="FFØØFF">magenta
                <OPTION VALUE="8ØØØØØ">maroon
                <OPTION VALUE="66CDAA">mediumaquamarine
                <OPTION VALUE="ØØØØCD">mediumblue
                <OPTION VALUE="BA55D3">mediumorchid
                <OPTION VALUE="937ØDB">mediumpurple
                <OPTION VALUE="3CB371">mediumseagreen
                <OPTION VALUE="7B68EE">mediumslateblue
                <OPTION VALUE="ØØFA9A">mediumspringgreen
                <OPTION VALUE="48D1CC">mediumturquoise
                <OPTION VALUE="C71585">mediumvioletred
                <OPTION VALUE="19197Ø">midnightblue
                <OPTION VALUE="F5FFFA">mintcream
                <OPTION VALUE="FFE4E1">mistyrose
                <OPTION VALUE="FFE4B5">moccasin
                <OPTION VALUE="FFDEAD">navajowhite
                <OPTION VALUE="ØØØØ8Ø">navy
                <OPTION VALUE="FDF5E6">oldlace
                <OPTION VALUE="8Ø8ØØØ">olive
                <OPTION VALUE="6B8E23">olivedrab
                <OPTION VALUE="FFA5ØØ">orange
                <OPTION VALUE="FF45ØØ">orangered
                <OPTION VALUE="DA7ØD6">orchid
                <OPTION VALUE="EEE8AA">palegoldenrod
                <OPTION VALUE="98FB98">palegreen
                <OPTION VALUE="AFEEEE">paleturquoise
                <OPTION VALUE="DB7Ø93">palevioletred
                <OPTION VALUE="FFEFD5">papayawhip
                <OPTION VALUE="FFDAB9">peachpuff
                <OPTION VALUE="CD853F">peru
                <OPTION VALUE="FFCØCB">pink
                <OPTION VALUE="DDAØDD">plum
                <OPTION VALUE="BØEØE6">powderblue
                <OPTION VALUE="8ØØØ8Ø">purple
                <OPTION VALUE="FFØØØØ">red
                <OPTION VALUE="BC8F8F">rosybrown
                <OPTION VALUE="4169E1">royalblue
                <OPTION VALUE="8B4513">saddlebrown
                <OPTION VALUE="FA8Ø72">salmon
                <OPTION VALUE="F4A46Ø">sandybrown
                <OPTION VALUE="2E8B57">seagreen
                <OPTION VALUE="FFF5EE">seashell
                <OPTION VALUE="AØ522D">sienna
                <OPTION VALUE="CØCØCØ">silver
                <OPTION VALUE="87CEEB">skyblue
                <OPTION VALUE="6A5ACD">slateblue
                <OPTION VALUE="7Ø8Ø9Ø">slategray
                <OPTION VALUE="FFFAFA">snow
                <OPTION VALUE="ØØFF7F">springgreen
                <OPTION VALUE="4682B4">steelblue
                <OPTION VALUE="D2B48C">tan
                <OPTION VALUE="ØØ8Ø8Ø">teal
                <OPTION VALUE="D8BFD8">thistle
                <OPTION VALUE="FF6347">tomato
                <OPTION VALUE="4ØEØDØ">turquoise
                <OPTION VALUE="EE82EE">violet
                <OPTION VALUE="F5DEB3">wheat
                <OPTION VALUE="FFFFFF">white
                <OPTION VALUE="F5F5F5">whitesmoke
                <OPTION VALUE="FFFFØØ">yellow
                <OPTION VALUE="9ACD32">yellowgreen
            </SELECT>
        </TD>
    </TR>
    <TR>
        <TD>GREEN</TD>
        <TD><INPUT TYPE=TEXT SIZE=3 VALUE="Ø"></TD>
        <TD><INPUT TYPE=TEXT SIZE=2 VALUE="ØØ"></TD>
        <TD>
            <INPUT TYPE=BUTTON VALUE=">>">
            <INPUT TYPE=BUTTON VALUE="<<">
        </TD>
    </TR>
    <TR>
        <TD>BLUE</TD>
        <TD><INPUT TYPE=TEXT SIZE=3 VALUE="Ø"></TD>
        <TD><INPUT TYPE=TEXT SIZE=2 VALUE="ØØ"></TD>
        <TD>
            <INPUT TYPE=BUTTON VALUE=">>">
            <INPUT TYPE=BUTTON VALUE="<<">
        </TD>
    </TR>
</TABLE>

The input fields in this table need some event handlers so that, when the user does something with each field, you can do something useful with the input. You need ONCHANGE event handlers for the TEXT fields; they'll be called when the user enters a new value in the TEXT field and moves focus to another field. You need ONCLICK event handlers for the BUTTON fields; they are called when the user clicks on a button. And, finally, you need an ONCHANGE event handler for the SELECT field; it is called when the user selects a new value and moves focus to another field.

There are six TEXT fields, six BUTTON fields, and a SELECT field, so you'll need a total of 13 event handler functions. Wrong! You need five event handler functions. Why? Think about it: What's the difference between the event handler that handles a change in the red decimal field and the event handler that handles a change in the green decimal field? Very little; they have to do the same thing, except that one pertains to the red component and one pertains to the green component. The code doesn't have to be different.

To create an RGB color one color at a time, you need to store each of the three components separately. To do that, let's create three global variables for them:

var redness = Ø;
var greenness = Ø;
var blueness = Ø;

Now let's give each of the TEXT and BUTTON fields names. The names will be based on which color they deal with and what they do with it, so the six TEXT fields are named "redDec," "redHex," "greenDec," "greenHex," "blueDec," and "blueHex." The six BUTTON fields are named "redPlus," "redMinus," "greenPlus," "greenMinus," "bluePlus," and "blueMinus."

Giving the fields names tied to their colors allows you to combine similar event handlers. Here is the handler for the change event on the decimal TEXT fields:

function readDecimal(text)
    {
    var suffixLength = 3;
    var nameLength = text.name.length;
    var colorName = text.name.substring(Ø, nameLength - suffixLength);
    var colorValue = parseInt(text.value);
    if (255 < colorValue || colorValue < Ø)
        {
        alert("Illegal hex value entered for " + colorName);
        return;
        }
    setColor(colorName, colorValue)
    }

The readDecimal function is specified in the <INPUT> tag as

ONCHANGE="readDecimal(this)"

passing a reference to the TEXT field as the single parameter. The TEXT field's name is available as its name property. You want the color part, which is the part of the name preceding the suffix Dec. To get the color portion, you use the substring method to extract the substring from the first character of the string to the first character of the Dec suffix. Having the color name (red, green, or blue), you now need the value. You get the color by using the built-in function parseInt() on the TEXT field's value, which is obtained by using its value property. If the color is out of range-less than 0 or greater than 255-an alert window is displayed, telling the user that the value entered is rejected, and the function returns. Otherwise, a new function, setColor(), is called with the name and value of the color.

Similarly, you need a function to handle the change event on the hexadecimal TEXT fields. Listing 6.3 shows the readHex()function, which serves that purpose.


Listing 6.3: readHex() ONCHANGE event handler
function readHex(text)
    {
    var suffixLength = 3;
    var nameLength = text.name.length;
    var colorName = text.name.substring(Ø, nameLength - suffixLength);
    var colorValue = parseInt("Øx" + text.value);
    if (255 < colorValue || colorValue < Ø)
        {
        alert("Illegal hex value entered for " + colorName);
        return;
        }
    setColor(colorName, colorValue);
    }

Notice that this function is exactly like readDecimal, except for one difference: The value is appended to the string "0x" in the call to parseInt(). This forces parseInt() to interpret the characters in the value as hexadecimal characters.

Finally, let's look at the event handlers for the plus (>>) and minus (<<) BUTTON fields. Listing 6.4 shows the event handler for the plus buttons.


Listing 6.4: plus() ONCLICK event handler
function plus(text)
    {
    var suffixLength = 4;
    var nameLength = text.name.length;
    var colorName = text.name.substring(Ø, nameLength - suffixLength);
    var colorValue = 1 + eval("" + colorName + "ness");
    if (255 < colorValue || colorValue < Ø)
        {
        alert("Sorry, can't go any higher on " + colorName);
        return;
        }
    setColor(colorName, colorValue);
    }

This function is also similar to the readDecimal() and readHex() functions. The difference is in how it acquires the color value. It creates a string containing the color name and the string "ness" (the leading quotes force Netscape to create a string value) and calls the built-in function eval(), using the string as its parameter. Notice that the string built is going to be either "redness," "greenness," or "blueness"-one of the global variables you're using to hold the three color components. The eval() function returns the value contained in the specified variable. The color value is set to 1 plus that value (placing the 1 first forces Netscape to create an integer value).

Finally, the function for handling the click event of the minus (<<) buttons is shown in Listing 6.5.


Listing 6.5: minus() ONCLICK event handler
function minus(text)
    {
    var suffixLength = 5;
    var nameLength = text.name.length;
    var colorName = text.name.substring(Ø, nameLength - suffixLength);
    var colorValue = -1 + eval("" + colorName + "ness");
    if (255 < colorValue || colorValue < Ø)
        {
        alert("Sorry, can't go any lower on " + colorName);
        return;
        }
    setColor(colorName, colorValue);
 }

The only difference between the plus() and minus() functions is that the minus() function adds -1 instead of 1 to the color value.

So now let's see the setColor() function, which is shown in Listing 6.6.


Listing 6.6: setColor() function
function setColor(colorName, colorValue)
    {
    eval("" + colorName + "ness = " + colorValue);
    setDec(colorName);
    setHex(colorName);
 }

This time, a string is constructed that looks like what you'd write to set the appropriate color variable. If this function is called with "red" and 100, for instance, a string "redness = 100" is created. The eval() function is called with this string as its argument and the new value is written to the appropriate variable. Then the functions setDec() and setHex() are called, with the name of the color as a parameter.

The setDec() function updates the decimal TEXT field and the setHex() function updates the hexadecimal TEXT field. The setDec() function is shown in Listing 6.7.


Listing 6.7: setDec() function
function setDec(color)
    {
    eval("document.chooserForm." + color + "Dec.value = " + color + "ness");
    }

This is a very simple function. It creates a string setting the appropriate field from the appropriate variable and then uses the eval() function to execute the string. If called with a string of "green," for instance, the string that gets executed is "document.chooserForm.greenDec.value = greenness." The chooserForm part is the name of the FORM element that contains all of the fields in the page; the FORM tag is <FORM name="chooserForm">.

The setHex() function is a little more complicated. Strangely, while parseInt() knows how to read hexadecimal values, there is no built-in function to create a hexadecimal string. The setHex() function is shown in Listing 6.8.


Listing 6.8: setHex() function
function setHex(color)
    {
    var value = eval("" + color + "ness");
    var command = "document.chooserForm." + color + "Hex.value = '";
    command += toHex2(value);
    command += "'";
    eval(command);
 }

Again, a string is created that is then executed by eval(). This time, the actual value of the color variable has to be extracted and converted to a hexadecimal string. That's the function of toHex2(), seen in Listing 6.9.


Listing 6.9: toHex2() function
function toHex2(value)
    {
    var val = "" + toHex(Math.floor(value / 16));
    val += toHex(value & 15);
    return val;
 }

The function toHex2() creates a string consisting of two hexadecimal digits. The first digit is created by the function toHex() (seen in Listing 6.10), called with the value divided by 16. (Recall that using Math.floor() on the result of a divide gives the integer quotient.) The second digit is also created by the function toHex(), called with the value logically ANDed with 15. This is equivalent to using the modulus operator with a second operand of 16 (try it and see!).


Listing 6.10: toHex() function
function toHex(value)
    {
    if (value <= 9)
        {
        return value;
        }
    else
        {
        if (value == 1Ø) return "A";
        if (value == 11) return "B";
        if (value == 12) return "C";
        if (value == 13) return "D";
        if (value == 14) return "E";
        if (value == 15) return "F";
        return "??" + value + "??";
        }
    }

If the value passed to toHex() is less than or equal to 9, the value is returned as is. Otherwise, the values of 10 to 15 are "translated" to "A," "B," "C," "D," "E," and "F," respectively. If this function were to be used in a library, which is a good possibility, it would need to be a little more robust; hence the final

return "??" + value + "??"

which handles values that are out of range.

So far, you have gotten data from the table's decimal, hexadecimal, plus, and minus input fields; set the appropriate variable; and updated the decimal and hexadecimal field values. It was relatively easy, and there wasn't a lot of code (discounting the 140 OPTION elements in the SELECT element). This particular technique, by the way, is an example of a powerful object-oriented paradigm called "model-view-controller," or MVC for short. The controller objects (the TEXT and BUTTON fields) update the model objects (the redness, greenness, and blueness variables); the model objects, in turn, update their views (the TEXT field values).

Now let's tie in the SELECT field. Listing 6.11 shows the SELECT field's ONCHANGE event handler, readSelector().


Listing 6.11: readSelector() ONCHANGE event handler
function readSelector(selector)
    {
    var index = selector.selectedIndex;
    if (index == -1)
        {
        return; // handle case when nothing is selected...
        }
    var value = parseInt("Øx" + selector[ index ].value);
    with Math
        {
        setColor("red", floor(value / 65536));
        value = value & 65535;
        setColor("green", floor(value / 256));
        setColor("blue", value & 255);
        }
    }

The readSelector() function gets the index of the selected field from the select object's selectedIndex property. Clicking on an already selected OPTION field results in "unselection" of that OPTION. When that happens, the selectedIndex property is set to -1, indicating no selection. In that case, readSelector() simply returns. Otherwise, the selected option's value is retrieved (selector[ index ].value), appended to a "0x" string as in the readHex() function in Listing 6.3, and passed to parseInt(). The result is divided by 65536 (0x010000), yielding the red component. The remainder is obtained by ANDing with 65535 (0x00FFFF). The remaining value is divided by 256, yielding the green component, and ANDed with 255 (0x0000FF) to yield the blue component. The setColor() function is called for each color. As a result, when a named color is selected from the list, its red, green, and blue components show up in the text fields. The user can then fine-tune the named color, adding to and subtracting from the red, green, and blue components to his or her liking.

Assigning a color to a <BODY> attribute

Although the top part of the page is devoted to selecting a color, the middle part is devoted to assigning the currently selected color to one of the five <BODY> attributes. As with the color selection, let's use a table to neatly arrange the attributes. Figure 6.5 shows the table we'll use.

Figure 6.5 : Attribute table.

Listing 6.12 shows the HTML used to generate this table.


Listing 6.12: Attribute table HTML
<TABLE BORDER="2" CELLSPACING="2">
    <TR>
        <TH ALIGN=CENTER>Background</TH>
        <TH ALIGN=CENTER>Foreground</TH>
        <TH ALIGN=CENTER>Link</TH>
        <TH ALIGN=CENTER>Visited Link</TH>
        <TH ALIGN=CENTER>Active Link</TH>
    </TR>
    <TR>
        <TD ALIGN=CENTER><INPUT TYPE=TEXT SIZE=7 NAME="BGCOLOR"></TD>
        <TD ALIGN=CENTER><INPUT TYPE=TEXT SIZE=7 NAME="TEXT"></TD>
        <TD ALIGN=CENTER><INPUT TYPE=TEXT SIZE=7 NAME="LINK"></TD>
        <TD ALIGN=CENTER><INPUT TYPE=TEXT SIZE=7 NAME="VLINK"></TD>
        <TD ALIGN=CENTER><INPUT TYPE=TEXT SIZE=7 NAME="ALINK"></TD>
    </TR>
</TABLE>

As with the color selection discussed earlier, let's put the model-view-controller paradigm to work. The model consists of five global variables that contain the current color values for each of the <BODY> attributes:

var newBGCOLOR = "#FFFFFF";
var newTEXT = "#ØØØØØØ";
var newLINK = "#ØØØØFF";
var newVLINK = "#FFØØØØ";
var newALINK = "#ØØFFØØ";

These colors are arbitrary, by the way; it's a serviceable combination-white background, black text, blue links, red visited links, and green active links.

You already have the views-the TEXT fields in the table seen in Listing 6.12. You need the controller shown in Listing 6.13.


Listing 6.13: A selector for attribute selection
Apply This Color To Which Attribute?
<SELECT ONCHANGE="attributeSelector(this)">
    <OPTION VALUE="BGCOLOR">Background
    <OPTION VALUE="TEXT" SELECTED>Foreground
    <OPTION VALUE="LINK">Link
    <OPTION VALUE="VLINK">Visited Link
    <OPTION VALUE="ALINK">Active Link
</SELECT>

Now you'll need some code to tie it all together. The attributeSelector() function, which you can see set as the SELECT field's ONCHANGE event handler in Listing 6.13, is a good start. Listing 6.14 shows the function.


Listing 6.14: attributeSelector() ONCHANGE event handler
function attributeSelector(selector)
    {
    var index = selector.selectedIndex;
    if (index == -1)
        {
        return; // handle case when nothing is selected...
        }
    setAttribute(selector[ index ].value);
 }

As with readSelector() in Listing 6.11, take care to guard against the possibility that the user has clicked on the currently selected option, leaving no option selected. If there is a valid selection, its value is passed to the setAttribute() function shown in Listing 6.15.


Listing 6.15: setAttribute() function
function setAttribute(attributeName)
    {
    var color = "#" + toHex2(redness) + toHex2(greenness) + toHex2(blueness);
    eval("new" + attributeName + " = \"" + color + "\"");
    eval("document.chooserForm." + attributeName + ".value = \"" + color + "\"");
    }

The setAttribute() function creates a color string consisting of a # character followed by the current values of the redness, greenness, and blueness variables. The values are run through toHex2() (see Listing 6.9) and a string is created to set the attribute variable and then executed. (In the case of the ALINK attribute, for example, the string would look like newALINK = "#rrggbb", where "rr," "gg," and "bb" are the current redness, greenness, and blueness values.) The attribute view in the table (see Listing 6.12) is then updated by creating a string and executing it. (In the case of the ALINK attribute, for example, the string would look like document.chooserForm.ALINK.value = "#rrggbb".)

You need one more function to protect the field values from being overwritten. The fields are TEXT fields, because you can easily write to them using JavaScript code, as does setAttribute(). This also means that the user can place text in them. You need a function to restore the fields, such as restoreAttribute(), shown in Listing 6.16.


Listing 6.16: restoreAttribute() function
function restoreAttribute(attribute)
    {
    eval("document.chooserForm." + attribute.name + ".value = new" + attribute.name);
    }

The restoreAttribute() function is set up as the ONCHANGE event handler for all five attribute input fields. The function creates and executes a string to restore the field's contents from the appropriate variable. The string created and executed for the BGCOLOR field, for example, would be

document.chooserForm.BGCOLOR.value = newBGCOLOR

Submit and Reset

The top part of the page selects the color. The middle part of the page assigns the selected color to an attribute. The bottom part of the page lets the user see the attribute combination or to reset the combination. The non-JavaScript solution used SUBMIT and RESET input fields. Because this isn't a form that will be submitted to a server, ordinary BUTTON fields will do nicely, as seen in Listing 6.17.


Listing 6.17: SUBMIT and RESET buttons (not!)
<INPUT TYPE=BUTTON NAME="Apply" VALUE="Let Me See What This Looks Like!" 
ONCLICK="resetViewScreen()">
<INPUT TYPE=BUTTON VALUE="Ycch! Start Over!" ONCLICK="setDefaults()">

The buttons have ONCLICK event handlers-resetViewScreen() and set- Defaults(). Listing 6.18 shows the simpler setDefaults() function.


Listing 6.18: setDefaults() function
function setDefaults()
    {
    setColor("red", Ø);
    setColor("green", Ø);
    setColor("blue", Ø);
    newBGCOLOR = "#FFFFFF";
    newTEXT = "#ØØØØØØ";
    newLINK = "#ØØØØFF";
    newVLINK = "#FFØØØØ";
    newALINK = "#ØØFFØØ";
    document.chooserForm.BGCOLOR.value = "#FFFFFF";
    document.chooserForm.TEXT.value = "#ØØØØØØ";
    document.chooserForm.LINK.value = "#ØØØØFF";
    document.chooserForm.VLINK.value = "#FFØØØØ";
    document.chooserForm.ALINK.value = "#ØØFFØØ";
    }

The setDefaults() function uses setColor() (Listing 6.6) to initialize the redness, greenness, and blueness variables to 0; the setColor() calls will initialize the color selection fields. The attribute color variables are reset to the default colors and the attribute fields are set to the same colors.

Listing 6.19 shows the rather busier resetViewScreen() function.


Listing 6.19: resetViewScreen() function
function resetViewScreen()
    {
    parent.ViewScreen.document.close();
    parent.ViewScreen.document.open();
    parent.ViewScreen.document.open();
    drawLine("<HTML" + gt);
        drawLine("<HEAD" + gt);
        drawLine("</HEAD" + gt);
        drawLine("<BODY BGCOLOR=" + newBGCOLOR + " TEXT=" + newTEXT + " LINK=" + newLINK + " VLINK=" 
		+ newVLINK + " ALINK=" + newALINK + gt);
            drawLine("<P" + gt + "Foreground text ");
            drawLine("<FONT COLOR=\"" + newLINK + "\"" + gt + "Link</FONT" + gt + " ");
            drawLine("<FONT COLOR=\"" + newVLINK + "\"" + gt + "Visited Link</FONT" + gt + " ");
            drawLine("<FONT COLOR=\"" + newALINK + "\"" + gt + "Active Link</FONT" + gt);
            drawLineBreak();
            drawLine("If you like this combination, you need to incorporate this into your HTML:");
            drawLineBreak();
            drawLine("&lt;BODY BGCOLOR=\"" + newBGCOLOR + "\" TEXT=\"" + newTEXT + "\" LINK=\"" + newLINK + 
			"\" VLINK=\"" + newVLINK + "\" ALINK=\"" + newALINK + "\"&gt;");
            drawLine("</P" + gt);
        drawLine("</BODY" + gt);
    drawLine("</HTML" + gt);
    }

The resetViewScreen() function draws the frame in the bottom of the window. It uses the functions drawLine() and drawLineBreak() to draw the frame, which you can see in Figure 6.6.

Figure 6.6 : Output frame.

function drawLine(s)
    {
    parent.ViewScreen.document.writeln("" + s);
    }

function drawLineBreak()
    {
    drawLine("<BR" + gt);
    }

Improving the solution

You can improve upon the JavaScript solution in several ways. There are other ways to select colors, such as using percentage values for the red, green, and blue components. You could create a client-side image map and generate an RGB color based on where the user clicks on the map.

The solution prevents the user from entering data into the attribute fields. Why not allow the user to enter a standard RGB color value in #rrggbb format?

It would be useful to reload the color selection table from one of the attribute fields. For example, you might try out a color combination and decide that it would be better if you could adjust one of the attributes a little bit. It would be easier to be able to click on a button and reload the red, green, and blue values directly from the attribute instead of loading the values by hand.

You could use cookies to save a color solution, and even let the user supply a name for the solution. When you brought up the color chooser the next time, you could reload the color table from one of the user's cookies.

Modifying the example for your own use

This is not the kind of page that needs to be modified so that you can use it; it's a complete application in its own right, and it is not tied to anyone's URLs. Feel free to try some of the modifications suggested in the previous section. Modify the visible text and button names. Do not alter the field names, however; the code requires the field names to tie into the data they control.