/html/js/bootstrap-typeahead.js

https://bitbucket.org/decore/my-svadba.ru · JavaScript · 285 lines · 200 code · 61 blank · 24 comment · 28 complexity · a71319e43efd22bf29161d0e75b892b7 MD5 · raw file

  1. /* =============================================================
  2. * bootstrap-typeahead.js v2.0.4
  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"; // jshint ;_;
  21. /* TYPEAHEAD PUBLIC CLASS DEFINITION
  22. * ================================= */
  23. var Typeahead = function (element, options) {
  24. this.$element = $(element)
  25. this.options = $.extend({}, $.fn.typeahead.defaults, options)
  26. this.matcher = this.options.matcher || this.matcher
  27. this.sorter = this.options.sorter || this.sorter
  28. this.highlighter = this.options.highlighter || this.highlighter
  29. this.updater = this.options.updater || this.updater
  30. this.$menu = $(this.options.menu).appendTo('body')
  31. this.source = this.options.source
  32. this.shown = false
  33. this.listen()
  34. }
  35. Typeahead.prototype = {
  36. constructor: Typeahead
  37. , select: function () {
  38. var val = this.$menu.find('.active').attr('data-value')
  39. this.$element
  40. .val(this.updater(val))
  41. .change()
  42. return this.hide()
  43. }
  44. , updater: function (item) {
  45. return item
  46. }
  47. , show: function () {
  48. var pos = $.extend({}, this.$element.offset(), {
  49. height: this.$element[0].offsetHeight
  50. })
  51. this.$menu.css({
  52. top: pos.top + pos.height
  53. , left: pos.left
  54. })
  55. this.$menu.show()
  56. this.shown = true
  57. return this
  58. }
  59. , hide: function () {
  60. this.$menu.hide()
  61. this.shown = false
  62. return this
  63. }
  64. , lookup: function (event) {
  65. var that = this
  66. , items
  67. , q
  68. this.query = this.$element.val()
  69. if (!this.query) {
  70. return this.shown ? this.hide() : this
  71. }
  72. items = $.grep(this.source, function (item) {
  73. return that.matcher(item)
  74. })
  75. items = this.sorter(items)
  76. if (!items.length) {
  77. return this.shown ? this.hide() : this
  78. }
  79. return this.render(items.slice(0, this.options.items)).show()
  80. }
  81. , matcher: function (item) {
  82. return ~item.toLowerCase().indexOf(this.query.toLowerCase())
  83. }
  84. , sorter: function (items) {
  85. var beginswith = []
  86. , caseSensitive = []
  87. , caseInsensitive = []
  88. , item
  89. while (item = items.shift()) {
  90. if (!item.toLowerCase().indexOf(this.query.toLowerCase())) beginswith.push(item)
  91. else if (~item.indexOf(this.query)) caseSensitive.push(item)
  92. else caseInsensitive.push(item)
  93. }
  94. return beginswith.concat(caseSensitive, caseInsensitive)
  95. }
  96. , highlighter: function (item) {
  97. var query = this.query.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, '\\$&')
  98. return item.replace(new RegExp('(' + query + ')', 'ig'), function ($1, match) {
  99. return '<strong>' + match + '</strong>'
  100. })
  101. }
  102. , render: function (items) {
  103. var that = this
  104. items = $(items).map(function (i, item) {
  105. i = $(that.options.item).attr('data-value', item)
  106. i.find('a').html(that.highlighter(item))
  107. return i[0]
  108. })
  109. items.first().addClass('active')
  110. this.$menu.html(items)
  111. return this
  112. }
  113. , next: function (event) {
  114. var active = this.$menu.find('.active').removeClass('active')
  115. , next = active.next()
  116. if (!next.length) {
  117. next = $(this.$menu.find('li')[0])
  118. }
  119. next.addClass('active')
  120. }
  121. , prev: function (event) {
  122. var active = this.$menu.find('.active').removeClass('active')
  123. , prev = active.prev()
  124. if (!prev.length) {
  125. prev = this.$menu.find('li').last()
  126. }
  127. prev.addClass('active')
  128. }
  129. , listen: function () {
  130. this.$element
  131. .on('blur', $.proxy(this.blur, this))
  132. .on('keypress', $.proxy(this.keypress, this))
  133. .on('keyup', $.proxy(this.keyup, this))
  134. if ($.browser.webkit || $.browser.msie) {
  135. this.$element.on('keydown', $.proxy(this.keypress, this))
  136. }
  137. this.$menu
  138. .on('click', $.proxy(this.click, this))
  139. .on('mouseenter', 'li', $.proxy(this.mouseenter, this))
  140. }
  141. , keyup: function (e) {
  142. switch(e.keyCode) {
  143. case 40: // down arrow
  144. case 38: // up arrow
  145. break
  146. case 9: // tab
  147. case 13: // enter
  148. if (!this.shown) return
  149. this.select()
  150. break
  151. case 27: // escape
  152. if (!this.shown) return
  153. this.hide()
  154. break
  155. default:
  156. this.lookup()
  157. }
  158. e.stopPropagation()
  159. e.preventDefault()
  160. }
  161. , keypress: function (e) {
  162. if (!this.shown) return
  163. switch(e.keyCode) {
  164. case 9: // tab
  165. case 13: // enter
  166. case 27: // escape
  167. e.preventDefault()
  168. break
  169. case 38: // up arrow
  170. if (e.type != 'keydown') break
  171. e.preventDefault()
  172. this.prev()
  173. break
  174. case 40: // down arrow
  175. if (e.type != 'keydown') break
  176. e.preventDefault()
  177. this.next()
  178. break
  179. }
  180. e.stopPropagation()
  181. }
  182. , blur: function (e) {
  183. var that = this
  184. setTimeout(function () { that.hide() }, 150)
  185. }
  186. , click: function (e) {
  187. e.stopPropagation()
  188. e.preventDefault()
  189. this.select()
  190. }
  191. , mouseenter: function (e) {
  192. this.$menu.find('.active').removeClass('active')
  193. $(e.currentTarget).addClass('active')
  194. }
  195. }
  196. /* TYPEAHEAD PLUGIN DEFINITION
  197. * =========================== */
  198. $.fn.typeahead = function (option) {
  199. return this.each(function () {
  200. var $this = $(this)
  201. , data = $this.data('typeahead')
  202. , options = typeof option == 'object' && option
  203. if (!data) $this.data('typeahead', (data = new Typeahead(this, options)))
  204. if (typeof option == 'string') data[option]()
  205. })
  206. }
  207. $.fn.typeahead.defaults = {
  208. source: []
  209. , items: 8
  210. , menu: '<ul class="typeahead dropdown-menu"></ul>'
  211. , item: '<li><a href="#"></a></li>'
  212. }
  213. $.fn.typeahead.Constructor = Typeahead
  214. /* TYPEAHEAD DATA-API
  215. * ================== */
  216. $(function () {
  217. $('body').on('focus.typeahead.data-api', '[data-provide="typeahead"]', function (e) {
  218. var $this = $(this)
  219. if ($this.data('typeahead')) return
  220. e.preventDefault()
  221. $this.typeahead($this.data())
  222. })
  223. })
  224. }(window.jQuery);