From dab9f5f3fad50bb902184e2fc58ac1f136393097 Mon Sep 17 00:00:00 2001 From: Daniel Farrell Date: Tue, 5 Mar 2013 17:03:15 -0500 Subject: [PATCH 01/30] Tag 1.1.0 --- js/bootstrap-combobox.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/bootstrap-combobox.js b/js/bootstrap-combobox.js index dc24005..e3ce2af 100644 --- 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.0 * ============================================================= * Copyright 2012 Daniel Farrell * From 9e5600a2d71b786518102c7c241538d97d72cbe9 Mon Sep 17 00:00:00 2001 From: Rob Jackson Date: Fri, 15 Mar 2013 17:14:23 +0000 Subject: [PATCH 02/30] Remove "name" attribute from source element to stop the form passing the parameter twice. --- js/bootstrap-combobox.js | 1 + 1 file changed, 1 insertion(+) diff --git a/js/bootstrap-combobox.js b/js/bootstrap-combobox.js index e3ce2af..03ccaa9 100644 --- a/js/bootstrap-combobox.js +++ b/js/bootstrap-combobox.js @@ -80,6 +80,7 @@ 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.$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')) From 2b656a2ebb0f5770aa514ecf49f4284abe1d61b2 Mon Sep 17 00:00:00 2001 From: Daniel Farrell Date: Fri, 15 Mar 2013 14:52:52 -0400 Subject: [PATCH 03/30] Release 1.1.1 --- js/bootstrap-combobox.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/js/bootstrap-combobox.js b/js/bootstrap-combobox.js index 03ccaa9..1eec3a4 100644 --- a/js/bootstrap-combobox.js +++ b/js/bootstrap-combobox.js @@ -1,5 +1,5 @@ /* ============================================================= - * bootstrap-combobox.js v1.1.0 + * bootstrap-combobox.js v1.1.1 * ============================================================= * Copyright 2012 Daniel Farrell * @@ -18,7 +18,7 @@ !function( $ ) { - "use strict" + "use strict"; var Combobox = function ( element, options ) { this.options = $.extend({}, $.fn.combobox.defaults, options) @@ -59,7 +59,7 @@ , selected = false this.$source.find('option').each(function() { var option = $(this) - if (option.val() == '') { + if (option.val() === '') { that.options.placeholder = option.text() return } @@ -199,7 +199,7 @@ var that = this this.focused = false var val = this.$element.val() - if (!this.selected && val != '' ) { + if (!this.selected && val !== '' ) { this.$element.val('') this.$source.val('').trigger('change') this.$target.val('').trigger('change') From 05ec87f3d21ffac1daac671bff17c92f6e4b6942 Mon Sep 17 00:00:00 2001 From: Guy Carpenter Date: Fri, 22 Mar 2013 14:20:36 +1000 Subject: [PATCH 04/30] Set value of hidden input from select box during initialization. --- js/bootstrap-combobox.js | 1 + 1 file changed, 1 insertion(+) mode change 100644 => 100755 js/bootstrap-combobox.js diff --git a/js/bootstrap-combobox.js b/js/bootstrap-combobox.js old mode 100644 new mode 100755 index 1eec3a4..56ed243 --- a/js/bootstrap-combobox.js +++ b/js/bootstrap-combobox.js @@ -80,6 +80,7 @@ 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.prop('value', 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')) From 5f607ed0073614c94f6774d753b7fe9adc6f3145 Mon Sep 17 00:00:00 2001 From: Guy Carpenter Date: Sat, 23 Mar 2013 07:52:34 +1000 Subject: [PATCH 05/30] Changed initial value fix to more consise form per request from Daniel Farrel --- js/bootstrap-combobox.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/bootstrap-combobox.js b/js/bootstrap-combobox.js index 56ed243..dc908bf 100755 --- a/js/bootstrap-combobox.js +++ b/js/bootstrap-combobox.js @@ -80,7 +80,7 @@ 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.prop('value', this.$source.val()) + 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')) From 5c1a044bbaec3a2cb0988178e56ff7fa70dd7a30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Grundko=CC=88tter?= Date: Sat, 22 Jun 2013 09:24:59 +0200 Subject: [PATCH 06/30] Added missing tabindex from original element --- js/bootstrap-combobox.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/js/bootstrap-combobox.js b/js/bootstrap-combobox.js index dc908bf..b5f919e 100755 --- a/js/bootstrap-combobox.js +++ b/js/bootstrap-combobox.js @@ -86,6 +86,8 @@ 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') } , toggle: function () { From 0901861c3bc8c89cfd568bc9e153c82327bbce9b Mon Sep 17 00:00:00 2001 From: Daniel Farrell Date: Sat, 22 Jun 2013 08:58:12 -0400 Subject: [PATCH 07/30] Increment version number --- js/bootstrap-combobox.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/bootstrap-combobox.js b/js/bootstrap-combobox.js index b5f919e..8c803c6 100755 --- a/js/bootstrap-combobox.js +++ b/js/bootstrap-combobox.js @@ -1,5 +1,5 @@ /* ============================================================= - * bootstrap-combobox.js v1.1.1 + * bootstrap-combobox.js v1.1.2 * ============================================================= * Copyright 2012 Daniel Farrell * From 91604266a7c3bf5cee0e7f9ab01fd47814ed4fcc Mon Sep 17 00:00:00 2001 From: dudabone Date: Sun, 23 Jun 2013 17:44:34 +0300 Subject: [PATCH 08/30] Update bootstrap-combobox.js --- js/bootstrap-combobox.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/bootstrap-combobox.js b/js/bootstrap-combobox.js index 8c803c6..ed1c358 100755 --- a/js/bootstrap-combobox.js +++ b/js/bootstrap-combobox.js @@ -65,7 +65,7 @@ } map[option.text()] = option.val() source.push(option.text()) - if(option.attr('selected')) selected = option.html() + if(option.attr('selected')) selected = option.text() }) this.map = map if (selected) { From 0421f16d5ea00b40200c32ef0774caf0ff08db86 Mon Sep 17 00:00:00 2001 From: Daniel Farrell Date: Sun, 23 Jun 2013 20:47:02 -0400 Subject: [PATCH 09/30] Increment version to 1.1.3 --- js/bootstrap-combobox.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/bootstrap-combobox.js b/js/bootstrap-combobox.js index ed1c358..edd7f1b 100755 --- a/js/bootstrap-combobox.js +++ b/js/bootstrap-combobox.js @@ -1,5 +1,5 @@ /* ============================================================= - * bootstrap-combobox.js v1.1.2 + * bootstrap-combobox.js v1.1.3 * ============================================================= * Copyright 2012 Daniel Farrell * From 1b6a7c57a6fe3cc272e1010edd019fc958e8b255 Mon Sep 17 00:00:00 2001 From: xiaohwan Date: Tue, 23 Jul 2013 06:08:51 -0700 Subject: [PATCH 10/30] Per jQuery doc, .prop() should be used to check whether an option is selected after jQuery 1.6. http://api.jquery.com/prop/ --- js/bootstrap-combobox.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/bootstrap-combobox.js b/js/bootstrap-combobox.js index 1b944a5..6d9e139 100755 --- a/js/bootstrap-combobox.js +++ b/js/bootstrap-combobox.js @@ -66,7 +66,7 @@ } map[option.text()] = option.val() source.push(option.text()) - if (option.attr('selected')) { + if (option.prop('selected')) { selected = option.text() selectedValue = option.val() } From c67e203d0542ca7680ac0735a24073a6564feaef Mon Sep 17 00:00:00 2001 From: Daniel Farrell Date: Fri, 26 Jul 2013 14:55:27 -0400 Subject: [PATCH 11/30] Set hidden field value if preselected, version 1.1.4 --- js/bootstrap-combobox.js | 9 +++++++-- js/tests/unit/bootstrap-combobox.js | 2 ++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/js/bootstrap-combobox.js b/js/bootstrap-combobox.js index edd7f1b..1b944a5 100755 --- a/js/bootstrap-combobox.js +++ b/js/bootstrap-combobox.js @@ -1,5 +1,5 @@ /* ============================================================= - * bootstrap-combobox.js v1.1.3 + * bootstrap-combobox.js v1.1.4 * ============================================================= * Copyright 2012 Daniel Farrell * @@ -57,6 +57,7 @@ , map = {} , source = [] , selected = false + , selectedValue = '' this.$source.find('option').each(function() { var option = $(this) if (option.val() === '') { @@ -65,11 +66,15 @@ } map[option.text()] = option.val() source.push(option.text()) - if(option.attr('selected')) selected = option.text() + if (option.attr('selected')) { + selected = option.text() + selectedValue = option.val() + } }) this.map = map if (selected) { this.$element.val(selected) + this.$target.val(selectedValue) this.$container.addClass('combobox-selected') this.selected = true } diff --git a/js/tests/unit/bootstrap-combobox.js b/js/tests/unit/bootstrap-combobox.js index 4d465c6..2c007f7 100644 --- a/js/tests/unit/bootstrap-combobox.js +++ b/js/tests/unit/bootstrap-combobox.js @@ -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') }) From df9e809a36ea3852e29a5662a26edbacffa5a9cd Mon Sep 17 00:00:00 2001 From: Kersten Burkhardt Date: Thu, 25 Apr 2013 15:28:19 +0300 Subject: [PATCH 12/30] made uglify compatible --- js/bootstrap-combobox.js | 202 +++++++++++++++++++-------------------- 1 file changed, 101 insertions(+), 101 deletions(-) diff --git a/js/bootstrap-combobox.js b/js/bootstrap-combobox.js index 6d9e139..999b8f5 100755 --- a/js/bootstrap-combobox.js +++ b/js/bootstrap-combobox.js @@ -21,22 +21,22 @@ "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() - } + 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(); + }; /* NOTE: COMBOBOX EXTENDS BOOTSTRAP-TYPEAHEAD.js ========================================== */ @@ -46,10 +46,10 @@ constructor: Combobox , setup: function () { - var combobox = $(this.options.template) - this.$source.before(combobox) - this.$source.hide() - return combobox + var combobox = $(this.options.template); + this.$source.before(combobox); + this.$source.hide(); + return combobox; } , parse: function () { @@ -57,94 +57,94 @@ , map = {} , source = [] , selected = false - , selectedValue = '' + , selectedValue = ''; this.$source.find('option').each(function() { - var option = $(this) + var option = $(this); if (option.val() === '') { - that.options.placeholder = option.text() - return + that.options.placeholder = option.text(); + return; } - map[option.text()] = option.val() - source.push(option.text()) + map[option.text()] = option.val(); + source.push(option.text()); if (option.prop('selected')) { - selected = option.text() - selectedValue = option.val() + selected = option.text(); + selectedValue = option.val(); } }) - this.map = map + this.map = map; if (selected) { - this.$element.val(selected) - this.$target.val(selectedValue) - 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') + 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'); } , toggle: function () { if (this.$container.hasClass('combobox-selected')) { - this.clearTarget() - this.triggerChange() - this.clearElement() + this.clearTarget(); + this.triggerChange(); + this.clearElement(); } else { if (this.shown) { - this.hide() + this.hide(); } else { - this.clearElement() - this.lookup() + this.clearElement(); + this.lookup(); } } } , 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() + 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) + this.query = this.$element.val(); + return this.process(this.source); } // modified typeahead function adding button handling and remove mouseleave @@ -153,19 +153,19 @@ .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)); } // modified typeahead function to clear on type and prevent on moving around @@ -180,46 +180,46 @@ case 16: // shift case 17: // ctrl case 18: // alt - break + break; case 9: // tab - case 13: // enter - if (!this.shown) return - this.select() - break + case 13: // enter + if (!this.shown) return; + this.select(); + break; - case 27: // escape - if (!this.shown) return - this.hide() - break + case 27: // escape + 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 , blur: function (e) { - var that = this - this.focused = false - var val = this.$element.val() + 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') + 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);} } // modified typeahead function to not hide , mouseleave: function (e) { - this.mousedover = false + this.mousedover = false; } - }) + }); /* COMBOBOX PLUGIN DEFINITION * =========================== */ @@ -228,18 +228,18 @@ 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: '
' , menu: '' , item: '
  • ' - } + }; - $.fn.combobox.Constructor = Combobox + $.fn.combobox.Constructor = Combobox; }( window.jQuery ); From 6779edeba89a4573b11bfcf14188a48991305816 Mon Sep 17 00:00:00 2001 From: Daniel Farrell Date: Sat, 5 Oct 2013 11:01:45 -0400 Subject: [PATCH 13/30] No longer depend on bootstrap-typeahead It went away in bootstrap 3.0, so it is not reliable anymore --- README.md | 6 +- js/bootstrap-combobox.js | 221 +++++++++++++--- js/tests/index.html | 2 - js/tests/unit/bootstrap-combobox.js | 2 +- js/tests/vendor/bootstrap-typeahead.js | 335 ------------------------- 5 files changed, 196 insertions(+), 370 deletions(-) delete mode 100644 js/tests/vendor/bootstrap-typeahead.js diff --git a/README.md b/README.md index ad3ae9e..f365ba3 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): @@ -26,3 +26,7 @@ Then just activate the plugin on a normal select box(suggest having a blank opti ## Live Example http://dl.dropbox.com/u/21368/bootstrap-combobox/index.html + +## License + +Licensed under the Apache License, Version 2.0 diff --git a/js/bootstrap-combobox.js b/js/bootstrap-combobox.js index 999b8f5..1d92d59 100755 --- a/js/bootstrap-combobox.js +++ b/js/bootstrap-combobox.js @@ -1,5 +1,5 @@ /* ============================================================= - * bootstrap-combobox.js v1.1.4 + * bootstrap-combobox.js v1.1.5 * ============================================================= * Copyright 2012 Daniel Farrell * @@ -20,6 +20,9 @@ "use strict"; + /* COMBOBOX PUBLIC CLASS DEFINITION + * ================================ */ + var Combobox = function ( element, options ) { this.options = $.extend({}, $.fn.combobox.defaults, options); this.$source = $(element); @@ -38,10 +41,7 @@ this.listen(); }; - /* NOTE: COMBOBOX EXTENDS BOOTSTRAP-TYPEAHEAD.js - ========================================== */ - - Combobox.prototype = $.extend({}, $.fn.typeahead.Constructor.prototype, { + Combobox.prototype = { constructor: Combobox @@ -95,6 +95,126 @@ this.$source.removeAttr('tabindex'); } + , 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(); + } + + , 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) { + 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(); + } + + , 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(); @@ -130,24 +250,6 @@ 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)) @@ -168,7 +270,49 @@ .on('click', $.proxy(this.toggle, this)); } - // modified typeahead function to clear on type and prevent on moving around + , 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 @@ -183,13 +327,13 @@ break; case 9: // tab - case 13: // enter - if (!this.shown) return; + case 13: // enter + if (!this.shown) {return;} this.select(); break; - case 27: // escape - if (!this.shown) return; + case 27: // escape + if (!this.shown) {return;} this.hide(); break; @@ -202,7 +346,10 @@ 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; @@ -215,11 +362,23 @@ if (!this.mousedover && this.shown) {setTimeout(function () { that.hide(); }, 200);} } - // modified typeahead function to not 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; } - }); + }; /* COMBOBOX PLUGIN DEFINITION * =========================== */ 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 2c007f7..9cacf64 100644 --- a/js/tests/unit/bootstrap-combobox.js +++ b/js/tests/unit/bootstrap-combobox.js @@ -228,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') 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); From d089ad921a073d87d57907a9b718d73684706984 Mon Sep 17 00:00:00 2001 From: Matthew Osborn Date: Mon, 14 Oct 2013 12:51:24 -0700 Subject: [PATCH 14/30] trigger original element shcnage event after hidden one --- js/bootstrap-combobox.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/bootstrap-combobox.js b/js/bootstrap-combobox.js index 1d92d59..207a8fd 100755 --- a/js/bootstrap-combobox.js +++ b/js/bootstrap-combobox.js @@ -98,8 +98,8 @@ , 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.$source.val(this.map[val]).trigger('change'); this.$container.addClass('combobox-selected'); this.selected = true; return this.hide(); From 2ad28201de23173e5c039ad5d2b95d761e0bafda Mon Sep 17 00:00:00 2001 From: Guy Lister Date: Fri, 7 Feb 2014 17:42:30 -0500 Subject: [PATCH 15/30] Prevent dropdown from disappearing when mouse downed on scrollbar. (Tested in IE8.) --- js/bootstrap-combobox.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/js/bootstrap-combobox.js b/js/bootstrap-combobox.js index 207a8fd..0181b3e 100755 --- a/js/bootstrap-combobox.js +++ b/js/bootstrap-combobox.js @@ -122,12 +122,16 @@ }) .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; } @@ -230,6 +234,11 @@ } } + , scrollSafety: function(e) { + if (e.target.tagName == 'UL') { + this.$element.off('blur'); + } + } , clearElement: function () { this.$element.val('').focus(); } From a3a545e6d2f699a0c4579214dce410b212f4991b Mon Sep 17 00:00:00 2001 From: Daniel Farrell Date: Sat, 8 Mar 2014 14:00:47 -0500 Subject: [PATCH 16/30] Update README to look for new maintainer --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index f365ba3..1bf34dd 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,10 @@ We had need of a combobox at work and after looking around at the available options I was not happy with any of them. The project had all it's styling based on Twitter's Bootstrap, so building on that made sense. +# Looking for a new maintainer + +I am looking for a new maintainer for this library if anyone is interested. I have changed jobs and no longer use it in any projects. It would be better served by being maintained by someone who uses it regularly. If you are interested let me know by commenting on [this issue](https://github.com/danielfarrell/bootstrap-combobox/issues/124). + ## How to use it The dependencies are the Bootstrap stylesheet(CSS or LESS). Include it and then the stylesheet(CSS or LESS) and javascript. From 14cf0cc7c8275c54f1251e7f9b065023851bad79 Mon Sep 17 00:00:00 2001 From: Patrick Wiseman Date: Thu, 10 Apr 2014 01:19:45 -0400 Subject: [PATCH 17/30] Updates for bootstrap 3 --- css/bootstrap-combobox.css | 116 +------------------------------------ js/bootstrap-combobox.js | 2 +- less/combobox.less | 110 ++++++++++------------------------- 3 files changed, 33 insertions(+), 195 deletions(-) diff --git a/css/bootstrap-combobox.css b/css/bootstrap-combobox.css index 46e09b9..eb36208 100644 --- a/css/bootstrap-combobox.css +++ b/css/bootstrap-combobox.css @@ -1,124 +1,15 @@ -.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; -} .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 +36,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 index 207a8fd..910daf2 100755 --- a/js/bootstrap-combobox.js +++ b/js/bootstrap-combobox.js @@ -394,7 +394,7 @@ }; $.fn.combobox.defaults = { - template: '
    ' + template: '
    ' , menu: '' , item: '
  • ' }; diff --git a/less/combobox.less b/less/combobox.less index 77c0e5d..90fbfd5 100644 --- a/less/combobox.less +++ b/less/combobox.less @@ -1,63 +1,19 @@ -.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; } } -.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 +21,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; } } From 696ee45b5df4138e801350b2d2b36f6766cf2b0b Mon Sep 17 00:00:00 2001 From: Jacob Robertson Date: Wed, 16 Apr 2014 11:40:22 -0400 Subject: [PATCH 18/30] Ignore .DS_Store --- .gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e43b0f9 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.DS_Store From b0a943eb6bf9c1baa88a52f6573876cabb255be4 Mon Sep 17 00:00:00 2001 From: Jacob Robertson Date: Wed, 16 Apr 2014 11:41:48 -0400 Subject: [PATCH 19/30] Graceful degradation for bootstrap 2 --- js/bootstrap-combobox.js | 11 +++++++++-- js/tests/unit/bootstrap-combobox.js | 14 +++++++++++++- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/js/bootstrap-combobox.js b/js/bootstrap-combobox.js index 910daf2..c93ffb7 100755 --- a/js/bootstrap-combobox.js +++ b/js/bootstrap-combobox.js @@ -46,7 +46,7 @@ constructor: Combobox , setup: function () { - var combobox = $(this.options.template); + var combobox = $(this.options.template(this.options.bsVersion)); this.$source.before(combobox); this.$source.hide(); return combobox; @@ -394,7 +394,14 @@ }; $.fn.combobox.defaults = { - template: '
    ' + bsVersion: '3' + , template: function(bsVersion) { + if (bsVersion == '2') { + return '
    ' + } else { + return '
    ' + } + } , menu: '' , item: '
  • ' }; diff --git a/js/tests/unit/bootstrap-combobox.js b/js/tests/unit/bootstrap-combobox.js index 9cacf64..d6ad703 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() @@ -296,4 +296,16 @@ $(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() + }) }) From 2fb999c7b675a84a2775a1abfd713354b7dbd6d6 Mon Sep 17 00:00:00 2001 From: Jacob Robertson Date: Wed, 16 Apr 2014 14:02:13 -0400 Subject: [PATCH 20/30] Cleaned up graceful degradation options --- js/bootstrap-combobox.js | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/js/bootstrap-combobox.js b/js/bootstrap-combobox.js index c93ffb7..06b1266 100755 --- a/js/bootstrap-combobox.js +++ b/js/bootstrap-combobox.js @@ -31,6 +31,7 @@ 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; @@ -46,7 +47,7 @@ constructor: Combobox , setup: function () { - var combobox = $(this.options.template(this.options.bsVersion)); + var combobox = $(this.template()); this.$source.before(combobox); this.$source.hide(); return combobox; @@ -153,6 +154,14 @@ 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()); } @@ -395,13 +404,6 @@ $.fn.combobox.defaults = { bsVersion: '3' - , template: function(bsVersion) { - if (bsVersion == '2') { - return '
    ' - } else { - return '
    ' - } - } , menu: '' , item: '
  • ' }; From acc4791ce778f4ea999e6195d8283b569516f280 Mon Sep 17 00:00:00 2001 From: Jacob Robertson Date: Wed, 16 Apr 2014 14:02:37 -0400 Subject: [PATCH 21/30] Updated README.md with combobox options --- README.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/README.md b/README.md index 1bf34dd..60aad33 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,26 @@ 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 + ## Live Example http://dl.dropbox.com/u/21368/bootstrap-combobox/index.html From bc7846bbbe9689d66a41f39ce7e90b5d37a7f498 Mon Sep 17 00:00:00 2001 From: Jacob Robertson Date: Fri, 2 May 2014 12:23:40 -0400 Subject: [PATCH 22/30] disabled state feature with tests. Based on https://github.com/danielfarrell/bootstrap-combobox/pull/95 --- js/bootstrap-combobox.js | 56 ++++++++++++++++++----------- js/tests/unit/bootstrap-combobox.js | 12 +++++++ 2 files changed, 47 insertions(+), 21 deletions(-) diff --git a/js/bootstrap-combobox.js b/js/bootstrap-combobox.js index 06b1266..714af05 100755 --- a/js/bootstrap-combobox.js +++ b/js/bootstrap-combobox.js @@ -53,6 +53,17 @@ return combobox; } + , disable: function() { + this.$element.prop('disabled', true) + this.$button.attr('disabled', true) + this.disabled = true + } + + , enable: function() { + this.$element.prop('disabled', false) + this.$button.attr('disabled', false) + this.disabled = false + } , parse: function () { var that = this , map = {} @@ -83,17 +94,19 @@ } , 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'); + 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 () { @@ -225,16 +238,18 @@ } , toggle: function () { - if (this.$container.hasClass('combobox-selected')) { - this.clearTarget(); - this.triggerChange(); - this.clearElement(); - } else { - if (this.shown) { - this.hide(); - } else { + if (!this.disabled) { + if (this.$container.hasClass('combobox-selected')) { + this.clearTarget(); + this.triggerChange(); this.clearElement(); - this.lookup(); + } else { + if (this.shown) { + this.hide(); + } else { + this.clearElement(); + this.lookup(); + } } } } @@ -391,7 +406,6 @@ /* COMBOBOX PLUGIN DEFINITION * =========================== */ - $.fn.combobox = function ( option ) { return this.each(function () { var $this = $(this) diff --git a/js/tests/unit/bootstrap-combobox.js b/js/tests/unit/bootstrap-combobox.js index d6ad703..f5dcd29 100644 --- a/js/tests/unit/bootstrap-combobox.js +++ b/js/tests/unit/bootstrap-combobox.js @@ -308,4 +308,16 @@ $(function () { 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() + }) }) From a3de45b8b4a01200d1b55f9fc40225846ae67855 Mon Sep 17 00:00:00 2001 From: Patrick Wiseman Date: Tue, 13 May 2014 13:12:14 -0400 Subject: [PATCH 23/30] Fixes bug where inline comboboxes did not work with bootstrap 3.0 styling. --- css/bootstrap-combobox.css | 4 ++++ less/combobox.less | 3 +++ 2 files changed, 7 insertions(+) diff --git a/css/bootstrap-combobox.css b/css/bootstrap-combobox.css index eb36208..9c991cf 100644 --- a/css/bootstrap-combobox.css +++ b/css/bootstrap-combobox.css @@ -4,6 +4,10 @@ margin-bottom: 0; vertical-align: top; } +.form-search .combobox-container .input-group-addon, +.form-inline .combobox-container .input-group-addon { + width: auto; +} .combobox-selected .caret { display: none; } diff --git a/less/combobox.less b/less/combobox.less index 90fbfd5..e5211ed 100644 --- a/less/combobox.less +++ b/less/combobox.less @@ -4,6 +4,9 @@ display: inline-block; margin-bottom: 0; vertical-align: top; + .input-group-addon{ + width: auto; + } } } From 34fa0b500f44da23b69c9c8a03e2c6c1b68152df Mon Sep 17 00:00:00 2001 From: Patrick Wiseman Date: Tue, 13 May 2014 13:12:29 -0400 Subject: [PATCH 24/30] Added demo for bootstrap 3.0 --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 60aad33..a9dcddc 100644 --- a/README.md +++ b/README.md @@ -49,8 +49,12 @@ When activating the plugin, you may include an object containing options for the ## 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 From 5b7e14c0ae95fa09aafd187e1d18d0c94503d6b6 Mon Sep 17 00:00:00 2001 From: Patrick Wiseman Date: Tue, 13 May 2014 15:22:32 -0400 Subject: [PATCH 25/30] Updated readme, closes issue #117 --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index a9dcddc..fbf483c 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,9 @@ When activating the plugin, you may include an object containing options for the `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 From 0c64a2f50ee770cd4620a9846c7bc2a12888c933 Mon Sep 17 00:00:00 2001 From: Patrick Wiseman Date: Wed, 14 May 2014 12:34:53 -0400 Subject: [PATCH 26/30] Add disabled class on container for when the combobox is disabled --- js/bootstrap-combobox.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/js/bootstrap-combobox.js b/js/bootstrap-combobox.js index 714af05..618912b 100755 --- a/js/bootstrap-combobox.js +++ b/js/bootstrap-combobox.js @@ -54,15 +54,17 @@ } , disable: function() { - this.$element.prop('disabled', true) - this.$button.attr('disabled', true) - this.disabled = true + 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.$element.prop('disabled', false); + this.$button.attr('disabled', false); + this.disabled = false; + this.$container.removeClass('combobox-disabled'); } , parse: function () { var that = this From d509dfcc49857db57a546d5661136ee4652bc0d7 Mon Sep 17 00:00:00 2001 From: Patrick Wiseman Date: Wed, 14 May 2014 12:39:51 -0400 Subject: [PATCH 27/30] Remove request for new maintainer --- README.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/README.md b/README.md index fbf483c..59dfa97 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,6 @@ We had need of a combobox at work and after looking around at the available options I was not happy with any of them. The project had all it's styling based on Twitter's Bootstrap, so building on that made sense. -# Looking for a new maintainer - -I am looking for a new maintainer for this library if anyone is interested. I have changed jobs and no longer use it in any projects. It would be better served by being maintained by someone who uses it regularly. If you are interested let me know by commenting on [this issue](https://github.com/danielfarrell/bootstrap-combobox/issues/124). - ## How to use it The dependencies are the Bootstrap stylesheet(CSS or LESS). Include it and then the stylesheet(CSS or LESS) and javascript. From a111a8c808d0adebeb80125a469327780ada796b Mon Sep 17 00:00:00 2001 From: Patrick Wiseman Date: Wed, 14 May 2014 12:49:20 -0400 Subject: [PATCH 28/30] Update version number --- js/bootstrap-combobox.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/bootstrap-combobox.js b/js/bootstrap-combobox.js index 618912b..402e829 100755 --- a/js/bootstrap-combobox.js +++ b/js/bootstrap-combobox.js @@ -1,5 +1,5 @@ /* ============================================================= - * bootstrap-combobox.js v1.1.5 + * bootstrap-combobox.js v1.1.6 * ============================================================= * Copyright 2012 Daniel Farrell * From 1103e650ff1f804705a7ba41aa21e99d316e9501 Mon Sep 17 00:00:00 2001 From: dburucu Date: Mon, 5 Jan 2015 14:36:29 +0100 Subject: [PATCH 29/30] Issue #155 you can set Option "newOptionsAllowed" to accept new options not in select-options. Default is true. --- js/bootstrap-combobox.js | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/js/bootstrap-combobox.js b/js/bootstrap-combobox.js index af5dc0d..c094112 100755 --- a/js/bootstrap-combobox.js +++ b/js/bootstrap-combobox.js @@ -35,6 +35,7 @@ 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(); @@ -389,11 +390,16 @@ 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'); + if (this.newOptionsAllowed) { + 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);} } @@ -431,6 +437,7 @@ bsVersion: '3' , menu: '' , item: '
  • ' + , newOptionsAllowed: true }; $.fn.combobox.Constructor = Combobox; From df500a3675d8965fed5e3726a987dd248dc82d44 Mon Sep 17 00:00:00 2001 From: RLHawk1 Date: Mon, 23 Feb 2015 13:40:13 -0500 Subject: [PATCH 30/30] Fixed bug if newOptionsAllowed was true Fixed bug that caused it to set the value to the option description instead of the option value if newOptionsAllowed was true. --- js/bootstrap-combobox.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/js/bootstrap-combobox.js b/js/bootstrap-combobox.js index c094112..92bbe76 100755 --- a/js/bootstrap-combobox.js +++ b/js/bootstrap-combobox.js @@ -391,7 +391,9 @@ this.focused = false; var val = this.$element.val(); if (this.newOptionsAllowed) { - this.$target.val(val); + if (this.length === 0) { + this.$target.val(val); + } } else { if (!this.selected && val !== '' ) { this.$element.val('');