js/bootstrap-typeahead.js
changeset 0 ba8ab09f730e
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/js/bootstrap-typeahead.js	Fri Jul 04 16:42:41 2014 +0400
     1.3 @@ -0,0 +1,335 @@
     1.4 +/* =============================================================
     1.5 + * bootstrap-typeahead.js v2.3.1
     1.6 + * http://twitter.github.com/bootstrap/javascript.html#typeahead
     1.7 + * =============================================================
     1.8 + * Copyright 2012 Twitter, Inc.
     1.9 + *
    1.10 + * Licensed under the Apache License, Version 2.0 (the "License");
    1.11 + * you may not use this file except in compliance with the License.
    1.12 + * You may obtain a copy of the License at
    1.13 + *
    1.14 + * http://www.apache.org/licenses/LICENSE-2.0
    1.15 + *
    1.16 + * Unless required by applicable law or agreed to in writing, software
    1.17 + * distributed under the License is distributed on an "AS IS" BASIS,
    1.18 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    1.19 + * See the License for the specific language governing permissions and
    1.20 + * limitations under the License.
    1.21 + * ============================================================ */
    1.22 +
    1.23 +
    1.24 +!function($){
    1.25 +
    1.26 +  "use strict"; // jshint ;_;
    1.27 +
    1.28 +
    1.29 + /* TYPEAHEAD PUBLIC CLASS DEFINITION
    1.30 +  * ================================= */
    1.31 +
    1.32 +  var Typeahead = function (element, options) {
    1.33 +    this.$element = $(element)
    1.34 +    this.options = $.extend({}, $.fn.typeahead.defaults, options)
    1.35 +    this.matcher = this.options.matcher || this.matcher
    1.36 +    this.sorter = this.options.sorter || this.sorter
    1.37 +    this.highlighter = this.options.highlighter || this.highlighter
    1.38 +    this.updater = this.options.updater || this.updater
    1.39 +    this.source = this.options.source
    1.40 +    this.$menu = $(this.options.menu)
    1.41 +    this.shown = false
    1.42 +    this.listen()
    1.43 +  }
    1.44 +
    1.45 +  Typeahead.prototype = {
    1.46 +
    1.47 +    constructor: Typeahead
    1.48 +
    1.49 +  , select: function () {
    1.50 +      var val = this.$menu.find('.active').attr('data-value')
    1.51 +      this.$element
    1.52 +        .val(this.updater(val))
    1.53 +        .change()
    1.54 +      return this.hide()
    1.55 +    }
    1.56 +
    1.57 +  , updater: function (item) {
    1.58 +      return item
    1.59 +    }
    1.60 +
    1.61 +  , show: function () {
    1.62 +      var pos = $.extend({}, this.$element.position(), {
    1.63 +        height: this.$element[0].offsetHeight
    1.64 +      })
    1.65 +
    1.66 +      this.$menu
    1.67 +        .insertAfter(this.$element)
    1.68 +        .css({
    1.69 +          top: pos.top + pos.height
    1.70 +        , left: pos.left
    1.71 +        })
    1.72 +        .show()
    1.73 +
    1.74 +      this.shown = true
    1.75 +      return this
    1.76 +    }
    1.77 +
    1.78 +  , hide: function () {
    1.79 +      this.$menu.hide()
    1.80 +      this.shown = false
    1.81 +      return this
    1.82 +    }
    1.83 +
    1.84 +  , lookup: function (event) {
    1.85 +      var items
    1.86 +
    1.87 +      this.query = this.$element.val()
    1.88 +
    1.89 +      if (!this.query || this.query.length < this.options.minLength) {
    1.90 +        return this.shown ? this.hide() : this
    1.91 +      }
    1.92 +
    1.93 +      items = $.isFunction(this.source) ? this.source(this.query, $.proxy(this.process, this)) : this.source
    1.94 +
    1.95 +      return items ? this.process(items) : this
    1.96 +    }
    1.97 +
    1.98 +  , process: function (items) {
    1.99 +      var that = this
   1.100 +
   1.101 +      items = $.grep(items, function (item) {
   1.102 +        return that.matcher(item)
   1.103 +      })
   1.104 +
   1.105 +      items = this.sorter(items)
   1.106 +
   1.107 +      if (!items.length) {
   1.108 +        return this.shown ? this.hide() : this
   1.109 +      }
   1.110 +
   1.111 +      return this.render(items.slice(0, this.options.items)).show()
   1.112 +    }
   1.113 +
   1.114 +  , matcher: function (item) {
   1.115 +      return ~item.toLowerCase().indexOf(this.query.toLowerCase())
   1.116 +    }
   1.117 +
   1.118 +  , sorter: function (items) {
   1.119 +      var beginswith = []
   1.120 +        , caseSensitive = []
   1.121 +        , caseInsensitive = []
   1.122 +        , item
   1.123 +
   1.124 +      while (item = items.shift()) {
   1.125 +        if (!item.toLowerCase().indexOf(this.query.toLowerCase())) beginswith.push(item)
   1.126 +        else if (~item.indexOf(this.query)) caseSensitive.push(item)
   1.127 +        else caseInsensitive.push(item)
   1.128 +      }
   1.129 +
   1.130 +      return beginswith.concat(caseSensitive, caseInsensitive)
   1.131 +    }
   1.132 +
   1.133 +  , highlighter: function (item) {
   1.134 +      var query = this.query.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, '\\$&')
   1.135 +      return item.replace(new RegExp('(' + query + ')', 'ig'), function ($1, match) {
   1.136 +        return '<strong>' + match + '</strong>'
   1.137 +      })
   1.138 +    }
   1.139 +
   1.140 +  , render: function (items) {
   1.141 +      var that = this
   1.142 +
   1.143 +      items = $(items).map(function (i, item) {
   1.144 +        i = $(that.options.item).attr('data-value', item)
   1.145 +        i.find('a').html(that.highlighter(item))
   1.146 +        return i[0]
   1.147 +      })
   1.148 +
   1.149 +      items.first().addClass('active')
   1.150 +      this.$menu.html(items)
   1.151 +      return this
   1.152 +    }
   1.153 +
   1.154 +  , next: function (event) {
   1.155 +      var active = this.$menu.find('.active').removeClass('active')
   1.156 +        , next = active.next()
   1.157 +
   1.158 +      if (!next.length) {
   1.159 +        next = $(this.$menu.find('li')[0])
   1.160 +      }
   1.161 +
   1.162 +      next.addClass('active')
   1.163 +    }
   1.164 +
   1.165 +  , prev: function (event) {
   1.166 +      var active = this.$menu.find('.active').removeClass('active')
   1.167 +        , prev = active.prev()
   1.168 +
   1.169 +      if (!prev.length) {
   1.170 +        prev = this.$menu.find('li').last()
   1.171 +      }
   1.172 +
   1.173 +      prev.addClass('active')
   1.174 +    }
   1.175 +
   1.176 +  , listen: function () {
   1.177 +      this.$element
   1.178 +        .on('focus',    $.proxy(this.focus, this))
   1.179 +        .on('blur',     $.proxy(this.blur, this))
   1.180 +        .on('keypress', $.proxy(this.keypress, this))
   1.181 +        .on('keyup',    $.proxy(this.keyup, this))
   1.182 +
   1.183 +      if (this.eventSupported('keydown')) {
   1.184 +        this.$element.on('keydown', $.proxy(this.keydown, this))
   1.185 +      }
   1.186 +
   1.187 +      this.$menu
   1.188 +        .on('click', $.proxy(this.click, this))
   1.189 +        .on('mouseenter', 'li', $.proxy(this.mouseenter, this))
   1.190 +        .on('mouseleave', 'li', $.proxy(this.mouseleave, this))
   1.191 +    }
   1.192 +
   1.193 +  , eventSupported: function(eventName) {
   1.194 +      var isSupported = eventName in this.$element
   1.195 +      if (!isSupported) {
   1.196 +        this.$element.setAttribute(eventName, 'return;')
   1.197 +        isSupported = typeof this.$element[eventName] === 'function'
   1.198 +      }
   1.199 +      return isSupported
   1.200 +    }
   1.201 +
   1.202 +  , move: function (e) {
   1.203 +      if (!this.shown) return
   1.204 +
   1.205 +      switch(e.keyCode) {
   1.206 +        case 9: // tab
   1.207 +        case 13: // enter
   1.208 +        case 27: // escape
   1.209 +          e.preventDefault()
   1.210 +          break
   1.211 +
   1.212 +        case 38: // up arrow
   1.213 +          e.preventDefault()
   1.214 +          this.prev()
   1.215 +          break
   1.216 +
   1.217 +        case 40: // down arrow
   1.218 +          e.preventDefault()
   1.219 +          this.next()
   1.220 +          break
   1.221 +      }
   1.222 +
   1.223 +      e.stopPropagation()
   1.224 +    }
   1.225 +
   1.226 +  , keydown: function (e) {
   1.227 +      this.suppressKeyPressRepeat = ~$.inArray(e.keyCode, [40,38,9,13,27])
   1.228 +      this.move(e)
   1.229 +    }
   1.230 +
   1.231 +  , keypress: function (e) {
   1.232 +      if (this.suppressKeyPressRepeat) return
   1.233 +      this.move(e)
   1.234 +    }
   1.235 +
   1.236 +  , keyup: function (e) {
   1.237 +      switch(e.keyCode) {
   1.238 +        case 40: // down arrow
   1.239 +        case 38: // up arrow
   1.240 +        case 16: // shift
   1.241 +        case 17: // ctrl
   1.242 +        case 18: // alt
   1.243 +          break
   1.244 +
   1.245 +        case 9: // tab
   1.246 +        case 13: // enter
   1.247 +          if (!this.shown) return
   1.248 +          this.select()
   1.249 +          break
   1.250 +
   1.251 +        case 27: // escape
   1.252 +          if (!this.shown) return
   1.253 +          this.hide()
   1.254 +          break
   1.255 +
   1.256 +        default:
   1.257 +          this.lookup()
   1.258 +      }
   1.259 +
   1.260 +      e.stopPropagation()
   1.261 +      e.preventDefault()
   1.262 +  }
   1.263 +
   1.264 +  , focus: function (e) {
   1.265 +      this.focused = true
   1.266 +    }
   1.267 +
   1.268 +  , blur: function (e) {
   1.269 +      this.focused = false
   1.270 +      if (!this.mousedover && this.shown) this.hide()
   1.271 +    }
   1.272 +
   1.273 +  , click: function (e) {
   1.274 +      e.stopPropagation()
   1.275 +      e.preventDefault()
   1.276 +      this.select()
   1.277 +      this.$element.focus()
   1.278 +    }
   1.279 +
   1.280 +  , mouseenter: function (e) {
   1.281 +      this.mousedover = true
   1.282 +      this.$menu.find('.active').removeClass('active')
   1.283 +      $(e.currentTarget).addClass('active')
   1.284 +    }
   1.285 +
   1.286 +  , mouseleave: function (e) {
   1.287 +      this.mousedover = false
   1.288 +      if (!this.focused && this.shown) this.hide()
   1.289 +    }
   1.290 +
   1.291 +  }
   1.292 +
   1.293 +
   1.294 +  /* TYPEAHEAD PLUGIN DEFINITION
   1.295 +   * =========================== */
   1.296 +
   1.297 +  var old = $.fn.typeahead
   1.298 +
   1.299 +  $.fn.typeahead = function (option) {
   1.300 +    return this.each(function () {
   1.301 +      var $this = $(this)
   1.302 +        , data = $this.data('typeahead')
   1.303 +        , options = typeof option == 'object' && option
   1.304 +      if (!data) $this.data('typeahead', (data = new Typeahead(this, options)))
   1.305 +      if (typeof option == 'string') data[option]()
   1.306 +    })
   1.307 +  }
   1.308 +
   1.309 +  $.fn.typeahead.defaults = {
   1.310 +    source: []
   1.311 +  , items: 8
   1.312 +  , menu: '<ul class="typeahead dropdown-menu"></ul>'
   1.313 +  , item: '<li><a href="#"></a></li>'
   1.314 +  , minLength: 1
   1.315 +  }
   1.316 +
   1.317 +  $.fn.typeahead.Constructor = Typeahead
   1.318 +
   1.319 +
   1.320 + /* TYPEAHEAD NO CONFLICT
   1.321 +  * =================== */
   1.322 +
   1.323 +  $.fn.typeahead.noConflict = function () {
   1.324 +    $.fn.typeahead = old
   1.325 +    return this
   1.326 +  }
   1.327 +
   1.328 +
   1.329 + /* TYPEAHEAD DATA-API
   1.330 +  * ================== */
   1.331 +
   1.332 +  $(document).on('focus.typeahead.data-api', '[data-provide="typeahead"]', function (e) {
   1.333 +    var $this = $(this)
   1.334 +    if ($this.data('typeahead')) return
   1.335 +    $this.typeahead($this.data())
   1.336 +  })
   1.337 +
   1.338 +}(window.jQuery);