public
Fork of ReinH/jquery-autocomplete
Description: jQuery Autocomplete plugin
Homepage: http://reinh.com
Clone URL: git://github.com/istruble/jquery-autocomplete.git
Safari-mac TAB behavior cleaned up and fixed RET-RET bug.

Tabbing in Safari-mac to another field was
  - moving the selection (good)
  - updating the input field (good)
  - moving focus to the next tab w/o ending the autocomplete (bad)
Now only cycles selection on TAB if active and lets the browser do
what it wants if not active.

Hitting RET-RET on an empty input field was:
  - activating autocomplete on RET (good)
  - throwing an error and remaining active on 2nd RET (bad)
Now it just closes on the second RET press.

Also changed the key calculation for keydown or keypressed events
to match browser key input quirks based on info at
  http://unixpapa.com/js/key.html
istruble (author)
Sat Apr 12 12:46:51 -0700 2008
commit  723d7ac6ffdbee6839f47ec8ed5823e385059909
tree    c96a148cea2c008204bda55535e5c3b4e678b59d
parent  1dbbaa3198a9fae6726ec2c0f2115a54fa60beea
...
2
3
4
5
6
7
8
9
10
 
 
 
 
 
 
 
 
 
 
 
 
 
11
12
 
13
14
15
16
 
 
 
17
18
19
 
 
20
21
22
23
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
25
26
 
 
27
28
29
30
 
 
 
 
 
 
 
 
31
32
...
2
3
4
 
 
 
 
 
 
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 
19
20
21
22
23
24
25
26
27
 
 
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
 
72
73
74
75
76
77
78
79
80
81
0
@@ -2,31 +2,80 @@
0
    "http://www.w3.org/TR/html4/strict.dtd">
0
 
0
 <html lang="en">
0
-<head>
0
-  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
0
-  <title>autocomplete</title>
0
-  <meta name="generator" content="TextMate http://macromates.com/">
0
-  <meta name="author" content="Yehuda Katz">
0
-  <link rel="stylesheet" href="jquery.ui.autocomplete.css" type="text/css" media="screen" />
0
+  <head>
0
+    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
0
+    <title>autocomplete</title>
0
+    <meta name="generator" content="TextMate http://macromates.com/">
0
+    <meta name="author" content="Yehuda Katz">
0
+    <link rel="stylesheet" href="jquery.ui.autocomplete.css" type="text/css" media="screen" />
0
+    <style type="text/css">
0
+      .matching {
0
+      font-weight: bold;
0
+      text-decoration: underline;
0
+      }
0
+    </style>
0
+
0
   <script src="jquery.js"></script>
0
-  <script src="jquery.dimensions.js"></script>  
0
+  <script src="jquery.dimensions.js"></script>
0
   <script src="jquery.templating.js"></script>
0
   <script src="jquery.ui.autocomplete.ext.js"></script>
0
   <script src="jquery.ui.autocomplete.js"></script>
0
   <script>
