jQuery Plugin: Custom Styled Select Input w/Keyboard Interaction

I have been working on a clients project recently and really didn’t like the way the standard selects looked on the page and as we all know they are impossible to style consistently across all browsers. Being a jQuery fan I knew that there must have been an existing plugin to help…but alas there were many plugins that kind of worked, some of them didn’t include a full feature set, some didn’t work in all browsers and some didn’t degrade back to regular selects when needed (e.g. GoogleBot, Screen Readers, no JavaScript, etc).

I did however manage to find a reasonable good custom select over at http://www.adelaidewebdesigns.com/2008/08/01/adelaide-web-designs-releases-customselect-with-icons (even happened to be a fellow aussie!). It wasn’t perfect and it wouldn’t allow for multiple selects on the one page and the icons weren’t really what I needed. Reading through the comments I found that someone else (http://www.ildavid.com/dblog/selectcustomizer/) had made some modifications to allow for multiple selects on the one page. This was great I was getting closer to the solution that I needed, but there were still some issues like keyboard navigation and the ability to click anywhere in the window to close the select just like a regular select.

So I set out to further modify the code and put in some of my own requirements and then re-share it, I have managed to get the drop-down to close when you click outside it, I have also introduced keyboard navigation (up, down and enter). To get the keyboard navigation working I needed to use the scrollTo plugin to allow smooth scrolling up and down in the select.

There are still a few minor bugs that need to be fixed but it works enough for me, one bug is that in IE you need to click in the drop-down area if you don’t want the page to jump when using the keys.

I would also like to get the menu to activate when a user is tabbing around the site, but that’s a job for another rainy day!


