From e333a039dae27e80b62a08f74152b924ea57259b Mon Sep 17 00:00:00 2001 From: Christoph Stickel Date: Wed, 8 Feb 2012 18:06:07 +0100 Subject: [PATCH 01/23] * Change event should not be triggered on init. This behavior differs from normal select boxes. --- js/jquery.sparkbox-select.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/jquery.sparkbox-select.js b/js/jquery.sparkbox-select.js index abc6d15..2b51261 100644 --- a/js/jquery.sparkbox-select.js +++ b/js/jquery.sparkbox-select.js @@ -150,7 +150,7 @@ $self.attr('tabindex', -1) .wrap('
') .after('') - .bind('change', updateSelect); + .bind('change sb-sync', updateSelect); if (iOS || android) { @@ -179,7 +179,7 @@ } } - $self.trigger('change'); + $self.trigger('sb-sync'); selectboxCounter++; }); From 1b61271393f5f98be457517d72a2b27585bc6bf3 Mon Sep 17 00:00:00 2001 From: mixer2 Date: Thu, 9 Feb 2012 01:03:44 +0100 Subject: [PATCH 02/23] * Keyboard selection bugfix --- js/jquery.sparkbox-select.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/jquery.sparkbox-select.js b/js/jquery.sparkbox-select.js index 2b51261..9720c25 100644 --- a/js/jquery.sparkbox-select.js +++ b/js/jquery.sparkbox-select.js @@ -96,7 +96,7 @@ } if (e.keyCode == RETURN || e.keyCode == SPACE) { - $current.trigger('click'); + $current.children('a').trigger('click'); return; } From 5181a20a78136a494f29d2f1568476d1a3070511 Mon Sep 17 00:00:00 2001 From: mixer2 Date: Thu, 9 Feb 2012 03:38:08 +0100 Subject: [PATCH 03/23] * removed a lot of bugs related to keyboard selection * changed keyboard selection to behave more like native select boxes --- js/jquery.sparkbox-select.js | 80 ++++++++++++++++++------------------ 1 file changed, 41 insertions(+), 39 deletions(-) diff --git a/js/jquery.sparkbox-select.js b/js/jquery.sparkbox-select.js index 9720c25..e20972e 100644 --- a/js/jquery.sparkbox-select.js +++ b/js/jquery.sparkbox-select.js @@ -15,7 +15,7 @@ var $this = $(this), $dropdown = $('.sb-dropdown[data-id=' + $this.parent().data('id') + ']'), $sbSelect = $this.siblings('.sb-select'); - + if (this.selectedIndex != -1) { $sbSelect.val(this[this.selectedIndex].innerHTML); @@ -70,61 +70,63 @@ $('.sb-dropdown').fadeOut('fast'); } }; - + // Manage keypress to replicate browser functionality var selectKeypress = function(e) { var $this = $(this), $current = $('.sb-dropdown[data-id=' + $this.data('id') + ']').find('.selected'); - - - // if ($('.sb-dropdown[data-id=' + $this.data('id') + ']').find('.selected') || $('.sb-dropdown[data-id=' + $this.data('id') + ']'); - // $this.siblings('ul').find('.selected'); - - if ((e.keyCode == UP || e.keyCode == DOWN || e.keyCode == SPACE) && $current.is(':hidden')) { + + //select in dropdown if it's open... else directly change select value to target element + function softSelect(el) { + if($current.is(':hidden')) $(el).children('a').trigger('click'); + else { + $current.removeClass('selected'); + $(el).addClass('selected'); + } + } + + if (e.keyCode >= 48 && e.keyCode <= 90) { + matchString += String.fromCharCode(e.keyCode); + + var matches = [], + matchFirstChar = false; + + if(!matchString.replace(new RegExp(matchString[0],"g"), '').length) matchFirstChar = true; + + $current.siblings('li').andSelf().children('a').each(function() { + if (this.innerHTML.toUpperCase().indexOf(matchFirstChar ? matchString[0] : matchString) === 0) { + matches.push(this); + } + }); + if (matches.length) { + if(matchFirstChar) { + softSelect($(matches[($(matches).index($current.find('a'))+1)%matches.length]).parent()); + } + else softSelect($(matches[0]).parent()); + } + } else clearKeyStrokes(); + + if ((e.keyCode == RETURN || e.keyCode == SPACE) && $current.is(':hidden')) { $current.focus(); + e.preventDefault(); return; } - + if (e.keyCode == UP && $current.prev().length) { e.preventDefault(); - $current.removeClass('selected'); - $current.prev().addClass('selected'); + softSelect($current.prev()); } else if (e.keyCode == DOWN && $current.next().length) { e.preventDefault(); - $current.removeClass('selected'); - $current.next().addClass('selected'); + softSelect($current.next()); } - - if (e.keyCode == RETURN || e.keyCode == SPACE) { + + if ((e.keyCode == TAB && $current.is(':visible')) || e.keyCode == RETURN || e.keyCode == SPACE) { $current.children('a').trigger('click'); - return; - } - - if (e.keyCode >= 48 && e.keyCode <= 90) { - matchString += String.fromCharCode(e.keyCode); - checkforMatch(e); - } - - if (e.keyCode == TAB && $current.is(':visible')) { e.preventDefault(); - $current.trigger('click'); - hideDropdown(e); + return; } }; - // Check keys pressed to see if there is a text match with one of the options - var checkforMatch = function(e) { - - var re = '/' + matchString + '.*/'; - - $(e.target).siblings('ul').find('a').each(function() { - if (this.innerHTML.toUpperCase().indexOf(matchString) === 0) { - $(this).trigger('click'); - return; - } - }); - }; - // Clear the string used for matching keystrokes to select options var clearKeyStrokes = function() { matchString = ''; From 23d483c4fbd72e0cbe27ddcab93e8a4cbeed26f9 Mon Sep 17 00:00:00 2001 From: mixer2 Date: Thu, 9 Feb 2012 10:20:14 +0100 Subject: [PATCH 04/23] * added esc key to close dropdown --- js/jquery.sparkbox-select.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/jquery.sparkbox-select.js b/js/jquery.sparkbox-select.js index e20972e..c194a9b 100644 --- a/js/jquery.sparkbox-select.js +++ b/js/jquery.sparkbox-select.js @@ -4,7 +4,7 @@ $.fn.sbCustomSelect = function(options) { var iOS = (navigator.userAgent.match(/iPhone/i)) || (navigator.userAgent.match(/iPod/i)) || (navigator.userAgent.match(/iPad/i)), android = (navigator.userAgent.match(/Android/i)), - UP = 38, DOWN = 40, SPACE = 32, RETURN = 13, TAB = 9, + UP = 38, DOWN = 40, SPACE = 32, RETURN = 13, TAB = 9, ESC = 27, matchString = '', settings = $.extend({ appendTo: false @@ -120,7 +120,7 @@ softSelect($current.next()); } - if ((e.keyCode == TAB && $current.is(':visible')) || e.keyCode == RETURN || e.keyCode == SPACE) { + if ((e.keyCode == TAB && $current.is(':visible')) || e.keyCode == RETURN || e.keyCode == SPACE || e.keyCode == ESC) { $current.children('a').trigger('click'); e.preventDefault(); return; From af548eadc1d6758b0d2c58c3d22a9e25c4c042b8 Mon Sep 17 00:00:00 2001 From: mixer2 Date: Thu, 9 Feb 2012 10:45:49 +0100 Subject: [PATCH 05/23] * dropdownSelection does now work for both click on the li or on the a * dropdownSelection doesn't throw an error anymore, if you click on the ul but not on the a element (for example if the ul has padding and someone clicks on the free space) --- js/jquery.sparkbox-select.js | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/js/jquery.sparkbox-select.js b/js/jquery.sparkbox-select.js index c194a9b..2fa5656 100644 --- a/js/jquery.sparkbox-select.js +++ b/js/jquery.sparkbox-select.js @@ -23,17 +23,18 @@ .filter(':contains(' + this[this.selectedIndex].innerHTML + ')').addClass('selected'); } }; - + // Update original select box, hide
    , and fire change event to keep everything in sync var dropdownSelection = function(e) { - var $target = $(e.target), - id = $target.closest('ul').attr('data-id'), - $option = $('.sb-custom[data-id=' + id + ']').find('option').filter('[value="' + $target.parent().data('value') + '"]'); - e.preventDefault(); + var $target = $(this).children('li').andSelf().filter($(e.target).parents('li').andSelf()).filter('li'); + if(!$target.length) return true; + + var id = $target.parent().attr('data-id'), + $option = $('.sb-custom[data-id=' + id + ']').find('option').filter('[value="' + $target.data('value') + '"]'); $option[0].selected = true; - $target.closest('ul').fadeOut('fast'); + $target.parent().fadeOut('fast'); $option.parent().trigger('change'); }; @@ -78,7 +79,7 @@ //select in dropdown if it's open... else directly change select value to target element function softSelect(el) { - if($current.is(':hidden')) $(el).children('a').trigger('click'); + if($current.is(':hidden')) $(el).trigger('click'); else { $current.removeClass('selected'); $(el).addClass('selected'); @@ -121,7 +122,7 @@ } if ((e.keyCode == TAB && $current.is(':visible')) || e.keyCode == RETURN || e.keyCode == SPACE || e.keyCode == ESC) { - $current.children('a').trigger('click'); + $current.trigger('click'); e.preventDefault(); return; } From f7eee0c4aea915fea8cb38606c9691f7d1602ae0 Mon Sep 17 00:00:00 2001 From: mixer2 Date: Thu, 9 Feb 2012 11:41:48 +0100 Subject: [PATCH 06/23] * it's now possible to update value of selectbox without closing dropdown and without triggering change event * up, down keys and search while dropdown is open doesn't trigger change event but update input value and keep dropdown open (just like native selectboxes) * hideDropdown should now work as expected --- js/jquery.sparkbox-select.js | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/js/jquery.sparkbox-select.js b/js/jquery.sparkbox-select.js index 2fa5656..ee95c95 100644 --- a/js/jquery.sparkbox-select.js +++ b/js/jquery.sparkbox-select.js @@ -25,7 +25,7 @@ }; // Update original select box, hide
      , and fire change event to keep everything in sync - var dropdownSelection = function(e) { + var dropdownSelection = function(e, preventClose) { e.preventDefault(); var $target = $(this).children('li').andSelf().filter($(e.target).parents('li').andSelf()).filter('li'); if(!$target.length) return true; @@ -34,8 +34,10 @@ $option = $('.sb-custom[data-id=' + id + ']').find('option').filter('[value="' + $target.data('value') + '"]'); $option[0].selected = true; - $target.parent().fadeOut('fast'); - $option.parent().trigger('change'); + if(!preventClose) { + hideDropdown({}); + $option.parent().trigger('change'); + } else $option.trigger('sb-sync'); }; // Create the
        that will be used to change the selection on a non iOS/Android browser @@ -67,9 +69,10 @@ // Hide the custom dropdown var hideDropdown = function(e) { - if (!$(e.target).closest('.sb-custom').length) { - $('.sb-dropdown').fadeOut('fast'); - } + var id = $(e.target).closest('.sb-dropdown').add($(e.target).closest('.sb-custom')).data('id'), + $prevent = $('.sb-dropdown[data-id=' + id + ']'); + + $('.sb-dropdown').not($prevent).fadeOut('fast'); }; // Manage keypress to replicate browser functionality @@ -82,7 +85,7 @@ if($current.is(':hidden')) $(el).trigger('click'); else { $current.removeClass('selected'); - $(el).addClass('selected'); + $(el).addClass('selected').trigger('click', true); } } From b3e37db66abb88e4d0d016c07dc91c618f67b4ce Mon Sep 17 00:00:00 2001 From: mixer2 Date: Thu, 9 Feb 2012 11:43:59 +0100 Subject: [PATCH 07/23] * added left and right keys as alias for up and down --- js/jquery.sparkbox-select.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/js/jquery.sparkbox-select.js b/js/jquery.sparkbox-select.js index ee95c95..b9f2bdf 100644 --- a/js/jquery.sparkbox-select.js +++ b/js/jquery.sparkbox-select.js @@ -4,7 +4,7 @@ $.fn.sbCustomSelect = function(options) { var iOS = (navigator.userAgent.match(/iPhone/i)) || (navigator.userAgent.match(/iPod/i)) || (navigator.userAgent.match(/iPad/i)), android = (navigator.userAgent.match(/Android/i)), - UP = 38, DOWN = 40, SPACE = 32, RETURN = 13, TAB = 9, ESC = 27, + LEFT = 37, UP = 38, RIGHT = 39, DOWN = 40, SPACE = 32, RETURN = 13, TAB = 9, ESC = 27, matchString = '', settings = $.extend({ appendTo: false @@ -116,10 +116,10 @@ return; } - if (e.keyCode == UP && $current.prev().length) { + if ((e.keyCode == UP || e.keyCode == LEFT) && $current.prev().length) { e.preventDefault(); softSelect($current.prev()); - } else if (e.keyCode == DOWN && $current.next().length) { + } else if ((e.keyCode == DOWN || e.keyCode == RIGHT) && $current.next().length) { e.preventDefault(); softSelect($current.next()); } From d09eaf713079ed1d3b8a6116634e5ec1125f1910 Mon Sep 17 00:00:00 2001 From: mixer2 Date: Thu, 9 Feb 2012 11:54:07 +0100 Subject: [PATCH 08/23] * set correct selected class, if there are labels with the same content --- js/jquery.sparkbox-select.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/js/jquery.sparkbox-select.js b/js/jquery.sparkbox-select.js index b9f2bdf..3a9230f 100644 --- a/js/jquery.sparkbox-select.js +++ b/js/jquery.sparkbox-select.js @@ -19,8 +19,7 @@ if (this.selectedIndex != -1) { $sbSelect.val(this[this.selectedIndex].innerHTML); - $dropdown.children().removeClass('selected') - .filter(':contains(' + this[this.selectedIndex].innerHTML + ')').addClass('selected'); + $dropdown.children().removeClass('selected').eq(this.selectedIndex).addClass('selected'); } }; From 8f74454e9c6999921a664fd26aa0bb07304a2e61 Mon Sep 17 00:00:00 2001 From: mixer2 Date: Thu, 9 Feb 2012 12:56:03 +0100 Subject: [PATCH 09/23] * handle scrolling position for long dropdownlists where a scrollbar is needed --- js/jquery.sparkbox-select.js | 38 ++++++++++++++++++++++++++++-------- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/js/jquery.sparkbox-select.js b/js/jquery.sparkbox-select.js index 3a9230f..6400b06 100644 --- a/js/jquery.sparkbox-select.js +++ b/js/jquery.sparkbox-select.js @@ -10,6 +10,27 @@ appendTo: false }, options); + var scrollToSelected = function() { + var $selected = $('.sb-dropdown:visible li.selected'); + if(!$selected.length) return true; + + //recover scroll position (after fadeOut) + if($selected.parent().data('sb-saved-scrollTo')) $selected.parent().scrollTop($selected.parent().data('sb-saved-scrollTo')); + + //we can't just use $selected.position().top, if the sb-dropdown is positioned static -.- + var scrollTop = $selected.parent().scrollTop(), + offset = ($selected.offset().top+scrollTop-$selected.parent().offset().top); + + if(offset < scrollTop) { + $selected.parent().scrollTop(offset); + $selected.parent().data('sb-saved-scrollTo', offset) + } else if($selected.parent().innerHeight()+scrollTop < offset+$selected.outerHeight()) { + var scrollTo = offset+$selected.outerHeight()-$selected.parent().innerHeight(); + $selected.parent().scrollTop(scrollTo); + $selected.parent().data('sb-saved-scrollTo', scrollTo); + } + } + // Sync custom display with original select box and set selected class and the correct
      • var updateSelect = function() { var $this = $(this), @@ -19,7 +40,9 @@ if (this.selectedIndex != -1) { $sbSelect.val(this[this.selectedIndex].innerHTML); - $dropdown.children().removeClass('selected').eq(this.selectedIndex).addClass('selected'); + var $selected = $dropdown.children().removeClass('selected').eq(this.selectedIndex).addClass('selected'); + + scrollToSelected(); } }; @@ -60,9 +83,11 @@ clearKeyStrokes(); - $('.sb-dropdown').filter('[data-id!=' + id + ']').fadeOut('fast'); - $('.sb-dropdown').filter('[data-id=' + id + ']').fadeIn('fast'); - + $('.sb-dropdown[data-id!=' + id + ']').fadeOut('fast'); + $('.sb-dropdown[data-id=' + id + ']').fadeIn('fast'); + + scrollToSelected(); + e.preventDefault(); }; @@ -82,10 +107,7 @@ //select in dropdown if it's open... else directly change select value to target element function softSelect(el) { if($current.is(':hidden')) $(el).trigger('click'); - else { - $current.removeClass('selected'); - $(el).addClass('selected').trigger('click', true); - } + else $(el).addClass('selected').trigger('click', true); } if (e.keyCode >= 48 && e.keyCode <= 90) { From 0645daa5866282591cfa9a6d2c0ee5aebe186da0 Mon Sep 17 00:00:00 2001 From: mixer2 Date: Thu, 9 Feb 2012 13:32:41 +0100 Subject: [PATCH 10/23] * added "Page Up" and "Page Down" key functionality --- js/jquery.sparkbox-select.js | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/js/jquery.sparkbox-select.js b/js/jquery.sparkbox-select.js index 6400b06..435895c 100644 --- a/js/jquery.sparkbox-select.js +++ b/js/jquery.sparkbox-select.js @@ -4,7 +4,7 @@ $.fn.sbCustomSelect = function(options) { var iOS = (navigator.userAgent.match(/iPhone/i)) || (navigator.userAgent.match(/iPod/i)) || (navigator.userAgent.match(/iPad/i)), android = (navigator.userAgent.match(/Android/i)), - LEFT = 37, UP = 38, RIGHT = 39, DOWN = 40, SPACE = 32, RETURN = 13, TAB = 9, ESC = 27, + LEFT = 37, UP = 38, RIGHT = 39, DOWN = 40, SPACE = 32, RETURN = 13, TAB = 9, ESC = 27, PGUP = 33, PGDOWN = 34, matchString = '', settings = $.extend({ appendTo: false @@ -137,12 +137,31 @@ return; } + function nextPage(elements) { + var targetDistance = $current.parent().innerHeight()-($current.outerHeight()*2), + distance = 0, + $target = $(elements).last(); + + elements.each(function() { + if(distance < targetDistance) distance += $(this).outerHeight(); + else { + $target = $(this); + return false; + } + }); + softSelect($target); + } + if ((e.keyCode == UP || e.keyCode == LEFT) && $current.prev().length) { e.preventDefault(); softSelect($current.prev()); } else if ((e.keyCode == DOWN || e.keyCode == RIGHT) && $current.next().length) { e.preventDefault(); softSelect($current.next()); + } else if(e.keyCode == PGUP) { + nextPage($current.prevAll()); + } else if(e.keyCode == PGDOWN) { + nextPage($current.nextAll()); } if ((e.keyCode == TAB && $current.is(':visible')) || e.keyCode == RETURN || e.keyCode == SPACE || e.keyCode == ESC) { From 47ba4361e28ea68951daa39ece11dffdb42a7eb4 Mon Sep 17 00:00:00 2001 From: mixer2 Date: Thu, 9 Feb 2012 13:55:39 +0100 Subject: [PATCH 11/23] * trigger change event only when value has changed --- js/jquery.sparkbox-select.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/js/jquery.sparkbox-select.js b/js/jquery.sparkbox-select.js index 435895c..4fb0686 100644 --- a/js/jquery.sparkbox-select.js +++ b/js/jquery.sparkbox-select.js @@ -54,11 +54,11 @@ var id = $target.parent().attr('data-id'), $option = $('.sb-custom[data-id=' + id + ']').find('option').filter('[value="' + $target.data('value') + '"]'); - + $option[0].selected = true; if(!preventClose) { hideDropdown({}); - $option.parent().trigger('change'); + if($option.parent().data('lastVal') != $option.parent().val()) $option.parent().data('lastVal', $option.parent().val()).trigger('change'); } else $option.trigger('sb-sync'); }; @@ -225,6 +225,7 @@ } } + $self.data('lastVal', $self.val()); $self.trigger('sb-sync'); selectboxCounter++; }); From 6734925f16157d1b21f8163b2d5b8d3200be8fdd Mon Sep 17 00:00:00 2001 From: mixer2 Date: Thu, 9 Feb 2012 15:48:51 +0100 Subject: [PATCH 12/23] * page up and page down keys bug fixed --- js/jquery.sparkbox-select.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/js/jquery.sparkbox-select.js b/js/jquery.sparkbox-select.js index 4fb0686..89d3ab7 100644 --- a/js/jquery.sparkbox-select.js +++ b/js/jquery.sparkbox-select.js @@ -159,9 +159,11 @@ e.preventDefault(); softSelect($current.next()); } else if(e.keyCode == PGUP) { - nextPage($current.prevAll()); + e.preventDefault(); + nextPage($current.prevAll()); } else if(e.keyCode == PGDOWN) { - nextPage($current.nextAll()); + e.preventDefault(); + nextPage($current.nextAll()); } if ((e.keyCode == TAB && $current.is(':visible')) || e.keyCode == RETURN || e.keyCode == SPACE || e.keyCode == ESC) { From be5cb7335cc8f53ddc8ed0d87b22c8e59062919e Mon Sep 17 00:00:00 2001 From: mixer2 Date: Thu, 9 Feb 2012 16:02:32 +0100 Subject: [PATCH 13/23] * trim labels before searching for alphanumeric keys matchstring (solves problem with leading whitespaces) --- js/jquery.sparkbox-select.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/jquery.sparkbox-select.js b/js/jquery.sparkbox-select.js index 89d3ab7..2eb3f05 100644 --- a/js/jquery.sparkbox-select.js +++ b/js/jquery.sparkbox-select.js @@ -119,7 +119,7 @@ if(!matchString.replace(new RegExp(matchString[0],"g"), '').length) matchFirstChar = true; $current.siblings('li').andSelf().children('a').each(function() { - if (this.innerHTML.toUpperCase().indexOf(matchFirstChar ? matchString[0] : matchString) === 0) { + if ($.trim(this.innerHTML.toUpperCase()).indexOf(matchFirstChar ? matchString[0] : matchString) === 0) { matches.push(this); } }); From f0b561feac1776ab22c9de6726936e2276129d78 Mon Sep 17 00:00:00 2001 From: mixer2 Date: Thu, 9 Feb 2012 21:49:03 +0100 Subject: [PATCH 14/23] * fixed page up and page down behavior for closed dropdown * improved readability * fixed missing preventDefault, for up,down,right,left keys, if there is no next or prev element --- js/jquery.sparkbox-select.js | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/js/jquery.sparkbox-select.js b/js/jquery.sparkbox-select.js index 2eb3f05..1f68ff4 100644 --- a/js/jquery.sparkbox-select.js +++ b/js/jquery.sparkbox-select.js @@ -138,6 +138,7 @@ } function nextPage(elements) { + $current.parent().show(); var targetDistance = $current.parent().innerHeight()-($current.outerHeight()*2), distance = 0, $target = $(elements).last(); @@ -149,21 +150,29 @@ return false; } }); + $current.parent().hide(); softSelect($target); } - if ((e.keyCode == UP || e.keyCode == LEFT) && $current.prev().length) { - e.preventDefault(); - softSelect($current.prev()); - } else if ((e.keyCode == DOWN || e.keyCode == RIGHT) && $current.next().length) { - e.preventDefault(); - softSelect($current.next()); - } else if(e.keyCode == PGUP) { - e.preventDefault(); - nextPage($current.prevAll()); - } else if(e.keyCode == PGDOWN) { - e.preventDefault(); - nextPage($current.nextAll()); + switch (e.keyCode) { + case UP: + case LEFT: + e.preventDefault(); + if($current.prev().length) softSelect($current.prev()); + break; + case RIGHT: + case DOWN: + e.preventDefault(); + if($current.next().length) softSelect($current.next()); + break; + case PGUP: + e.preventDefault(); + nextPage($current.prevAll()); + break; + case PGDOWN: + e.preventDefault(); + nextPage($current.nextAll()); + break; } if ((e.keyCode == TAB && $current.is(':visible')) || e.keyCode == RETURN || e.keyCode == SPACE || e.keyCode == ESC) { From 8c33fca2f9db67c0401bb4c9c24f4d0b1c001abf Mon Sep 17 00:00:00 2001 From: mixer2 Date: Thu, 9 Feb 2012 22:00:54 +0100 Subject: [PATCH 15/23] * fixed page up, page bug wich closed the dropdown --- js/jquery.sparkbox-select.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/js/jquery.sparkbox-select.js b/js/jquery.sparkbox-select.js index 1f68ff4..07a348d 100644 --- a/js/jquery.sparkbox-select.js +++ b/js/jquery.sparkbox-select.js @@ -138,7 +138,11 @@ } function nextPage(elements) { - $current.parent().show(); + var hideAfter = false; + if($current.is(':hidden')) { + $current.parent().show(); + hideAfter = true; + } var targetDistance = $current.parent().innerHeight()-($current.outerHeight()*2), distance = 0, $target = $(elements).last(); @@ -150,7 +154,7 @@ return false; } }); - $current.parent().hide(); + if(hideAfter) $current.parent().hide(); softSelect($target); } From c8573f5047432ffc419d9485ea6c4a8c474d2f2c Mon Sep 17 00:00:00 2001 From: mixer2 Date: Sat, 11 Feb 2012 00:44:48 +0100 Subject: [PATCH 16/23] * get rid of the data-id stuff... replaced with references to jquery objects (improved performance and correct behavior for selects with 2 options with the same value) * removed selectboxCounter * performance improvements * cleaned source * event delegation for all events (cleaner source + better performance) * bugfix for scroll position recovery * updated tests to match fixed behavior --- js/jquery.sparkbox-select.js | 116 +++++++++++++++++------------------ test/tests.js | 4 +- 2 files changed, 60 insertions(+), 60 deletions(-) diff --git a/js/jquery.sparkbox-select.js b/js/jquery.sparkbox-select.js index 07a348d..271c721 100644 --- a/js/jquery.sparkbox-select.js +++ b/js/jquery.sparkbox-select.js @@ -1,6 +1,5 @@ (function($) { - var selectboxCounter = 0; - + $.fn.sbCustomSelect = function(options) { var iOS = (navigator.userAgent.match(/iPhone/i)) || (navigator.userAgent.match(/iPod/i)) || (navigator.userAgent.match(/iPad/i)), android = (navigator.userAgent.match(/Android/i)), @@ -10,91 +9,89 @@ appendTo: false }, options); - var scrollToSelected = function() { - var $selected = $('.sb-dropdown:visible li.selected'); + var scrollToSelected = function($dropdown, recover) { + var $selected = $dropdown.find('li.selected'); if(!$selected.length) return true; //recover scroll position (after fadeOut) - if($selected.parent().data('sb-saved-scrollTo')) $selected.parent().scrollTop($selected.parent().data('sb-saved-scrollTo')); + if($dropdown.data('sb-saved-scrollTo') && recover) $dropdown.scrollTop($dropdown.data('sb-saved-scrollTo')); //we can't just use $selected.position().top, if the sb-dropdown is positioned static -.- - var scrollTop = $selected.parent().scrollTop(), - offset = ($selected.offset().top+scrollTop-$selected.parent().offset().top); + var scrollTop = $dropdown.scrollTop(), + offset = ($selected.offset().top+scrollTop-$dropdown.offset().top); if(offset < scrollTop) { - $selected.parent().scrollTop(offset); - $selected.parent().data('sb-saved-scrollTo', offset) - } else if($selected.parent().innerHeight()+scrollTop < offset+$selected.outerHeight()) { - var scrollTo = offset+$selected.outerHeight()-$selected.parent().innerHeight(); - $selected.parent().scrollTop(scrollTo); - $selected.parent().data('sb-saved-scrollTo', scrollTo); + $dropdown.scrollTop(offset); + } else if($dropdown.innerHeight()+scrollTop < offset+$selected.outerHeight()) { + var scrollTo = offset+$selected.outerHeight()-$dropdown.innerHeight(); + $dropdown.scrollTop(scrollTo); } + $dropdown.data('sb-saved-scrollTo', $dropdown.scrollTop()); } // Sync custom display with original select box and set selected class and the correct
      • - var updateSelect = function() { - var $this = $(this), - $dropdown = $('.sb-dropdown[data-id=' + $this.parent().data('id') + ']'), - $sbSelect = $this.siblings('.sb-select'); + var updateSelect = function(select) { + var $select = this === window ? $(select) : $(this), + $dropdown = $select.parent().data('sb-dropdown'), + $sbSelect = $select.siblings('.sb-select'); - if (this.selectedIndex != -1) { - $sbSelect.val(this[this.selectedIndex].innerHTML); - - var $selected = $dropdown.children().removeClass('selected').eq(this.selectedIndex).addClass('selected'); + if ($select.attr("selectedIndex") != -1) { + $sbSelect.val($select.children().eq($select.attr("selectedIndex")).html()); - scrollToSelected(); + if($dropdown) { + $dropdown.children('.selected').removeClass('selected'); + $dropdown.children().eq($select.attr("selectedIndex")).addClass('selected'); + scrollToSelected($dropdown); + } } }; // Update original select box, hide
          , and fire change event to keep everything in sync var dropdownSelection = function(e, preventClose) { e.preventDefault(); - var $target = $(this).children('li').andSelf().filter($(e.target).parents('li').andSelf()).filter('li'); + var $target = $(e.target).parent('li').andSelf().filter('li'); if(!$target.length) return true; - var id = $target.parent().attr('data-id'), - $option = $('.sb-custom[data-id=' + id + ']').find('option').filter('[value="' + $target.data('value') + '"]'); + var $option = $target.data('sb-option'); $option[0].selected = true; if(!preventClose) { hideDropdown({}); if($option.parent().data('lastVal') != $option.parent().val()) $option.parent().data('lastVal', $option.parent().val()).trigger('change'); - } else $option.trigger('sb-sync'); + } else updateSelect($option.parent()); }; // Create the
            that will be used to change the selection on a non iOS/Android browser - var createDropdown = function($select, i) { + var createDropdown = function($select) { var $options = $select.children(), - $dropdown = $('
              '); + $dropdown = $('
                ').data('sb-custom', $select.parent()); $options.each(function() { $this = $(this); - $dropdown.append('
              • ' + $this.text() + '
              • '); + $('
              • ' + $this.text() + '
              • ').data('sb-option', $this).appendTo($dropdown); }); - $dropdown.bind('click', dropdownSelection); return $dropdown; }; // Clear keystroke matching string and show dropdown var viewList = function(e) { - var $this = $(this), - id = $this.data('id'); - + var $dropdown = $(this).parent().data('sb-dropdown') || $(this); + clearKeyStrokes(); - $('.sb-dropdown[data-id!=' + id + ']').fadeOut('fast'); - $('.sb-dropdown[data-id=' + id + ']').fadeIn('fast'); + hideDropdown({target: $dropdown}); + $dropdown.fadeIn('fast'); - scrollToSelected(); + scrollToSelected($dropdown, true); e.preventDefault(); }; // Hide the custom dropdown var hideDropdown = function(e) { - var id = $(e.target).closest('.sb-dropdown').add($(e.target).closest('.sb-custom')).data('id'), - $prevent = $('.sb-dropdown[data-id=' + id + ']'); + var $matches = $(e.target).closest('.sb-dropdown, .sb-custom'), + $prevent = $matches.data('sb-dropdown') || $matches; $('.sb-dropdown').not($prevent).fadeOut('fast'); }; @@ -102,7 +99,7 @@ // Manage keypress to replicate browser functionality var selectKeypress = function(e) { var $this = $(this), - $current = $('.sb-dropdown[data-id=' + $this.data('id') + ']').find('.selected'); + $current = $this.parent().data('sb-dropdown').find('.selected'); //select in dropdown if it's open... else directly change select value to target element function softSelect(el) { @@ -206,12 +203,13 @@ * After all of the setup is complete, trigger the change event on the original select box to update the .sb-select input */ this.each(function() { - var $self = $(this); + var $self = $(this), + $sbCustom = $('
                '); $self.attr('tabindex', -1) - .wrap('
                ') - .after('') - .bind('change sb-sync', updateSelect); + .after($sbCustom) + .appendTo($sbCustom) + .after(''); if (iOS || android) { @@ -224,34 +222,36 @@ 'z-index': 1000 }); } else { - - $self.next().bind('click', viewList); - + + $sbCustom.data('sb-dropdown', createDropdown($self)); + if (!settings.appendTo) { - $self.after(createDropdown($self, selectboxCounter)); + $sbCustom.append($sbCustom.data('sb-dropdown')); } else { var offset = $self.parent().offset(); - - $(settings.appendTo).append(createDropdown($self, selectboxCounter).css({ + + $(settings.appendTo).append($sbCustom.data('sb-dropdown').css({ 'top': offset.top, 'left': offset.left, 'width': $self.parent().width() * 0.8 })); } + + $self.data('lastVal', $self.val()); } - $self.data('lastVal', $self.val()); - $self.trigger('sb-sync'); - selectboxCounter++; + updateSelect($self); }); // Hide dropdown when click is outside of the input or dropdown - $(document).bind('click', hideDropdown); - - $('.sb-custom').find('.sb-select').live('keydown', selectKeypress); - $('.sb-custom').bind('blur', clearKeyStrokes); - $(document).delegate('.sb-dropdown', 'focus', viewList); - + $(document).bind('click', hideDropdown) + .delegate('.sb-custom', 'blur', clearKeyStrokes) + .delegate('.sb-custom select', 'change', updateSelect) + .delegate('.sb-select', 'keydown', selectKeypress) + .delegate('.sb-select', 'click', viewList) + .delegate('.sb-dropdown', 'focus', viewList) + .delegate('.sb-dropdown', 'click', dropdownSelection); + return this; }; })(jQuery); \ No newline at end of file diff --git a/test/tests.js b/test/tests.js index c1d8854..adc581a 100644 --- a/test/tests.js +++ b/test/tests.js @@ -43,8 +43,8 @@ $(document).ready(function() { $originalSelect.val('foxfire').trigger('change'); equal($originalSelect.val(), 'foxfire', 'The original select is set correctly'); - equal($sbDropdown.find('.selected').data('value'), 'foxfire', 'The dropdown is set correctly'); - + equal($sbDropdown.find('.selected').data('sb-option').attr('value'), 'foxfire', 'The dropdown is set correctly'); + $sbDropdown.find('a').eq(0).trigger('click'); }); From fd989a674cd1324a5fc08b69955fd8e6546c68e6 Mon Sep 17 00:00:00 2001 From: mixer2 Date: Sat, 11 Feb 2012 13:03:31 +0100 Subject: [PATCH 17/23] * keep focus while scrolling with dropdown scrollbar (else keyboard selection won't work after scrolling) * more responsive fade animations (stop them that they can't stack if an dropdown gets closed and reopened very fast multiple times) * close dropdown for all mousebuttons for clicks outside the dropdown --- js/jquery.sparkbox-select.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/js/jquery.sparkbox-select.js b/js/jquery.sparkbox-select.js index 271c721..fed705a 100644 --- a/js/jquery.sparkbox-select.js +++ b/js/jquery.sparkbox-select.js @@ -81,7 +81,7 @@ clearKeyStrokes(); hideDropdown({target: $dropdown}); - $dropdown.fadeIn('fast'); + $dropdown.addClass('active').stop().fadeTo('fast', 1); scrollToSelected($dropdown, true); @@ -93,9 +93,13 @@ var $matches = $(e.target).closest('.sb-dropdown, .sb-custom'), $prevent = $matches.data('sb-dropdown') || $matches; - $('.sb-dropdown').not($prevent).fadeOut('fast'); + $('.sb-dropdown').not($prevent).removeClass('active').stop().fadeOut('fast'); }; + var keepFocus = function() { + if(($(this).parent().data('sb-dropdown').is('.active'))) $(this).focus(); + } + // Manage keypress to replicate browser functionality var selectKeypress = function(e) { var $this = $(this), @@ -244,13 +248,14 @@ }); // Hide dropdown when click is outside of the input or dropdown - $(document).bind('click', hideDropdown) + $(document).bind('mousedown', hideDropdown) .delegate('.sb-custom', 'blur', clearKeyStrokes) .delegate('.sb-custom select', 'change', updateSelect) .delegate('.sb-select', 'keydown', selectKeypress) .delegate('.sb-select', 'click', viewList) .delegate('.sb-dropdown', 'focus', viewList) - .delegate('.sb-dropdown', 'click', dropdownSelection); + .delegate('.sb-dropdown', 'click', dropdownSelection) + .delegate('.sb-select', 'focusout', keepFocus); return this; }; From 0bdd496fa612ae77465fca241bc0cc69ba35d19d Mon Sep 17 00:00:00 2001 From: mixer2 Date: Sat, 11 Feb 2012 20:23:40 +0100 Subject: [PATCH 18/23] * Bugfix for Issue 10 (dropdown positions with appendTo after resize) https://github.com/sparkbox/Custom-Selectbox/issues/10 --- js/jquery.sparkbox-select.js | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/js/jquery.sparkbox-select.js b/js/jquery.sparkbox-select.js index fed705a..e1e25f9 100644 --- a/js/jquery.sparkbox-select.js +++ b/js/jquery.sparkbox-select.js @@ -29,6 +29,23 @@ $dropdown.data('sb-saved-scrollTo', $dropdown.scrollTop()); } + var fixDropdownPositions = function(element) { + if (settings.appendTo) { + var $elements = $(element).is('.sb-dropdown') ? $(element) : $('.sb-dropdown:visible'); + + $elements.each(function() { + var $sbCustom = $(this).data('sb-custom'), + offset = $sbCustom.offset(); + + $(this).css({ + 'top': offset.top, + 'left': offset.left, + 'width': $sbCustom.width() * 0.8 + }); + }); + } + } + // Sync custom display with original select box and set selected class and the correct
              • var updateSelect = function(select) { var $select = this === window ? $(select) : $(this), @@ -81,6 +98,7 @@ clearKeyStrokes(); hideDropdown({target: $dropdown}); + fixDropdownPositions($dropdown); $dropdown.addClass('active').stop().fadeTo('fast', 1); scrollToSelected($dropdown, true); @@ -232,13 +250,7 @@ if (!settings.appendTo) { $sbCustom.append($sbCustom.data('sb-dropdown')); } else { - var offset = $self.parent().offset(); - - $(settings.appendTo).append($sbCustom.data('sb-dropdown').css({ - 'top': offset.top, - 'left': offset.left, - 'width': $self.parent().width() * 0.8 - })); + $(settings.appendTo).append($sbCustom.data('sb-dropdown')); } $self.data('lastVal', $self.val()); @@ -256,6 +268,7 @@ .delegate('.sb-dropdown', 'focus', viewList) .delegate('.sb-dropdown', 'click', dropdownSelection) .delegate('.sb-select', 'focusout', keepFocus); + $(window).bind('resize', fixDropdownPositions); return this; }; From 5b8ac2fb0713bbbaa662ce3b1e5c33fa13246e6b Mon Sep 17 00:00:00 2001 From: mixer2 Date: Sat, 11 Feb 2012 21:26:03 +0100 Subject: [PATCH 19/23] * setting for dropdown position update polling * improved performance for fixDropdownPositions (important if polling setting is used) --- js/jquery.sparkbox-select.js | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/js/jquery.sparkbox-select.js b/js/jquery.sparkbox-select.js index e1e25f9..2dfb988 100644 --- a/js/jquery.sparkbox-select.js +++ b/js/jquery.sparkbox-select.js @@ -5,8 +5,10 @@ android = (navigator.userAgent.match(/Android/i)), LEFT = 37, UP = 38, RIGHT = 39, DOWN = 40, SPACE = 32, RETURN = 13, TAB = 9, ESC = 27, PGUP = 33, PGDOWN = 34, matchString = '', + visibleDropdowns = $(), settings = $.extend({ - appendTo: false + appendTo: false, + pollPosition: false //Polling frequency in microsecounds (500-1000 should be enough), default false disableds polling. Use this option only if your selects position may change while dropdown is opened. }, options); var scrollToSelected = function($dropdown, recover) { @@ -31,7 +33,7 @@ var fixDropdownPositions = function(element) { if (settings.appendTo) { - var $elements = $(element).is('.sb-dropdown') ? $(element) : $('.sb-dropdown:visible'); + var $elements = $(element).is('.sb-dropdown') ? $(element) : visibleDropdowns; $elements.each(function() { var $sbCustom = $(this).data('sb-custom'), @@ -99,6 +101,7 @@ hideDropdown({target: $dropdown}); fixDropdownPositions($dropdown); + visibleDropdowns = visibleDropdowns.add($dropdown); $dropdown.addClass('active').stop().fadeTo('fast', 1); scrollToSelected($dropdown, true); @@ -111,7 +114,9 @@ var $matches = $(e.target).closest('.sb-dropdown, .sb-custom'), $prevent = $matches.data('sb-dropdown') || $matches; - $('.sb-dropdown').not($prevent).removeClass('active').stop().fadeOut('fast'); + $('.sb-dropdown').not($prevent).removeClass('active').stop().fadeOut('fast', function() { + visibleDropdowns = visibleDropdowns.not($(this)); + }); }; var keepFocus = function() { @@ -251,6 +256,13 @@ $sbCustom.append($sbCustom.data('sb-dropdown')); } else { $(settings.appendTo).append($sbCustom.data('sb-dropdown')); + if(typeof(settings.pollPosition) === 'number') { + var callback = function() { + fixDropdownPositions(); + window.setTimeout(callback, settings.pollPosition); + }; + callback(); + } } $self.data('lastVal', $self.val()); From d75fb7b2a6ecf668f2a6ac12cdfe678ecfaf437e Mon Sep 17 00:00:00 2001 From: mixer2 Date: Wed, 15 Feb 2012 11:25:44 +0100 Subject: [PATCH 20/23] * preventDefault if a dropdown closes (fixes bug if parent element of dropdown has a scrollbar) * updated jQuery version * added compatibility for current jQuery version (dropped compatibility for pre 1.6 jQuery) --- index.html | 2 +- js/jquery.sparkbox-select.js | 12 +++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/index.html b/index.html index 13f5272..f4f2c37 100644 --- a/index.html +++ b/index.html @@ -21,7 +21,7 @@ - + diff --git a/js/jquery.sparkbox-select.js b/js/jquery.sparkbox-select.js index 2dfb988..bfde9af 100644 --- a/js/jquery.sparkbox-select.js +++ b/js/jquery.sparkbox-select.js @@ -54,12 +54,12 @@ $dropdown = $select.parent().data('sb-dropdown'), $sbSelect = $select.siblings('.sb-select'); - if ($select.attr("selectedIndex") != -1) { - $sbSelect.val($select.children().eq($select.attr("selectedIndex")).html()); + if ($select.prop("selectedIndex") != -1) { + $sbSelect.val($select.children().eq($select.prop("selectedIndex")).html()); if($dropdown) { $dropdown.children('.selected').removeClass('selected'); - $dropdown.children().eq($select.attr("selectedIndex")).addClass('selected'); + $dropdown.children().eq($select.prop("selectedIndex")).addClass('selected'); scrollToSelected($dropdown); } } @@ -112,9 +112,11 @@ // Hide the custom dropdown var hideDropdown = function(e) { var $matches = $(e.target).closest('.sb-dropdown, .sb-custom'), - $prevent = $matches.data('sb-dropdown') || $matches; + $prevent = $matches.data('sb-dropdown') || $matches, + $toHide = $('.sb-dropdown.active').not($prevent); - $('.sb-dropdown').not($prevent).removeClass('active').stop().fadeOut('fast', function() { + if($toHide.length && e.preventDefault) e.preventDefault(); + $toHide.removeClass('active').stop().fadeOut('fast', function() { visibleDropdowns = visibleDropdowns.not($(this)); }); }; From 4957a7ac01cfef594651b8d6106d72233efb1f17 Mon Sep 17 00:00:00 2001 From: mixer2 Date: Wed, 15 Feb 2012 11:27:25 +0100 Subject: [PATCH 21/23] * updated jQuery version for unit testing --- test/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/index.html b/test/index.html index 6017b7c..e140973 100644 --- a/test/index.html +++ b/test/index.html @@ -16,7 +16,7 @@ - + From 97578c8c4ba8e7670cad651e5cd7cceaaa8b19eb Mon Sep 17 00:00:00 2001 From: mixer2 Date: Wed, 15 Feb 2012 12:13:13 +0100 Subject: [PATCH 22/23] * fixed bug with focus keeping behavior (it should now be possible to select items, while sb-input isn't in the visible area (scrolling with long dropdowns) in webkit browsers) --- js/jquery.sparkbox-select.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/js/jquery.sparkbox-select.js b/js/jquery.sparkbox-select.js index bfde9af..42ae546 100644 --- a/js/jquery.sparkbox-select.js +++ b/js/jquery.sparkbox-select.js @@ -68,6 +68,7 @@ // Update original select box, hide
                  , and fire change event to keep everything in sync var dropdownSelection = function(e, preventClose) { e.preventDefault(); + setFocus.call($(this).data('sb-custom')); var $target = $(e.target).parent('li').andSelf().filter('li'); if(!$target.length) return true; @@ -121,9 +122,9 @@ }); }; - var keepFocus = function() { - if(($(this).parent().data('sb-dropdown').is('.active'))) $(this).focus(); - } + var setFocus = function() { + $(this).children('input.sb-select').focus(); + }; // Manage keypress to replicate browser functionality var selectKeypress = function(e) { @@ -276,12 +277,12 @@ // Hide dropdown when click is outside of the input or dropdown $(document).bind('mousedown', hideDropdown) .delegate('.sb-custom', 'blur', clearKeyStrokes) + .delegate('.sb-custom', 'click', setFocus) .delegate('.sb-custom select', 'change', updateSelect) .delegate('.sb-select', 'keydown', selectKeypress) .delegate('.sb-select', 'click', viewList) .delegate('.sb-dropdown', 'focus', viewList) - .delegate('.sb-dropdown', 'click', dropdownSelection) - .delegate('.sb-select', 'focusout', keepFocus); + .delegate('.sb-dropdown', 'click', dropdownSelection); $(window).bind('resize', fixDropdownPositions); return this; From 9b2ec3e542193c635bbfd33bccd1e5438009d282 Mon Sep 17 00:00:00 2001 From: mixer2 Date: Thu, 8 Mar 2012 23:14:38 +0100 Subject: [PATCH 23/23] * fixed Firefox bug, that select by clicking on an option doesn't work the first time after scrolling in the dropdown --- js/jquery.sparkbox-select.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/js/jquery.sparkbox-select.js b/js/jquery.sparkbox-select.js index 42ae546..40889b8 100644 --- a/js/jquery.sparkbox-select.js +++ b/js/jquery.sparkbox-select.js @@ -98,6 +98,9 @@ var viewList = function(e) { var $dropdown = $(this).parent().data('sb-dropdown') || $(this); + //if it's already the active dropdown don't do anything + if($dropdown.is('.active')) return false; + clearKeyStrokes(); hideDropdown({target: $dropdown});