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; } }