Thiago Belem
Thiago Belem

Reputation: 7832

Capture keypress to filter elements

I'm creating a <select> replacement using jQuery to replace it with divs and links.

Now I want to filter it when I start to type something with the new select open.

Like Google Translate does on the language selector.

Do you have any advice how do i proceed?

I started something with:

$(document).bind('keypress', function(event) {
   //...
});

But I capture only single keys, not the whole typed string.


Important:

Upvotes: 5

Views: 9292

Answers (9)

roberkules
roberkules

Reputation: 6605

If you don't want to use a hidden dropdown (which i still strongly advise) i suggest following:

Check out the full working demo with a simple language dropdown at http://jsfiddle.net/aWE8b/

The demo contains:

  • Custom Dropdown
  • Supported keys:
    • Backspace
    • Enter
    • Escape
    • Left, Up
    • Right, Down
  • 2 Modes:
    • Filter: remove unmatched items
    • Select: highlight first matched item

Upvotes: 0

quasistoic
quasistoic

Reputation: 4687

How to do what you asked

Each time the keypress event is triggered on the document, keep a record of the character that was typed, either in a variable (accessible from the global scope or in a closure) or in an element on the page (you may choose to use display: hidden; if you don't want this to be visible to the user).

Then, do a pass over the elements in your dropdown and hide those that don't contain/start with the string you've built from individual keystrokes.

What's actually recommended

Use an input element to contain the user's typed keystrokes, and let the user see the element.

Why?

Because it's an interaction behavior users are already familiar with. If you don't use an input element, you open several new questions that are already solved with an input element:

  1. How does the user know that they can type to filter the list? Input elements are a clear declaration to the user: "You should type here!"
  2. How does the user know what string is going to be filtered on when they press more than one key? When I quickly type 'hu' in that Google Translate interface, 'Hungarian' is selected, as I would expect. However, what happens when I type it slowly? If it's slow enough, 'Hatian Creole' is selected, and then 'Ukranian'.
  3. What if I mistype something and want to start over? Is there a button to press to clear it out? How long do I need to wait for the filter to clear on its own?

You could certainly create a new interface element that solves all of these problems, and if that's your goal, go for it, but there are preexisting standards for these sorts of things.

For one, that previously-mentioned autocomplete widget is really handy, and really flexible.

Upvotes: 1

actionshrimp
actionshrimp

Reputation: 5229

What's the reason you don't want to use an input, is it just so if JS isn't available you can graceful fall back to the select? If so this might do what you want, I've used the select as a basis and taken the values from there although I have ended up putting an input in there.

Following on from Neal's answer, I think autocomplete is fairly similar to what you need, but if you want to display the results differently I've got a second approach in the same example that displays the filtered results.

Full example version here: http://jsfiddle.net/R7APm/9/

Here's the JS from it:

$opts = $('option', '#myselect');

//Grab the text from the select (could use option values instead with .val()
var values = [];
$.each($opts, function(i, opt) {
    values.push($(opt).text());
});

$div = $('<div/>');

//Autocomplete version
$input = $('<input/>').autocomplete({
    source: values
});

//Filter in div version
$wordlist = $('<div id="wordlist"/>');
$wordlist.text(values.join(", "));

$input.keyup(function() {
    matches = $.grep(values, function(el, i) {
        if (el.toLowerCase().indexOf($input.val().toLowerCase()) >= 0) {
            return true;
        } else {
            return false;
        }
    });

    $wordlist.text(matches.join(", "));
});

//Add new elements to wrapper div
$div.append($input);
$div.append($wordlist);

//Replace select with wrapper div
$('#replaceme').replaceWith($div);
$input.focus();

Upvotes: 0

stecb
stecb

Reputation: 14746

You could achieve this by 'listening' about what is pressed on the window, and then detecting the particular letter/string pressed, search through items list and if you find it change its css properties or add a new 'selected' class i.e. (demo => http://jsfiddle.net/steweb/mC6tn/ ..try pressing whatever :) and added after something found press left or right btns, or enter) :

JS: (supposing that each element you want to find something into and select it has class 'elem')

var whatYouAreSearching = $('<div class="searching-string"></div>'); //just to see what string you're typing
$(document.body).append(whatYouAreSearching);

function search(what){
    what = what.toLowerCase();
    $('.elem').removeClass('selected'); //reset everything
    $.each($('.elem'),function(index,el){
        if($(el).html().toLowerCase().indexOf(what) > -1){
            $(el).addClass('selected');
            return false; //found, 'break' the each loop
        }
    });
}

var letterPressed = [];
var timeOutResetLetters = null;

$(window).keypress(function(e) {
    clearTimeout(timeOutResetLetters); //clear timeout, important!
    timeOutResetLetters = setTimeout(function(){ //if 500 ms of inactivity, reset array of letters pressed and searching string
        letterPressed = []; 
        whatYouAreSearching.html('');
    },500);
    letterPressed.push(String.fromCharCode(e.keyCode)); //look at the comment, thanks Niclas Sahlin 
    whatYouAreSearching.html(letterPressed.join('')); //show string
    search(letterPressed.join('')); //and search string by 'joining' characters array
});

EDIT added left/right/enter keys handling

$(window).keydown(function(e){ //left right handling
    var currSelected = $('.elem.selected');

    if(e.keyCode == "37"){ //left, select prev
        if(currSelected.prev() && currSelected.prev().hasClass('elem')){
            currSelected.prev().addClass('selected');
            currSelected.removeClass('selected');   
        }
    }else if(e.keyCode == "39"){ //right, select next
        if(currSelected.next() && currSelected.next().hasClass('elem')){
            currSelected.next().addClass('selected');
            currSelected.removeClass('selected');   
        }
    }else if(e.keyCode == "13"){ //enter
       $('.entered').remove();
       $(document.body).append(currSelected.clone().addClass('entered'));
    }
});

Upvotes: 3

Joshua - Pendo
Joshua - Pendo

Reputation: 4371

What I've tried to do is:

1) get the keypressed (only 0-9, a-z and space) and save this inside a div 2) created a LI list (you can use a div with a-elements, that's fine aswell) 3) find LI-items with the text that's being saved 4) add up/right down/left arrows to get next/previous item in the list

