The Syntax Breakdown

This application involves two files: index.html and multi.html. index.html, shown in Example 4.1, utilizes nested framesets to achieve the surrounding border effect.

Example 4-1. index.html

     1  <HTML>
     2  <HEAD>
     3  <TITLE>Multi-Search Engine Interface Site</TITLE>
     4  <SCRIPT LANGUAGE="JavaScript1.2">
     5  <!--
     6  var black = '<BODY BGCOLOR=BLACK></BODY>';
     7  var white = '<BODY BGCOLOR=WHITE></BODY>';
     8  //-->
     9  </SCRIPT>
    10  </HEAD>
    11  <FRAMESET ROWS="15,*,50" FRAMEBORDER=0 BORDER=0>
    12  <FRAME SRC="javascript: parent.black;" SCROLLING=NO>
    13  <FRAMESET COLS="15,*,15"  FRAMEBORDER=0 BORDER=0>
    14    <FRAME SRC="javascript: parent.black;"SCROLLING=NO>
    15    <FRAME SRC="javascript: parent.white;">
    16    <FRAME SRC="javascript: parent.black;" 
    17      SCROLLING=NO>
    18    </FRAMESET>
    19  <FRAME SRC="multi.html" SCROLLING=NO>
    20  </FRAMESET>
    21  </HTML>

The two JavaScript variables black and white at lines 6 and 7 are evaluated as HTML strings in the SRC attribute of the frames in lines 12 and 14-16. We reviewed this in the sidebar JavaScript Technique: Cheating the SRC Attribute in Chapter 2. If you’ve been following along in order, this shouldn’t be rocket science. The only frames that see any real action are frames[2], which displays the search results, and frames[4], which houses the search engine interface. The rest are purely for show. Let’s move on to multi.html, shown in Example 4.2.

