Skip to content

Commit 6779ede

Browse files
committed
No longer depend on bootstrap-typeahead
It went away in bootstrap 3.0, so it is not reliable anymore
1 parent df9e809 commit 6779ede

File tree

5 files changed

+196
-370
lines changed

5 files changed

+196
-370
lines changed

README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ We had need of a combobox at work and after looking around at the available opti
44

55
## How to use it
66

7-
The dependencies are the Bootstrap stylesheet(CSS or LESS) and also the typeahead javascript plugin. Include them and then the stylesheet(CSS or LESS) and javascript.
7+
The dependencies are the Bootstrap stylesheet(CSS or LESS). Include it and then the stylesheet(CSS or LESS) and javascript.
88

99
Then just activate the plugin on a normal select box(suggest having a blank option first):
1010

@@ -26,3 +26,7 @@ Then just activate the plugin on a normal select box(suggest having a blank opti
2626
## Live Example
2727

2828
http://dl.dropbox.com/u/21368/bootstrap-combobox/index.html
29+
30+
## License
31+
32+
Licensed under the Apache License, Version 2.0

js/bootstrap-combobox.js

Lines changed: 190 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/* =============================================================
2-
* bootstrap-combobox.js v1.1.4
2+
* bootstrap-combobox.js v1.1.5
33
* =============================================================
44
* Copyright 2012 Daniel Farrell
55
*
@@ -20,6 +20,9 @@
2020

2121
"use strict";
2222

23+
/* COMBOBOX PUBLIC CLASS DEFINITION
24+
* ================================ */
25+
2326
var Combobox = function ( element, options ) {
2427
this.options = $.extend({}, $.fn.combobox.defaults, options);
2528
this.$source = $(element);
@@ -38,10 +41,7 @@
3841
this.listen();
3942
};
4043

41-
/* NOTE: COMBOBOX EXTENDS BOOTSTRAP-TYPEAHEAD.js
42-
========================================== */
43-
44-
Combobox.prototype = $.extend({}, $.fn.typeahead.Constructor.prototype, {
44+
Combobox.prototype = {
4545

4646
constructor: Combobox
4747

@@ -95,6 +95,126 @@
9595
this.$source.removeAttr('tabindex');
9696
}
9797

98+
, select: function () {
99+
var val = this.$menu.find('.active').attr('data-value');
100+
this.$element.val(this.updater(val)).trigger('change');
101+
this.$source.val(this.map[val]).trigger('change');
102+
this.$target.val(this.map[val]).trigger('change');
103+
this.$container.addClass('combobox-selected');
104+
this.selected = true;
105+
return this.hide();
106+
}
107+
108+
, updater: function (item) {
109+
return item;
110+
}
111+
112+
, show: function () {
113+
var pos = $.extend({}, this.$element.position(), {
114+
height: this.$element[0].offsetHeight
115+
});
116+
117+
this.$menu
118+
.insertAfter(this.$element)
119+
.css({
120+
top: pos.top + pos.height
121+
, left: pos.left
122+
})
123+
.show();
124+
125+
this.shown = true;
126+
return this;
127+
}
128+
129+
, hide: function () {
130+
this.$menu.hide();
131+
this.shown = false;
132+
return this;
133+
}
134+
135+
, lookup: function (event) {
136+
this.query = this.$element.val();
137+
return this.process(this.source);
138+
}
139+
140+
, process: function (items) {
141+
var that = this;
142+
143+
items = $.grep(items, function (item) {
144+
return that.matcher(item);
145+
})
146+
147+
items = this.sorter(items);
148+
149+
if (!items.length) {
150+
return this.shown ? this.hide() : this;
151+
}
152+
153+
return this.render(items.slice(0, this.options.items)).show();
154+
}
155+
156+
, matcher: function (item) {
157+
return ~item.toLowerCase().indexOf(this.query.toLowerCase());
158+
}
159+
160+
, sorter: function (items) {
161+
var beginswith = []
162+
, caseSensitive = []
163+
, caseInsensitive = []
164+
, item;
165+
166+
while (item = items.shift()) {
167+
if (!item.toLowerCase().indexOf(this.query.toLowerCase())) {beginswith.push(item);}
168+
else if (~item.indexOf(this.query)) {caseSensitive.push(item);}
169+
else {caseInsensitive.push(item);}
170+
}
171+
172+
return beginswith.concat(caseSensitive, caseInsensitive);
173+
}
174+
175+
, highlighter: function (item) {
176+
var query = this.query.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, '\\$&');
177+
return item.replace(new RegExp('(' + query + ')', 'ig'), function ($1, match) {
178+
return '<strong>' + match + '</strong>';
179+
})
180+
}
181+
182+
, render: function (items) {
183+
var that = this;
184+
185+
items = $(items).map(function (i, item) {
186+
i = $(that.options.item).attr('data-value', item);
187+
i.find('a').html(that.highlighter(item));
188+
return i[0];
189+
})
190+
191+
items.first().addClass('active');
192+
this.$menu.html(items);
193+
return this;
194+
}
195+
196+
, next: function (event) {
197+
var active = this.$menu.find('.active').removeClass('active')
198+
, next = active.next();
199+
200+
if (!next.length) {
201+
next = $(this.$menu.find('li')[0]);
202+
}
203+
204+
next.addClass('active');
205+
}
206+
207+
, prev: function (event) {
208+
var active = this.$menu.find('.active').removeClass('active')
209+
, prev = active.prev();
210+
211+
if (!prev.length) {
212+
prev = this.$menu.find('li').last();
213+
}
214+
215+
prev.addClass('active');
216+
}
217+
98218
, toggle: function () {
99219
if (this.$container.hasClass('combobox-selected')) {
100220
this.clearTarget();
@@ -130,24 +250,6 @@
130250
this.options.items = this.source.length;
131251
}
132252

133-
// modified typeahead function adding container and target handling
134-
, select: function () {
135-
var val = this.$menu.find('.active').attr('data-value');
136-
this.$element.val(this.updater(val)).trigger('change');
137-
this.$source.val(this.map[val]).trigger('change');
138-
this.$target.val(this.map[val]).trigger('change');
139-
this.$container.addClass('combobox-selected');
140-
this.selected = true;
141-
return this.hide();
142-
}
143-
144-
// modified typeahead function removing the blank handling and source function handling
145-
, lookup: function (event) {
146-
this.query = this.$element.val();
147-
return this.process(this.source);
148-
}
149-
150-
// modified typeahead function adding button handling and remove mouseleave
151253
, listen: function () {
152254
this.$element
153255
.on('focus', $.proxy(this.focus, this))
@@ -168,7 +270,49 @@
168270
.on('click', $.proxy(this.toggle, this));
169271
}
170272

171-
// modified typeahead function to clear on type and prevent on moving around
273+
, eventSupported: function(eventName) {
274+
var isSupported = eventName in this.$element;
275+
if (!isSupported) {
276+
this.$element.setAttribute(eventName, 'return;');
277+
isSupported = typeof this.$element[eventName] === 'function';
278+
}
279+
return isSupported;
280+
}
281+
282+
, move: function (e) {
283+
if (!this.shown) {return;}
284+
285+
switch(e.keyCode) {
286+
case 9: // tab
287+
case 13: // enter
288+
case 27: // escape
289+
e.preventDefault();
290+
break;
291+
292+
case 38: // up arrow
293+
e.preventDefault();
294+
this.prev();
295+
break;
296+
297+
case 40: // down arrow
298+
e.preventDefault();
299+
this.next();
300+
break;
301+
}
302+
303+
e.stopPropagation();
304+
}
305+
306+
, keydown: function (e) {
307+
this.suppressKeyPressRepeat = ~$.inArray(e.keyCode, [40,38,9,13,27]);
308+
this.move(e);
309+
}
310+
311+
, keypress: function (e) {
312+
if (this.suppressKeyPressRepeat) {return;}
313+
this.move(e);
314+
}
315+
172316
, keyup: function (e) {
173317
switch(e.keyCode) {
174318
case 40: // down arrow
@@ -183,13 +327,13 @@
183327
break;
184328

185329
case 9: // tab
186-
case 13: // enter
187-
if (!this.shown) return;
330+
case 13: // enter
331+
if (!this.shown) {return;}
188332
this.select();
189333
break;
190334

191-
case 27: // escape
192-
if (!this.shown) return;
335+
case 27: // escape
336+
if (!this.shown) {return;}
193337
this.hide();
194338
break;
195339

@@ -202,7 +346,10 @@
202346
e.preventDefault();
203347
}
204348

205-
// modified typeahead function to force a match and add a delay on hide
349+
, focus: function (e) {
350+
this.focused = true;
351+
}
352+
206353
, blur: function (e) {
207354
var that = this;
208355
this.focused = false;
@@ -215,11 +362,23 @@
215362
if (!this.mousedover && this.shown) {setTimeout(function () { that.hide(); }, 200);}
216363
}
217364

218-
// modified typeahead function to not hide
365+
, click: function (e) {
366+
e.stopPropagation();
367+
e.preventDefault();
368+
this.select();
369+
this.$element.focus();
370+
}
371+
372+
, mouseenter: function (e) {
373+
this.mousedover = true;
374+
this.$menu.find('.active').removeClass('active');
375+
$(e.currentTarget).addClass('active');
376+
}
377+
219378
, mouseleave: function (e) {
220379
this.mousedover = false;
221380
}
222-
});
381+
};
223382

224383
/* COMBOBOX PLUGIN DEFINITION
225384
* =========================== */

js/tests/index.html

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,12 @@
66
<!-- jquery -->
77
<!--<script src="http://code.jquery.com/jquery-1.7.min.js"></script>-->
88
<script src="vendor/jquery.js"></script>
9-
<script src="vendor/bootstrap-typeahead.js"></script>
109

1110
<!-- qunit -->
1211
<link rel="stylesheet" href="vendor/qunit.css" type="text/css" media="screen" />
1312
<script src="vendor/qunit.js"></script>
1413

1514
<!-- plugin sources -->
16-
1715
<script src="../../js/bootstrap-combobox.js"></script>
1816

1917
<!-- unit tests -->

js/tests/unit/bootstrap-combobox.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ $(function () {
228228
, combobox = $select.data('combobox')
229229

230230
$input.val('DOES NOT EXIST')
231-
combobox.lookup()
231+
$input.trigger('keyup')
232232
$input.trigger('blur')
233233

234234
equal($input.val(), '', 'input value was correctly set')

0 commit comments

Comments
 (0)