让 bootstrap typeahead 支持复杂对象数据源

bootstrap 的typeahead默认不支持复杂对象数组作为数据源,实际中很难满足业务需求,为了实现这个目标,特地花了几个小时加以改造使之支持复杂对象数据源,但是暂时不支持嵌套属性,只支持[{name:'name',age:10}]这种对象数组,大多数情况下应该够用了

下面是bootstrap-typeahead.js改造后大代码,喜欢的可以直接使用改造比较少,如果需要看改动,找文件对比工具和原文件对比就知道了

调用例子

 1   $('#ttext').typeahead({
 2             source: function (query,process) {
 3                 return $.getJSON(
 4                         '/test/typeahead',
 5                         { query: query },
 6                         function (data) {
 7                             return process(data);
 8                         });
 9             },
10             minLength:3,
11             
labeler:['value','name']//下拉列表需要显示的对象属性 12 ,propName:'value' //用来匹配搜索的对象属性,默认是对象的value属性,依然支持单值数据源,只需要把这个属性设置为'' 13 }) 14 });

 

 

 

  1 * bootstrap-typeahead.js v2.2.1
  2  * http://twitter.github.com/bootstrap/javascript.html#typeahead
  3  * =============================================================
  4  * Copyright 2012 Twitter, Inc.
  5  *
  6  * Licensed under the Apache License, Version 2.0 (the "License");
  7  * you may not use this file except in compliance with the License.
  8  * You may obtain a copy of the License at
  9  *
 10  * http://www.apache.org/licenses/LICENSE-2.0
 11  *
 12  * Unless required by applicable law or agreed to in writing, software
 13  * distributed under the License is distributed on an "AS IS" BASIS,
 14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 15  * See the License for the specific language governing permissions and
 16  * limitations under the License.
 17  * ============================================================ */
 18 
 19 
 20 !function($){
 21 
 22   "use strict"; // jshint ;_;
 23 
 24 
 25  /* TYPEAHEAD PUBLIC CLASS DEFINITION
 26   * ================================= */
 27 
 28   var Typeahead = function (element, options) {
 29     this.$element = $(element)
 30     this.options = $.extend({}, $.fn.typeahead.defaults, options)
 31     this.matcher = this.options.matcher || this.matcher
 32     this.sorter = this.options.sorter || this.sorter
 33     this.highlighter = this.options.highlighter || this.highlighter
 34     this.updater = this.options.updater || this.updater
 35     
 36     this.$menu = $(this.options.menu).appendTo('body')
 37     this.source = this.options.source
 38     this.propName= this.options.propName || ""
 39     this.labeler = this.options.labeler || [this.propName]
 40     this.shown = false
 41     this.listen()
 42   }
 43 
 44   Typeahead.prototype = {
 45 
 46     constructor: Typeahead
 47   , propName:'value'
 48   , labeler:['value']
 49   , select: function () {
 50       var val = this.$menu.find('.active').attr('data-value')
 51     
 52       this.$element
 53         .val(this.updater(val))
 54         .change()
 55       return this.hide()
 56     }
 57 
 58   , updater: function (item) {
 59       return item;
 60     }
 61   , show: function () {
 62       var pos = $.extend({}, this.$element.offset(), {
 63         height: this.$element[0].offsetHeight
 64       })
 65 
 66       this.$menu.css({
 67         top: pos.top + pos.height
 68       , left: pos.left
 69       })
 70 
 71       this.$menu.show()
 72       this.shown = true
 73       return this
 74     }
 75 
 76   , hide: function () {
 77       this.$menu.hide()
 78       this.shown = false
 79       return this
 80     }
 81 
 82   , lookup: function (event) {
 83       var items
 84 
 85       this.query = this.$element.val()
 86 
 87       if (!this.query || this.query.length < this.options.minLength) {
 88         return this.shown ? this.hide() : this
 89       }
 90 
 91       items = $.isFunction(this.source) ? this.source(this.query, $.proxy(this.process, this)) : this.source
 92 
 93       return items ? this.process(items) : this
 94     }
 95 
 96   , process: function (items) {
 97       var that = this
 98       
 99       items = $.grep(items, function (item) {
100         var mat = that.matcher(item)
101         //console.log(mat)
102         return mat
103       })
104      
105     
106       items = this.sorter(items)
107      
108       if (!items.length) {
109         return this.shown ? this.hide() : this
110       }
111 
112       return this.render(items.slice(0, this.options.items)).show()
113     }
114 
115   , matcher: function (item) {
116      // console.log(item)
117       if(this.propName=="")
118           return ~item.toLowerCase().indexOf(this.query.toLowerCase())
119       else
120           return ~item[this.propName].toLowerCase().indexOf(this.query.toLowerCase())
121     }
122 
123   , sorter: function (items) {
124       var beginswith = []
125         , caseSensitive = []
126         , caseInsensitive = []
127         , item
128 
129       while (item = items.shift()) {
130         
131         var myPropVal = this.propName==""?item:item[this.propName];
132         if (!myPropVal.toLowerCase().indexOf(this.query.toLowerCase())) beginswith.push(item)
133         else if (~myPropVal.indexOf(this.query)) caseSensitive.push(item)
134         else caseInsensitive.push(item)
135       }
136 
137       return beginswith.concat(caseSensitive, caseInsensitive)
138     }
139 
140   , highlighter: function (item) {      
141       var myPropVal=''
142       if(this.labeler.length==0){
143          myPropVal = item
144       }else{
145         
146         $.each(this.labeler, function(i, pname){
147            myPropVal =  myPropVal.concat(item[pname]).concat(' ')
148         })
149       }
150      
151       var query = this.query.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, '\\$&')
152       return myPropVal.replace(new RegExp('(' + query + ')', 'ig'), function ($1, match) {
153         return '<strong>' + match + '</strong>'
154       })
155     }
156 
157   , render: function (items) {
158       var that = this
159        
160       items = $(items).map(function (i, item) {
161      
162         i = $(that.options.item).attr('data-value',that.propName==""?item:item[that.propName])
163         i.find('a').html(that.highlighter(item))
164         return i[0]
165       })
166 
167       items.first().addClass('active')
168       this.$menu.html(items)
169       return this
170     }
171 
172   , next: function (event) {
173       var active = this.$menu.find('.active').removeClass('active')
174         , next = active.next()
175 
176       if (!next.length) {
177         next = $(this.$menu.find('li')[0])
178       }
179 
180       next.addClass('active')
181     }
182 
183   , prev: function (event) {
184       var active = this.$menu.find('.active').removeClass('active')
185         , prev = active.prev()
186 
187       if (!prev.length) {
188         prev = this.$menu.find('li').last()
189       }
190 
191       prev.addClass('active')
192     }
193 
194   , listen: function () {
195       this.$element
196         .on('blur',     $.proxy(this.blur, this))
197         .on('keypress', $.proxy(this.keypress, this))
198         .on('keyup',    $.proxy(this.keyup, this))
199 
200       if (this.eventSupported('keydown')) {
201         this.$element.on('keydown', $.proxy(this.keydown, this))
202       }
203 
204       this.$menu
205         .on('click', $.proxy(this.click, this))
206         .on('mouseenter', 'li', $.proxy(this.mouseenter, this))
207     }
208 
209   , eventSupported: function(eventName) {
210       var isSupported = eventName in this.$element
211       if (!isSupported) {
212         this.$element.setAttribute(eventName, 'return;')
213         isSupported = typeof this.$element[eventName] === 'function'
214       }
215       return isSupported
216     }
217 
218   , move: function (e) {
219       if (!this.shown) return
220 
221       switch(e.keyCode) {
222         case 9: // tab
223         case 13: // enter
224         case 27: // escape
225           e.preventDefault()
226           break
227 
228         case 38: // up arrow
229           e.preventDefault()
230           this.prev()
231           break
232 
233         case 40: // down arrow
234           e.preventDefault()
235           this.next()
236           break
237       }
238 
239       e.stopPropagation()
240     }
241 
242   , keydown: function (e) {
243       this.suppressKeyPressRepeat = !~$.inArray(e.keyCode, [40,38,9,13,27])
244       this.move(e)
245     }
246 
247   , keypress: function (e) {
248       if (this.suppressKeyPressRepeat) return
249       this.move(e)
250     }
251 
252   , keyup: function (e) {
253       switch(e.keyCode) {
254         case 40: // down arrow
255         case 38: // up arrow
256         case 16: // shift
257         case 17: // ctrl
258         case 18: // alt
259           break
260 
261         case 9: // tab
262         case 13: // enter
263           if (!this.shown) return
264           this.select()
265           break
266 
267         case 27: // escape
268           if (!this.shown) return
269           this.hide()
270           break
271 
272         default:
273           this.lookup()
274       }
275 
276       e.stopPropagation()
277       e.preventDefault()
278   }
279 
280   , blur: function (e) {
281       var that = this
282       setTimeout(function () { that.hide() }, 150)
283     }
284 
285   , click: function (e) {
286       e.stopPropagation()
287       e.preventDefault()
288       this.select()
289     }
290 
291   , mouseenter: function (e) {
292       this.$menu.find('.active').removeClass('active')
293       $(e.currentTarget).addClass('active')
294     }
295 
296   }
297 
298 
299   /* TYPEAHEAD PLUGIN DEFINITION
300    * =========================== */
301 
302   $.fn.typeahead = function (option) {
303     return this.each(function () {
304       var $this = $(this)
305         , data = $this.data('typeahead')
306         , options = typeof option == 'object' && option
307       if (!data) $this.data('typeahead', (data = new Typeahead(this, options)))
308       if (typeof option == 'string') data[option]()
309     })
310   }
311 
312   $.fn.typeahead.defaults = {
313     source: []
314   , items: 8
315   , menu: '<ul class="typeahead dropdown-menu"></ul>'
316   , item: '<li><a href="#"></a></li>'
317   , minLength: 3
318   , propName:'value'
319   , labeler:['value']
320   
321   }
322 
323   $.fn.typeahead.Constructor = Typeahead
324 
325 
326  /*   TYPEAHEAD DATA-API
327   * ================== */
328 
329   $(document).on('focus.typeahead.data-api', '[data-provide="typeahead"]', function (e) {
330     var $this = $(this)
331     if ($this.data('typeahead')) return
332     e.preventDefault()
333     $this.typeahead($this.data())
334   })
335 
336 }(window.jQuery);



posted @ 2012-11-18 23:07  来如风  阅读(4120)  评论(0编辑  收藏  举报