Live Search As You Type and Live Sorting Tables With JS
I’ve been showing this code to some people, and a lot of people are liking it. I’ve decided to release this to the public and write a tutorial to explain how this works. If you haven’t noticed, I always write detailed tutorials behind the code and never just offer the code as some Javascript library for you to include. I think it’s important to not just give people code but to show them exactly why this code works, so they can better understand how to program in general as well as to better understand how to tweak your code and get the most efficiency from it. So following in the pattern of all my previous Javascript and CSS tutorials, let’s get started on this tutorial.
So what will we be doing today? I am going to show you how to build a really cool table. I know you’re thinking “ugh, that’s it?”. But trust me, this is really impressive stuff. What is our table going to have? It will be fully sortable based on column. When you click on a column heading, all in real time, the rows will be sorted on column. If you click the column again, it reverse sorts it all. This is all real time, which means there are no server requests to you nor any page loading. It all happens dynamically and instantly.
Our table will also be completely searchable. What type of search? Live search. You know iTunes live search as you type? Well, this is a clone. You might have seen something like this in a previous tutorial I wrote about Spotlight-like search. What we have in this sample code is similar to that in the sense that it’s live search. It’s also completely different. Every time you typed a letter on that previous tutorial, a request was sent to your server. There was also an associated delay because of this. The code for this search removes both of those. It’s completely client-side. This means that all searching code is executed on the client’s machine, which means no extra requests for your server and absolutely no delay. It means super-fast, real, live, dynamic searching. It’s so very impressive. We will basically be building an iTunes-like table for the web. Here, have a look:
Try sorting and searching. Slick, right? Ok, so now you want to know how to add this yourself, right? The great thing is that the javascript code is totally independent of implementation. It will perform on any code, so, in a sense, this javascript can apply this specialized searchable, sortable table to any raw data table. Before we start, you can download all of our sample code and images here. Let’s now dig into the CSS:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | body { font-family: "Lucida Grande", Arial, Sans-Serif; } #container { width: 503px; background-image: url('gray_bg.gif'); background-repeat: repeat-x; } #search_section { width: 150px; float: right; margin-bottom: 7; margin-top: 25; margin-right: 15; } #search_section input { width: 150px; } #search_box_label { font-size: 10px; text-align: center; color: #222; margin-top: 2px; } |
This is the first part of the CSS. It sets up a good font for all the following elements. It creates a container id that has a certain width. This container also has the backround image set up like my example above. This makes it look like iTunes. You may wish to delete those two lines relating to background image. The next 3 css selectors relate to the search box and its label. Nothing particular special in here. Let’s take a look at the CSS for the table now:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | table.mac_os_styled_table { clear: both; border: 1px solid #D9D9D9; padding: 0; border-collapse: collapse; font-size: 11px; width: 503px; } table.mac_os_styled_table td { padding: 2 5 2 5; margin: 0; height: 17px; } table.mac_os_styled_table tr { margin: 0; padding 0; } table.mac_os_styled_table tr td { border-left: 1px solid #D9D9D9; border-right: 1px solid #D9D9D9; } table.mac_os_styled_table tr.header { background: url('thead_bg.gif'); background-repeat: repeat-x; border-top: 1px solid #555; border-bottom: 1px solid #555; height: 16px; font-weight: bold; color: #222; } table.mac_os_styled_table tr.header td { border-left: 1px solid #A5A5A5; border-right: 1px solid #A5A5A5; cursor:pointer } table.mac_os_styled_table tr.alt { background-color: #F1F5FA; } |
Here we set up the class mac_os_styled_table. Any table you want to have this should include this as a class. We tell it to have a border and to also collapse that border. We then set the global margin and padding for all the td and tr elements in our table. table.mac_os_styled_table tr td puts colored borders to the left and right of our table cells. The next selector sets up the CSS properties for our header row. We have it have a nice background image similar to iTunes. We set up colors and borders and set it to bold text. The next selector makes left and right borders for header td cells. It also makes the mouse cursor the pointer when we hover over them. We do this because our header cells are clickable to re-sort. We want the user to know this, so we change the pointer icon. Finally, we set the light blue background to any table row that is an alternate row (just like in iTunes). By now, we have some sweet looking tables, but they don’t even exist yet. Let’s code up a quick HTML table. Refresher course coming for those of you who haven’t made tables since CSS positioning came out.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | <div id="container"> <img src="gray_bg_right.gif" style="float:right;" /><img src="gray_bg_left.gif" style="float:left;" /> <div id="search_section"> <input type="search" id="search" onkeyup="doSearch(this);" /> <div id="search_box_label">Search</div> </div> <table class="mac_os_styled_table" id="main_table"> <tr class="header"> <td width="167" onclick="headerClicked(0);" id="header_0"><img src="down_arrow.gif" class="arrow" alt="Down" id="arrow_down_0" /><img src="up_arrow.gif" class="arrow" alt="Up" id="arrow_up_0" /> Name</td> <td width="167" onclick="headerClicked(1);" id="header_1"><img src="down_arrow.gif" class="arrow" alt="Down" id="arrow_down_1" /><img src="up_arrow.gif" class="arrow" alt="Up" id="arrow_up_1" /> Artist</td> <td width="167" onclick="headerClicked(2);" id="header_2"><img src="down_arrow.gif" class="arrow" alt="Down" id="arrow_down_2" /><img src="up_arrow.gif" class="arrow" alt="Up" id="arrow_up_2" /> Album</td> </tr> <tr><td>Move Along</td><td>All-American Rejects</td><td>Move Along</td></tr> <tr><td>Streets of Philadelphia</td><td>Bruce Springsteen</td><td>The Essentials</td></tr> <tr><td>Guerrilla Radio</td><td>Rage Against the Machine</td><td>The Battle Of Los Angeles</td></tr> <tr><td>Be Gentle With Me</td><td>The Boy Least Likely To</td><td>The Best Party Ever</td></tr> <tr><td>Runaway Train</td><td>Soul Asylum</td><td>Grave Dancers Union</td></tr> </table> </div> |
We start off by making our container division element. On line 2, we have some specific images for the example above. You can either keep these or get rid of them. If you are going to be using these images, I’d appreciate if you download them and upload them to your own server, so I can save my bandwith. Download the entire source package here. Back to the code. Starting on line 3, we begin to setup the search section. We create an input box that will call the doSearch() method whenever a user types something. We also make the label that says search. Line 7 begins our table. We set it to our CSS class we defined above. The first table row sets up each column. Each column header (the td) has a specific id in the format of header_#. We also tell it to call the headerClicked() function when it’s clicked. Inside the cell we put two images. One of a down area and one of an up arrow. We then also put some text that will be our column heading, in this case: Name, Artist, and Album. All of the following table rows are just raw data. You can put anything you want. They can be as simple as information about songs to as complex as tax records. Anything and everything will work.
We now get to the complex part of this code, the Javascript. We are going to take this section by section, function by function. Mine as well start with our global variables:
1 2 3 4 5 | //globals var globalSortCol; var numHeaders = 3; var headerClickedVal = -1; var headerClickedDir =""; |
Most of these will make sense later, so I won’t bother explaining them now. One of the easy ones, however, is numHeaders. Simply set this to the number of columns you have. That’s it for now, let’s head on to our first function:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | function headerClicked(id_num) { for(var i = 0; i < numHeaders; i++) { document.getElementById('header_'+i).style.background='url(thead_bg.gif)'; document.getElementById('arrow_down_'+i).style.display='none'; document.getElementById('arrow_up_'+i).style.display='none'; } document.getElementById('header_'+id_num).style.background='url(thead_bg_sel.png)'; if(headerClickedVal == id_num && headerClickedDir == "d") { document.getElementById('arrow_up_'+id_num).style.display=''; headerClickedDir = "u"; reverseTable(); } else { document.getElementById('arrow_down_'+id_num).style.display=''; headerClickedVal = id_num; headerClickedDir = "d"; sortTableOnColumn(id_num); } } |
This function, headerClicked(), takes one parameter. The value id_num tells the function which header was clicked. The for loop cycles through each column heading and tells it to return to default style (no selected state and no arrows). Line 7 then tells the specific column that was clicked to change the background to the selected format. The if statement will tell us if the user clicked a column that was already selected and also if that column was displaying in a->b->c order or ‘d’. ‘u’ would mean z->y->x. This is where our global variables come in. The headerClickedDir tells us which direction the arrow is pointing, and headerClickedVal tells us which column is selected.
So if that if-statement returned true that means the user wants us to reverse the direction of sorting. We simply show the up arrow, change the direction to ‘u’ and then reverse the table using the method reverseTable(), which we will get to shortly.
If, however, the statement returned false, it means we are sorting on a new column, or the user has clicked an up arrow column. Now we simply display a down arrow on our header cell, change the header value to our header id, change the direction to down, and finally call the function sortTableOnColumn(). This method, as we will see later will sort our table on the column numbered id_num.
I’m going to be skipping around the Javascirpt code and talking about certain functions in an order different to the sample code. I’m just leaving the more complex stuff for later. This will have no effect on code execution though. The next function is a simple one that will go through our array and alternate line colors.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | function reAlternateLines() { var tbl = document.getElementById('main_table'); var rows = tbl.rows; var counter = 1; for(var i=1; i<rows.length; i++) { if(rows[i].style.display!='none') { if(counter%2==0) { rows[i].className="alt"; } else { rows[i].className=""; } counter++; } } } |
For the purposes of this sample, I’m assuming we only have one table. If that is not the case, you will need to add some more code that helps pick which table we are going to use as seen in line 2. In our case, we just are going to use main_table. Line 3, then gives us all the rows that are in our table. We start a for loop and loop through all the elements in the array, except for row #1, which is our header row. We don’t want to use that in our alternating color function. On line 6, we check to make sure this row is even shown to the user. If it is, we check whether our counter variable is even or odd. We apply the correct class name based on whether it’s even or odd. That’s it for this function. Let’s move on.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | function isNumeric(sText) { var validChars = "0123456789."; for (var i = 0; i < sText.length; i++) { if (validChars.indexOf(sText.charAt(i)) == -1) { return false; } } return true; } function sortRowArrayOnColumn(a,b) { var x = a.cells[globalSortCol].innerHTML.toLowerCase(); var y = b.cells[globalSortCol].innerHTML.toLowerCase(); if(isNumeric(x) && isNumeric(y)) { x = x * 1; y = y * 1; } return ((x < y) ? -1 : ((x > y) ? 1 : 0)); } |
These are our two helper function for our sorting code. The first function will tell us whether a string is a number or not. It just cycles through each letter and tells us if each is a number. Now the sortRowArrayOnColumn() function. We will pass two rows to the second function: a and b. The code extracts the innerHTML from the cell we are sorting on (the global variable globalSortCol let’s us know which one), and then we return a value based on which one comes before which (taking into account whether they are numbers). Pretty simple helper functions. Now let’s write the code that does the sorting.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | function sortTableOnColumn(col) { var tbl = document.getElementById('main_table'); var rows = tbl.rows; var row_array = new Array(); for(var i=1; i<rows.length; i++) { row_array[i-1] = rows[i]; } globalSortCol = col; row_array.sort(sortRowArrayOnColumn); globalSortCol = ""; var innerHTMLArray = new Array(); for(var l = 0; l < row_array.length; l++) { var innerHTMLArray2 = new Array(); for(var c = 0; c < numHeaders; c++) { innerHTMLArray2[c] = row_array[l].cells[c].innerHTML; } innerHTMLArray[l] = innerHTMLArray2; } for(var l = 0; l < innerHTMLArray.length; l++) { for(var c = 0; c < numHeaders; c++) { tbl.rows[l+1].cells[c].innerHTML = innerHTMLArray[l][c]; } } doSearch(document.getElementById('search')); reAlternateLines(); } |
This is a complex method, and we will need to break it down slowly. Like you saw before, we get an array of the rows of our table in the first 2 lines of the function. On line 4 we create a new Javascript array object. We loop through our array of rows and put all of them except for the header row (row #1) into this new array. Line 8 sets the global variable I referred to previously to it’s correct value, the column we are sorting on. Line 9 then executes the search using our helper function we just wrote to do the comparison checks. Line 10 simply removes the value from our global variable to be safe.
Now we create a new array called innerHTMLArray. We loop through and fill this array up with the innerHTML values of our sorted array. How we do this is by creating a second array which holds the innerHTML of each cell in that row (Thanks IE for making this code harder than it should be). We do this because we need to make a copy of this value before we reset our table. From what I’ve found, there is no easier way to copy an array, and this loop actually only copies the part we want, the innerHTML of each cell.
The next for loop (lines 20-24) resets our table. It loops through all the rows and changes each cell’s innerHTML to the innerHTML of the sorted row’s cell. This may be a confusing concept right here. Basically what we did is to make a copy of all our rows. Sort those rows. Then copy the sorted values into a new array. We then loop through our original rows and set each row’s value to the new sorted row’s value. In essence, it just rearranges the order of the rows.
Finally, we need to call the doSearch() method to re-hide any rows that were hidden due to their elimination through our search words. Then, we just re-color our rows to alternate. That’s it!
The next function will simply reverse the rows in a table:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | function reverseTable() { var tbl = document.getElementById('main_table'); var rows = tbl.rows; var row_array = new Array(); for(var i=1; i<rows.length; i++) { row_array[i-1] = rows[i]; } var innerHTMLArray = new Array(); for(var l = 0; l < row_array.length; l++) { var innerHTMLArray2 = new Array(); for(var c = 0; c < numHeaders; c++) { innerHTMLArray2[c] = row_array[l].cells[c].innerHTML; } innerHTMLArray[l] = innerHTMLArray2; } for(var l = 0; l < innerHTMLArray.length; l++) { for(var c = 0; c < numHeaders; c++) { tbl.rows[l+1].cells[c].innerHTML = innerHTMLArray[innerHTMLArray.length-1-l][c]; } } doSearch(document.getElementById('search')); reAlternateLines(); } |
This code looks a lot like what we just did. This time we don’t have any sorting function calls. These lines are gone:
1 2 3 | globalSortCol = col; row_array.sort(sortRowArrayOnColumn); globalSortCol = ""; |
Instead in our final for loop, we just set the innerHTML to the value of the reversed row. In a sense, we move forwards through the array and put the previous values in backwards. The rest of the function is exactly the same as the previous one. We end it the same way by recalling the search method and re-coloring our rows.
The final function we have left is our doSearch() method.
1 2 3 4 5 6 7 8 9 10 11 12 | function doSearch(search_box) { var q = search_box.value; q = q.toLowerCase(); var q_ars = new Array(); q_ars = q.split(' '); for(var d = 0; d < q_ars.length; d++) { if(q_ars[d]=="") { q_ars.splice(d,1); } } |
We’re going to take this one in pieces. This first part will set the variable q to the value of our search box. We make q lowercase and split it into an array called q_ars after each space. We do this because we want to separetely search on each part of the string. For instance, a user can type in “bru essent,” and we want that to find “Bruce Springsteen” in the artist field and “The Essentials” in the album field.
The last for-loop you see here will go through and remove any empty strings from our array. This is helpful if the user has typed nothing or if the user has typed a string with an ending space. Next:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | var tbl = document.getElementById('main_table'); var rows = tbl.rows; for(var i=1; i<rows.length; i++) { var cells = rows[i].cells; var q_parts_found = ""; for(var j=0; j<cells.length; j++) { var val = cells[j].innerHTML; val = val.toLowerCase(); for(var k =0; k < q_ars.length; k++) { if(q_ars[k]!="" && val.indexOf(q_ars[k]) != -1) { q_parts_found += k + " "; rows[i].style.display=''; } } } |
Like usual, we are getting an array of the rows. We then loop through each row. We declare a variable cells, which returns all the cells of that particular row. The next for loop will loop through each cell. Inside this loop, we take the innerHTML of that cell (which is the data we want to search on). The next loop goes through and repeats over each part of the search string from q_ars. We check to see if our cell data contains our search string. If so we append the id of q_ars to a string. We also display the row (this is for when a user might type backspace and we need to reshow rows that were previously hidden.
Let me explain the logic behind this a bit. We want to make sure that each row matches every single part of our search string (that is broken into pieces). We will make a string called q_parts_found which will track which parts of the search string have been found. Note, that there may be doubles, but what we care about is that all of them were found. That’s what we do next:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | var foundCell = 1; for(var n = 0; n < q_ars.length; n++) { if(q_parts_found.indexOf(n)==-1) { foundCell = 0; } } if(foundCell == 1) { rows[i].style.display=''; } else { rows[i].style.display='none'; } } reAlternateLines(); |
Note, that we are still inside the row for-loop. We loop the number of items there are in q_ars. The if statement tells us whether or not every single part was found. If any piece was not found, foundCell is set to 0. Then, finally, we go through and show or hide cells based on whether they were found or not. Then, at the very, very end of the function we re-color our rows to look nice.
Let’s add one last piece of code that will call the headerClicked function on our first row. This code goes outside all funcitons. It is executed as it is read. This will basically default our table to it’s initial status: sorted on first column. It’s as simple as:
1 | headerClicked(0); |
And that, friends, is all there is to it. You can now go ahead and start integrating powerful, intuitive tables right into your web apps in almost now code. I hope you enjoyed this tutorial. If you have any comments, concerns, code changes or whatever, feel free to comment below. All this code in this tutorial is published as Public Domain (as I usually do). Please feel free to use the code however you like. I don’t require, but do request that you come back here and leave a comment pointing to your implementation of the code. I love to see how people are using the code.
If you liked this tutorial, be sure to check out my previous tutorials about CSS and Javascript. Also, be on the look out for new tutorials coming soon. You may want to subscribe to my feed. Thanks for reading.
15 Replies
Henry on 5/8/2007 at 00:39Awesome tutorial, Dustin! I’m really excited to have this at my disposal for an address book app I’m working on, it’ll be perfect.
Also — Dugg
Crea un livesearch al estilo iTunes | aNieto2K on 5/8/2007 at 03:43[...] podemos hacer exactamente lo mismo online, con este manual podremos conseguir, además de un aspecto similar al que iTunes nos ofrece, la [...]
tech.kedesfase.com » links for 2007-05-08 on 5/8/2007 at 06:25[...] Live Search As You Type and Live Sorting Tables With JS » Dustin Bachrach Blog Busqueda dinamica y reordenación de una tabla según se esta tecleando. Javascript (tags: programacion javascript) [...]
Hitesh Lad on 5/25/2007 at 11:45The script worked great. I am using it internally at my company for a webapp. I made a modification so that it would skip certain columns, just 2 changes. To use it, just add a class to the header of ’searchtable_nosearch’. Here is most of it:
var tbl = document.getElementById(’main_table’);
var headrow = tbl.tHead.rows[0].cells;
var rows = tbl.rows;
for(var i=1; i
Anonymous on 7/5/2007 at 16:36Very nice.
Two quick cosmetic issues. To emulate the blue glow when you click on the itunes box:
input{
border:1px solid;
border-left-color: #999;
border-right-color: #888;
border-top-color:#888;
border-bottom-color:#999;}input:focus{
border:1px solid #38C;}This works in ff, opera, no ie. I suppose if you wanted to, you could make it more glow like, rather than a blue box.
Also, the arrows should float right.
.arrow {float:right; padding:3px;}Just really small issues. But thanks for this. I really enjoy learning from you.
Henrik N on 7/23/2007 at 15:46I implemented a similar live search/filtering for a current project, but I was getting very poor performance with ~100+ items. I profiled it – most of the time was spent poking through the DOM looking for matches.
Instead, I had my server side code output a JavaScript representation of the data in addition to the HTML: something like
var items = [{searchspace: "The item title\nThe item description", id:123}, {searchspace: "Another item\nWith description.", id:456}];
My searchable items could be ….I can then just loop over this array and hide/show elements by id. I don’t have any benchmark numbers, but whereas looping over the DOM with 100+ items would freeze the browser for a couple of seconds, this is instantaneous.
Henrik N on 7/23/2007 at 15:48Swallowed my HTML. That should be:
My searchable items could be <tr id=”item_123″>…</tr>.
Bijo Jacob on 7/28/2007 at 10:28Please can u send the css and java script . i am not able to download.
And this is an amazing work.That you have done
Thanking You
onder on 8/6/2007 at 13:22Nice job, nice tutorial. Thanks alot…
links for 2007-08-15 | giancarlo.dimassa.net on 8/14/2007 at 18:51[...] Live Search As You Type and Live Sorting Tables With JS » Dustin Bachrach Blog (tags: javascript webdesign ajax css table tutorial blog html opensource programming sorting) Bookmark to: [...]
Andrew Barrows on 8/15/2007 at 16:41dude……………………………………………………………..I HAVE BEEN REDICULOUSLY BUSY WITH WORK AND THIS IS GOING TO SAVE ME SO MUCH TIME!!!! Im so excited to get this damn thing done quickly. To give you a background, Im a web developer for a huge investment firm and this is exactly what they need, well I can’t use php or mysql to run it with a database and I’m pressed for time but low and behold I found your site…..Dustin, I dont know you but I cannot thank you enough, your saving me a shitload of work, it looks great too! Anycase, best regards and drop me an email at pv2barrows@REMOVEMEIFYOURHUMANgmail.com I want to get the information to send a donation. Again I really appreciate it and Ill let you know how it go’s tommorrow!!!!!
Andrew Barrows
Director of Interactive Media @ ADIQUS Inc.
Co-Founder of Binary Catalyst Inc.
Lead Web Developer @ American Century Investments
pv2barrows@REMOVEMEIFYOURHUMANgmail.com
vernon on 12/2/2007 at 19:26I LOVE YOUR SCRIPT I WORKING ON A WEB SITE AND I SAW THIS AND IT’s WHAT WE NEED BUT I WANT TO KNOW IF IT CAN TAKE A TXT DOC RACE REPORT
LIKE BELOW WITH 150 LINES WITH OUT ME HAVING TO PUT AND BEFORE AND AFTER EACH LINE WE ARE PIGEON RACERS
THEY WANT THE RACES POST ON THE NET EACH WEEK WITH A SEARCHABLE REPORT
I HAVE BEEN PULLING MY LITTLE BIT OF HAIR OUT TRYING TO FIND A SCRIPT YOURS WORKS AND IT IS EASY TO MODIFIY AND I AM A BEGINNER.HERE IS A SAMPLE OF A RACE SHEET THANK YOU SEE I YOU CAN THINK OF SOME THING
POS NAME BAND NUMBER CLR X ARRIVAL MILES TOWIN YPM PT
1 BAMA LOFT/3 20650 AU 07 GHC BB C 12:31:07 303.950 00.00 1776.411 150
2 HEAVEN ANGE/3 20391 AU 07 GHC BC H 12:32:49 305.284 00.20 1774.383 149
3 JERRY BELLO/7 20380 AU 07 GHC BBAR C 12:32:55 305.249 00.30 1773.465 148
Binay on 2/27/2008 at 03:32Hi
Is there a way through which rest of the record displayed below the filtered record.For example:
search for “Boy” in your demo. it will remove rest of record from list.I want to sort in a way that row containing “Boy” come on top followed by rest of record.
Please help me!
Regards,
Binay K
Josh on 2/29/2008 at 19:55Great work! Thank you so much for this. I work at a fire department, and have become the go to guy for just about everything that has to do with technology, so we got new Panasonic Toughbooks to mount on our apparatus. One of the things the assistant chiefs wanted me to develop was something to quickly search for pre-fire plans to get rid of the paper copies on all of the vehicles. Well, thanks to you, my job became much easier. I have modified it to add more columns. My questions to you or any other javascript guru:
1. Is there a way that the code can be modified for search priority? Another words, when a building number, such as 102, is typed in the search box, is there a way to give priority to that number to show first? Example: when 102 is entered in the search box, I get building 1020 that shows first and 102 follows, I would like for 102 to show first. Now this is with out having to click on the top of the table to sort.
2. I think with this question, the first one would have to be implemented or the code modified even further. So here it is: Is there a way when someone types in such as 102 and hits enter, the file that is tied to it automatically comes up?
Once again, thank you very much for such a great code.
Josh
Nenad on 3/17/2008 at 07:13Hi, greetings from Serbia, brilliant piece of code, helped me a lot in the project I am currently working on. Thanks!!!