For now it keeps all items in the list, you can hide them aswell if you want. It's just an illustration.

// EDIT:

Here's a starters setup: http://www.pendemo.nl/keyinputtest.html In firefox the body doesn't get automatically selected which causes that you need to click the body/webpage again before a second input is being detected. In Chrome it detects keys pressed after eachother.

// EDIT2:

Working in firefox. Text is now saved inside a div (you can type a string you want and see it being placed in the body).

// EDIT3:

Added an UL list with some items. typing some text will filter this list with items matching the typed string.

// EDIT4:

Only a-z and space are being used to search. Still looking for a way on how to find the backspace key. Since Chrome is adding 'history.go-1' by default. Firefox however does notice the backspace key. event.preventDefault() is not changing anything in Chrome.

// EDIT5:

Added up+right arrow to select next and left+down array to select previous item.

// EDIT6:

Upon using the enter key, the current value is alert. Ofcourse the scripts stops after the alert. But as you can see the value is available for you.

Here's the code I came up with so far:

var typed_string = '';

$(document).ready(function() { $('#typedtext').select(); });

$(document).bind('keypress', function(event) {
    event.preventDefault();

    if((event.which > 47 && event.which < 58) || (event.which > 96 && event.which < 123) || event.which == 32) {
        typed_string = typed_string + String.fromCharCode(event.which);
        $('#typedtext').html(typed_string);

        $('#testlist li').css('background-color', '#ffffff');
        $('#testlist li:contains('+typed_string+')').css('background-color', 'green').first().addClass('selected').css('background-color', 'red');

        $('#typedtext').select();
    }

    if(event.which == 13) {
        alert('Selected item: ' + $('.selected').html());
        $('#typedtext').select();
    }
});

$(document).keyup(function(event) {
    event.preventDefault();
    if(event.which == 38 || event.which == 37) {
        $('#testlist li').css('background-color', '#ffffff');
        $('#testlist li:contains('+typed_string+')').css('background-color', 'green');
        $('.selected').removeClass('selected').prev().css('background-color', 'red').addClass('selected');
    }

    if(event.which == 39 || event.which == 40) {
        $('#testlist li').css('background-color', '#ffffff');
        $('#testlist li:contains('+typed_string+')').css('background-color', 'green');
        $('.selected').removeClass('selected').next().css('background-color', 'red').addClass('selected');
    }
});

function clearInput() {
    typed_string = '';  
}

And I'm using this HTML:

Press any key to save text.<br /><br />
<div id="typedtext" style="border: 1px solid #09F; padding: 5px;"></div>

<br /><br />
Let's test to filter some items from the li. (use up/down or left/right keys to select next item)
<ul id="testlist">
    <li>item</li>
    <li>test</li>
    <li>more test</li>
    <li>another item</li>
    <li>test item</li>
    <li>more item</li>
    <li>foo</li>
    <li>foo bar</li>
    <li>bar</li>
    <li>more foo</li>
</ul>

I think it's easy enough for you to change the HTML code to fit your needs.

Upvotes: 0

netbrain
netbrain

Reputation: 9304

I created my own version of a <select> with <div> tags, check it out here.. If i understand you correctly, this is what you want..

http://jsfiddle.net/myTPC/

It also supports backspace (atleast in firefox).

Upvotes: 0

Naor
Naor

Reputation: 24063

What about this:

var word='';
$(document).bind('keypress', function(event) {
    //UPDATE word ACCORDING TO THE CHAR, LIKE:
    if (event.charCode >=48 && event.charCode <=57) {
        ....    ....
        ....    ....
    }
    //OR
    switch(event.charCode) {
        ....    ....
        ....    ....
    }

});

Upvotes: 0

locrizak
locrizak

Reputation: 12281

you can try something like this:

var _interval;

$("#textarea/field_id").bind('keypress', function(event) {
   clearTimeout(_interval);
   _interval = setTimeout(onTimeout, 1000);
});

function onTimeout(){
   var words = $("#textarea/field_id").val().split(' ');//do what you want with the string in the field
}

This will capture the entire string onTimeout. Play with the timeout a little bit to get it nice and clean!

Upvotes: 0

Naftali
Naftali

Reputation: 146300

You can try using jQuery UI's autocomplete

Upvotes: 3

Related Questions