Reputation: 43
http://www.bulgaria-web-developers.com/projects/javascript/selectbox/
Is there any "keypress" event, so that user can jump to particular option? like we have country field and user wants to move to UK, then he need to scroll for finding UK in the list.
Upvotes: 4
Views: 4077
Reputation: 1038
Here is working plugin with keypress. It dynamically searches sequence from n symbols that you type. If you wait 1,5 sec search will start again from 1-st symbol.
(function ($, undefined) {
var PROP_NAME = 'selectbox',
FALSE = false,
TRUE = true,
selectbox_keys = "",
selectbox_keys_timeout = 0;
/**
* Selectbox manager.
* Use the singleton instance of this class, $.selectbox, to interact with the select box.
* Settings for (groups of) select boxes are maintained in an instance object,
* allowing multiple different settings on the same page
*/
function Selectbox() {
this._state = [];
this._defaults = { // Global defaults for all the select box instances
classHolder: "sbHolder",
classHolderDisabled: "sbHolderDisabled",
classSelector: "sbSelector",
classOptions: "sbOptions",
classGroup: "sbGroup",
classSub: "sbSub",
classDisabled: "sbDisabled",
classToggleOpen: "sbToggleOpen",
classToggle: "sbToggle",
classFocus: "sbFocus",
speed: 200,
effect: "slide", // "slide" or "fade"
onChange: null, //Define a callback function when the selectbox is changed
onOpen: null, //Define a callback function when the selectbox is open
onClose: null //Define a callback function when the selectbox is closed
};
}
$.extend(Selectbox.prototype, {
/**
* Is the first field in a jQuery collection open as a selectbox
*
* @param {Object} target
* @return {Boolean}
*/
_isOpenSelectbox: function (target) {
if (!target) {
return FALSE;
}
var inst = this._getInst(target);
return inst.isOpen;
},
/**
* Is the first field in a jQuery collection disabled as a selectbox
*
* @param {HTMLElement} target
* @return {Boolean}
*/
_isDisabledSelectbox: function (target) {
if (!target) {
return FALSE;
}
var inst = this._getInst(target);
return inst.isDisabled;
},
/**
* Attach the select box to a jQuery selection.
*
* @param {HTMLElement} target
* @param {Object} settings
*/
_attachSelectbox: function (target, settings) {
if (this._getInst(target)) {
return FALSE;
}
var $target = $(target),
self = this,
inst = self._newInst($target),
sbHolder, sbSelector, sbToggle, sbOptions,
s = FALSE, optGroup = $target.find("optgroup"), opts = $target.find("option"), olen = opts.length;
$target.attr("sb", inst.uid);
$.extend(inst.settings, self._defaults, settings);
self._state[inst.uid] = FALSE;
$target.hide();
function closeOthers() {
var key, sel,
uid = this.attr("id").split("_")[1];
for (key in self._state) {
if (key !== uid) {
if (self._state.hasOwnProperty(key)) {
sel = $("select[sb='" + key + "']")[0];
if (sel) {
self._closeSelectbox(sel);
}
}
}
}
}
sbHolder = $("<div>", {
"id": "sbHolder_" + inst.uid,
"class": inst.settings.classHolder,
"tabindex": $target.attr("tabindex")
});
sbSelector = $("<a>", {
"id": "sbSelector_" + inst.uid,
"href": "#",
"class": inst.settings.classSelector,
"click": function (e) {
e.preventDefault();
closeOthers.apply($(this), []);
var uid = $(this).attr("id").split("_")[1];
if (self._state[uid]) {
self._closeSelectbox(target);
} else {
self._openSelectbox(target);
}
}
});
sbToggle = $("<a>", {
"id": "sbToggle_" + inst.uid,
"href": "#",
"class": inst.settings.classToggle,
"click": function (e) {
e.preventDefault();
closeOthers.apply($(this), []);
var uid = $(this).attr("id").split("_")[1];
if (self._state[uid]) {
self._closeSelectbox(target);
} else {
self._openSelectbox(target);
}
}
});
sbToggle.appendTo(sbHolder);
sbOptions = $("<ul>", {
"id": "sbOptions_" + inst.uid,
"class": inst.settings.classOptions,
"css": {
"display": "none"
}
});
$target.children().each(function(i) {
var that = $(this), li, config = {};
if (that.is("option")) {
getOptions(that);
} else if (that.is("optgroup")) {
li = $("<li>");
$("<span>", {
"text": that.attr("label")
}).addClass(inst.settings.classGroup).appendTo(li);
li.appendTo(sbOptions);
if (that.is(":disabled")) {
config.disabled = true;
}
config.sub = true;
getOptions(that.find("option"), config);
}
});
function getOptions () {
var sub = arguments[1] && arguments[1].sub ? true : false,
disabled = arguments[1] && arguments[1].disabled ? true : false;
arguments[0].each(function (i) {
var that = $(this),
li = $("<li>"),
child;
if (that.is(":selected")) {
sbSelector.text(that.text());
s = TRUE;
}
if (i === olen - 1) {
li.addClass("last");
}
if (!that.is(":disabled") && !disabled) {
child = $("<a>", {
"href": "#" + that.val(),
"rel": that.val()
}).text(that.text()).bind("click.sb", function (e) {
if (e && e.preventDefault) {
e.preventDefault();
}
var t = sbToggle,
$this = $(this),
uid = t.attr("id").split("_")[1];
self._changeSelectbox(target, $this.attr("rel"), $this.text());
self._closeSelectbox(target);
}).bind("mouseover.sb", function () {
var $this = $(this);
$this.parent().siblings().find("a").removeClass(inst.settings.classFocus);
$this.addClass(inst.settings.classFocus);
}).bind("mouseout.sb", function () {
$(this).removeClass(inst.settings.classFocus);
});
if (sub) {
child.addClass(inst.settings.classSub);
}
if (that.is(":selected")) {
child.addClass(inst.settings.classFocus);
}
child.appendTo(li);
} else {
child = $("<span>", {
"text": that.text()
}).addClass(inst.settings.classDisabled);
if (sub) {
child.addClass(inst.settings.classSub);
}
child.appendTo(li);
}
li.appendTo(sbOptions);
});
}
if (!s) {
sbSelector.text(opts.first().text());
}
$.data(target, PROP_NAME, inst);
//sbHolder.data("uid", inst.uid).bind("keydown.sb", function (e) {
sbHolder.data("uid", inst.uid).keypress(function(e) {
var key = e.charCode ? e.charCode : e.keyCode ? e.keyCode : 0,
$this = $(this),
uid = $this.data("uid"),
inst = $this.siblings("select[sb='"+uid+"']").data(PROP_NAME),
trgt = $this.siblings(["select[sb='", uid, "']"].join("")).get(0),
$f = $this.find("ul").find("a." + inst.settings.classFocus);
switch (key) {
case 37: //Arrow Left
case 38: //Arrow Up
if ($f.length > 0) {
var $next;
$("a", $this).removeClass(inst.settings.classFocus);
$next = $f.parent().prevAll("li:has(a)").eq(0).find("a");
if ($next.length > 0) {
$next.addClass(inst.settings.classFocus).focus();
$("#sbSelector_" + uid).text($next.text());
}
}
break;
case 39: //Arrow Right
case 40: //Arrow Down
var $next;
$("a", $this).removeClass(inst.settings.classFocus);
if ($f.length > 0) {
$next = $f.parent().nextAll("li:has(a)").eq(0).find("a");
} else {
$next = $this.find("ul").find("a").eq(0);
}
if ($next.length > 0) {
$next.addClass(inst.settings.classFocus).focus();
$("#sbSelector_" + uid).text($next.text());
}
break;
case 13: //Enter
if ($f.length > 0) {
self._changeSelectbox(trgt, $f.attr("rel"), $f.text());
}
self._closeSelectbox(trgt);
break;
case 9: //Tab
if (trgt) {
var inst = self._getInst(trgt);
if (inst/* && inst.isOpen*/) {
if ($f.length > 0) {
self._changeSelectbox(trgt, $f.attr("rel"), $f.text());
}
self._closeSelectbox(trgt);
}
}
var i = parseInt($this.attr("tabindex"), 10);
if (!e.shiftKey) {
i++;
} else {
i--;
}
$("*[tabindex='" + i + "']").focus();
break;
case 27: //Escape
self._closeSelectbox(trgt);
break;
default:
var theChar = String.fromCharCode(key);
var cur_check = selectbox_keys+theChar.toLowerCase();
$next = -1;
$this.find("ul").find("a").each(function() {
var text = jQuery(this).text().toLowerCase();
if (text.indexOf(cur_check) == 0) {
$next = jQuery(this);
selectbox_keys = cur_check;
return 0;
}
});
if ($next != -1) {
$("a", $this).removeClass(inst.settings.classFocus);
$next.addClass(inst.settings.classFocus).focus();
$("#sbSelector_" + uid).text($next.text());
}
clearTimeout(selectbox_keys_timeout);
var selectbox_keys_timeout = setTimeout(function() {
selectbox_keys = "";
},1500);
break;
}
e.stopPropagation();
return false;
}).delegate("a", "mouseover", function (e) {
$(this).addClass(inst.settings.classFocus);
}).delegate("a", "mouseout", function (e) {
$(this).removeClass(inst.settings.classFocus);
});
sbSelector.appendTo(sbHolder);
sbOptions.appendTo(sbHolder);
sbHolder.insertAfter($target);
$("html").on('mousedown', function(e) {
e.stopPropagation();
$("select").selectbox('close');
});
$([".", inst.settings.classHolder, ", .", inst.settings.classSelector].join("")).mousedown(function(e) {
e.stopPropagation();
});
},
/**
* Remove the selectbox functionality completely. This will return the element back to its pre-init state.
*
* @param {HTMLElement} target
*/
_detachSelectbox: function (target) {
var inst = this._getInst(target);
if (!inst) {
return FALSE;
}
$("#sbHolder_" + inst.uid).remove();
$.data(target, PROP_NAME, null);
$(target).show();
},
/**
* Change selected attribute of the selectbox.
*
* @param {HTMLElement} target
* @param {String} value
* @param {String} text
*/
_changeSelectbox: function (target, value, text) {
var onChange,
inst = this._getInst(target);
if (inst) {
onChange = this._get(inst, 'onChange');
$("#sbSelector_" + inst.uid).text(text);
}
value = value.replace(/\'/g, "\\'");
$(target).find("option[value='" + value + "']").attr("selected", TRUE);
if (inst && onChange) {
onChange.apply((inst.input ? inst.input[0] : null), [value, inst]);
} else if (inst && inst.input) {
inst.input.trigger('change');
}
},
/**
* Enable the selectbox.
*
* @param {HTMLElement} target
*/
_enableSelectbox: function (target) {
var inst = this._getInst(target);
if (!inst || !inst.isDisabled) {
return FALSE;
}
$("#sbHolder_" + inst.uid).removeClass(inst.settings.classHolderDisabled);
inst.isDisabled = FALSE;
$.data(target, PROP_NAME, inst);
},
/**
* Disable the selectbox.
*
* @param {HTMLElement} target
*/
_disableSelectbox: function (target) {
var inst = this._getInst(target);
if (!inst || inst.isDisabled) {
return FALSE;
}
$("#sbHolder_" + inst.uid).addClass(inst.settings.classHolderDisabled);
inst.isDisabled = TRUE;
$.data(target, PROP_NAME, inst);
},
/**
* Get or set any selectbox option. If no value is specified, will act as a getter.
*
* @param {HTMLElement} target
* @param {String} name
* @param {Object} value
*/
_optionSelectbox: function (target, name, value) {
var inst = this._getInst(target);
if (!inst) {
return FALSE;
}
//TODO check name
inst[name] = value;
$.data(target, PROP_NAME, inst);
},
/**
* Call up attached selectbox
*
* @param {HTMLElement} target
*/
_openSelectbox: function (target) {
var inst = this._getInst(target);
//if (!inst || this._state[inst.uid] || inst.isDisabled) {
if (!inst || inst.isOpen || inst.isDisabled) {
return;
}
var el = $("#sbOptions_" + inst.uid),
viewportHeight = parseInt($(window).height(), 10),
offset = $("#sbHolder_" + inst.uid).offset(),
scrollTop = $(window).scrollTop(),
height = el.prev().height(),
diff = viewportHeight - (offset.top - scrollTop) - height / 2,
onOpen = this._get(inst, 'onOpen');
el.css({
"top": height + "px",
"maxHeight": (diff - height) + "px"
});
inst.settings.effect === "fade" ? el.fadeIn(inst.settings.speed) : el.slideDown(inst.settings.speed);
$("#sbToggle_" + inst.uid).addClass(inst.settings.classToggleOpen);
this._state[inst.uid] = TRUE;
inst.isOpen = TRUE;
if (onOpen) {
onOpen.apply((inst.input ? inst.input[0] : null), [inst]);
}
$.data(target, PROP_NAME, inst);
},
/**
* Close opened selectbox
*
* @param {HTMLElement} target
*/
_closeSelectbox: function (target) {
var inst = this._getInst(target);
//if (!inst || !this._state[inst.uid]) {
if (!inst || !inst.isOpen) {
return;
}
var onClose = this._get(inst, 'onClose');
inst.settings.effect === "fade" ? $("#sbOptions_" + inst.uid).fadeOut(inst.settings.speed) : $("#sbOptions_" + inst.uid).slideUp(inst.settings.speed);
$("#sbToggle_" + inst.uid).removeClass(inst.settings.classToggleOpen);
this._state[inst.uid] = FALSE;
inst.isOpen = FALSE;
if (onClose) {
onClose.apply((inst.input ? inst.input[0] : null), [inst]);
}
$.data(target, PROP_NAME, inst);
},
/**
* Create a new instance object
*
* @param {HTMLElement} target
* @return {Object}
*/
_newInst: function(target) {
var id = target[0].id.replace(/([^A-Za-z0-9_-])/g, '\\\\$1');
return {
id: id,
input: target,
uid: Math.floor(Math.random() * 99999999),
isOpen: FALSE,
isDisabled: FALSE,
settings: {}
};
},
/**
* Retrieve the instance data for the target control.
*
* @param {HTMLElement} target
* @return {Object} - the associated instance data
* @throws error if a jQuery problem getting data
*/
_getInst: function(target) {
try {
return $.data(target, PROP_NAME);
}
catch (err) {
throw 'Missing instance data for this selectbox';
}
},
/**
* Get a setting value, defaulting if necessary
*
* @param {Object} inst
* @param {String} name
* @return {Mixed}
*/
_get: function(inst, name) {
return inst.settings[name] !== undefined ? inst.settings[name] : this._defaults[name];
}
});
/**
* Invoke the selectbox functionality.
*
* @param {Object|String} options
* @return {Object}
*/
$.fn.selectbox = function (options) {
var otherArgs = Array.prototype.slice.call(arguments, 1);
if (typeof options == 'string' && options == 'isDisabled') {
return $.selectbox['_' + options + 'Selectbox'].apply($.selectbox, [this[0]].concat(otherArgs));
}
if (options == 'option' && arguments.length == 2 && typeof arguments[1] == 'string') {
return $.selectbox['_' + options + 'Selectbox'].apply($.selectbox, [this[0]].concat(otherArgs));
}
return this.each(function() {
typeof options == 'string' ?
$.selectbox['_' + options + 'Selectbox'].apply($.selectbox, [this].concat(otherArgs)) :
$.selectbox._attachSelectbox(this, options);
});
};
$.selectbox = new Selectbox(); // singleton instance
$.selectbox.version = "0.2";
})(jQuery);
Upvotes: 1
Reputation: 1820
OK. Building on 'epocas' answer, I have integrated this code into the "jquery.selectbox-0.2.js" file, rather than extending it, as in 'epocas' solution...
The insert occurs here
sbHolder.data("uid", inst.uid).bind("keydown.sb", function (e) { ... });
And here is the full insert:
sbHolder.data("uid", inst.uid).bind("keydown.sb", function (e) {
var letterTranslator = function (match){
return lookupLetters[match] || match;
};
String.prototype.toLowerI = function (){
return this.toLowerCase().replace(patternLetters, letterTranslator);
};
var key = e.charCode ? e.charCode : e.keyCode ? e.keyCode : 0,
$this = $(this),
uid = $this.data("uid"),
inst = $this.siblings("select[sb='"+uid+"']").data(PROP_NAME),
trgt = $this.siblings(["select[sb='", uid, "']"].join("")).get(0),
$f = $this.find("ul").find("a." + inst.settings.classFocus),
sb = $this,
currentOptionText = $("#sbSelector_" + uid).text().toLowerI(),
keyText = String.fromCharCode(e.keyCode).toLowerI(),
selectOptions = $this.find("li a"),
firstIndexWithLetter = -1,
bSet = false,
moveToNext = false,
bFoundOption = false,
patternLetters = /[öäüÖÄÜáàâéèêúùûóòôÁÀÂÉÈÊÚÙÛÓÒÔßãÃõÕçÇñÑ]/g,
patternDateDmy = /^(?:\D+)?(\d{1,2})\.(\d{1,2})\.(\d{2,4})$/,
lookupLetters = {
"ä": "a", "ö": "o", "ü": "u",
"Ä": "A", "Ö": "O", "Ü": "U",
"á": "a", "à": "a", "â": "a",
"é": "e", "è": "e", "ê": "e",
"ú": "u", "ù": "u", "û": "u",
"ó": "o", "ò": "o", "ô": "o",
"Á": "A", "À": "A", "Â": "A",
"É": "E", "È": "E", "Ê": "E",
"Ú": "U", "Ù": "U", "Û": "U",
"Ó": "O", "Ò": "O", "Ô": "O",
"ß": "s", "ã": "a", "Ã": "A",
"õ": "o", "Õ": "O", "ç": "c",
"Ç": "C", "ñ": "n", "Ñ": "N"
};
if (currentOptionText.substring(0, 1) == keyText){
moveToNext = true;
}
$("a", $this).removeClass(inst.settings.classFocus);
for (var i = 0; i < selectOptions.length; i++){
var optionText = $(selectOptions[i]).text().toLowerI();
if (optionText.substring(0, 1) == keyText){
if (moveToNext){
if (firstIndexWithLetter == -1) {
firstIndexWithLetter = i;
}
if (bFoundOption){
$(selectOptions[i]).addClass(inst.settings.classFocus).focus();
$("#sbSelector_" + uid).text(optionText);
bSet = true;
break;
}
if (optionText == currentOptionText){
bFoundOption = true;
}
}
else{
$(selectOptions[i]).addClass(inst.settings.classFocus).focus();
$("#sbSelector_" + uid).text(optionText);
break;
}
}
}
switch (key) {
case 37: //Arrow Left
case 38: //Arrow Up
if ($f.length > 0) {
var $next;
$("a", $this).removeClass(inst.settings.classFocus);
$next = $f.parent().prevAll("li:has(a)").eq(0).find("a");
if ($next.length > 0) {
$next.addClass(inst.settings.classFocus).focus();
$("#sbSelector_" + uid).text($next.text());
}
}
break;
case 39: //Arrow Right
case 40: //Arrow Down
var $next;
$("a", $this).removeClass(inst.settings.classFocus);
if ($f.length > 0) {
$next = $f.parent().nextAll("li:has(a)").eq(0).find("a");
} else {
$next = $this.find("ul").find("a").eq(0);
}
if ($next.length > 0) {
$next.addClass(inst.settings.classFocus).focus();
$("#sbSelector_" + uid).text($next.text());
}
break;
case 13: //Enter
if ($f.length > 0) {
self._changeSelectbox(trgt, $f.attr("rel"), $f.text());
}
self._closeSelectbox(trgt);
break;
case 9: //Tab
if (trgt) {
var inst = self._getInst(trgt);
if (inst/* && inst.isOpen*/) {
if ($f.length > 0) {
self._changeSelectbox(trgt, $f.attr("rel"), $f.text());
}
self._closeSelectbox(trgt);
}
}
var i = parseInt($this.attr("tabindex"), 10);
if (!e.shiftKey) {
i++;
} else {
i--;
}
$("*[tabindex='" + i + "']").focus();
break;
case 27: //Escape
self._closeSelectbox(trgt);
break;
}
e.stopPropagation();
return false;
}).delegate("a", "mouseover", function (e) {
$(this).addClass(inst.settings.classFocus);
}).delegate("a", "mouseout", function (e) {
$(this).removeClass(inst.settings.classFocus);
});
Upvotes: 1
Reputation: 38
based on The Suresh Atta solution I've created the following jquery extension to implement a solution
/*** EXTEND SELECT BOX TO HAVE KeyUp feature *********/
jQuery.fn.KeyUpSelectBox = function (options)
{
var myHandler = this;
this.init = function ()
{
var sb = $(this).selectbox(options);
var sbSelector = sb.attr('sb');
$("#sbHolder_" + sbSelector).on('keyup', function (e)
{
var currentOptionText = $("#sbSelector_" + sbSelector).text().toLowerI();
var keyText = String.fromCharCode(e.keyCode).toLowerI();
var selectOptions = myHandler.find('option');
var firstIndexWithLetter = -1;
var bSet = false;
var moveToNext = false;
var bFoundOption = false;
if (currentOptionText.substring(0, 1) == keyText)
{
moveToNext = true;
}
for (var i = 0; i < selectOptions.length; i++)
{
var optionText = $(selectOptions[i]).text().toLowerI();
//console.log(optionText + ': ' + optionText.substring(0, 1));
if (optionText.substring(0, 1) == keyText)
{
if (moveToNext)
{
if (firstIndexWithLetter == -1) { firstIndexWithLetter = i; }
if (bFoundOption)
{
sb.selectbox("change", $(selectOptions[i]).attr('value'), $(selectOptions[i]).html());
sb.val($(selectOptions[i]).attr('value'));
bSet = true;
break;
}
if (optionText == currentOptionText)
{
bFoundOption = true;
}
}
else
{
sb.selectbox("change", $(selectOptions[i]).attr('value'), $(selectOptions[i]).html());
sb.val($(selectOptions[i]).attr('value'));
break;
}
}
}
if (moveToNext && !bSet && (firstIndexWithLetter != -1))
{
sb.selectbox("change", $(selectOptions[firstIndexWithLetter]).attr('value'), $(selectOptions[firstIndexWithLetter]).html());
sb.val($(selectOptions[firstIndexWithLetter]).attr('value'));
}
});
}
myHandler.init();
return myHandler;
}
This extension uses a method named toLowerI that is a string prototype:
var patternLetters = /[öäüÖÄÜáàâéèêúùûóòôÁÀÂÉÈÊÚÙÛÓÒÔßãÃõÕçÇñÑ]/g;
var patternDateDmy = /^(?:\D+)?(\d{1,2})\.(\d{1,2})\.(\d{2,4})$/;
var lookupLetters = {
"ä": "a", "ö": "o", "ü": "u",
"Ä": "A", "Ö": "O", "Ü": "U",
"á": "a", "à": "a", "â": "a",
"é": "e", "è": "e", "ê": "e",
"ú": "u", "ù": "u", "û": "u",
"ó": "o", "ò": "o", "ô": "o",
"Á": "A", "À": "A", "Â": "A",
"É": "E", "È": "E", "Ê": "E",
"Ú": "U", "Ù": "U", "Û": "U",
"Ó": "O", "Ò": "O", "Ô": "O",
"ß": "s", "ã": "a", "Ã": "A",
"õ": "o", "Õ": "O", "ç": "c",
"Ç": "C", "ñ": "n", "Ñ": "N"
};
var letterTranslator = function (match)
{
return lookupLetters[match] || match;
}
String.prototype.toLowerI = function ()
{
return this.toLowerCase().replace(patternLetters, letterTranslator);
};
To use it, instead of calling .selectbox() call .KeyUpSelectBox()
Upvotes: 1
Reputation: 122006
seems like buggy
give a try with
$(document).keypress(function(e){
console.log(String.fromCharCode(e.keyCode));
});
Upvotes: 1
Reputation: 3507
Well you can make your own.
.keyup()
event-listener.trigger('click')
This should work (in theory).
Upvotes: 0