Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations Mike Lewis on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Advanced Drop down 2

Status
Not open for further replies.

theguru97321

IS-IT--Management
Feb 3, 2003
216
0
0
US
I have a form that contains a drop down with 1800+ options. Is there a way to simplify the selection process.

I know you can press I, or J or Q to jump to the that first item that starts with that letter. But some of the items may have 100s of options beginning with the same letter, can I some how continue to type, and have it keep closing in on the item I'm looking for.

As in.. 1800 in the list, I want Spam but there are 100s of items that being with S and Spam is in the middle.

I'd like a field to allow me to type spa and hopefully it will move the drop down to spa, with space and spade and spam down the list a bit more. so if i keep typeing spa then m spam will be the selected item...

Is this possible???? Even with out Javascript, is there any way to do this.
 
If you can count on the order of items in the select list being "in order" (whether numerically or alphabetically), then you can set up a text field that responds onkeyup as you type in it and it does a binary search on the drop-down list, looking for matches.

This works neatly if you have enough room to add a text field.

I have another solution, but let me know if this one interests you first.

--Dave
 
I can guarentee the drop down is in order, as it is populated from a MySQL Query.

Please show how you mean, I have plenty of room.
 
Okay... Here's an example of a text field I use that corresponds to a drop-down list:

Code:
<input type="text" name="SOFTWARE_FINDER" onkeyup="findValue(this, eval('SOFTWARE.EXISTING_SOFTWARE'),0,SOFTWARE.EXISTING_SOFTWARE.length-1,0);" size="30">

You can see that the onkeyup event calls 'findValue(...)'. Parameters sent include:
* the text field itself ('this');
* the actual dropdown (SELECT) list (form name is 'SOFTWARE', list name is 'EXISTING_SOFTWARE');
* a lower-bound index (initially 0);
* an upper-bound index (initially the length of the list minus 1)
* the default index to select if no matches are found (here: 0)

Now, here is the function:
Code:
function findValue(finderFieldObj, selectListObj, listStart, listEnd, defaultSelect)
{
[b]//this first bit is specific to my code and what I want to have happen when the user hits the enter key in the field.  My drop-down list has a 'me'-created attribute named 'button' that names the corresponding button who's onclick action is what I want to happen.  The button.click() action does the same thing as the list's onchange action.[/b]
	if(event.keyCode == 13)
	{
		finderFieldObj.value = "";
		if(selectListObj.button)
		{
			finderFieldObj.focus();
			var button = eval("SOFTWARE."+selectListObj.button);
			button.click();
		}//end if
		else
		{
			finderFieldObj.blur();
		}//end else
		return;
	}//end if

[b]//grab what the user has typed:[/b]
	var typed = finderFieldObj.value.toUpperCase();

[b]//determine the middle index of the drop-down list:[/b]
	var midIndex = listStart + Math.floor((listEnd-listStart)/2);

[b]//get the corresonding text (better than getting the 'value' since the user sees the text):[/b]
	var midlist = selectListObj.options[midIndex].text.toUpperCase();

[b]//do nothing if nothing is in the box (happens if user uses the backspace or delete key, for example, to delete the contents of the box[/b]
	if(typed == "")
		return;

[b]//if there is a match of what the user has typed with the beginning of the list's middle item, then go backwards, one item at a time, and find the FIRST instance of such a match:[/b]
	if(midlist.indexOf(typed) == 0)
	{
		var index = midIndex;
		var indexMatch = midIndex;
		while(index > 0)
		{
			index--;
			if(selectListObj.options[index].text.toUpperCase().indexOf(typed) == 0)
			{
				indexMatch = index;
				continue;
			}//end if
			else
			{
				break;
			}//end else
		}//end while
		selectListObj.selectedIndex = indexMatch;
		return;
	}//end if
[b]//the following are the conditions that arise when no match is found (it took me a while to nail these down):[/b]
	else if(midIndex == 0 || listStart == listEnd || midIndex == listStart)
	{
		selectListObj.selectedIndex = defaultSelect;
		return;
	}//end else if
[b]//else if what is typed might be found in the first half of the list, cut the list in half and recursively call this function again:[/b]
	else if(typed < midlist)
	{
		findValue(finderFieldObj, selectListObj, [b]listStart[/b], [b]midIndex[/b], defaultSelect);
	}//end else if
[b]//else if what is typed might be found in the second half of the list, cut the list in half and recursively call this function again:[/b]
	else if(typed > midlist)
	{
		findValue(finderFieldObj, selectListObj, [b]midIndex[/b], [b]listEnd[/b], defaultSelect);
	}//end else if
}//end findValue(var,var,var,var,var)

