diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..e43b0f9
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+.DS_Store
diff --git a/README.md b/README.md
index ad3ae9e..59dfa97 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@ We had need of a combobox at work and after looking around at the available opti
## How to use it
-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.
+The dependencies are the Bootstrap stylesheet(CSS or LESS). Include it and then the stylesheet(CSS or LESS) and javascript.
Then just activate the plugin on a normal select box(suggest having a blank option first):
@@ -23,6 +23,37 @@ Then just activate the plugin on a normal select box(suggest having a blank opti
});
+### Options
+
+When activating the plugin, you may include an object containing options for the combobox
+
+ $('.combobox').combobox({bsVersion: '2'});
+
+`menu`: Custom markup for the dropdown menu list element.
+
+`item`: Custom markup for the dropdown menu list items.
+
+`matcher`: Custom function with one `item` argument that compares the item to the input. Defaults to matching on the query being a substring of the item, case insenstive
+
+ `sorter`: Custom function that sorts a list `items` for display in the dropdown
+
+ `highlighter`: Custom function for highlighting an `item`. Defaults to bolding the query within a matched item
+
+ `template`: Custom function that returns markup for the combobox.
+
+ `bsVersion`: Version of bootstrap being used. This is used by the default `template` function to generate markup correctly. Defaults to '3'. Set to '2' for compatibility with Bootstrap 2
+
+## Dependencies
+Uses the latest 1.X version of jQuery and the latest 2.X or 3.X of bootstrap.
+
## Live Example
+### Bootstrap 2.0 Version
http://dl.dropbox.com/u/21368/bootstrap-combobox/index.html
+
+### Bootstrap 3.0 Version
+http://bootstrap-combobox-test.herokuapp.com/
+
+## License
+
+Licensed under the Apache License, Version 2.0
diff --git a/css/bootstrap-combobox.css b/css/bootstrap-combobox.css
index 46e09b9..9c991cf 100644
--- a/css/bootstrap-combobox.css
+++ b/css/bootstrap-combobox.css
@@ -1,124 +1,19 @@
-.combobox-container {
- margin-bottom: 5px;
- *zoom: 1;
-}
-.combobox-container:before,
-.combobox-container:after {
- display: table;
- content: "";
-}
-.combobox-container:after {
- clear: both;
-}
-.combobox-container input,
-.combobox-container .uneditable-input {
- -webkit-border-radius: 0 3px 3px 0;
- -moz-border-radius: 0 3px 3px 0;
- border-radius: 0 3px 3px 0;
-}
-.combobox-container input:focus,
-.combobox-container .uneditable-input:focus {
- position: relative;
- z-index: 2;
-}
-.combobox-container .uneditable-input {
- border-left-color: #ccc;
-}
-.combobox-container .add-on {
- float: left;
- display: inline-block;
- width: auto;
- min-width: 16px;
- height: inherit !important;
- margin-right: -1px;
- padding: 4px 5px;
- font-weight: normal;
- color: #999999;
- text-align: center;
- text-shadow: 0 1px 0 #ffffff;
- background-color: #f5f5f5;
- border: 1px solid #ccc;
- -webkit-border-radius: 3px 0 0 3px;
- -moz-border-radius: 3px 0 0 3px;
- border-radius: 3px 0 0 3px;
-
-}
-.combobox-container .active {
- background-color: #a9dba9;
- border-color: #46a546;
-}
-.combobox-container input,
-.combobox-container .uneditable-input {
- float: left;
- -webkit-border-radius: 3px 0 0 3px;
- -moz-border-radius: 3px 0 0 3px;
- border-radius: 3px 0 0 3px;
-}
-.combobox-container .uneditable-input {
- border-left-color: #eee;
- border-right-color: #ccc;
-}
-.combobox-container .add-on {
- margin-right: 0;
- margin-left: -1px;
- -webkit-border-radius: 0 3px 3px 0;
- -moz-border-radius: 0 3px 3px 0;
- border-radius: 0 3px 3px 0;
-}
-.combobox-container input:first-child {
- *margin-left: -160px;
-}
-.combobox-container input:first-child + .add-on {
- *margin-left: -21px;
-}
-.combobox-container select {
- display: inline-block;
- width: 0;
- height: 0;
- border: 0;
- padding: 0;
- margin: 0;
- text-indent: -99999px;
- *text-indent: 0;
-}
.form-search .combobox-container,
.form-inline .combobox-container {
display: inline-block;
margin-bottom: 0;
vertical-align: top;
}
-.form-search .combobox-container .add-on,
-.form-inline .combobox-container .add-on {
- vertical-align: middle;
-}
-.combobox-selected .combobox-clear {
- display: inline-block;
+.form-search .combobox-container .input-group-addon,
+.form-inline .combobox-container .input-group-addon {
+ width: auto;
}
.combobox-selected .caret {
display: none;
}
-.combobox-clear {
+/* :not doesn't work in IE8 */
+.combobox-container:not(.combobox-selected) .glyphicon-remove {
display: none;
- width: 14px;
- height: 14px;
- line-height: 14px;
- vertical-align: top;
- opacity: 0.3;
- filter: alpha(opacity=30);
-}
-.dropdown:hover .combobox-clear,
-.open.dropdown .combobox-clear {
- opacity: 1;
- filter: alpha(opacity=100);
-}
-.btn .combobox-clear {
- margin-top: 1px;
- margin-left: 1px;
-}
-.btn:hover .combobox-clear,
-.open.btn-group .combobox-clear {
- opacity: 1;
- filter: alpha(opacity=100);
}
.typeahead-long {
max-height: 300px;
@@ -145,6 +40,3 @@
.control-group.success .combobox-container .caret {
border-top-color: #468847;
}
-.btn .combobox-clear [class^="icon-"] {
- line-height: 1.4em;
-}
\ No newline at end of file
diff --git a/js/bootstrap-combobox.js b/js/bootstrap-combobox.js
old mode 100644
new mode 100755
index dc24005..92bbe76
--- a/js/bootstrap-combobox.js
+++ b/js/bootstrap-combobox.js
@@ -1,5 +1,5 @@
/* =============================================================
- * bootstrap-combobox.js v1.1.0-wip
+ * bootstrap-combobox.js v1.1.6
* =============================================================
* Copyright 2012 Daniel Farrell
*
@@ -18,148 +18,337 @@
!function( $ ) {
- "use strict"
+ "use strict";
- var Combobox = function ( element, options ) {
- this.options = $.extend({}, $.fn.combobox.defaults, options)
- this.$source = $(element)
- this.$container = this.setup()
- this.$element = this.$container.find('input[type=text]')
- this.$target = this.$container.find('input[type=hidden]')
- this.$button = this.$container.find('.dropdown-toggle')
- this.$menu = $(this.options.menu).appendTo('body')
- this.matcher = this.options.matcher || this.matcher
- this.sorter = this.options.sorter || this.sorter
- this.highlighter = this.options.highlighter || this.highlighter
- this.shown = false
- this.selected = false
- this.refresh()
- this.transferAttributes()
- this.listen()
- }
+ /* COMBOBOX PUBLIC CLASS DEFINITION
+ * ================================ */
- /* NOTE: COMBOBOX EXTENDS BOOTSTRAP-TYPEAHEAD.js
- ========================================== */
-
- Combobox.prototype = $.extend({}, $.fn.typeahead.Constructor.prototype, {
+ var Combobox = function ( element, options ) {
+ this.options = $.extend({}, $.fn.combobox.defaults, options);
+ this.$source = $(element);
+ this.$container = this.setup();
+ this.$element = this.$container.find('input[type=text]');
+ this.$target = this.$container.find('input[type=hidden]');
+ this.$button = this.$container.find('.dropdown-toggle');
+ this.$menu = $(this.options.menu).appendTo('body');
+ this.template = this.options.template || this.template
+ this.matcher = this.options.matcher || this.matcher;
+ this.sorter = this.options.sorter || this.sorter;
+ this.highlighter = this.options.highlighter || this.highlighter;
+ this.newOptionsAllowed = this.options.newOptionsAllowed;
+ this.shown = false;
+ this.selected = false;
+ this.refresh();
+ this.transferAttributes();
+ this.listen();
+ };
+
+ Combobox.prototype = {
constructor: Combobox
, setup: function () {
- var combobox = $(this.options.template)
- this.$source.before(combobox)
- this.$source.hide()
- return combobox
+ var combobox = $(this.template());
+ this.$source.before(combobox);
+ this.$source.hide();
+ return combobox;
}
+ , disable: function() {
+ this.$element.prop('disabled', true);
+ this.$button.attr('disabled', true);
+ this.disabled = true;
+ this.$container.addClass('combobox-disabled');
+ }
+
+ , enable: function() {
+ this.$element.prop('disabled', false);
+ this.$button.attr('disabled', false);
+ this.disabled = false;
+ this.$container.removeClass('combobox-disabled');
+ }
, parse: function () {
var that = this
, map = {}
, source = []
, selected = false
+ , selectedValue = '';
this.$source.find('option').each(function() {
- var option = $(this)
- if (option.val() == '') {
- that.options.placeholder = option.text()
- return
+ var option = $(this);
+ if (option.val() === '') {
+ that.options.placeholder = option.text();
+ return;
+ }
+ map[option.text()] = option.val();
+ source.push(option.text());
+ if (option.prop('selected')) {
+ selected = option.text();
+ selectedValue = option.val();
}
- map[option.text()] = option.val()
- source.push(option.text())
- if(option.attr('selected')) selected = option.html()
})
- this.map = map
+ this.map = map;
if (selected) {
- this.$element.val(selected)
- this.$container.addClass('combobox-selected')
- this.selected = true
+ this.$element.val(selected);
+ this.$target.val(selectedValue);
+ this.$container.addClass('combobox-selected');
+ this.selected = true;
}
- return source
+ return source;
}
, transferAttributes: function() {
this.options.placeholder = this.$source.attr('data-placeholder') || this.options.placeholder
this.$element.attr('placeholder', this.options.placeholder)
this.$target.prop('name', this.$source.prop('name'))
+ this.$target.val(this.$source.val())
+ this.$source.removeAttr('name') // Remove from source otherwise form will pass parameter twice.
this.$element.attr('required', this.$source.attr('required'))
this.$element.attr('rel', this.$source.attr('rel'))
this.$element.attr('title', this.$source.attr('title'))
this.$element.attr('class', this.$source.attr('class'))
+ this.$element.attr('tabindex', this.$source.attr('tabindex'))
+ this.$source.removeAttr('tabindex')
+ if (this.$source.attr('disabled')!==undefined)
+ this.disable();
}
+ , select: function () {
+ var val = this.$menu.find('.active').attr('data-value');
+ this.$element.val(this.updater(val)).trigger('change');
+ this.$target.val(this.map[val]).trigger('change');
+ this.$source.val(this.map[val]).trigger('change');
+ this.$container.addClass('combobox-selected');
+ this.selected = true;
+ return this.hide();
+ }
+
+ , updater: function (item) {
+ return item;
+ }
+
+ , show: function () {
+ var pos = $.extend({}, this.$element.position(), {
+ height: this.$element[0].offsetHeight
+ });
+
+ this.$menu
+ .insertAfter(this.$element)
+ .css({
+ top: pos.top + pos.height
+ , left: pos.left
+ })
+ .show();
+
+ $('.dropdown-menu').on('mousedown', $.proxy(this.scrollSafety, this));
+
+ this.shown = true;
+ return this;
+ }
+
+ , hide: function () {
+ this.$menu.hide();
+ $('.dropdown-menu').off('mousedown', $.proxy(this.scrollSafety, this));
+ this.$element.on('blur', $.proxy(this.blur, this));
+ this.shown = false;
+ return this;
+ }
+
+ , lookup: function (event) {
+ this.query = this.$element.val();
+ return this.process(this.source);
+ }
+
+ , process: function (items) {
+ var that = this;
+
+ items = $.grep(items, function (item) {
+ return that.matcher(item);
+ })
+
+ items = this.sorter(items);
+
+ if (!items.length) {
+ return this.shown ? this.hide() : this;
+ }
+
+ return this.render(items.slice(0, this.options.items)).show();
+ }
+
+ , template: function() {
+ if (this.options.bsVersion == '2') {
+ return '
'
+ } else {
+ return ''
+ }
+ }
+
+ , matcher: function (item) {
+ return ~item.toLowerCase().indexOf(this.query.toLowerCase());
+ }
+
+ , sorter: function (items) {
+ var beginswith = []
+ , caseSensitive = []
+ , caseInsensitive = []
+ , item;
+
+ while (item = items.shift()) {
+ if (!item.toLowerCase().indexOf(this.query.toLowerCase())) {beginswith.push(item);}
+ else if (~item.indexOf(this.query)) {caseSensitive.push(item);}
+ else {caseInsensitive.push(item);}
+ }
+
+ return beginswith.concat(caseSensitive, caseInsensitive);
+ }
+
+ , highlighter: function (item) {
+ var query = this.query.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, '\\$&');
+ return item.replace(new RegExp('(' + query + ')', 'ig'), function ($1, match) {
+ return '' + match + '';
+ })
+ }
+
+ , render: function (items) {
+ var that = this;
+
+ items = $(items).map(function (i, item) {
+ i = $(that.options.item).attr('data-value', item);
+ i.find('a').html(that.highlighter(item));
+ return i[0];
+ })
+
+ items.first().addClass('active');
+ this.$menu.html(items);
+ return this;
+ }
+
+ , next: function (event) {
+ var active = this.$menu.find('.active').removeClass('active')
+ , next = active.next();
+
+ if (!next.length) {
+ next = $(this.$menu.find('li')[0]);
+ }
+
+ next.addClass('active');
+ }
+
+ , prev: function (event) {
+ var active = this.$menu.find('.active').removeClass('active')
+ , prev = active.prev();
+
+ if (!prev.length) {
+ prev = this.$menu.find('li').last();
+ }
+
+ prev.addClass('active');
+ }
+
, toggle: function () {
- if (this.$container.hasClass('combobox-selected')) {
- this.clearTarget()
- this.triggerChange()
- this.clearElement()
- } else {
- if (this.shown) {
- this.hide()
+ if (!this.disabled) {
+ if (this.$container.hasClass('combobox-selected')) {
+ this.clearTarget();
+ this.triggerChange();
+ this.clearElement();
} else {
- this.clearElement()
- this.lookup()
+ if (this.shown) {
+ this.hide();
+ } else {
+ this.clearElement();
+ this.lookup();
+ }
}
}
}
+ , scrollSafety: function(e) {
+ if (e.target.tagName == 'UL') {
+ this.$element.off('blur');
+ }
+ }
, clearElement: function () {
- this.$element.val('').focus()
+ this.$element.val('').focus();
}
, clearTarget: function () {
- this.$source.val('')
- this.$target.val('')
- this.$container.removeClass('combobox-selected')
- this.selected = false
+ this.$source.val('');
+ this.$target.val('');
+ this.$container.removeClass('combobox-selected');
+ this.selected = false;
}
, triggerChange: function () {
- this.$source.trigger('change')
+ this.$source.trigger('change');
}
, refresh: function () {
- this.source = this.parse()
- this.options.items = this.source.length
+ this.source = this.parse();
+ this.options.items = this.source.length;
}
- // modified typeahead function adding container and target handling
- , select: function () {
- var val = this.$menu.find('.active').attr('data-value')
- this.$element.val(this.updater(val)).trigger('change')
- this.$source.val(this.map[val]).trigger('change')
- this.$target.val(this.map[val]).trigger('change')
- this.$container.addClass('combobox-selected')
- this.selected = true
- return this.hide()
- }
-
- // modified typeahead function removing the blank handling and source function handling
- , lookup: function (event) {
- this.query = this.$element.val()
- return this.process(this.source)
- }
-
- // modified typeahead function adding button handling and remove mouseleave
, listen: function () {
this.$element
.on('focus', $.proxy(this.focus, this))
.on('blur', $.proxy(this.blur, this))
.on('keypress', $.proxy(this.keypress, this))
- .on('keyup', $.proxy(this.keyup, this))
+ .on('keyup', $.proxy(this.keyup, this));
if (this.eventSupported('keydown')) {
- this.$element.on('keydown', $.proxy(this.keydown, this))
+ this.$element.on('keydown', $.proxy(this.keydown, this));
}
this.$menu
.on('click', $.proxy(this.click, this))
.on('mouseenter', 'li', $.proxy(this.mouseenter, this))
- .on('mouseleave', 'li', $.proxy(this.mouseleave, this))
+ .on('mouseleave', 'li', $.proxy(this.mouseleave, this));
this.$button
- .on('click', $.proxy(this.toggle, this))
+ .on('click', $.proxy(this.toggle, this));
+ }
+
+ , eventSupported: function(eventName) {
+ var isSupported = eventName in this.$element;
+ if (!isSupported) {
+ this.$element.setAttribute(eventName, 'return;');
+ isSupported = typeof this.$element[eventName] === 'function';
+ }
+ return isSupported;
+ }
+
+ , move: function (e) {
+ if (!this.shown) {return;}
+
+ switch(e.keyCode) {
+ case 9: // tab
+ case 13: // enter
+ case 27: // escape
+ e.preventDefault();
+ break;
+
+ case 38: // up arrow
+ e.preventDefault();
+ this.prev();
+ break;
+
+ case 40: // down arrow
+ e.preventDefault();
+ this.next();
+ break;
+ }
+
+ e.stopPropagation();
+ }
+
+ , keydown: function (e) {
+ this.suppressKeyPressRepeat = ~$.inArray(e.keyCode, [40,38,9,13,27]);
+ this.move(e);
+ }
+
+ , keypress: function (e) {
+ if (this.suppressKeyPressRepeat) {return;}
+ this.move(e);
}
- // modified typeahead function to clear on type and prevent on moving around
, keyup: function (e) {
switch(e.keyCode) {
case 40: // down arrow
@@ -171,66 +360,88 @@
case 16: // shift
case 17: // ctrl
case 18: // alt
- break
+ break;
case 9: // tab
case 13: // enter
- if (!this.shown) return
- this.select()
- break
+ if (!this.shown) {return;}
+ this.select();
+ break;
case 27: // escape
- if (!this.shown) return
- this.hide()
- break
+ if (!this.shown) {return;}
+ this.hide();
+ break;
default:
- this.clearTarget()
- this.lookup()
+ this.clearTarget();
+ this.lookup();
}
- e.stopPropagation()
- e.preventDefault()
+ e.stopPropagation();
+ e.preventDefault();
}
- // modified typeahead function to force a match and add a delay on hide
+ , focus: function (e) {
+ this.focused = true;
+ }
+
, blur: function (e) {
- var that = this
- this.focused = false
- var val = this.$element.val()
- if (!this.selected && val != '' ) {
- this.$element.val('')
- this.$source.val('').trigger('change')
- this.$target.val('').trigger('change')
+ var that = this;
+ this.focused = false;
+ var val = this.$element.val();
+ if (this.newOptionsAllowed) {
+ if (this.length === 0) {
+ this.$target.val(val);
+ }
+ } else {
+ if (!this.selected && val !== '' ) {
+ this.$element.val('');
+ this.$source.val('').trigger('change');
+ this.$target.val('').trigger('change');
+ }
}
- if (!this.mousedover && this.shown) setTimeout(function () { that.hide() }, 200)
+
+ if (!this.mousedover && this.shown) {setTimeout(function () { that.hide(); }, 200);}
+ }
+
+ , click: function (e) {
+ e.stopPropagation();
+ e.preventDefault();
+ this.select();
+ this.$element.focus();
+ }
+
+ , mouseenter: function (e) {
+ this.mousedover = true;
+ this.$menu.find('.active').removeClass('active');
+ $(e.currentTarget).addClass('active');
}
- // modified typeahead function to not hide
, mouseleave: function (e) {
- this.mousedover = false
+ this.mousedover = false;
}
- })
+ };
/* COMBOBOX PLUGIN DEFINITION
* =========================== */
-
$.fn.combobox = function ( option ) {
return this.each(function () {
var $this = $(this)
, data = $this.data('combobox')
- , options = typeof option == 'object' && option
- if(!data) $this.data('combobox', (data = new Combobox(this, options)))
- if (typeof option == 'string') data[option]()
- })
- }
+ , options = typeof option == 'object' && option;
+ if(!data) {$this.data('combobox', (data = new Combobox(this, options)));}
+ if (typeof option == 'string') {data[option]();}
+ });
+ };
$.fn.combobox.defaults = {
- template: '
'
+ bsVersion: '3'
, menu: ''
, item: ''
- }
+ , newOptionsAllowed: true
+ };
- $.fn.combobox.Constructor = Combobox
+ $.fn.combobox.Constructor = Combobox;
}( window.jQuery );
diff --git a/js/tests/index.html b/js/tests/index.html
index e135dd8..35cc29e 100644
--- a/js/tests/index.html
+++ b/js/tests/index.html
@@ -6,14 +6,12 @@
-
-
diff --git a/js/tests/unit/bootstrap-combobox.js b/js/tests/unit/bootstrap-combobox.js
index 4d465c6..f5dcd29 100644
--- a/js/tests/unit/bootstrap-combobox.js
+++ b/js/tests/unit/bootstrap-combobox.js
@@ -138,7 +138,7 @@ $(function () {
, $input = combobox.$element
, $source = combobox.$source
, $target = combobox.$target
-
+
$input.val('a')
combobox.lookup()
@@ -214,9 +214,11 @@ $(function () {
test("should set as selected if select was selected before load", function () {
var $select = $('')
, $input = $select.combobox().data('combobox').$element
+ , $target = $select.combobox().data('combobox').$target
, combobox = $select.data('combobox')
equal($input.val(), 'ab', 'input value was correctly set')
+ equal($target.val(), 'ab', 'hidden input value was correctly set')
equal($select.val(), 'ab', 'select value was correctly set')
})
@@ -226,7 +228,7 @@ $(function () {
, combobox = $select.data('combobox')
$input.val('DOES NOT EXIST')
- combobox.lookup()
+ $input.trigger('keyup')
$input.trigger('blur')
equal($input.val(), '', 'input value was correctly set')
@@ -294,4 +296,28 @@ $(function () {
combobox.$menu.remove()
})
+
+ test("should use bootstrap 2 classes if bsVersion option is set to '2'", function() {
+ var $select = $('')
+ , $input = $select.combobox({bsVersion: '2'}).data('combobox').$element
+ , combobox = $select.data('combobox')
+
+ ok($input.parent('.input-append').length > 0)
+ ok($input.siblings('span.add-on').length > 0)
+ ok($input.siblings('span.add-on').children('i.icon-remove').length > 0)
+
+ combobox.$menu.remove()
+ })
+
+ test("should respect disabled attribute", function() {
+ var $select = $('')
+ , $input = $select.combobox().data('combobox').$element
+ , combobox = $select.data('combobox')
+
+ equal($input.prop('disabled'), true)
+ equal(combobox.$button.attr('disabled'), "disabled")
+ equal(combobox.disabled, true)
+
+ combobox.$menu.remove()
+ })
})
diff --git a/js/tests/vendor/bootstrap-typeahead.js b/js/tests/vendor/bootstrap-typeahead.js
deleted file mode 100644
index 960f2af..0000000
--- a/js/tests/vendor/bootstrap-typeahead.js
+++ /dev/null
@@ -1,335 +0,0 @@
-/* =============================================================
- * bootstrap-typeahead.js v2.3.0
- * http://twitter.github.com/bootstrap/javascript.html#typeahead
- * =============================================================
- * Copyright 2012 Twitter, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- * ============================================================ */
-
-
-!function($){
-
- "use strict"; // jshint ;_;
-
-
- /* TYPEAHEAD PUBLIC CLASS DEFINITION
- * ================================= */
-
- var Typeahead = function (element, options) {
- this.$element = $(element)
- this.options = $.extend({}, $.fn.typeahead.defaults, options)
- this.matcher = this.options.matcher || this.matcher
- this.sorter = this.options.sorter || this.sorter
- this.highlighter = this.options.highlighter || this.highlighter
- this.updater = this.options.updater || this.updater
- this.source = this.options.source
- this.$menu = $(this.options.menu)
- this.shown = false
- this.listen()
- }
-
- Typeahead.prototype = {
-
- constructor: Typeahead
-
- , select: function () {
- var val = this.$menu.find('.active').attr('data-value')
- this.$element
- .val(this.updater(val))
- .change()
- return this.hide()
- }
-
- , updater: function (item) {
- return item
- }
-
- , show: function () {
- var pos = $.extend({}, this.$element.position(), {
- height: this.$element[0].offsetHeight
- })
-
- this.$menu
- .insertAfter(this.$element)
- .css({
- top: pos.top + pos.height
- , left: pos.left
- })
- .show()
-
- this.shown = true
- return this
- }
-
- , hide: function () {
- this.$menu.hide()
- this.shown = false
- return this
- }
-
- , lookup: function (event) {
- var items
-
- this.query = this.$element.val()
-
- if (!this.query || this.query.length < this.options.minLength) {
- return this.shown ? this.hide() : this
- }
-
- items = $.isFunction(this.source) ? this.source(this.query, $.proxy(this.process, this)) : this.source
-
- return items ? this.process(items) : this
- }
-
- , process: function (items) {
- var that = this
-
- items = $.grep(items, function (item) {
- return that.matcher(item)
- })
-
- items = this.sorter(items)
-
- if (!items.length) {
- return this.shown ? this.hide() : this
- }
-
- return this.render(items.slice(0, this.options.items)).show()
- }
-
- , matcher: function (item) {
- return ~item.toLowerCase().indexOf(this.query.toLowerCase())
- }
-
- , sorter: function (items) {
- var beginswith = []
- , caseSensitive = []
- , caseInsensitive = []
- , item
-
- while (item = items.shift()) {
- if (!item.toLowerCase().indexOf(this.query.toLowerCase())) beginswith.push(item)
- else if (~item.indexOf(this.query)) caseSensitive.push(item)
- else caseInsensitive.push(item)
- }
-
- return beginswith.concat(caseSensitive, caseInsensitive)
- }
-
- , highlighter: function (item) {
- var query = this.query.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, '\\$&')
- return item.replace(new RegExp('(' + query + ')', 'ig'), function ($1, match) {
- return '' + match + ''
- })
- }
-
- , render: function (items) {
- var that = this
-
- items = $(items).map(function (i, item) {
- i = $(that.options.item).attr('data-value', item)
- i.find('a').html(that.highlighter(item))
- return i[0]
- })
-
- items.first().addClass('active')
- this.$menu.html(items)
- return this
- }
-
- , next: function (event) {
- var active = this.$menu.find('.active').removeClass('active')
- , next = active.next()
-
- if (!next.length) {
- next = $(this.$menu.find('li')[0])
- }
-
- next.addClass('active')
- }
-
- , prev: function (event) {
- var active = this.$menu.find('.active').removeClass('active')
- , prev = active.prev()
-
- if (!prev.length) {
- prev = this.$menu.find('li').last()
- }
-
- prev.addClass('active')
- }
-
- , listen: function () {
- this.$element
- .on('focus', $.proxy(this.focus, this))
- .on('blur', $.proxy(this.blur, this))
- .on('keypress', $.proxy(this.keypress, this))
- .on('keyup', $.proxy(this.keyup, this))
-
- if (this.eventSupported('keydown')) {
- this.$element.on('keydown', $.proxy(this.keydown, this))
- }
-
- this.$menu
- .on('click', $.proxy(this.click, this))
- .on('mouseenter', 'li', $.proxy(this.mouseenter, this))
- .on('mouseleave', 'li', $.proxy(this.mouseleave, this))
- }
-
- , eventSupported: function(eventName) {
- var isSupported = eventName in this.$element
- if (!isSupported) {
- this.$element.setAttribute(eventName, 'return;')
- isSupported = typeof this.$element[eventName] === 'function'
- }
- return isSupported
- }
-
- , move: function (e) {
- if (!this.shown) return
-
- switch(e.keyCode) {
- case 9: // tab
- case 13: // enter
- case 27: // escape
- e.preventDefault()
- break
-
- case 38: // up arrow
- e.preventDefault()
- this.prev()
- break
-
- case 40: // down arrow
- e.preventDefault()
- this.next()
- break
- }
-
- e.stopPropagation()
- }
-
- , keydown: function (e) {
- this.suppressKeyPressRepeat = ~$.inArray(e.keyCode, [40,38,9,13,27])
- this.move(e)
- }
-
- , keypress: function (e) {
- if (this.suppressKeyPressRepeat) return
- this.move(e)
- }
-
- , keyup: function (e) {
- switch(e.keyCode) {
- case 40: // down arrow
- case 38: // up arrow
- case 16: // shift
- case 17: // ctrl
- case 18: // alt
- break
-
- case 9: // tab
- case 13: // enter
- if (!this.shown) return
- this.select()
- break
-
- case 27: // escape
- if (!this.shown) return
- this.hide()
- break
-
- default:
- this.lookup()
- }
-
- e.stopPropagation()
- e.preventDefault()
- }
-
- , focus: function (e) {
- this.focused = true
- }
-
- , blur: function (e) {
- this.focused = false
- if (!this.mousedover && this.shown) this.hide()
- }
-
- , click: function (e) {
- e.stopPropagation()
- e.preventDefault()
- this.select()
- this.$element.focus()
- }
-
- , mouseenter: function (e) {
- this.mousedover = true
- this.$menu.find('.active').removeClass('active')
- $(e.currentTarget).addClass('active')
- }
-
- , mouseleave: function (e) {
- this.mousedover = false
- if (!this.focused && this.shown) this.hide()
- }
-
- }
-
-
- /* TYPEAHEAD PLUGIN DEFINITION
- * =========================== */
-
- var old = $.fn.typeahead
-
- $.fn.typeahead = function (option) {
- return this.each(function () {
- var $this = $(this)
- , data = $this.data('typeahead')
- , options = typeof option == 'object' && option
- if (!data) $this.data('typeahead', (data = new Typeahead(this, options)))
- if (typeof option == 'string') data[option]()
- })
- }
-
- $.fn.typeahead.defaults = {
- source: []
- , items: 8
- , menu: ''
- , item: ''
- , minLength: 1
- }
-
- $.fn.typeahead.Constructor = Typeahead
-
-
- /* TYPEAHEAD NO CONFLICT
- * =================== */
-
- $.fn.typeahead.noConflict = function () {
- $.fn.typeahead = old
- return this
- }
-
-
- /* TYPEAHEAD DATA-API
- * ================== */
-
- $(document).on('focus.typeahead.data-api', '[data-provide="typeahead"]', function (e) {
- var $this = $(this)
- if ($this.data('typeahead')) return
- $this.typeahead($this.data())
- })
-
-}(window.jQuery);
diff --git a/less/combobox.less b/less/combobox.less
index 77c0e5d..e5211ed 100644
--- a/less/combobox.less
+++ b/less/combobox.less
@@ -1,63 +1,22 @@
-.combobox-container {
- .input-append();
- select {
- display: inline-block;
- width: 0;
- height: 0;
- border: 0;
- padding: 0;
- margin: 0;
- text-indent: -99999px;
- *text-indent: 0;
- }
-}
-
-.form-search .combobox-container,
-.form-inline .combobox-container {
- display: inline-block;
- margin-bottom: 0;
- vertical-align: top;
-}
-
-.form-search .combobox-container .add-on,
-.form-inline .combobox-container .add-on {
- vertical-align: middle;
-}
-
-.combobox-selected {
- .combobox-clear {
+.form-search,
+.form-inline {
+ .combobox-container {
display: inline-block;
- }
- .caret {
- display: none;
+ margin-bottom: 0;
+ vertical-align: top;
+ .input-group-addon{
+ width: auto;
+ }
}
}
-.combobox-clear {
+.combobox-selected .caret {
display: none;
- width: 14px;
- height: 14px;
- line-height: 14px;
- vertical-align: top;
- .opacity(30);
-}
-
-.dropdown:hover .combobox-clear,
-.open.dropdown .combobox-clear {
- .opacity(100);
-}
-
-.btn .combobox-clear {
- margin-top: 1px;
- margin-left: 1px;
-}
-
-.btn .combobox-clear [class^="icon-"] {
- line-height: 1.4em;
}
-.btn:hover .combobox-clear, .open.btn-group .combobox-clear {
- .opacity(100);
+/* :not doesn't work in IE8 */
+.combobox-container:not(.combobox-selected) .glyphicon-remove {
+ display: none;
}
.typeahead-long {
@@ -65,38 +24,32 @@
overflow-y: auto;
}
-.control-group.error {
- .combobox-container {
- .add-on {
- color: #B94A48;
- border-color: #B94A48;
- }
- .caret {
- border-top-color: #B94A48;
- }
+.control-group.error .combobox-container{
+ .add-on {
+ color: #B94A48;
+ border-color: #B94A48;
+ }
+ .caret {
+ border-top-color: #B94A48;
}
}
-.control-group.warning {
- .combobox-container {
- .add-on {
- color: #C09853;
- border-color: #C09853;
- }
- .caret {
- border-top-color: #C09853;
- }
+.control-group.warning .combobox-container {
+ .add-on {
+ color: #C09853;
+ border-color: #C09853;
+ }
+ .caret {
+ border-top-color: #C09853;
}
}
-.control-group.success {
- .combobox-container {
- .add-on {
- color: #468847;
- border-color: #468847;
- }
- .caret {
- border-top-color: #468847;
- }
+.control-group.success .combobox-container{
+ .add-on {
+ color: #468847;
+ border-color: #468847;
+ }
+ .caret {
+ border-top-color: #468847;
}
}