diff --git a/js/bootstrap-typeahead.js b/js/bootstrap-typeahead.js index c2ccdea2..fd6a7ad7 100644 --- a/js/bootstrap-typeahead.js +++ b/js/bootstrap-typeahead.js @@ -1,5 +1,5 @@ /* ============================================================= - * bootstrap-typeahead.js v2.1.1 + * bootstrap-typeahead.js v2.1.1-j3 * http://twitter.github.com/bootstrap/javascript.html#typeahead * ============================================================= * Copyright 2012 Twitter, Inc. @@ -29,13 +29,30 @@ var Typeahead = function (element, options) { this.$element = $(element) this.options = $.extend({}, $.fn.typeahead.defaults, options) + if (this.options.target) this.$target = $(this.options.target) + this.searcher = this.options.searcher || (typeof this.options.source == 'string' ? this.searcherAjax : this.searcher) 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.$menu = $(this.options.menu).appendTo('body') this.source = this.options.source + this.strict = this.options.strict this.shown = false + + if (element.nodeName == 'SELECT') this.replaceSelect() + + this.text = this.$element.val() + + this.$element + .attr('data-text', this.value) + .attr('autocomplete', "off") + + if (typeof this.$target != 'undefined') this.$element.attr('data-value', this.$target.val()) + else if (typeof this.$element.attr('data-value') == 'undefined') this.$element.attr('data-value', this.strict ? '' : this.value) + + this.$menu.css('min-width', this.$element.width() + 12) + this.listen() } @@ -43,16 +60,73 @@ constructor: Typeahead + , replaceSelect: function () { + this.$target = this.$element + this.$element = $('') + + this.source = {} + this.strict = true + + var options = this.$target.find('option') + var $option; + for (var i=0; i 0 ? li.find('.item-text').text() : li.text() + + val = this.updater(val, 'value') + text = this.updater(text, 'text') + this.$element - .val(this.updater(val)) - .change() + .val(text) + .attr('data-value', val) + + this.text = text + + if (typeof this.$target != 'undefined') { + this.$target + .val(val) + .trigger('change') + } + + this.$element.trigger('change') + return this.hide() } - , updater: function (item) { - return item + , updater: function (text, type) { + return text } , show: function () { @@ -81,36 +155,71 @@ this.query = this.$element.val() - if (!this.query || this.query.length < this.options.minLength) { + if (!this.query) { return this.shown ? this.hide() : this } - items = $.isFunction(this.source) ? this.source(this.query, $.proxy(this.process, this)) : this.source + items = this.searcher() + + if (typeof items == 'undefined') { + return this + } - return items ? this.process(items) : this + if ($(items).length === 0) { + return this.shown ? this.hide() : this + } + + return this.render(items).show() } - , process: function (items) { + , searcher: function () { var that = this - - items = $.grep(items, function (item) { - return that.matcher(item) + , array + , object = {} + + + array = $.map(this.source, function (item, key) { + if (!that.matcher(item)) return + object[key] = item + return item }) + + return $.isArray(this.source) ? array : object + } - items = this.sorter(items) + , searcherAjax: function () { + var that = this + + if (this.ajaxTimeout) clearTimeout(this.ajaxTimeout) - if (!items.length) { - return this.shown ? this.hide() : this - } + this.ajaxTimeout = setTimeout(function () { + if (that.ajaxTimeout) clearTimeout(that.ajaxTimeout) - return this.render(items.slice(0, this.options.items)).show() - } + if (that.query === "") { + that.hide() + return + } + + $.get(that.source, {'q': that.query, 'limit': that.options.items }, function (items) { + if ($(items).length === 0) { + if (that.shown) that.hide() + return + } + + that.render(items).show() + }) + }, this.options.ajaxdelay) + } , matcher: function (item) { return ~item.toLowerCase().indexOf(this.query.toLowerCase()) } , sorter: function (items) { + return $.isArray(items) ? this.sortArray(items) : this.sortObject(items) + } + + , sortArray: function (items) { var beginswith = [] , caseSensitive = [] , caseInsensitive = [] @@ -125,6 +234,26 @@ return beginswith.concat(caseSensitive, caseInsensitive) } + , sortObject: function (items) { + var sorted = {} + + for (key in items) { + if (!items[key].toLowerCase().indexOf(this.query.toLowerCase())) sorted[key] = items[key] + delete items[key] + } + + for (key in items) { + if (~items[key].indexOf(this.query)) sorted[key] = items[key] + delete items[key] + } + + for (key in items) { + sorted[key] = items[key] + } + + return sorted + } + , highlighter: function (item) { var query = this.query.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, '\\$&') return item.replace(new RegExp('(' + query + ')', 'ig'), function ($1, match) { @@ -134,24 +263,40 @@ , 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] + , list = $([]) + + $.map(items, function (item, value) { + if (list.length >= that.options.items) return + + var li + , a + , text + + if ($.isArray(items)) value = item + + li = $(that.options.item) + a = li.find('a').length ? li.find('a') : li + a.html(that.highlighter(item)) + + li.attr('data-value', value) + if (li.find('a').length === 0) li.addClass('dropdown-header') + + list.push(li[0]) }) - items.first().addClass('active') - this.$menu.html(items) + list.not('.dropdown-header').first().addClass('active') + + this.$menu.html(list) + return this } - + , next: function (event) { var active = this.$menu.find('.active').removeClass('active') - , next = active.next() + , next = active.nextAll('li:not(.dropdown-header)').first() if (!next.length) { - next = $(this.$menu.find('li')[0]) + next = $(this.$menu.find('li:not(.dropdown-header)')[0]) } next.addClass('active') @@ -159,10 +304,10 @@ , prev: function (event) { var active = this.$menu.find('.active').removeClass('active') - , prev = active.prev() + , prev = active.prevAll('li:not(.dropdown-header)').first() if (!prev.length) { - prev = this.$menu.find('li').last() + prev = this.$menu.find('li:not(.dropdown-header)').last() } prev.addClass('active') @@ -171,6 +316,7 @@ , listen: function () { this.$element .on('blur', $.proxy(this.blur, this)) + .on('change', $.proxy(this.change, this)) .on('keypress', $.proxy(this.keypress, this)) .on('keyup', $.proxy(this.keyup, this)) @@ -181,6 +327,8 @@ this.$menu .on('click', $.proxy(this.click, this)) .on('mouseenter', 'li', $.proxy(this.mouseenter, this)) + + $(window).on('unload', $.proxy(this.destroyReplacement, this)); } , move: function (e) { @@ -197,7 +345,7 @@ e.preventDefault() this.prev() break - + case 40: // down arrow e.preventDefault() this.next() @@ -242,9 +390,22 @@ e.preventDefault() } + , change: function (e) { + var value + + if (this.$element.val() != this.text) { + value = this.$element.val() === '' || this.strict ? '' : this.$element.val() + + this.$element.val(value) + this.$element.attr('data-value', value) + this.text = value + if (typeof this.$target != 'undefined') this.$target.val(value) + } + } + , blur: function (e) { var that = this - setTimeout(function () { that.hide() }, 150) + setTimeout(function () { if (!that.$menu.is(':hover')) that.hide() }, 150) } , click: function (e) { @@ -279,6 +440,7 @@ , items: 8 , menu: '' , item: '
  • ' + , ajaxdelay: 400 , minLength: 1 } @@ -289,12 +451,15 @@ * ================== */ $(function () { - $('body').on('focus.typeahead.data-api', '[data-provide="typeahead"]', function (e) { - var $this = $(this) - if ($this.data('typeahead')) return - e.preventDefault() - $this.typeahead($this.data()) - }) + $('body') + .off('focus.typeahead.data-api') // overwriting Twitter's typeahead + .on('focus.typeahead.data-api', '[data-provide="typeahead"]', function (e) { + var $this = $(this) + if ($this.data('typeahead')) return + if ($this.is('select')) $this.attr('autofocus', true) + e.preventDefault() + $this.typeahead($this.data()) + }) }) }(window.jQuery); diff --git a/less/jasny/forms.less b/less/jasny/forms.less index 25dbe4d7..a682e044 100644 --- a/less/jasny/forms.less +++ b/less/jasny/forms.less @@ -18,6 +18,9 @@ label input[type="radio"] { vertical-align: middle; } +select { + padding-left: 2px; +} // Smaller labels // --------------