There are a lot of additional keycodes you can try to capture to add functionality here that will, for example, allow the user to hit the 'Page Up' key and have the select list jump up a programmer-determined number of spaces (for example, 10) in the select list. If you get the above working and you're interested in some of these, let me know.

Let me know if you have any questions.

--Dave
 
Very nice. I like how you divide the list to speed up the searching. May I offer a few ways to improve this code?

1. "eval()" is very slow. I suggest changing this:
Code:
var button = eval("SOFTWARE."+selectListObj.button);
to this:
Code:
var button = document.SOFTWARE[selectListObj.button];

2. I'd add a second function so the search function only executes after the user has stopped typing for a half-second. So you would change this:
Code:
<input type="text" name="SOFTWARE_FINDER" onkeyup="findValue(this, document.SOFTWARE.EXISTING_SOFTWARE,0,document.SOFTWARE.EXISTING_SOFTWARE.length-1,0);" size="30">
to this:
Code:
<input type="text" name="SOFTWARE_FINDER" onkeyup="startFind(this,this.form,'EXISTING_SOFTWARE');" size="30">
and add this code:
Code:
var searchTimer,activeFinderFieldObj,activeSelectListObj;
function startFind(finderFieldObj,formObj,selectListName){
  cancelTimeout(searchTimer);
  activeFinderFieldObj=finderFieldObj;
  activeSelectListObj=formObj[selectListName];
  setTimeout('findValue(activeFinderFieldObj, activeSelectListObj, 0, activeSelectListObj.length-1, 0)',500);
}

Adam
while(ignorance){perpetuate(violence,fear,hatred);life=life-1};
 
Adam,

Thanks for those tips!

I knew eval(...) was a processing-hog, but for some reason, I couldn't think of an alternative! Also, in this case, even though the function gets called recursively, this eval(...) statement will only ever be executed once (if keycode is 13, eval(...) is used and no recursive calls are made).

I tend to use eval(...) whenever I get stuck though, so now I am armed for the next time!

I like the timer idea a LOT! I know that most-often, a user isn't just going to type in one letter and hope to find their solution. They'll type in three or four. I added it to my code and had to make the following adjustments:

Code:
function startFind(finderFieldObj,formObj,selectListName){
  [b]if(searchTimer)[/b]
   cancelTimeout(searchTimer);
  activeFinderFieldObj=finderFieldObj;
  activeSelectListObj=formObj[selectListName];
  setTimeout('findValue(activeFinderFieldObj, activeSelectListObj, 0, activeSelectListObj.length-1, 0)',500);
}

Also, I had to move my check for event.keyCode from the findValue(...) function into the startFind(...) function. I guess the event is not maintained.

Works like a charm!

Thanks again!

--Dave
 
These are very nice posts. I've done something similar to this a while ago. Since I had more than 10000+ options in the SELECT menu, in order to scroll the list while you type in the text box (no Enter required) and scroll fast, it's better to create an index array since you already knew what will be in the menu list from the database. The code works fine for every browser except the KDE's Konqueror (it doesn't scrollintoview) and it is my targeted browser, so I gave up. I will try the code you guys posted here and let you know how will it works for 10000+ items in the SELECT list..:)
 
plectrum,

No enter-key is required in this method either. Hitting enter only establishes the chosen option ("Yes, this is the one I want."). Otherwise, the selected value changes as the user types.

I'd be interested in seeing your index array method.

--Dave
 
Very good script for searching. Some little comments:

1) I've changed the event code when a user hit enter (13)
Code:
finderFieldObj.value = "";

for:
Code:
finderFieldObj.value = document.SOFTWARE.EXISTING_SOFTWARE.options[document.SOFTWARE.EXISTING_SOFTWARE.selectedIndex].text

so that an enter give the value of the list to the textbox
(you can simplify the code with the selectListObj variable if you are using it in your function)

2)I've had an OnChange event on my select list (existing_software in this case) so that it give the value of the selected list in case the user browse the list and don't use my textbox, in this case it will look like this:

Code:
<select name="EXISTING_SOFTWARE" onChange="document.SOFTWARE.SOFTWARE_FINDER.value=document.SOFTWARE.EXISTING_SOFTWARE.options[document.SOFTWARE.EXISTING_SOFTWARE.selectedIndex].text">


3) there is a glitch in the code so that if let say i type a letter that exist (let say "I"), when i erase it, my select list doesn't adjust.. so I've had a line to set my select list value to 0 when my textfield finder is ""

