SCRIPTING:

Rene Magritte in a Lothar Wolleh photo
Rene Magritte in a Lothar Wolleh photo
SELECT MENU ONE, UPDATE MENU TWO
A script many asked for.
How to rewrite all the contents, texts and values both, of a select box (a drop down menu, that is) when a certain selection is performed in another drop down menu.
Requires a study of this file, since to achieve such apparently simple a thing, it needs lots of work!
September 2001
{ @ }



How a SELECT object is built up: OPTIONS, Text, Value
You must understand the role of the OPTIONS and the difference that they present as far as TEXT and VALUE are concerned

A most wanted script for the form elements so that upon selecting one option can be accordingly triggered an update to the set of options of another select list.
A select-one menu is defined by the following tags and properties (mandatory their allocation in between a form tag):
<select name="aName" onChange="myFunction()">
<option value="aValue for option 0">text for option 0 here</option>
<option value="aValue for option 1">text for option 1 here</option>
... ... ... ... ... ... et caetera ... ... ... ... ... ...
</select>

Please note:
  1. Every select tag must carry a unique name to identify it
  2. The onChange property is what determines the subroutine which has to run upon selection of an item
  3. The options define the amount of selections. Note that:
    1. They are always counted starting with zero
    2. Each of them carries an invisible value and a visible text
So what we want to achieve is, within an environment where there are at least two select menus, to completely rewrite both the values and the texts of a select menu upon a selection (onChange) in the other select menu.
It is important for you to fully understand that when you're on rewriting the texts of a select tag, you must also rewrite the value properties of each option and not just their texts. In fact the texts reveal, through a string which is either appealing or explicative to humans, what the option is about, but it is its hidden value which normally carries a string of programming data that would be meaningless to humans but that none the less are mission critical for your application. A typical example is the option which carries in the text a string describing a website title (such as: my cool website...) and that carries in the value an often complex Url [Uniform Resource Locator, a web address that is] which might be weird enough to justify the necessity to hide it (such as: http://www.supersonicrafts/specials/unmanned/stealth/comments/02730.987.00564.denver.09.asp).
I really do not care if you need or not to rewrite both of them, both values and texts: I want you to do it anyway if we are to spread good programming practice...



Dealing with the challenge
Achieving your goal won't be easy

Once you have to rewrite the texts and values of a menu set, it is obvious and apparent that the list of new options (both their texts and values) are not visible yet, and none the less they have to be stored somewhere in order to be quickly grabbed upon request: they have to exist.
The best way to store these sets of alternative texts and values are, obviously, arrays; in fact an array gets indexed starting with zero, like an option, and thus everything could be achieved by a loop that iterates the array and deals each indexed entry to the corresponding index of the select object.
Our main concern here is portability: that is, a code that may allow you to use it in many environments without having to build different instances of the same objects in order to address different sets of select tags.
First, since we will have to deal with value and text for each option we'd build a kind of object that can store 2 properties. here is the code that does it:
function selectObjectSet(){
this.value=new Array();
this.text=new Array();
}
As you see it is a function. It uses the keyword this: when you use built in javascript methods such as:
someStringObjectHere.toLowerCase()
you're putting first an object [someStringObjectHere] and then linking to it, through a dot, a method [toLowerCase()] that will perform some task on it (in our case it will lowercase the linked string).
As you may guess, nothing prevents you from going out of the borderlines of the built in objects and methods and to define/create your own: this is what the keyword this is for: it means that the function in case will have an object before it (whose temporary placeholder is exactly the keyword this which flags that the function name will be the name of a method which will be wanting an object before it linked thorough a dot. Ok?).
Torrid Taylor
In this case we are building a method (we say in this case this is, more appropriately, just an object, since it does not perform or inflict any modification to the linked object, but simply describe that it has two trailing properties named value and text); this method (better: object) is thus called selectObjectSet and will accomplish a pretty simple thing: it will be the storehouse for the two properties that we called value and text (we could have called them johnnie and walker as well...) to make more intuitive what they will be meant for and to store what...
We can now get avail of another keyword in javascript, the keyword new and initialize through it as many new selectObjectSet objects as we prefer.
Thus here is the trick: we build an array whose each entry is a... new selectObjectSet object...:
var storehouse=new Array()
storehouse[0]=new selectObjectSet()
storehouse[1]=new selectObjectSet()
storehouse[2]=new selectObjectSet()
storehouse[3]=new selectObjectSet()
//... you could go on. Just start with index ZERO
In other words, you have to initialize one of those strings (if just one, just one. But do be sure, absolutely sure, that you start with the first index set as zero, and whatever subsequent object counting up from there) for each select object that you have to redraw. Suppose you have select1 and select2: both select1 and select2 have their full set of texts and values. But since you want to rewrite one of them, you have to gather somewhere the texts and values of the new select which is to be drawn: thus you get avail of a storehouse entry.