$.fn.SelectCustomizer = function(){
    // Select Customizer jQuery plug-in
	// based on customselect by Ace Web Design http://www.adelaidewebdesigns.com/2008/08/01/adelaide-web-designs-releases-customselect-with-icons/
	// modified by David Vian http://www.ildavid.com/dblog
	// and then modified AGAIN be Dean Collins http://www.dblog.com.au
    return this.each(function(){
        var obj = $(this);
		var name = obj.attr('id');
		var id_slc_options = name+'_options';
		var id_icn_select = name+'_iconselect';
		var id_holder = name+'_holder';
		var custom_select = name+'_customselect';
        obj.after("
"); obj.find('option').each(function(i){ $("#"+id_slc_options).append("
" + $(this).html() + "
"); }); obj.before("
" + this.title + "
").remove(); $("#"+id_icn_select).click(function(a){ if($("#"+id_holder).css('display') == 'none') { $("#"+id_holder).fadeIn(200); $("#"+id_holder).focus(); a.stopPropagation(); $(document).keypress(function(e) { if(!e) var e = window.event; e.cancelBubble = true; e.returnValue = false; if (e.stopPropagation) { e.stopPropagation(); e.preventDefault(); } }); $(document).keyup(function(e) { if(e.which == 40) { var lastSelected = $("#"+id_holder+" .selectedclass"); if(lastSelected.size() == 0) { var nextSelected = $("#"+id_slc_options+" div:first:"); } else { var nextSelected = lastSelected.next(".selectitems"); } if(nextSelected.size() == 1) { lastSelected.removeClass("selectedclass"); nextSelected.addClass("selectedclass"); $("#"+custom_select).val(nextSelected.title); $("#"+id_icn_select).html(nextSelected.html()); var rowOffset = (nextSelected.offset().top - $("#"+id_holder).offset().top); if(rowOffset > 130) { $("#"+id_slc_options).scrollTo(($("#"+id_slc_options).scrollTop() + 27) + "px"); } } } else if(e.which == 38) { var lastSelected = $("#"+id_holder+" .selectedclass"); var nextSelected = lastSelected.prev(".selectitems"); if(nextSelected.size() == 1) { lastSelected.removeClass("selectedclass"); nextSelected.addClass("selectedclass"); $("#"+custom_select).val(nextSelected.title); $("#"+id_icn_select).html(nextSelected.html()); var rowOffset = (nextSelected.offset().top - $("#"+id_holder).offset().top); if(rowOffset > 0) { $("#"+id_slc_options).scrollTo(($("#"+id_slc_options).scrollTop() - 27) + "px"); } } } else if(e.which == 13) { $("#"+id_holder).fadeOut(250); $(document).unbind('keyup'); $(document).unbind('keypress'); $('body').unbind('click'); } }); $('body').click(function(){ $("#"+id_holder).fadeOut(200); $('body').unbind('click'); $(document).unbind('keyup'); $(document).unbind('keypress'); }); } else { $("#"+id_holder).fadeOut(200); $('body').unbind('click'); $(document).unbind('keyup'); $(document).unbind('keypress'); } }); $("#"+id_holder).append($("#"+id_slc_options)[0]); $("#"+id_holder).append("
"); $("#"+id_slc_options+" > div:last").addClass("last"); $("#"+id_holder+ " .selectitems").mouseover(function(){ $(this).addClass("hoverclass"); }); $("#"+id_holder+" .selectitems").mouseout(function(){ $(this).removeClass("hoverclass"); }); $("#"+id_holder+" .selectitems").click(function(){ $("#"+id_holder+" .selectedclass").removeClass("selectedclass"); $(this).addClass("selectedclass"); var thisselection = $(this).html(); $("#"+custom_select).val(this.title); $("#"+id_icn_select).html(thisselection); $("#"+id_holder).fadeOut(250); $(document).unbind('keyup'); $(document).unbind('keypress'); $('body').unbind('click'); }); }); }

You can see a working demo here

You can also download a full working example here

0
Share

10 Comments

  1. an

    hey! hello from Russia!
    come from search Google to you -)))

    thx for work! but…

    how about onchange in select?
    and how about

    best regards, sergey

  2. an

    and option selected… (select active option)

  3. Peter simmons

    I made some minor modifications so that it would work for me.

    1. Wrapped the plugin in an enclosure to make it compatible with using other libraries such as prototype.js mootools.js
    2. Added a hide any selectwrapper lists on line 22 before showing the one that was clicked. This fixed a bug which you can see on the demo if you click the second drop down and then click the first drop down (without clicking anywhere else) the second drop down still shows and it shows above the first one.

    Here is the updated code

    (function($){ // hide the jquery namespace and make it compatible with other libs
    $.fn.SelectCustomizer = function(){
    // Select Customizer jQuery plug-in
    // based on customselect by Ace Web Design http://www.adelaidewebdesigns.com/2008/08/01/adelaide-web-designs-releases-customselect-with-icons/
    // modified by David Vian http://www.ildavid.com/dblog
    // and then modified AGAIN be Dean Collins http://www.dblog.com.au
    // and then by Pete Simmons for BrightTALK for http://www.investment-intelligence.co.uk/
    return this.each(function(){
    var obj = $(this);
    var name = obj.attr(‘id’);
    var id_slc_options = name + ‘_options’;
    var id_icn_select = name + ‘_iconselect’;
    var id_holder = name + ‘_holder’;
    var custom_select = name + ‘_customselect’;
    obj.after(” “);
    obj.find(‘option’).each(function(i){
    $(“#” + id_slc_options).append(“” + $(this).html() + “”);
    });
    obj.before(“” + this.title + ” “).remove();
    $(“#” + id_icn_select).click(function(a){
    // hide any other custom selects’ list
    $(‘.selectwrapper’).hide();
    if ($(“#” + id_holder).css(‘display’) == ‘none’) {
    $(“#” + id_holder).fadeIn(200);
    $(“#” + id_holder).focus();
    a.stopPropagation();
    $(document).keypress(function(e){
    if (!e)
    var e = window.event;
    e.cancelBubble = true;
    e.returnValue = false;
    if (e.stopPropagation) {
    e.stopPropagation();
    e.preventDefault();
    }
    });
    $(document).keyup(function(e){

    if (e.which == 40) {
    var lastSelected = $(“#” + id_holder + ” .selectedclass”);
    if (lastSelected.size() == 0) {
    var nextSelected = $(“#” + id_slc_options + ” div:first:”);
    }
    else {
    var nextSelected = lastSelected.next(“.selectitems”);
    }
    if (nextSelected.size() == 1) {
    lastSelected.removeClass(“selectedclass”);
    nextSelected.addClass(“selectedclass”);
    $(“#” + custom_select).val(nextSelected.title);
    $(“#” + id_icn_select).html(nextSelected.html());
    var rowOffset = (nextSelected.offset().top – $(“#” + id_holder).offset().top);
    if (rowOffset > 130) {
    $(“#” + id_slc_options).scrollTo(($(“#” + id_slc_options).scrollTop() + 27) + “px”);
    }
    }

    }
    else if (e.which == 38) {
    var lastSelected = $(“#” + id_holder + ” .selectedclass”);
    var nextSelected = lastSelected.prev(“.selectitems”);
    if (nextSelected.size() == 1) {
    lastSelected.removeClass(“selectedclass”);
    nextSelected.addClass(“selectedclass”);
    $(“#” + custom_select).val(nextSelected.title);
    $(“#” + id_icn_select).html(nextSelected.html());
    var rowOffset = (nextSelected.offset().top – $(“#” + id_holder).offset().top);
    if (rowOffset > 0) {
    $(“#” + id_slc_options).scrollTo(($(“#” + id_slc_options).scrollTop() – 27) + “px”);
    }
    }
    }
    else if (e.which == 13) {
    $(“#” + id_holder).fadeOut(250);
    $(document).unbind(‘keyup’);
    $(document).unbind(‘keypress’);
    $(‘body’).unbind(‘click’);
    }

    });
    $(‘body’).click(function(){
    $(“#” + id_holder).fadeOut(200);
    $(‘body’).unbind(‘click’);
    $(document).unbind(‘keyup’);
    $(document).unbind(‘keypress’);
    });
    }
    else {
    $(“#” + id_holder).fadeOut(200);
    $(‘body’).unbind(‘click’);
    $(document).unbind(‘keyup’);
    $(document).unbind(‘keypress’);
    }

    });
    $(“#” + id_holder).append($(“#” + id_slc_options)[0]);
    $(“#” + id_holder).append(“”);
    $(“#” + id_slc_options + ” > div:last”).addClass(“last”);
    $(“#” + id_holder + ” .selectitems”).mouseover(function(){
    $(this).addClass(“hoverclass”);
    });
    $(“#” + id_holder + ” .selectitems”).mouseout(function(){
    $(this).removeClass(“hoverclass”);
    });
    $(“#” + id_holder + ” .selectitems”).click(function(){
    $(“#” + id_holder + ” .selectedclass”).removeClass(“selectedclass”);
    $(this).addClass(“selectedclass”);
    var thisselection = $(this).html();
    $(“#” + custom_select).val(this.title);
    $(“#” + id_icn_select).html(thisselection);
    $(“#” + id_holder).fadeOut(250);
    $(document).unbind(‘keyup’);
    $(document).unbind(‘keypress’);
    $(‘body’).unbind(‘click’);
    });
    });
    }

    })(jQuery); // pass jQuery in as the value for the $ parameter in the closure function

  4. Peter simmons

    In answer to the an from Russia. The value get’s put into an input tag. So you should be able to attach an onchange event to this to get the desired effect or get the value from here for working out what the selected option is.

  5. Sergiu

    Hello, thank you for your plugin, but i have a problem with it, the form never sends data in FF, in IE it works…ca you give me a solution please..i realy cannot find any solution for it.

  6. Sergiu

    and the onChange function does’nt work too( i wanted to manipulate with data from value using java script but it doesn’t work too((((

  7. brian

    great work! I especially love the fact that when clicking off of the menu it now closes up — a behavior that mimics real form dropdown select menus. Any insight on how you accomplished that?
    thanks again (and to Paul and David before you).

  8. Valoo

    Hi.
    Nice script :) But there is some problem with IE6. The page is blinking, when select is “redrawing”. Maybe someone can help me with it? Refresh demo page in IE6 and you’ll see.

    P.S.: Sorry for my English :)

  9. Martin

    Very nice ; Good looking, Validates W3C and relying on real markups but I’m unable to acces it with Tab-Navigation.
    Tab-navigation is the only features missing in your plugin.

    … Sad! :(

  10. Scorpio

    How to get the selected value as alert from above example.

Leave a Reply