/* ============================================================= * bootstrap-combobox.js v1.1.1 * ============================================================= * Copyright 2012 Daniel Farrell * * 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"; 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() } /* NOTE: COMBOBOX EXTENDS BOOTSTRAP-TYPEAHEAD.js ========================================== */ Combobox.prototype = $.extend({}, $.fn.typeahead.Constructor.prototype, { constructor: Combobox, setup: function() { var combobox = $(this.options.template) this.$source.before(combobox) this.$source.hide() return combobox }, parse: function() { var that = this, map = {}, source = [], selected = false this.$source.find('option').each(function() { var option = $(this) if (option.val() === '') { that.options.placeholder = option.text() return } map[option.text()] = option.val() source.push(option.text()) if (option.attr('selected')) selected = option.html() }) this.map = map if (selected) { this.$element.val(selected) this.$container.addClass('combobox-selected') this.selected = true } 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')) }, toggle: function(e) { if (this.shown) { this.hide() } else { // don't clear the textbox when the arrow is clicked and keep the focus // inside the textbox to make sure that when the focus leaves (e.g. user // clicks outside of it) that the dropdown closes as expected this.lookup(e, '') // pass empty query to show all options (this seems more intuitive to me?) this.$element.focus() } }, clearElement: function() { this.$element.val('').focus() }, clearTarget: function() { this.$source.val('') this.$target.val('') this.$container.removeClass('combobox-selected') this.selected = false }, triggerChange: function() { this.$source.trigger('change') }, refresh: function() { 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.$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() }, // modified typeahead function removing the blank handling and source function handling lookup: function(event, existingVal) { if (existingVal != undefined) this.query = existingVal else 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)) 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)) this.$button.on('click', $.proxy(this.toggle, this)) }, // modified typeahead function to clear on type and prevent on moving around keyup: function(e) { switch (e.keyCode) { case 40: // down arrow case 39: // right arrow case 38: // up arrow case 37: // left arrow case 36: // home case 35: // end case 16: // shift case 17: // ctrl case 18: // alt break case 9: // tab case 13: // enter if (!this.shown) { if (this.options.enter_triggers_change === true) this.select() else return } this.select() break case 27: // escape if (!this.shown) return this.hide() break default: this.clearTarget() this.lookup() } 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() if (!!this.options.force_match) { if (!this.selected && val !== '') { this.$element.val('') this.$target.val('').trigger('change') this.$source.val('').trigger('change') } } else { this.$target.val(val).trigger('change') this.$source.val(val).trigger('change') } if (!this.mousedover && this.shown) setTimeout(function() { that.hide() }, 200) }, // modified typeahead function to not hide mouseleave: function(e) { this.mousedover = false }, val: function(val) { if (val != undefined) { this.$element.val(val) this.$target.val(val).trigger('change') this.$source.val(val).trigger('change') return val; } else return this.$target.val() } }) /* 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]() }) } $.fn.combobox.defaults = { template: '
', menu: '', item: '
  • ', force_match: true, enter_triggers_change: false } $.fn.combobox.Constructor = Combobox }(window.jQuery);