before:

Code:
    if(typed == "")
        return;

with:

Code:
if(typed == ""){  document.SOFTWARE.EXISTING_SOFTWARE.selectedIndex = 0;
return;
}

That's it for now, I don't know if it can be useful for you, it's really minor things but it still improve the original script :eek:)
 
These are good. It depends on what you want to do. Putting the value of the existing software into the textbox may not be such a good idea if the next software to be requested is not similarly-named (i.e., the user is going to have to clear the box and begin typing the next name he/she wants next).

You're certainly correct about going to a different selectedIndex if typed == "". Depending on the scenario, the selectedIndex you might like to go to is -1.

Thanks for taking the time to look!

--Dave
 
Like you said it depend on what you want to do. You made me think of another feature to add to this script: being able to browse the list with the arrow key. Here is the code (in the findvalue function)

Code:
// if the right or up arrow has been select then goto the next record

else if (event.keyCode == 38 || event.keyCode == 39) {
 if (selectListObj.selectedIndex < selectListObj.options.length-1) {
selectListObj.selectedIndex =    selectListObj.selectedIndex+1;
finderFieldObj.value = selectListObj.options[document.demande.NO_EMPLOYE_BPR.selectedIndex].text;
		}
		return
}
	
// if the left or down arrow has been selected goto previous record
else if (event.keyCode == 40 || event.keyCode == 37) {
if (selectListObj.selectedIndex > 0) {
selectListObj.selectedIndex = selectListObj.selectedIndex-1;
finderFieldObj.value = selectListObj.options[selectListObj.selectedIndex].text;
		}
return
}	
[/code}
 
In the code I use, I do have something like that. I simplified greatly for the sake of not overwhelming anyone before. I have something like this:

Code:
var currIndex = selectListObj.selectedIndex;
//if SELECT list is empty, don't do anything
if(currIndex == -1)
 if(selectListObj.length == 0)
  return;

var kC = event.keyCode;
if(kC == 38) //up-arrow
{
 //if at top of list, wrap to the end
 if(currIndex == 0 || currIndex == -1)
  selectListObj.selectedIndex = selectListObj.length - 1;
 else
 selectListObj.selectedIndex = currIndex - 1;

 return;
}//end else if
else if(kC == 40) //down-arrow
{
 if(currIndex == -1 || finderFieldSource.value == "")
 {
  selectListObj.selectedIndex = 0;
 }//end if
 else
 {
  //wraps to the top if already at the bottom of the list
  selectListObj.selectedIndex 
   = (currIndex + 1)%selectListObj.length;
 }//end else
 return;
}//end else if
else if(kC == 33) //page up
{
 if(currIndex < 10)
 {
  //go to the beginning
  if(selectListObj.length >= 1)
   selectListObj.selectedIndex = 0;
 }//end if
 else
 {
  selectListObj.selectedIndex = currIndex - 10;
 }//end else
 return;
}//end else if
else if(kC == 34) //page down
{
 if(selectListObj.length - currIndex < 10)
 {
  //go to the end
  selectListObj.selectedIndex = selectListObj.length - 1;
 }//end if
 else
 {
  selectListObj.selectedIndex = currIndex + 10;
 }//end else
 return;
}//end else if
else if(kC == 36 || kC == 35 || kC == 37 || kC == 39)
//home, end, left-arrow, and right-arrow, respectively
{
 return;
}//end else if

This code would go in before the var typed = ... line.

You could even include something like this to avoid unnecessary processing time lost:

Code:
//The following is a list of keyCodes which are not of interest
// to this code:
// 16	shift
// 17	ctrl
// 18	alt key
// 19	pause/break
// 20	caps lock
// 27	escape
// 44	print screen/sysrq
// 45	insert
// 91	left-hand Windows key
// 92	right-hand Windows key
// 93	"right-click" key
// 112 - 123	F1 through F12
// 144	num lock
// 148	scroll lock
var listOfKeyCodesToDiscard =
 "_16_17_18_19_20_27_44_45_91_92_93_112_113_114_115_116_117_118";

listOfKeyCodesToDiscard += "_119_120_121_122_123_144_148_";

if(listOfKeyCodesToDiscard.indexOf("_"+kC+"_") > -1)
 return;

That would go towards the top of the function.

'seems like you are enjoying the code! THANKS!

--Dave
 
That's cool, Jeff! I'll have to check out the code later. I'm reading from the library right now.

--Dave
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top