1

I have a search box that hides all lines in a list that don't contain the entered text.

This worked great until the list became 10,000 lines long. One keystroke is fine but if the user types a several letter word, the function is iterated for each keypress.

What I want to do is to abandon any previous execution of the function if a new key is pressed.

The function is very simple, as follows:

    $("#search").keyup(function(e) {
       goSearch();
    });

function goSearch()
{
var searchString = $("#search").val().toLowerCase();
$(".lplist").each(function(index, element) {
    var row = "#row-" + element.id.substr(5);
        if ($(element).text().toLowerCase().indexOf(searchString,0) != -1)
    $(row).show();
    else
    $(row).hide();
});
}

Thanks

Galadai
  • 77
  • 6

3 Answers3

0

Introduce a counter var keypressCount that is being increased by your keypress event handler. at the start of goSearch() write its value into a buffer. Then at each run of your $(".lplist").each() you ask if the current keypressCount is the same as the buffered one; if not, you return. I would suggest you use a for() though since it is easier to break; than $.each().

Update: You will have to make sure that there is time for new keypress events to be fired/received, so wrap the anonymous function of your $.each() inside a timeout.

Reference: http://www.garrickcheung.com/javascript/what-i-learned-about-multi-threading-in-javascript/

Gung Foo
  • 13,392
  • 5
  • 31
  • 39
  • Smart thought, but it will not work, as JavaScript is single threaded. When `goSearch()` is called, nothing else will be executed until it exits. So, the `keypressCount` variable will not be updated until `goSearch()` finishes its execution, even if the `keypress` event happened during `goSearch()` execution. – German Latorre Mar 12 '13 at 08:47
  • it will be smarter if `keypressCount` is a global variable. read up on scopes if you must. :) – Gung Foo Mar 12 '13 at 08:53
  • you can manipulate a global variable (declared outside of a function) from inside any function and it will keep it's value even after the function returns. – Gung Foo Mar 12 '13 at 09:04
  • Sure, but variable will not be updated until function finishes its execution, as JavaScript is single-threaded. That is why your solution does not work. – German Latorre Mar 12 '13 at 09:06
  • Good, I see you noticed that and updated your answer accordingly, :-) – German Latorre Mar 12 '13 at 09:08
0

You can't directly. Javascript is not multi-threaded so your function will run and block any key-presses until it is done.

The way this is made tolerable from a user-experience point of view is to not trigger a function immediately on a key event, but to wait a short period of time and then fire the event.

While the user is typing, the timeout function will continually be set and reset and so the gosearch function won't be called, and so the user won't have their typing interrupted.

When the user pauses typing, the timeout will countdown to zero and call the search function, which will run and block typing until it completes. But that's okay (so long as it completes within a second or so) as the user is probably not currently trying to type.

You can also do what you actually asked by breaking up your gosearch function into chunks, where each call to the function: * Reads a counter of the number of lines processed so far, and then processes another 500 lines and increments the counter. * Calls another gosearch using setTimeout with a value of zero for the time. This yields events to other 'threads', and allows for fast changing of search terms.

var goSearchTimeout = null;
var linesSearched = 0;

function keySearch(e){
    if(goSearchTimeout != null){
        clearTimeout(goSearchTimeout);
        linesSearched = 0;
    }
    goSearchTimeout = setTimeout(goSearch, 500);
}

$("#search").keyup(keySearch);

function highLight(index, element) {

    if(index >= linesSearched){
        var row = "#row-" + element.id.substr(5);
        if ($(element).text().toLowerCase().indexOf(searchString,0) != -1){
        $(row).show();
    else{
        $(row).hide();
    }
    if(index > linesSearched + 500){
        linesSearched = index;
        goSearchTimeout = setTimeout(goSearch);
        return;
    }
}

function goSearch(){
    goSearchTimeout = null;
    var searchString = $("#search").val().toLowerCase();
    $(".lplist").each(highLight);
}

If you're going to use timeout callbacks like this, I'd strongly recommend wrapping your code up into jQuery widgets, so that you can use variables on the object to store the variables goSearchTimeout etc rather than having them float around as global variables.

Danack
  • 24,939
  • 16
  • 90
  • 122
  • javascript IS multithreaded, that is why you got an event system – Gung Foo Mar 12 '13 at 08:58
  • No it's not http://stackoverflow.com/questions/1663125/is-javascript-multithreaded Which is why you shouldn't write long running functions. – Danack Mar 12 '13 at 08:59
  • point taken! i found this: http://www.garrickcheung.com/javascript/what-i-learned-about-multi-threading-in-javascript/ – Gung Foo Mar 12 '13 at 09:03
  • Btw even in environments that are multi-threaded, all GUI events tend to go through a single context are in effect single-threaded. This is true for Windows and Java Swing applications at least, where poorly written applications can block the GUI from responding by not returning quickly from GUI event notification calls. – Danack Mar 12 '13 at 09:11
0

You can use a global variable to save search string and stop execution when search string changes.

IMPORTANT: You must set a timeout in each iteration so that function execution is paused and global variables are updated, as JavaScript is single-threaded.

Your code would look like this:

var searchString;

$("#search").keyup(function(e) {
    // Update search string
    searchString = $("#search").val().toLowerCase();

    // Get items to be searched
    var items = $(".lplist");

    // Start searching!
    goSearch(items, searchString, 0);
});

function goSearch(items, filter, iterator)
{
    // Exit if search changed
    if (searchString != filter) {
        return;
    }

    // Exit if there are no items left
    if (iterator >= items.length) {
        return;
    }

    // Main logic goes here
    var element = items[iterator];
    var row = "#row-" + element.id.substr(5);
    if ($(element).text().toLowerCase().indexOf(filter, 0) != -1)
        $(row).show();
    else
        $(row).hide();

    // Schedule next iteration in 5 ms (tune time for better performance)
    setTimeout(function() {
        goSearch(items, filter, iterator + 1);
    }, 5);
}
German Latorre
  • 10,058
  • 14
  • 48
  • 59