Skip to content

Instantly share code, notes, and snippets.

@joshvermaire
Created February 21, 2012 17:11
Show Gist options
  • Save joshvermaire/1877474 to your computer and use it in GitHub Desktop.
Save joshvermaire/1877474 to your computer and use it in GitHub Desktop.
My Take on Twitter Bootstrap's Typeahead
Shorthand = (element, options) ->
@$el = $(element)
@options = $.extend({}, $.fn.shorthand.defaults, options)
for key, val of @options
@[key] = val
delete @options
@$menu = $(@menu)[@method](@parent)
@listen()
@init()
this
Shorthand.prototype =
init: ->
select: ->
val = @$menu.find('.' + @activeClass).data('value')
@$el.val(val)
@$menu.hide()
show: ->
pos = $.extend({}, @$el.offset(),
height: @$el[0].offsetHeight
)
if @parent is $.fn.typeahead.defaults.parent
@$menu.css
zIndex: 999
position: "absolute"
top: pos.top + pos.height
left: pos.left
@$menu.show()
@shown = true
this
hide: ->
@$menu.hide()
@shown = false
this
lookup: (e) ->
self = this
@query = @$el.val()
unless @query
if @shown then @hide() else return this
items = $.grep(@source, (item) ->
if self.matcher(item) then item
)
unless @sorted then items = @sorter(items)
unless items.length
if @shown then @hide() else return this
@render(items.slice(0, @num)).show()
matcher: (item) ->
value = unless typeof item is 'object' then item else item[@value]
~value.toLowerCase().indexOf(@query.toLowerCase())
sorter: (items) ->
beginswith = []
caseSensitive = []
caseInsensitive = []
while item = items.shift()
value = unless typof item is 'object' then item else item[@value]
unless value.toLowerCase().indexOf(@query.toLowerCase())
beginswith.push item
else if ~value.indexOf(@query)
caseSensitive.push item
else
caseInsensitive.push item
beginswith.concat caseSensitive, caseInsensitive
highlighter: (item) ->
value = unless typeof item is 'object' then item else item[@value]
value.replace new RegExp("(" + @query + ")", "ig"), ($1, match) ->
"<strong>" + match + "</strong>"
render: (items) ->
unless @template
@renderStatic(items)
else
@renderTemplate(items)
this
renderStatic: (items) ->
html = for item in items
value = unless typeof item is 'object' then item else item[@value]
$i = $(@item).data('value', value)
$i.find('a').html(@highlighter(value))
$i[0]
$(html[0]).addClass @activeClass
@$menu.html(html)
renderTemplate: (items) ->
html = for item in items
value = unless typeof item is 'object' then item else item[@value]
$i = $(template(@template, item)).data('value', value)
$i.find('a').html(@highlighter(value))
$i[0]
$(html[0]).addClass @activeClass
@$menu.html(html)
next: ->
active = @activeClass
$activeEl = @$menu.find('.' + active).removeClass("active")
$next = $activeEl.next()
$next = $(@$menu.children().first()) unless $next.length
$next.addClass active
prev: ->
active = @activeClass
$activeEl = @$menu.find('.' + active).removeClass(active)
$prev = $activeEl.prev()
$prev = @$menu.children().last() unless $prev.length
$prev.addClass active
listen: ->
@$el
.on('blur', $.proxy(@blur, this))
.on('keypress', $.proxy(@keypress, this))
.on('keyup', $.proxy(@keyup, this))
if $.browser.webkit or $.browser.msie
@$el.on('keydown', $.proxy(@keypress, this))
@$menu
.on('click', $.proxy(@click, this))
.on('mouseenter', 'li', $.proxy(@mouseenter, this))
keyup: (e) ->
switch(e.keyCode)
when 40, 38
break
when 9, 13
@select() if @shown
when 27
@hide()
else
@lookup()
keypress: (e) ->
return unless @shown
switch(e.keyCode)
when 9, 13, 27
break
when 38
@prev()
when 40
@next()
blur: ->
self = this
setTimeout( ->
self.hide()
, self.blurTimeout)
click: ->
@select()
mouseenter: (e) ->
active = @activeClass
for child in @$menu.children()
if child.classList.contains(active) then child.classList.remove(active)
e.currentTarget.classList.add(active)
$.fn.shorthand = (options) ->
this.each ->
unless this.shorthand
this.shorthand = true
new Shorthand(this, options)
$.fn.shorthand.defaults =
source: [{j: 'hi', url: 'http://placekitten.com/27/27'},{j: 'no', url: 'http://placekitten.com/28/28'},{j: 'nooop', url: 'http://placekitten.com/50/50'},{j: 'chuckbomination', url: 'http://placekitten.com/60/60'},{j: 'chucktastic', url: 'http://placekitten.com/70/70'},{j: 'chucktasta', url: 'http://placekitten.com/27/27'}]
parent: 'body'
num: 2
menu: '<ul class="typeahead shorthand dropdown-menu suggestions"></ul>'
item: '<li class="item"><img src="http://placekitten.com/27/27" class="avatar" /><a class="name"></a></li>'
# item: '<li class="item"><a href="#" class="name"></a></li>'
template: '<li class="item"><img src="<%= url %>" class="avatar" /><a class="name"></a></li>'
value: 'j'
method: "appendTo"
shown: false
sorted: true
blurTimeout: 150
activeClass: 'active'
$.fn.shorthand.Constructor = Shorthand
#Private Functions for Templating from Underscore.js
escape = (string) ->
("" + string).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#x27;").replace /\//g, "&#x2F;"
templateSettings =
evaluate : /<%([\s\S]+?)%>/g
interpolate : /<%=([\s\S]+?)%>/g
escape : /<%-([\s\S]+?)%>/g
template = (str, data) ->
c = templateSettings
tmpl = "var __p=[],print=function(){__p.push.apply(__p,arguments);};" + "with(obj||{}){__p.push('" + str.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(c.escape or noMatch, (match, code) ->
"',_.escape(" + unescape(code) + "),'"
).replace(c.interpolate or noMatch, (match, code) ->
"'," + unescape(code) + ",'"
).replace(c.evaluate or noMatch, (match, code) ->
"');" + unescape(code).replace(/[\r\n\t]/g, " ") + ";__p.push('"
).replace(/\r/g, "\\r").replace(/\n/g, "\\n").replace(/\t/g, "\\t") + "');}return __p.join('');"
func = new Function("obj", "_", tmpl)
return func(data) if data
(data) ->
func.call this, data
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment