Created
February 21, 2012 17:11
-
-
Save joshvermaire/1877474 to your computer and use it in GitHub Desktop.
My Take on Twitter Bootstrap's Typeahead
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'").replace /\//g, "/" | |
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