0
+    console = typeof(console) !== 'undefined' ? console : {};
0
+    console.log = typeof(console.log) !== 'undefined' ? console.log : function(msg) { $('#log').html(msg + '<br/>\n' + $('#log').html() ); };
0
+
0
     $(function() {
0
-      $("input.autocomplete").autocomplete({
0
-        ajax: "list", 
0
+      $("input.autocomplete.0").autocomplete({
0
+        ajax: "list",
0
         match: function(typed) { return this.text.match(new RegExp(typed)); },
0
         insertText: function(obj) { return obj.text },
0
         templateText: "<li>Hey: <%= text %></li>"
0
       })
0
+
0
+      var list1 = [{text: 'Curious George'}, {text: 'George of the Jungle'}, {text: 'Felix the Cat'}];
0
+      $("input.autocomplete.1").autocomplete({
0
+        list: list1,
0
+        match: function(typed) {
0
+          this.typed = typed;
0
+          this.pre_match = this.text;
0
+          this.match = this.post_match = '';
0
+          if (!this.ajax && !typed || typed.length == 0) { return true; }
0
+          var match_at = this.text.search(new RegExp("\\b" + typed, "i"));
0
+          if (match_at != -1) {
0
+            this.pre_match = this.text.slice(0,match_at);
0
+            this.match = this.text.slice(match_at,match_at + typed.length);
0
+            this.post_match = this.text.slice(match_at + typed.length);
0
+            return true;
0
+          }
0
+          return false;
0
+        },
0
+        insertText: function(obj) { return obj.text },
0
+        templateText: "<li><%= pre_match %><span class='matching' ><%= match %></span><%= post_match %></li>"
0
+      });
0
+
0
+      $("input.autocomplete.2").autocomplete({
0
+        ajax: "list",
0
+        match: function(typed) { return this.text.match(new RegExp(typed, "i")); },
0
+        insertText: function(obj) { return obj.text },
0
+        templateText: "<li>Hey: <%= text %></li>"
0
+      })
0
+
0
+      $("input.autocomplete")
0
         .bind("activate.autocomplete", function(e, d) { console.log(d); })
0
         .bind("cancel.autocomplete", function(e) { console.log("Cancelled"); });
0
     });
0
+
0
+
0
   </script>
0
 </head>
0
 <body>
0
-  <input type="text" class="autocomplete" autocomplete="off" />
0
+  <input type="text" class="autocomplete 0" />
0
+
0
+  <br />
0
+  <form onsubmit="return false;">
0
+    <input type="text" class="autocomplete 1" />
0
+    <input type="text" class="autocomplete 2" />
0
+  </form>
0
+<div id="log"></div>
0
 </body>
0
 </html>
...
11
12
13
14
 
15
16
17
...
20
21
22
23
 
24
25
26
 
27
28
 
 
 
 
 
 
 
 
 
 
 
29
30
31
 
32
33
34
35
 
 
36
37
 
38
39
 
 
 
 
40
41
 
42
43
44
45
46
47
48
 
49
50
51
52
53
54
 
55
56
57
 
58
59
60
61
62
63
64
 
65
66
 
67
68
69
70
71
72
73
74
 
 
 
 
 
 
 
 
75
76
 
77
78
79
...
82
83
84
85
 
86
87
 
88
89
90
...
103
104
105
106
 
107
108
109
 
110
111
 
112
113
114
115
116
117
 
 
 
 
118
119
120
...
127
128
129
130
 
131
132
 
133
134
 
135
136
 
137
138
 
139
140
 
141
142
143
144
 
145
146
147
...
150
151
152
153
 
154
...
11
12
13
 
14
15
16
17
...
20
21
22
 
23
24
25
 
26
27
 
28
29
30
31
32
33
34
35
36
37
38
39
40
 
41
42
43
 
 
44
45
46
 
47
48
 
49
50
51
52
53
 
54
55
56
57
58
59
60
 
61
62
63
64
65
66
 
67
68
69
 
70
71
72
73
74
75
76
 
77
78
 
79
80
81
 
 
 
 
 
 
82
83
84
85
86
87
88
89
90
 
91
92
93
94
...
97
98
99
 
100
101
 
102
103
104
105
...
118
119
120
 
121
122
123
124
125
126
 
127
128
129
 
 
 
 
130
131
132
133
134
135
136
...
143
144
145
 
146
147
 
148
149
 
150
151
 
152
153
 
154
155
 
156
157
158
159
 
160
161
162
163
...
166
167
168
 
169
170
0
@@ -11,7 +11,7 @@
0
  */
0
 
0
 /*
0
- * @description Form autocomplete plugin using preloaded or Ajax JSON data source 
0
+ * @description Form autocomplete plugin using preloaded or Ajax JSON data source
0
  *
0
  * @example $('input#user-name').autocomplete({list: ["quentin", "adam", "admin"]})
0
  * @desc Simple autocomplete with basic JSON data source
0
@@ -20,60 +20,75 @@
0
  * @desc Simple autocomplete with Ajax loaded JSON data source
0
  *
0
  */
0
- 
0
+
0
 
0
 (function($) {
0
-  
0
+
0
   $.ui = $.ui || {}; $.ui.autocomplete = $.ui.autocomplete || {}; var active;
0
-    
0
+
0
+  var KEY = {
0
+    ESC: 27,
0
+    RETURN: 13,
0
+    TAB: 9,
0
+    BS: 8,
0
+    DEL: 46,
0
+    UP: 38,
0
+    DOWN: 40
0
+  };
0
+
0
   $.fn.autocompleteMode = function(container, input, size, opt) {
0
     var original = input.val(); var selected = -1; var self = this;
0
-    
0
+
0
     $.data(document.body, "autocompleteMode", true);
0
 
0
-    $("body").one("cancel.autocomplete", function() { 
0
-      input.trigger("cancel.autocomplete"); $("body").trigger("off.autocomplete"); input.val(original); 
0
+    $("body").one("cancel.autocomplete", function() {
0
+      input.trigger("cancel.autocomplete"); $("body").trigger("off.autocomplete"); input.val(original);
0
     });
0
-    
0
+
0
     $("body").one("activate.autocomplete", function() {
0
-      input.trigger("activate.autocomplete", [$.data(active[0], "originalObject")]); $("body").trigger("off.autocomplete");
0
+      // Try hitting return to activate autocomplete and then hitting it again on blank input
0
+      // to close it.  w/o checking the active object first this input.trigger() will barf.
0
+      active && input.trigger("activate.autocomplete", [$.data(active[0], "originalObject")]);
0
+      $("body").trigger("off.autocomplete");
0
     });
0
-    
0
+
0
     $("body").one("off.autocomplete", function(e, reset) {
0
       container.remove();
0
       $.data(document.body, "autocompleteMode", false);
0
       input.unbind("keydown.autocomplete");
0
       $("body").add(window).unbind("click.autocomplete").unbind("cancel.autocomplete").unbind("activate.autocomplete");
0
     });
0
-    
0
+
0
     // If a click bubbles all the way up to the window, close the autocomplete
0
     $(window).bind("click.autocomplete", function() { $("body").trigger("cancel.autocomplete"); });
0
 
0
     var select = function() {
0
       active = $("> *", container).removeClass("active").slice(selected, selected + 1).addClass("active");
0
-      input.trigger("itemSelected.autocomplete", [$.data(active[0], "originalObject")]);     
0
+      input.trigger("itemSelected.autocomplete", [$.data(active[0], "originalObject")]);
0
       input.val(opt.insertText($.data(active[0], "originalObject")));
0
     };
0
-    
0
+
0
     container.mouseover(function(e) {
0
       // If you hover over the container, but not its children, return
0
       if(e.target == container[0]) return;
0
       // Set the selected item to the item hovered over and make it active
0
       selected = $("> *", container).index($(e.target).is('li') ? $(e.target)[0] : $(e.target).parents('li')[0]); select();
0
     }).bind("click.autocomplete", function(e) {
0
-      $("body").trigger("activate.autocomplete"); $.data(document.body, "suppressKey", false); 
0
+      $("body").trigger("activate.autocomplete"); $.data(document.body, "suppressKey", false);
0
     });
0
-    
0
+
0
     input
0
       .bind("keydown.autocomplete", function(e) {
0
-        if(e.which == 27) { $("body").trigger("cancel.autocomplete"); }
0
-        else if(e.which == 13) { $("body").trigger("activate.autocomplete"); }
0
-        else if(e.which == 40 || e.which == 9 || e.which == 38) {
0
-          switch(e.which) {
0
-            case 40: 
0
-            case 9:
0
+        var k = e.which || e.keyCode; // in IE e.which is undefined
0
+
0
+        if(k == KEY.ESC) { $("body").trigger("cancel.autocomplete"); }
0
+        else if(k == KEY.RETURN) { $("body").trigger("activate.autocomplete"); }
0
+        else if(k == KEY.UP || k == KEY.TAB || k == KEY.DOWN) {
0
+          switch(k) {
0
+            case KEY.DOWN:
0
+            case KEY.TAB:
0
               selected = selected >= size - 1 ? 0 : selected + 1; break;
0
-            case 38:
0
+            case KEY.UP:
0
               selected = selected <= 0 ? size - 1 : selected - 1; break;
0
             default: break;
0
           }
0
@@ -82,9 +97,9 @@
0
         $.data(document.body, "suppressKey", true);
0
       });
0
   };
0
-  
0
+
0
   $.fn.autocomplete = function(opt) {
0
-    
0
+
0
     opt = $.extend({}, {
0
       timeout: 1000,
0
       getList: function(input) { input.trigger("updateList", [opt.list]); },
0
@@ -103,18 +118,19 @@
0
     } }
0
 
0
     return this.each(function() {
0
-  
0
+
0
       $(this)
0
         .keypress(function(e) {
0
           var typingTimeout = $.data(this, "typingTimeout");
0
+          var k = e.keyCode || e.which; // keyCode == 0 in Gecko/FF on keypress
0
           if(typingTimeout) window.clearInterval(typingTimeout);
0
-                    
0
+
0
           if($.data(document.body, "suppressKey"))
0
             return $.data(document.body, "suppressKey", false);
0
-          else if($.data(document.body, "autocompleteMode") && e.charCode < 32 && e.keyCode != 8 && e.keyCode != 46) return false;          
0
-          else {
0
-            $.data(this, "typingTimeout", window.setTimeout(function() { 
0
-              $(e.target).trigger("autocomplete"); 
0
+          else if($.data(document.body, "autocompleteMode") && k < 32 && k != KEY.BS && k != KEY.DEL) return false;
0
+          else if (k != KEY.TAB  && k != KEY.ESC) { // don't start the meter running if we are tabbing out or escaping this nuthouse
0
+            $.data(this, "typingTimeout", window.setTimeout(function() {
0
+               $(e.target).trigger("autocomplete");
0
             }, opt.timeout));
0
           }
0
         })
0
@@ -127,21 +143,21 @@
0
               .map(function() {
0
                 var node = $(opt.template(this))[0];
0
                 $.data(node, "originalObject", this);
0
-                return node; 
0
+                return node;
0
               });
0
-          
0
+
0
             $("body").trigger("off.autocomplete");
0
-          
0
+
0
             if(!list.length) return false;
0
-          
0
+
0
             var container = list.wrapAll(opt.wrapper).parents(":last").children();
0
-            
0
+
0
             var offset = self.offset();
0
-          
0
+
0
             opt.container = container
0
               .css({top: offset.top + self.outerHeight(), left: offset.left, width: self.width()})
0
               .appendTo("body");
0
-          
0
+
0
             $("body").autocompleteMode(container, self, list.length, opt);
0
           });
0
 
0
@@ -150,5 +166,5 @@
0
 
0
     });
0
   };
0
-  
0
+
0
 })(jQuery);

Comments