Browse Source

better handling of first character typed. fixes #196

Igor Vaynberg 12 years ago
parent
commit
ef364d2118
2 changed files with 105 additions and 114 deletions
  1. 0 1
      select2.css
  2. 105 113
      select2.js

+ 0 - 1
select2.css

@@ -261,7 +261,6 @@ Version: @@ver@@ Timestamp: @@timestamp@@
 }
 
 .select2-dropdown-open .select2-choice {
-    border: 1px solid #aaa;
     border-bottom-color: transparent;
     -webkit-box-shadow: 0 1px 0 #fff inset;
        -moz-box-shadow: 0 1px 0 #fff inset;

+ 105 - 113
select2.js

@@ -211,6 +211,34 @@ the specific language governing permissions and limitations under the Apache Lic
         });
     }
 
+    function focus($el) {
+        if ($el[0] === document.activeElement) return;
+
+        /* set the focus in a 0 timeout - that way the focus is set after the processing
+            of the current event has finished - which seems like the only reliable way
+            to set focus */
+        window.setTimeout(function() {
+            var el=$el[0], pos=$el.val().length, range;
+
+            $el.focus();
+
+            /* after the focus is set move the caret to the end, necessary when we val()
+                just before setting focus */
+            if(el.setSelectionRange)
+            {
+                el.setSelectionRange(pos, pos);
+            }
+            else if (el.createTextRange) {
+                range = el.createTextRange();
+                range.collapse(true);
+                range.moveEnd('character', pos);
+                range.moveStart('character', pos);
+                range.select();
+            }
+
+        }, 0);
+    }
+
     function killEvent(event) {
         event.preventDefault();
         event.stopPropagation();
@@ -1075,7 +1103,7 @@ the specific language governing permissions and limitations under the Apache Lic
             $("#select2-drop-mask").hide();
             this.dropdown.removeAttr("id"); // only the active dropdown has the select2-drop id
             this.dropdown.hide();
-            this.container.removeClass("select2-dropdown-open").removeClass("select2-container-active");
+            this.container.removeClass("select2-dropdown-open");
             this.results.empty();
             this.clearSearch();
 
@@ -1355,7 +1383,6 @@ the specific language governing permissions and limitations under the Apache Lic
 
             this.close();
             this.container.removeClass("select2-container-active");
-            this.dropdown.removeClass("select2-drop-active");
             // synonymous to .is(':focus'), which is available in jquery >= 1.6
             if (this.search[0] === document.activeElement) { this.search.blur(); }
             this.clearSearch();
@@ -1364,18 +1391,7 @@ the specific language governing permissions and limitations under the Apache Lic
 
         // abstract
         focusSearch: function () {
-            // need to do it here as well as in timeout so it works in IE
-            this.search.show();
-            this.search.focus();
-
-            /* we do this in a timeout so that current event processing can complete before this code is executed.
-             this makes sure the search field is focussed even if the current event would blur it */
-            window.setTimeout(this.bind(function () {
-                // reset the value so IE places the cursor at the end of the input box
-                this.search.show();
-                this.search.focus();
-                this.search.val(this.search.val());
-            }), 10);
+            focus(this.search);
         },
 
         // abstract
@@ -1467,11 +1483,12 @@ the specific language governing permissions and limitations under the Apache Lic
             var container = $(document.createElement("div")).attr({
                 "class": "select2-container"
             }).html([
-                "    <a href='javascript:void(0)' onclick='return false;' class='select2-choice'>",
+                "<a href='javascript:void(0)' onclick='return false;' class='select2-choice' tabindex='-1'>",
                 "   <span></span><abbr class='select2-search-choice-close' style='display:none;'></abbr>",
                 "   <div><b></b></div>" ,
                 "</a>",
-                "    <div class='select2-drop select2-offscreen'>" ,
+                "<input class='select2-focusser select2-offscreen' type='text'/>",
+                "<div class='select2-drop' style='display:none'>" ,
                 "   <div class='select2-search'>" ,
                 "       <input type='text' autocomplete='off' class='select2-input'/>" ,
                 "   </div>" ,
@@ -1487,8 +1504,7 @@ the specific language governing permissions and limitations under the Apache Lic
 
             this.parent.disable.apply(this, arguments);
 
-            this.selection.attr("tabIndex", "-1");
-            this.search.attr("tabIndex", "-1");
+            this.focusser.attr("disabled", "disabled");
         },
 
         // single
@@ -1497,44 +1513,43 @@ the specific language governing permissions and limitations under the Apache Lic
 
             this.parent.enable.apply(this, arguments);
 
-            if (this.elementTabIndex) {
-                this.selection.attr("tabIndex", this.elementTabIndex);
-            } else {
-                this.selection.removeAttr("tabIndex");
-            }
-
-            this.search.removeAttr("tabIndex");
+            this.focusser.removeAttr("disabled");
         },
 
         // single
         opening: function () {
-            this.search.show();
             this.parent.opening.apply(this, arguments);
-            this.dropdown.removeClass("select2-offscreen");
+            this.focusser.attr("disabled", "disabled");
         },
 
         // single
         close: function () {
             if (!this.opened()) return;
             this.parent.close.apply(this, arguments);
-            this.dropdown.removeAttr("style").addClass("select2-offscreen").insertAfter(this.selection).show();
+            this.focusser.removeAttr("disabled");
+            focus(this.focusser);
         },
 
         // single
         focus: function () {
-            this.close();
-            this.selection.focus();
+            if (this.opened()) {
+                this.close();
+            } else {
+                this.focusser.removeAttr("disabled");
+                this.focusser.focus();
+            }
         },
 
         // single
         isFocused: function () {
-            return this.selection[0] === document.activeElement;
+            return this.container.hasClass("select2-container-active");
         },
 
         // single
         cancel: function () {
             this.parent.cancel.apply(this, arguments);
-            this.selection.focus();
+            this.focusser.removeAttr("disabled");
+            this.focusser.focus();
         },
 
         // single
@@ -1547,6 +1562,8 @@ the specific language governing permissions and limitations under the Apache Lic
 
             this.selection = selection = container.find(".select2-choice");
 
+            this.focusser = container.find(".select2-focusser");
+
             this.search.bind("keydown", this.bind(function (e) {
                 if (!this.enabled) return;
 
@@ -1556,56 +1573,60 @@ the specific language governing permissions and limitations under the Apache Lic
                     return;
                 }
 
-                if (this.opened()) {
-                    switch (e.which) {
-                        case KEY.UP:
-                        case KEY.DOWN:
-                            this.moveHighlight((e.which === KEY.UP) ? -1 : 1);
-                            killEvent(e);
-                            return;
-                        case KEY.TAB:
-                        case KEY.ENTER:
-                            this.selectHighlighted();
-                            killEvent(e);
-                            return;
-                        case KEY.ESC:
-                            this.cancel(e);
-                            killEvent(e);
-                            return;
-                    }
-                } else {
-
-                    if (e.which === KEY.TAB || KEY.isControl(e) || KEY.isFunctionKey(e) || e.which === KEY.ESC) {
+                switch (e.which) {
+                    case KEY.UP:
+                    case KEY.DOWN:
+                        this.moveHighlight((e.which === KEY.UP) ? -1 : 1);
+                        killEvent(e);
                         return;
-                    }
-
-                    if (this.opts.openOnEnter === false && e.which === KEY.ENTER) {
+                    case KEY.TAB:
+                    case KEY.ENTER:
+                        this.selectHighlighted();
+                        killEvent(e);
                         return;
-                    }
+                    case KEY.ESC:
+                        this.cancel(e);
+                        killEvent(e);
+                        return;
+                }
+            }));
+
+            this.focusser.bind("keydown", this.bind(function (e) {
+                if (!this.enabled) return;
 
+                if (e.which === KEY.TAB || KEY.isControl(e) || KEY.isFunctionKey(e) || e.which === KEY.ESC) {
+                    return;
+                }
+
+                if (this.opts.openOnEnter === false && e.which === KEY.ENTER) {
+                    killEvent(e);
+                    return;
+                }
+
+                if (e.which == KEY.DOWN || e.which == KEY.UP
+                    || (e.which == KEY.ENTER && this.opts.openOnEnter)) {
                     this.open();
+                    killEvent(e);
+                    return;
+                }
 
-                    if (e.which === KEY.ENTER) {
-                        // do not propagate the event otherwise we open, and propagate enter which closes
-                        return;
+                if (e.which == KEY.DELETE || e.which == KEY.BACKSPACE) {
+                    if (this.opts.allowClear) {
+                        this.clear();
                     }
+                    killEvent(e);
+                    return;
                 }
             }));
 
-            this.search.bind("focus", this.bind(function() {
-                this.selection.attr("tabIndex", "-1");
-            }));
-            this.search.bind("blur", this.bind(function() {
-                if (!this.opened()) this.container.removeClass("select2-container-active");
-                window.setTimeout(this.bind(function() {
-                    // restore original tab index
-                    var ti=this.elementTabIndex || 0;
-                    if (ti) {
-                        this.selection.attr("tabIndex", ti);
-                    } else {
-                        this.selection.removeAttr("tabIndex");
-                    }
-                }), 10);
+
+            installKeyUpChangeEvent(this.focusser);
+            this.focusser.bind("keyup-change input", this.bind(function(e) {
+                if (this.opened()) return;
+                this.open();
+                this.search.val(this.focusser.val());
+                this.focusser.val("");
+                killEvent(e);
             }));
 
             selection.delegate("abbr", "mousedown", this.bind(function (e) {
@@ -1622,63 +1643,33 @@ the specific language governing permissions and limitations under the Apache Lic
 
                 if (this.opened()) {
                     this.close();
-                    this.selection.focus();
                 } else if (this.enabled) {
                     this.open();
                 }
 
+                killEvent(e);
+
                 clickingInside = false;
             }));
 
             dropdown.bind("mousedown", this.bind(function() { this.search.focus(); }));
 
-            selection.bind("focus", this.bind(function() {
-                if (!this.enabled) return;
-
-                this.container.addClass("select2-container-active");
-                // hide the search so the tab key does not focus on it
-                this.search.attr("tabIndex", "-1");
+            selection.bind("focus", this.bind(function(e) {
+                killEvent(e);
             }));
 
-            selection.bind("blur", this.bind(function() {
+            this.focusser.bind("focus", this.bind(function(){
+                this.container.addClass("select2-container-active");
+            })).bind("blur", this.bind(function() {
                 if (!this.opened()) {
                     this.container.removeClass("select2-container-active");
                 }
-                window.setTimeout(this.bind(function() { this.search.attr("tabIndex", this.elementTabIndex || 0); }), 10);
             }));
-
-            selection.bind("keydown", this.bind(function(e) {
-                if (!this.enabled) return;
-
-                if (e.which == KEY.DOWN || e.which == KEY.UP
-                    || (e.which == KEY.ENTER && this.opts.openOnEnter)) {
-                    this.open();
-                    killEvent(e);
-                    return;
-                }
-
-                if (e.which == KEY.DELETE || e.which == KEY.BACKSPACE) {
-                    if (this.opts.allowClear) {
-                        this.clear();
-                    }
-                    killEvent(e);
-                    return;
-                }
-            }));
-            selection.bind("keypress", this.bind(function(e) {
-		if (e.which == KEY.DELETE || e.which == KEY.BACKSPACE || e.which == KEY.TAB || e.which == KEY.ENTER || e.which == 0) {
-			return;
-		}
-                var key = String.fromCharCode(e.which);
-                this.search.val(key);
-                this.open();
-            }));
-
+            this.search.bind("focus", this.bind(function(){
+                this.container.addClass("select2-container-active");
+            }))
             this.setPlaceholder();
 
-            this.search.bind("focus", this.bind(function() {
-                this.container.addClass("select2-container-active");
-            }));
         },
 
         // single
@@ -1872,6 +1863,7 @@ the specific language governing permissions and limitations under the Apache Lic
         // single
         clearSearch: function () {
             this.search.val("");
+            this.focusser.val("");
         },
 
         // single