blob: 677e4868858b3e7ff2371df62ddca014a113e204 [file] [log] [blame]
Tyler Scott93cae872015-07-21 14:58:23 -06001/*
2 * The following code is a jquery extention written to add autocomplete functionality to bootstrap input groups.
Tyler Scottfc990392015-08-10 13:48:03 -06003 *
Tyler Scott93cae872015-07-21 14:58:23 -06004 * Usage:
Tyler Scottfc990392015-08-10 13:48:03 -06005 *
Tyler Scott93cae872015-07-21 14:58:23 -06006 * Then simply call $('.someClass').autoComplete(getSuggestions) on it to enable auto completion.
Tyler Scottfc990392015-08-10 13:48:03 -06007 *
Tyler Scott93cae872015-07-21 14:58:23 -06008 * getSuggestions returns by calling its callback parameter with an array of valid strings.
Tyler Scottfc990392015-08-10 13:48:03 -06009 *
Tyler Scottc55879f2015-07-28 14:56:37 -060010 * Autocomplete can be manually triggered by triggering the autoComplete event.
Tyler Scottfc990392015-08-10 13:48:03 -060011 *
Tyler Scott93cae872015-07-21 14:58:23 -060012 */
13(function(){
14 "use strict";
15 if (!jQuery){
16 throw new Error("jQuery is required and must be loaded before this script.")
17 }
18 jQuery.fn.extend({
19 /**
Tyler Scotte564a5a2015-09-16 17:43:33 -060020 * This setups up the default autocomplete functionality on an object.
21 * Using either a list of options or supplying a function that returns them,
22 * auto complete will create a menu for users to navigate and select a
23 * valid option.
24 * @param {Array<String>|function(String, function(Array<String>))} suggestions
Tyler Scott93cae872015-07-21 14:58:23 -060025 */
26 autoComplete: function(suggestions) {
27
28 var element = $('<div></div>');
29 element.addClass('list-group')
Tyler Scottfe8e4932015-07-28 17:45:45 -060030 .addClass('autoComplete')
Tyler Scott93cae872015-07-21 14:58:23 -060031 .css({
Tyler Scottfe8e4932015-07-28 17:45:45 -060032 'top': this.parent().height()
Tyler Scott93cae872015-07-21 14:58:23 -060033 });
34
Tyler Scott03854852015-08-07 15:45:10 -060035 this.attr('autocomplete', 'off')
36 .after(element);
Tyler Scott93cae872015-07-21 14:58:23 -060037
38 var getSuggestions = function(current, callback){
39 callback(suggestions.reduce(function(prev, suggestion){
40 if (current.toLowerCase().indexOf(suggestion.substr(0, current.length).toLowerCase()) === 0){
41 prev.push(suggestion);
42 }
43 return prev;
44 }, []));
45 }
46
Tyler Scott9eb6abd2015-08-04 14:48:23 -060047 var lastList = [];
48
Tyler Scott93cae872015-07-21 14:58:23 -060049 var setAutoComplete = function(list){
Tyler Scott9eb6abd2015-08-04 14:48:23 -060050 lastList = list;
Tyler Scott93cae872015-07-21 14:58:23 -060051 element.empty();
52
53 element.html(list.reduce(function(prev, current){
54 return [prev, '<a href="#" class="list-group-item">', current, '</a>'].join("");
55 }, ""));
56
57 }
58
59 if (suggestions instanceof Function){
60 getSuggestions = suggestions;
61 }
62
63 var input = this;
64
Tyler Scott9eb6abd2015-08-04 14:48:23 -060065 var matcher = /^\/([-_\w]+\/)*/; //Returns only the absolute path.
66
67 var getValue = function(){
68 var res = matcher.exec(input.val());
69 if (res){
70 return res[0]; //Return the absolute match
71 } else {
72 throw new Error("Empty or incorrectly formatted path.");
73 }
74 }
75
Tyler Scott93cae872015-07-21 14:58:23 -060076 element.bind('click', 'a', function(){
Tyler Scott39f72252015-08-07 16:21:45 -060077 input.val($(event.target).text());
Tyler Scottfc990392015-08-10 13:48:03 -060078 getSuggestions(getValue(), setAutoComplete);
79 input.focus();
Tyler Scott93cae872015-07-21 14:58:23 -060080 });
81
Tyler Scottfc990392015-08-10 13:48:03 -060082 var updateFromList = function(){
83 var val = input.val(); //Needs to be unfiltered, for filtering existing results.
Tyler Scott149955a2015-08-13 15:25:41 -060084 var temp = lastList;
Tyler Scottfc990392015-08-10 13:48:03 -060085 setAutoComplete(lastList.reduce(function(prev, current){
86 if (current.indexOf(val) === 0){
87 prev.push(current);
88 }
89 return prev;
90 }, []));
Tyler Scott149955a2015-08-13 15:25:41 -060091 lastList = temp;
Tyler Scottfc990392015-08-10 13:48:03 -060092 }
93
Tyler Scott93cae872015-07-21 14:58:23 -060094 this.keydown(function(e){
95 switch(e.which){
96 case 38: //up
97 var active = element.find('.active');
98 if (active.length === 0){
99 element.find(':first-child').addClass('active');
100 } else {
101 if (!active.is(':first-child')){
Tyler Scottfe8e4932015-07-28 17:45:45 -0600102 var top = active.removeClass('active').prev().addClass('active').offset().top;
103 active.parent().stop().animate({scrollTop: top}, 500);
Tyler Scott93cae872015-07-21 14:58:23 -0600104 }
105 }
106 e.preventDefault();
107 break;
108
109 case 40: //down
110 var active = element.find('.active');
111 if (active.length === 0){
112 element.find(':first-child').addClass('active');
113 } else {
114 if (!active.is(':last-child')){
Tyler Scottfe8e4932015-07-28 17:45:45 -0600115 var top = active.removeClass('active').next().addClass('active').offset().top;
116 active.parent().stop().animate({scrollTop: top}, 500);
Tyler Scott93cae872015-07-21 14:58:23 -0600117 }
118 }
119 e.preventDefault();
120 break;
121
122 case 13: //Enter
123 var active = element.find('.active');
124 if (active.length === 1){
125 $(this).val(active.text());
126 e.preventDefault();
Tyler Scottfc990392015-08-10 13:48:03 -0600127 getSuggestions(getValue(), setAutoComplete);
Tyler Scott93cae872015-07-21 14:58:23 -0600128 }
129 break;
130
131 case 9: //Tab
Tyler Scott9eb6abd2015-08-04 14:48:23 -0600132 getSuggestions(getValue(), setAutoComplete);
Tyler Scottfe8e4932015-07-28 17:45:45 -0600133 e.preventDefault(); //Don't print tab or select a different element.
Tyler Scott93cae872015-07-21 14:58:23 -0600134 break;
Tyler Scott9eb6abd2015-08-04 14:48:23 -0600135
Tyler Scott149955a2015-08-13 15:25:41 -0600136 case 8:
137 console.log("Detected backspace");
138 if (input.val().slice(-1) == "/"){
139 e.preventDefault();
140 input.val(input.val().slice(0,-1)); //Manually backspace early. (Have to do it manually)
141 getSuggestions(getValue(), setAutoComplete);
142 } else {
143 updateFromList(setAutoComplete);
144 }
145
Tyler Scott9eb6abd2015-08-04 14:48:23 -0600146 default:
Tyler Scottfc990392015-08-10 13:48:03 -0600147 updateFromList();
Tyler Scott9eb6abd2015-08-04 14:48:23 -0600148
Tyler Scott93cae872015-07-21 14:58:23 -0600149 }
150
Tyler Scott9eb6abd2015-08-04 14:48:23 -0600151 })
152 .keyup(function(e){
Tyler Scottfc990392015-08-10 13:48:03 -0600153 switch (e.which){
154
155 case 191:
Tyler Scott9eb6abd2015-08-04 14:48:23 -0600156 getSuggestions(getValue(), setAutoComplete);
Tyler Scottfc990392015-08-10 13:48:03 -0600157
158 break;
159 case 38:
160 case 40:
161 case 13:
162 case 9:
163 // Do nothing
164
165 break;
166 default:
167 updateFromList();
Tyler Scott9eb6abd2015-08-04 14:48:23 -0600168 }
Tyler Scott93cae872015-07-21 14:58:23 -0600169 });
170
Tyler Scottc55879f2015-07-28 14:56:37 -0600171 this.on('autoComplete', function(){
Tyler Scott9eb6abd2015-08-04 14:48:23 -0600172 getSuggestions(getValue(), setAutoComplete);
Tyler Scottc55879f2015-07-28 14:56:37 -0600173 });
174
Tyler Scott93cae872015-07-21 14:58:23 -0600175 return this;
176
177 }
178 });
179})();
180