bootstrap-typeahead.js 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. /* =============================================================
  2. * bootstrap-typeahead.js v2.0.2
  3. * http://twitter.github.com/bootstrap/javascript.html#typeahead
  4. * =============================================================
  5. * Copyright 2012 Twitter, Inc.
  6. *
  7. * Licensed under the Apache License, Version 2.0 (the "License");
  8. * you may not use this file except in compliance with the License.
  9. * You may obtain a copy of the License at
  10. *
  11. * http://www.apache.org/licenses/LICENSE-2.0
  12. *
  13. * Unless required by applicable law or agreed to in writing, software
  14. * distributed under the License is distributed on an "AS IS" BASIS,
  15. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  16. * See the License for the specific language governing permissions and
  17. * limitations under the License.
  18. * ============================================================ */
  19. !function( $ ){
  20. "use strict"
  21. var Typeahead = function ( element, options ) {
  22. this.$element = $(element)
  23. this.options = $.extend({}, $.fn.typeahead.defaults, options)
  24. this.matcher = this.options.matcher || this.matcher
  25. this.sorter = this.options.sorter || this.sorter
  26. this.highlighter = this.options.highlighter || this.highlighter
  27. this.$menu = $(this.options.menu).appendTo('body')
  28. this.source = this.options.source
  29. this.shown = false
  30. this.listen()
  31. }
  32. Typeahead.prototype = {
  33. constructor: Typeahead
  34. , select: function () {
  35. var val = this.$menu.find('.active').attr('data-value')
  36. this.$element.val(val)
  37. this.$element.change();
  38. return this.hide()
  39. }
  40. , show: function () {
  41. var pos = $.extend({}, this.$element.offset(), {
  42. height: this.$element[0].offsetHeight
  43. })
  44. this.$menu.css({
  45. top: pos.top + pos.height
  46. , left: pos.left
  47. })
  48. this.$menu.show()
  49. this.shown = true
  50. return this
  51. }
  52. , hide: function () {
  53. this.$menu.hide()
  54. this.shown = false
  55. return this
  56. }
  57. , lookup: function (event) {
  58. var that = this
  59. , items
  60. , q
  61. this.query = this.$element.val()
  62. if (!this.query) {
  63. return this.shown ? this.hide() : this
  64. }
  65. items = $.grep(this.source, function (item) {
  66. if (that.matcher(item)) return item
  67. })
  68. items = this.sorter(items)
  69. if (!items.length) {
  70. return this.shown ? this.hide() : this
  71. }
  72. return this.render(items.slice(0, this.options.items)).show()
  73. }
  74. , matcher: function (item) {
  75. return ~item.toLowerCase().indexOf(this.query.toLowerCase())
  76. }
  77. , sorter: function (items) {
  78. var beginswith = []
  79. , caseSensitive = []
  80. , caseInsensitive = []
  81. , item
  82. while (item = items.shift()) {
  83. if (!item.toLowerCase().indexOf(this.query.toLowerCase())) beginswith.push(item)
  84. else if (~item.indexOf(this.query)) caseSensitive.push(item)
  85. else caseInsensitive.push(item)
  86. }
  87. return beginswith.concat(caseSensitive, caseInsensitive)
  88. }
  89. , highlighter: function (item) {
  90. return item.replace(new RegExp('(' + this.query + ')', 'ig'), function ($1, match) {
  91. return '<strong>' + match + '</strong>'
  92. })
  93. }
  94. , render: function (items) {
  95. var that = this
  96. items = $(items).map(function (i, item) {
  97. i = $(that.options.item).attr('data-value', item)
  98. i.find('a').html(that.highlighter(item))
  99. return i[0]
  100. })
  101. items.first().addClass('active')
  102. this.$menu.html(items)
  103. return this
  104. }
  105. , next: function (event) {
  106. var active = this.$menu.find('.active').removeClass('active')
  107. , next = active.next()
  108. if (!next.length) {
  109. next = $(this.$menu.find('li')[0])
  110. }
  111. next.addClass('active')
  112. }
  113. , prev: function (event) {
  114. var active = this.$menu.find('.active').removeClass('active')
  115. , prev = active.prev()
  116. if (!prev.length) {
  117. prev = this.$menu.find('li').last()
  118. }
  119. prev.addClass('active')
  120. }
  121. , listen: function () {
  122. this.$element
  123. .on('blur', $.proxy(this.blur, this))
  124. .on('keypress', $.proxy(this.keypress, this))
  125. .on('keyup', $.proxy(this.keyup, this))
  126. if ($.browser.webkit || $.browser.msie) {
  127. this.$element.on('keydown', $.proxy(this.keypress, this))
  128. }
  129. this.$menu
  130. .on('click', $.proxy(this.click, this))
  131. .on('mouseenter', 'li', $.proxy(this.mouseenter, this))
  132. }
  133. , keyup: function (e) {
  134. switch(e.keyCode) {
  135. case 40: // down arrow
  136. case 38: // up arrow
  137. break
  138. case 9: // tab
  139. case 13: // enter
  140. if (!this.shown) return
  141. this.select()
  142. break
  143. case 27: // escape
  144. if (!this.shown) return
  145. this.hide()
  146. break
  147. default:
  148. this.lookup()
  149. }
  150. e.stopPropagation()
  151. e.preventDefault()
  152. }
  153. , keypress: function (e) {
  154. if (!this.shown) return
  155. switch(e.keyCode) {
  156. case 9: // tab
  157. case 13: // enter
  158. case 27: // escape
  159. e.preventDefault()
  160. break
  161. case 38: // up arrow
  162. e.preventDefault()
  163. this.prev()
  164. break
  165. case 40: // down arrow
  166. e.preventDefault()
  167. this.next()
  168. break
  169. }
  170. e.stopPropagation()
  171. }
  172. , blur: function (e) {
  173. var that = this
  174. setTimeout(function () { that.hide() }, 150)
  175. }
  176. , click: function (e) {
  177. e.stopPropagation()
  178. e.preventDefault()
  179. this.select()
  180. }
  181. , mouseenter: function (e) {
  182. this.$menu.find('.active').removeClass('active')
  183. $(e.currentTarget).addClass('active')
  184. }
  185. }
  186. /* TYPEAHEAD PLUGIN DEFINITION
  187. * =========================== */
  188. $.fn.typeahead = function ( option ) {
  189. return this.each(function () {
  190. var $this = $(this)
  191. , data = $this.data('typeahead')
  192. , options = typeof option == 'object' && option
  193. if (!data) $this.data('typeahead', (data = new Typeahead(this, options)))
  194. if (typeof option == 'string') data[option]()
  195. })
  196. }
  197. $.fn.typeahead.defaults = {
  198. source: []
  199. , items: 8
  200. , menu: '<ul class="typeahead dropdown-menu"></ul>'
  201. , item: '<li><a href="#"></a></li>'
  202. }
  203. $.fn.typeahead.Constructor = Typeahead
  204. /* TYPEAHEAD DATA-API
  205. * ================== */
  206. $(function () {
  207. $('body').on('focus.typeahead.data-api', '[data-provide="typeahead"]', function (e) {
  208. var $this = $(this)
  209. if ($this.data('typeahead')) return
  210. e.preventDefault()
  211. $this.typeahead($this.data())
  212. })
  213. })
  214. }( window.jQuery );