So let's imagine the simplest possible case: you have only one storehouse entry since you need to update only one select box (although I must say that the simplest case is the case where you are forced to use ... 2 storehouse entries, since once you have redrawn the list of options, what if you want to reset them to their original...? They got lost until you have handwritten a copy in another storehouse entry. Got it? Think of it...).
Once you have initialized it
var storehouse=new Array()
storehouse[0]=new selectObjectSet()
you must remember that every selectObjectSet() has two properties as we defined it:
  1. .text
  2. .value
This means that every new selectObjectSet object will have those properties, so even this single storehouse[0] entry will have it:
storehouse[0].text
storehouse[0].value

Take courage, you're required a last effort: we initialized each property (both text and value, that is) of every selectObjectSet object like a... new Array, remember?
So they are arrays: thus to store in it both texts and values of the new select menu which is going to be built you just have to index the array and assigning the values:
var storehouse=new Array()
storehouse[0]=new selectObjectSet()

storehouse[0].text[0]="New FIRST TEXT"
storehouse[0].text[1]="New SECOND TEXT"
storehouse[0].text[2]="New THIRD TEXT"

storehouse[0].value[0]="New FIRST VALUE"
storehouse[0].value[1]="New SECOND VALUE"
storehouse[0].value[2]="New THIRD VALUE"
I believe this is convincing, although you are right: it requires your participation: you have to write all the texts. This is not a shortfall of the method: it is simply impossible for a computer to design from scratch a new select menu without knowing with what texts and values it has to populate it. Once again, convincing: isn't it?
Now you have to build the two selects: say the first is named select1 and is the one that triggers the update in the second select named select2. Thus in the options of the select1 you have to set the values to the storehouse[index] it must grab upon selecting that option:
<select name="select1" onChange="updateMenu()">
<option value="storehouse[0]">Reset target menu</option>
<option value="storehouse[1]">Change target menu</option>
</select>

<select name="select2" onChange="foofoo(this.options[this.selectedIndex])">
<option value="old value option 0">old text option 0</option>
<option value="old value option 1">old text option 1</option>
<option value="old value option 2">old text option 2</option>
</select>

Now in the SCRIPT include for storehouse[0] a list of texts and values to reset the old values (we assume you want to do this) and in storehouse[1] the new values: Here full script prior to the basic function:
function selectObjectSet(){
this.value=new Array();
this.text=new Array();
}

var storehouse=new Array()
storehouse[0]=new selectObjectSet()
storehouse[1]=new selectObjectSet()

//comment: storehouse[0]: old texts:
storehouse[0].text[0]="old text option 0"
storehouse[0].text[1]="old text option 1"
storehouse[0].text[2]="old text option 2"

//comment: storehouse[0]: old values:
storehouse[0].value[0]="old value option 0"
storehouse[0].value[1]="old value option 1"
storehouse[0].value[2]="old value option 2"

//comment: storehouse[1]: new texts:
storehouse[1].text[0]="new text option 0"
storehouse[1].text[1]="new text option 1"
storehouse[1].text[2]="new text option 2"

//comment: storehouse[1]: new values:
storehouse[1].value[0]="new value option 0"
storehouse[1].value[1]="new value option 1"
storehouse[1].value[2]="new value option 2"
a Richard Swiatlowski photo
a Richard Swiatlowski photo

Please study the indexes: storehouse has only two: [0] and [1], but each of them hosts an array of two different types (text and values) which follow their own indexing.


The subroutine, finally
Last step

Now here is your subroutine: it is the subroutine which is invoked by the onChange event of the select1 (that was the name we assigned to it), while the select2 (which is to undergo the changes) invokes onChange a foofoo() subroutine which is just a placeholder. In our case we want to define it just for providing you with a working example:
function foofoo(obj){
alert(obj.value)
}

That will simply trigger an alert showing the value, so you will see also values are updated in the example we provide below. Main function now!
function updateMenu(target,store){
store=eval(store)
target.options.length=store.text.length
for(var i=0;i<store.text.length;i++){
target.options[i].text=store.text[i]
target.options[i].value=store.value[i]
}
}

You invoke it from select1 passing arguments as follows:
  1. target argument: must be a full reference to the targeted select box, thus a path like:
    document.formName.select2
    in our case formName would be, obviously, select2 (do not get confused assigning select1...)
  2. The store argument must be exactly the following line:
    this.options[this.selectedIndex].value
Thus here is the invocation from the onChange event of select1 (and please, do not forget the comma in between the two arguments and do not... invert them! And do not add the keyword options after select2 name...
onChange="updateMenu(document.forms[0].select2, this.options[this.selectedIndex].value)"

Working example:

Last, remember that on Netscape 4 select boxes don't get resized so the longest string present in a menu at the instant the menu is loaded for the first time, will also be the longest size available throughout whatever redrawing (that is, set a first option long enough to accommodate the longest in your storehouse).