Example 4-2. multi.html

     1  <HTML>
     2  <HEAD>
     3  <TITLE>Multi-Engine Menu</TITLE>
     4  <SCRIPT LANGUAGE="JavaScript1.2">
     5  <!--
     6  
     7  parent.frames[2].location.href  = 'javascript: parent.white';
     8  
     9  var NN     = (document.layers ? true : false);
    10  var curSlide   = 0;
    11  var hideName   = (NN ? 'hide' : 'hidden');
    12  var showName   = (NN ? 'show' : 'visible');
    13  var perLyr   = 4;
    14  var engWdh   = 100;
    15  var engHgt   = 20;
    16  var left     = 375;
    17  var top     = 10;
    18  var zIdx     = -1;
    19  var imgPath   = 'images/';
    20  var arrayHandles = new Array('out', 'over');
    21  
    22  for (var i = 0; i < arrayHandles.length; i++) {
    23    eval('var ' + arrayHandles[i] + ' = new Array()');
    24    }
    25  
    26  var engines = new Array( 
    27    new Array('HotBot', 
    28      'http://www.hotbot.com/?MT=', 
    29      'http://www.hotbot.com/'), 
    30    new Array('InfoSeek', 
    31      'http://www.infoseek.com/Titles?col=WW&sv=IS&lk=noframes&qt=', 
    32      'http://www.infoseek.com/'), 
    33    new Array('Yahoo', 
    34      'http://search.yahoo.com/bin/search?p=', 
    35      'http://www.yahoo.com/'), 
    36    new Array('AltaVista', 
    37      'http://www.altavista.com/cgi-bin/query?pg=q&kl=XX&q=', 
    38      'http://www.altavista.digital.com/'),  
    39    new Array('Lycos', 
    40      'http://www.lycos.com/cgi-bin/pursuit?matchmode=and&cat=lycos' + 
    41        '&query=',
    42      'http://www.lycos.com/'), 
    43    new Array('Money.com',   
    44      'http://jcgi.pathfinder.com/money/plus/news/searchResults.oft?' + 
    45        'vcssortby=DATE&search=', 
    46      'http://www.money.com/'),  
    47    new Array('DejaNews', 
    48      'http://www.dejanews.com/dnquery.xp?QRY=', 
    49      'http://www.dejanews.com/'), 
    50    new Array('Insight', 
    51      'http://www.insight.com/cgi-bin/bp/870762397/web/result.html?' + 
    52        'a=s&f=p&t=A&d=', 
    53      'http://www.insight.com/'), 
    54    new Array('Scientific American', 
    55      'http://www.sciam.com/cgi-bin/search.cgi?' + 
    56      'searchby=strict&groupby=confidence&docs=100&query=', 
    57      'http://www.sciam.com/cgi-bin/search.cgi'), 
    58    new Array('Image Surfer', 
    59      'http://isurf.interpix.com/cgi-bin/isurf/keyword_search.cgi?q=', 
    60      'http://www.interpix.com/'), 
    61    new Array('MovieFinder.com', 
    62      'http://www.moviefinder.com/search/results/1,10,,00.html?' +   
    63        'simple=true&type=movie&mpos=begin&spat=', 
    64      'http://www.moviefinder.com/'), 
    65    new Array('Monster Board', 
    66      'http://www.monsterboard.com/pf/search/USresult.htm? ' + 
    67        'loc=&EmploymentType=F&KEYWORDS=', 
    68      'http://www.monsterboard.com/'), 
    69    new Array('MusicSearch.com', 
    70      'http://www.musicsearch.com/global/search/search.cgi?QUERY=', 
    71      'http://www.musicsearch.com/'), 
    72    new Array('ZD Net', 
    73      'http://xlink.zdnet.com/cgi-bin/texis/xlink/xlink/search.html?' + 
    74        'Utext=', 
    75      'http://www.zdnet.com/'), 
    76    new Array('Biography.com', 
    77      'http://www.biography.com/cgi-bin/biomain.cgi?search=FIND&field=', 
    78      'http://www.biography.com/'), 
    79    new Array('Entertainment Weekly', 
    80      'http://cgi.pathfinder.com/cgi-bin/ew/cg/pshell?venue=pathfinder&q=', 
    81      'http://www.entertainmentweekly.com/'), 
    82    new Array('SavvySearch', 
    83      'http://numan.cs.colostate.edu:1969/nph-search?' +  
    84        'classic=on&Boolean=OR&Hits=10&Mode=MakePlan&df=normal' + 
    85        '&AutoStep=on&KW=', 
    86      'http://www.savvysearch.com/'), 
    87    new Array('Discovery Online', 
    88      'http://www.discovery.com/cgi-bin/searcher/-?' + 
    89        'output=title&exclude=/search&search=', 
    90      'http://www.discovery.com/'), 
    91    new Array('Borders.com', 
    92      'http://www.borders.com:8080/fcgi-bin/db2www/search/' + 
    93        'search.d2w/QResults?doingQuickSearch=1&srchPage=QResults' + 
    94        '&mediaType=Book&keyword=', 
    95        'http://www.borders.com/'), 
    96    new Array('Life Magazine', 
    97      'http://cgi.pathfinder.com/cgi-bin/life/cg/pshell?' + 
    98        'venue=life&pg=q&date=all&x=15&y=16&q=', 
    99      'http://www.life.com/')
   100        );
   101  
   102  engines = engines.sort();
   103  
   104  function imagePreLoad(imgName, idx) {
   105    for(var j = 0; j < arrayHandles.length; j++) {
   106      eval(arrayHandles[j] + "[" + idx + "] = new Image()");
   107      eval(arrayHandles[j] + "[" + idx + "].src = '" + imgPath + 
   108      imgName + arrayHandles[j] + ".jpg'");
   109      }
   110    }
   111  
   112  function engineLinks() {
   113      genLayer('sliderule', left - 20, top + 2, 25, engHgt, true, 
   114        '<A HREF="javascript: changeSlide(1);" ' + 
   115        'onMouseOver="hideStatus(); return true;">' + 
   116        '<IMG SRC="' + imgPath + 'ahead.gif" BORDER=0></A><BR>' + 
   117        '<A HREF="javascript: changeSlide(-1);" ' + 
   118        'onMouseOver="hideStatus(); return true;">' + 
   119        '<IMG SRC="' + imgPath + 'back.gif" BORDER=0></A>');  
   120      lyrCount = Math.ceil
   121      (engines.length / perLyr);
   122      for (var i = 0; i < lyrCount; i++) {
   123      var engLinkStr = '<TABLE BORDER=0 CELLPADDING=0 CELLSPACING=0><TR>';
   124      for (var j = 0; j < perLyr; j++) {
   125        var imgIdx   = (i * perLyr) + j;
   126        if (imgIdx == engines.length) { break; }
   127        var imgName  = nameFormat(engines[imgIdx][0]);
   128        imagePreLoad(imgName, imgIdx);
   129        engLinkStr += '<TD><A HREF="javascript: ' + 
   130          'callSearch(document.forms[0].elements[0].value, ' + 
   131          imgIdx + ');" onMouseOver="hideStatus(); imageSwap(\'' + 
   132          imgName + '\', ' + imgIdx + ', 1); return true" ' + 
   133          'onMouseOut="imageSwap(\'' + imgName + '\', ' + imgIdx + 
   134          ', 0);"><IMG NAME="' + imgName + '" SRC="' + imgPath + imgName + 
   135          'out.jpg' + '" BORDER=0></A></TD>';
   136        }
   137      engLinkStr += '</TR></TABLE>';
   138      genLayer('slide' + i, left, top, engWdh, engHgt, false, engLinkStr);
   139      }  
   140    }
   141  
   142  function genLayer(sName, sLeft, sTop, sWdh, sHgt, sVis, copy) {
   143    if (NN) {
   144      document.writeln('<LAYER NAME="' + sName + '" LEFT=' + sLeft + 
   145        ' TOP=' + sTop + ' WIDTH=' + sWdh + ' HEIGHT=' + sHgt + 
   146        ' VISIBILITY=' + sVis + ' z-Index=' + (++zIdx) + '>' + 
   147        copy + '</LAYER>');
   148      }
   149    else {
   150      document.writeln('<DIV ID="' + sName + 
   151        '" STYLE="position:absolute; overflow:none;left: ' + 
   152        sLeft + 'px; top:' + sTop + 'px; width:' + sWdh + 'px; height:' + 
   153        sHgt + 'px; visibility:' + sVis + ' z-Index=' + (++zIdx) + 
   154        '">' + copy + '</DIV>');
   155      }
   156    }
   157  
   158  function nameFormat(str) {
   159    var tempArray = str.split(' ');
   160    return tempArray.join('').toLowerCase();
   161    }
   162  
   163  function hideSlide(name) { refSlide(name).visibility = hideName; }
   164  
   165  function showSlide(name) { refSlide(name).visibility = showName; }
   166  
   167  function refSlide(name) { 
   168    if (NN) { return document.layers[name]; }
   169    else { return eval('document.all.' + name + '.style'); 
   170    }
   171  
   172  function changeSlide(offset) {
   173    hideSlide('slide' + curSlide);
   174    curSlide = (curSlide + offset < 0 ? slideShow.length - 1 : 
   175      (curSlide + offset == slideShow.length ? 0 : curSlide + offset));
   176    showSlide('slide' + curSlide);
   177    }
   178  
   179  function imageSwap(imagePrefix, imageIndex, arrayIdx) {
   180    document[imagePrefix].src = eval(arrayHandles[arrayIdx] + 
   181      "[" + imageIndex + "].src");
   182    }
   183  
   184  function callSearch(searchTxt, idx) {
   185    if (searchTxt == "") { 
   186      parent.frames[2].location.href = engines[idx][2] + 
   187        escape(searchTxt); 
   188      }
   189    else { 
   190      parent.frames[2].location.href = engines[idx][1] + 
   191        escape(searchTxt); 
   192      }
   193    }
   194  
   195  function hideStatus() { window.status = ''; }
   196  
   197  //-->
   198  </SCRIPT>
   199  
   200  </HEAD>
   201  <BODY BGCOLOR="BLACK" onLoad="showSlide('slide0');">
   202  <SCRIPT LANGUAGE="JavaScript1.2">
   203  <!--
   204  engineLinks();
   205  //-->
   206  </SCRIPT>
   207  <FORM onSubmit="return false;">
   208  <TABLE CELLPADDING=0>
   209    <TR>
   210      <TD>
   211      <FONT FACE=Arial>
   212      <IMG SRC="images/searchtext.jpg">
   213      </TD>
   214      <TD>
   215      <INPUT TYPE=TEXT SIZE=25>
   216      </TD>
   217    </TR>
   218  </TABLE>
   219  </FORM>
   220  </BODY>
   221  </HTML>

More than 200 lines of code, but you’ve seen most of it already. This shouldn’t be that bad. Let’s begin at line 7.

parent.frames[2].location.href ='javascript: parent.white';

If you count the frames in index.html, you’ll see that frames[2] is where the search results show up. Setting the location.href property in this frame makes things a bit smoother if you decide to reload the application. This automatically sets the results document content to some “local” HTML so that you don’t have to wait for any previous search queries to reload.

By the way, even though you get a neat display of search engine results in frames[2], once you follow a search results link, you’re at the mercy of the search engine designers. Some will let you follow the links while staying in the same frame. Others, unfortunately, like InfoSeek, will force the document into the top window of the browser.

Strolling down Memory Lane

Let’s take a trip down Memory Lane (RAM, in case you’re wondering). As you examine the variables below, you’ll see some newcomers, but several will bear a striking resemblance to those you’ve worked with in Chapter 3. Look, there’s NN and curSlide! And they brought hideName and showName, too. Not to mention imagePath and zIdx:

var NN           = (document.layers ? true : false);
var curSlide     = 0;
var hideName     = (NN ? 'hide' : 'hidden');
var showName     = (NN ? 'show' : 'visible');
var perLyr       = 4;
var engWdh       = 100;
var engHgt       = 20;
var left         = 375;
var top          = 10;
var zIdx         = -1;
var imgPath      = 'images/';
var arrayHandles = new Array('out', 'over');

These variables all have the same function they did in Chapter 3. They just pick up where they left off. As for the new ones, perLyr defines the number of search engines you want to display per layer. Variables engWdh and engHgt define default width and height values for each layer, respectively. Variables left and top hold values for positioning the layers. Variable arrayHandlescontains an array used for dynamically preloading images. Hold that thought for just a bit; we’ll go over it shortly.

Talking about family reunions, the variables aren’t the only familiar code. Check out the functions from way back when.

Lines 142-156:

function genLayer(sName, sLeft, sTop, sWdh, sHgt, sVis, copy) {
  if (NN) {
    document.writeln('<LAYER NAME="' + sName + '" LEFT=' + sLeft +
      ' TOP=' + sTop + ' WIDTH=' + sWdh + ' HEIGHT=' + sHgt +
      ' VISIBILITY=' + sVis + ' z-Index=' + (++zIdx) + '>' +
      copy + '</LAYER>');
    }
  else {
    document.writeln('<DIV ID="' + sName +
      '" STYLE="position:absolute; overflow:none;left: ' +
      sLeft + 'px; top:' + sTop + 'px; width:' + sWdh + 'px; height:' +
      sHgt + 'px; visibility:' + sVis + ' z-Index=' + (++zIdx) +
      '">' + copy + '</DIV>');
    }
  }

Lines 163-177:

function hideSlide(name) { refSlide(name).visibility = hideName; }

function showSlide(name) { refSlide(name).visibility = showName; }

function refSlide(name) {
  if (NN) { return document.layers[name]; }
  else { return eval('document.all.' + name + '.style'); }
  }

function changeSlide(offset) {
  hideSlide('slide' + curSlide);
  curSlide = (curSlide + offset < 0 ? slideShow.length - 1 :
    (curSlide + offset == slideShow.length ? 0 : curSlide + offset));
  showSlide('slide' + curSlide);
  }

Five functions: genSlide(), refSlide(), hideSlide(), showSlide(), and changeSlide(). All of them operate the same way they did in Chapter 3; if you’re not clear on how any of them works, flip back a chapter and check them out. There are actually two more functions, imagePreLoad() and imageSwap(), which perform the same operations as well, but they’ve been modified enough to merit new discussion.

Dynamically Preloading Images

One of the big web paradigms is performing conventionally static operations dynamically. Why do something statically when you can manage it more easily on the fly? That’s what the following code does with image preloading. What’s the typical modus operandi when you want to preload images to perform rollovers? It might look something like this:

var myImage1On = new Image();
myImage1On.src = 'images/myImgOn1.gif';
var myImage1Off = new Image();
myImage1Off.src = 'images/myImgOff1.gif';

Simple enough. But that’s four lines of code for one pair of image rollovers. What if you have five or ten pairs? That’s 20 or 40 lines. If you ever have to make changes, that’ll get messy in no time. The Multiple Search Engine Interface introduces a way to pull off the same preloading, no matter how many (theoretically) image pairs you have. We’ll need three things:

  1. An array of Image objects for each set of images you’ll need. This application uses one array for the images used when the mouse pointer arrow is over the link and one array for the images that roll back when the mouse pointer arrow moves out of the link.

  2. A simple naming convention for the images. The myImg1On.gif/myImgOff1.gif convention will work fine. See the sidebar JavaScript Technique: Using Well-Constructed Naming Conventions in Chapter 3 for more information. The naming convention must incorporate the names of the arrays in step 1.

  3. The eval() method.

For step 1, this application will use two arrays. One will be named out and will contain Image objects of those images that roll over when the mouse-pointer arrow is outside the linked image. The other will be named over and will contain Image objects of those images that roll over when the mouse-pointer arrow is over the linked image. Those variables will be represented for now in an array of strings called arrayHandles, line 20:

var arrayHandles = new Array('out', 'over')

For step 2, we’ll use a very simple naming convention. All image pairs will have the same prefix followed by either out.jpg or over.jpg, depending on the image. For example, the image rollovers associated with InfoSeek are named infoseekout.jpg and infoseekover.jpg.

For step 3, we’ll first iterate through each element of arrayHandles and use eval() to create the arrays soon to hold the Image objects. Enter lines 22-24:

for (var i = 0; i < arrayHandles.length; i++) {
  eval('var ' + arrayHandles[i] + ' = new Array()');
  }

Performing the above for loop is equivalent to hardcoding this:

var out    = new Array();
var over   = new Array();

To polish off the preloading, we use eval() again in function preLoadImages() to dynamically create Image objects and assign the SRC property of each. Here is the function in lines 104-110:

function imagePreLoad(imgName, idx) {
  for(var j = 0; j < arrayHandles.length; j++) {
    eval(arrayHandles[j] + "[" + idx + "] = new Image()");
    eval(arrayHandles[j] + "[" + idx + "].src = '" + imgPath +
      imgName + arrayHandles[j] + ".jpg'");
    }
  }

imagePreLoad() accepts two arguments, a name prefix (e.g., Infoseek) and an integer used to assign the appropriate array element to a new Image object. Once again, a for loop iterates through arrayHandles, utilizing each element string to access one of the arrays just created and assign it a unique reference. For example. Calling imagePreLoad('infoseek', 0) is equivalent to hardcoding the following:

out[0]      = new Image();
out[0].src  = 'images/infoseekout.jpg';
over[0]     = new Image();
over[0].src = 'images/infoseekover.jpg';

But that’s four lines of code, exactly what I wanted to avoid doing over and over. Every time I want a new image rollover pair, I can make a call to preLoadImages(). And that is working smarter, not harder.

Start Your Engines

Variable engines at lines 26-100 represents an array of elements, each containing another array of elements with specific search engine information. Variable engines has 20 fairly long elements, so let’s takes a look at just the first one as shown in lines 27-29:

new Array('HotBot',
          'http://www.hotbot.com/?MT=',
          'http://www.hotbot.com/'),

Element 0 identifies the search engine name, HotBot. Element 1 identifies the URL with the query string, which, when included with query text, will call the search engine and return the results page. Element 2 represents the URL of the search engine home page. This is used in place of Element 1 if the user attempts a null search (searching with an empty string).

engineLinks( )

Function engineLinks() is similar to function genScreen() in Chapter 3 because it is responsible for managing the creation of the layers. It does have its differences, though. Examine lines 112-140.

Managing layers

The first thing this function takes care of is generating the layer containing the navigation links:

genLayer('sliderule', left - 20, top + 2, 25, engHgt, true,
  '<A HREF="javascript: changeSlide(1);" '+
  'onMouseOver="hideStatus(); return true;"><IMG SRC="' +
  imgPath + 'ahead.gif" BORDER=0></A><BR><A HREF="javascript: ' +
  'changeSlide(-1);" onMouseOver="hideStatus(); return true;">' +
  '<IMG SRC="' + imgPath + 'back.gif" BORDER=0></A>');

This happens with a simple call to genLayer(). There are no real surprises here. The layer will contain two linked images: a forward and a backward arrow. Notice that the left and top pixel values passed in are relative to the left and top positions, left - 20 and top + 2, of the soon-to-be created engine link layers.

Next up, variable lyrCount determines the number of layers of search engine buttons to create, depending on the number of buttons you want per layer and the number of engines you have allotted in the engines array. It is really pretty easy. Divide the number of search engines (engines.length) by the number of engines you want to display per layer (perLyr). If the remainder is anything but 0, you’ll need one more layer.

Let’s use the values of the application. engines.length is 20, and perLyr is 4. Therefore, variable lyrCount is 5. If I had used 21 engines, 21 / 4 = 5.25. A remainder of .25 indicates the need for an extra layer, so lyrCount would be set to 6. Here is the code again:

lyrCount = Math.ceil(engines.length / perLyr);

The conditional operator performs exactly as described above. If the remainder is 0, set lyrCount to engines.length/perLyr. Otherwise set lyrCountto Math.ceil(engines.length/perLyr). Determining lyrCountis important. Once determined, engineLinks() creates lyrCountlayers in lines 122-136:

for (var i = 0; i < lyrCount; i++) {
  var engLinkStr = '<TABLE BORDER=0 CELLPADDING=0 CELLSPACING=0><TR>';
  for (var j = 0; j < perLyr; j++) {
    var imgIdx   = (i * perLyr) + j;
    if (imgIdx >= engines.length) { break; }
    var imgName  = nameFormat(engines[imgIdx][0]);
    imagePreLoad(imgName, imgIdx);
    engLinkStr += '<TD><A HREF="javascript: ' +
      callSearch(document.forms[0].elements[0].value, ' + imgIdx +
      ');" onMouseOver="hideStatus(); imageSwap(\'' + imgName + '\', ' +
      imgIdx + ', 1); return true" onMouseOut="imageSwap(\'' + imgName +
      '\', ' + imgIdx + ', 0);"><IMG NAME="' + imgName + '" SRC="' +
      imgPath + imgName + 'out.jpg' + '" BORDER=0></A></TD>';
    }

For each layer, engineLinks() declares local variable engLinkStr, which will contain the code for each slide. After creating engLinkStr, which as you can see in line 123 starts the table that will encapsulate the images, a nested for loop makes perLyr iterations to create the table cells that will contain the image.

For each perLyr iteration, local variable imgIdxis assigned the value (i * perLyr) + j. That expression is simply an integer that starts at and is incremented by 1 at the beginning of every iteration. imgIdx will be used to identify the prefix of the images (which is the name of the search engine in element in each array in engines) and then preload the images as discussed earlier. Table 4.1 offers a quick multiplication scheme when perLyr is 4.

Table 4-1. Calculating Layers to Display (perLayer is 4)

When i is . . .

And j values at . . .

(i * perLyr) + j keeps rising by 1. . .

0

0, 1, 2, 3

0, 1, 2, 3

1

0, 1, 2, 3

4, 5, 6, 7

2

0, 1, 2, 3

8, 9, 10, 11

3

0, 1, 2, 3

12, 13, 14, 15

4

0, 1, 2, 3

16, 17, 18, 19

There are 20 integers, 0-19.

Now that we know the value of imgIdx, we have to make sure we haven’t gone too far. Line 126 handles that:

if (imgIdx == engines.length) { break; }

Since the value of imgIdx is incremented unconditionally each iteration, once it reaches the engines.length, there are no more search engines to display links for, so the function will “break” out of the for loop.

Preloading images

The time has come to preload the pair of images for each search engine. Before that happens, we need to know the image prefix. Simply enough, the prefix is the lowercase version of the search engine name. That is, the “InfoSeek” image prefix is infoseek; the HotBot image prefix is hotbot, and so on. Variable imgIdx identifies the correct image prefix in line 127:

var imgName = nameFormat(engines[imgIdx][0]);

Element 0 of each array in engines contains the search engine name. Variable imgIdx identifies the correct element index in engines, which returns that search engine name. All that is left is to convert all the letters to lowercase. Function nameFormat() does the trick at lines 158-161:

function nameFormat(str) {
  var tempArray = str.split(' ');
  return tempArray.join('').toLowerCase();
  }

All whitespace is removed by splitting the string passed by whitespaces into array elements, then joined. Now imgName has a lowercase, whitespace-free image prefix. It is ready to be passed with imgIdx to imagePreload() in line 128.

Building the link

Time to build a linked image with appropriate rollover code for each search engine. Enter lines 129-135:

engLinkStr += '<TD><A HREF="javascript: ' +
  'callSearch(document.forms[0].elements[0].value, ' + imgIdx + ');" ' +
  'onMouseOver="hideStatus(); imageSwap(\'' + imgName + '\', ' + imgIdx +
  ', 1); return true" onMouseOut="imageSwap(\'' + imgName + '\', ' +
   imgIdx + ', 0);"><IMG NAME="' + imgName + '" SRC="' + imgPath +
   imgName + 'out.jpg' + '" BORDER=0></A></TD>';

Let’s consider this. Each search engine link will need the same four requirements:

  1. Code that calls the appropriate search engine when the user clicks on the image

  2. Code for the onMouseOver event handler that rolls over the image

  3. Code for the onMouseOut event handler that rolls the image back

  4. An IMG tag with a unique NAME and the SRC attribute set to the corresponding image path

Dissecting the string set to engLinkStr will reveal how each requirement is satisfied.

The first requirement is satisfied with the following code:

HREF="javascript: callSearch(document.forms[0].elements[0].value, ' +
  imgIdx + ');"

You can see that the link created, upon being clicked, will call function callSearch(), in which document.forms[0].elements[0].value will be passed in along with the corresponding value of imgIdx. More on callSearch() soon. For now it’s safe to say that requirement 1 is in the bag.

The second requirement is satisfied by the following code:

'onMouseOver="hideStatus(); imageSwap(\'' + imgName + '\', ' + imgIdx +
  ', 1); return true" ' +

This code handles creating the call to hideStatus() for clearing the status bar of annoying URLs, then the call to imageSwap(), passing in the three necessary parameters imgName, imgIdx, and an integer (1) corresponding to the element in arrayHandles.

The third requirement is remedied like so:

'onMouseOut="imageSwap(\'' + imgName + '\', ' + imgIdx + ', 0);">' +

Not much of a change. The only appreciable difference is passing in 0 instead of 1.

And now for the fourth requirement:

'<IMG NAME="' + imgName + '" SRC="' + imgPath + imgName + 'out.jpg' +
  '" BORDER=0></A></TD>';

The name of each image is set to the value of imgName. That is how it will be referenced in function imageSwap(). The SRC attribute is set to the concatenation of imgPath, imgName, and out.jpg. Since the images will start in the mouse-arrow pointer out position, the SRC tag is set to the images corresponding with the out.jpg substring. For example, the opening image for HotBot is located at images/hotbotout.jpg.

Lines 137-138 add the finishing touches:

engLinkStr += '</TR></TABLE>';
genLayer('slide' + i, left, top, engWdh, engHgt, false, engLinkStr);

That is, engLinkStr receives the HTML to close the table, and all that remains is to create the layer with genLayer(). Notice that all calls to genLayer() here pass in false. Remember that passing in false hides the layer from view. All the layers are hidden until the page is loaded. Then slide0 is revealed in the onLoad event handler at line 201.

imageSwap( )

You saw it Chapter 3, but this version is a bit different. Consider lines 179-182:

function imageSwap(imagePrefix, imageIndex, arrayIdx) {
  document[imagePrefix].src = eval(arrayHandles[arrayIdx] + "[" +
  imageIndex + "].src");
  }

This function performs the image rollovers. Argument imagePrefix identifies the source of the image to be switched. Arguments imageIndex and arrayIdx are integers that properly access the correct Image object in arrayHandles.

callSearch( )

When the HTML form and the layers are in place, the user needs only enter search text and click the search engine of choice. When users click a search engine image, function callSearch() gets the call. Here it is in lines 184-193:

function callSearch(searchTxt, idx) {
  if (searchTxt == "") {
     parent.frames[2].location.href = engines[idx][2] +
       escape(searchTxt);
     }
  else {
     parent.frames[2].location.href = engines[idx][1] +
       escape(searchTxt);
     }
  }

callSearch() expects two arguments. searchTxtcontains the text entered in the text field, and idx contains an integer that corresponds with the search engine information in the engines array. The application loads one of two documents in frames[2]. If the user enters no search text, frames[2] is loaded with the default home page of the search engine. This URL is contained in element 2 of each array in engines. If, however, the user enters search text, the application loads frames[2] with the URL and query string of the search engine, plus the escaped version of the text the user entered.

You might be wondering where I got those lengthy query strings in element 1 of each array in engines. Where could I possibly come up with those values?

Actually, I checked the source code of each search page and built the query string based on the HTML form used to submit search text. Let’s start with an easy example. MusicSearch.com has a single text field for searching. The ACTION attribute of the form is http://www.musicsearch.com/global/search/search.cgi. The name of the field is QUERY. Therefore, the URL with query string should look like this:

http://www.musicsearch.com/global/search/search.cgi?QUERY= +
  escape(searchTxt);

That’s pretty easy. One name-value pair. Search engines can have plenty of options, though. Consider the meta-search engine (one that searches other organizations’ databases instead of its own) SavvySearch. With SavvySearch, you enter search text, and then can use checkboxes to choose which media to search, such as search engines, newsgroups, etc. You can also impose Boolean search rules, set the number of results to return from each database, and choose the amount of information displayed about each search result.

The ACTION attribute of the SavvySearch form is http://numan.cs.colostate.edu:1969/nph-search. Here is a list of the required form elements:

  • The name of the select list for Boolean searches is Boolean

  • The name of the results count select list is Hits

  • The name of the result amount radio buttons is df

  • The name of the text field is KW

I set the Boolean select list value to OR, the Hits select list value to 10, the results display button dfvalue to normal, and the search text field KW to escape(searchTxt). I didn’t invent values OR, 10, and normal. Those are either option values in the select lists or radio button values, all of which are in the HTML source code.

The form also contains two hidden fields, one with the name Mode, and the other with the name AutoStep. The Mode HIDDEN field has the value MakePlan. The HIDDEN field AutoStep has the value on. I’m not sure what purpose they serve, but that’s not important. All you have to do is add them to the query string. Submitting a query to SavvySearch, then, requires the following URL:

http://numan.cs.colostate.edu:1969/nph-search? ' +
  classic=on&Boolean=OR&Hits=10&Mode=MakePlan&df=normal&AutoStep=on&KW= +
  escape(searchTxt)

Another nice thing about “decrypting” the query strings is that the order of the name-value pairs generally doesn’t matter. As long as they are in the query string, things will work fine.

Get JavaScript Application Cookbook now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.