Skip to content

Instantly share code, notes, and snippets.

@richhollis
Forked from thorst/README.markdown
Last active August 29, 2015 14:14
Show Gist options
  • Save richhollis/b8da355691a515f91f21 to your computer and use it in GitHub Desktop.
Save richhollis/b8da355691a515f91f21 to your computer and use it in GitHub Desktop.

Revisions

  1. richhollis revised this gist Jan 28, 2015. 1 changed file with 0 additions and 6 deletions.
    6 changes: 0 additions & 6 deletions bootstrap-typeahead.js
    Original file line number Diff line number Diff line change
    @@ -234,10 +234,7 @@ Original Source: https://gist.github.com/thorst/2960885
    // we want enter and escape to behave normally when not shown
    // e.g. we are in a modal dialog, for example
    if(!this.shown && (e.keyCode == 27 || e.keyCode == 13))
    {
    console.log("default!");
    return;
    }

    e.stopPropagation()
    e.preventDefault()
    @@ -278,10 +275,7 @@ Original Source: https://gist.github.com/thorst/2960885
    // we want enter and escape to behave normally when not shown
    // e.g. we are in a modal dialog, for example
    if(!this.shown && (e.keyCode == 27 || e.keyCode == 13))
    {
    console.log("default!");
    return;
    }

    e.stopPropagation()
    if (!this.shown) return
  2. richhollis revised this gist Jan 28, 2015. 1 changed file with 21 additions and 2 deletions.
    23 changes: 21 additions & 2 deletions bootstrap-typeahead.js
    Original file line number Diff line number Diff line change
    @@ -1,3 +1,6 @@
    /*
    Original Source: https://gist.github.com/thorst/2960885
    */
    /* =============================================================
    * bootstrap-typeahead.js v2.0.0
    * http://twitter.github.com/bootstrap/javascript.html#typeahead
    @@ -228,12 +231,20 @@
    }

    , keyup: function (e) {
    // we want enter and escape to behave normally when not shown
    // e.g. we are in a modal dialog, for example
    if(!this.shown && (e.keyCode == 27 || e.keyCode == 13))
    {
    console.log("default!");
    return;
    }

    e.stopPropagation()
    e.preventDefault()

    switch(e.keyCode) {
    case 40: // up/down/left/right arrow
    case 39:
    case 39:
    case 38:
    case 37:
    break
    @@ -264,6 +275,14 @@
    }

    , keypress: function (e) {
    // we want enter and escape to behave normally when not shown
    // e.g. we are in a modal dialog, for example
    if(!this.shown && (e.keyCode == 27 || e.keyCode == 13))
    {
    console.log("default!");
    return;
    }

    e.stopPropagation()
    if (!this.shown) return

    @@ -351,4 +370,4 @@
    })
    })

    }( window.jQuery );
    }( window.jQuery );
  3. @thorst thorst revised this gist Jun 20, 2012. 1 changed file with 6 additions and 4 deletions.
    10 changes: 6 additions & 4 deletions README.markdown
    Original file line number Diff line number Diff line change
    @@ -1,7 +1,9 @@
    # This is a fork (of a fork) of Bootstrap Typeahead that adds minimal but powerful extensions.
    Original :: http://twitter.github.com/bootstrap/assets/js/bootstrap-typeahead.js
    Original Bootstrap code :: http://twitter.github.com/bootstrap/assets/js/bootstrap-typeahead.js

    Fork :: https://gist.github.com/1866577
    Original Fork code :: https://gist.github.com/1866577

    This Fork (you are currently viewing) :: https://gist.github.com/2960885

    ##Original Fork adds:##

    @@ -15,11 +17,11 @@ Fork :: https://gist.github.com/1866577

    ##My Fork Adds:##

    1. minLength parameter- aka jquery ui autocomplete. Requires textbox to have a set length before calling source.
    1. minLength parameter- ala jquery ui autocomplete. Requires textbox to have a set length before calling source.

    2. By default no options are selected. If you tab while no option is selected it will leave your textbox value as is.

    3. Up/Down - when it reaches the start or finish the next press will deselect all options, then next press will start scroll over options again. This is needed to allow tab off input.
    3. Up/Down - when it reaches the start or finish the next press will deselect all options, then next press will start scroll over options again. This is needed to allow tab off of an input.

    4. Left/Right no longer refresh datasource. There will probably be other keys we want to ignore.

  4. @thorst thorst revised this gist Jun 20, 2012. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions README.markdown
    Original file line number Diff line number Diff line change
    @@ -3,7 +3,7 @@ Original :: http://twitter.github.com/bootstrap/assets/js/bootstrap-typeahead.js

    Fork :: https://gist.github.com/1866577

    Original Fork adds:
    ##Original Fork adds:##

    1. Ajax source

    @@ -13,7 +13,7 @@ Original Fork adds:

    4. Up/Down to select options

    My Fork Adds:
    ##My Fork Adds:##

    1. minLength parameter- aka jquery ui autocomplete. Requires textbox to have a set length before calling source.

  5. @thorst thorst revised this gist Jun 20, 2012. 1 changed file with 8 additions and 0 deletions.
    8 changes: 8 additions & 0 deletions README.markdown
    Original file line number Diff line number Diff line change
    @@ -4,15 +4,23 @@ Original :: http://twitter.github.com/bootstrap/assets/js/bootstrap-typeahead.js
    Fork :: https://gist.github.com/1866577

    Original Fork adds:

    1. Ajax source

    2. Source as an object (with property parameter to determine which value in object to use)

    3. Onselect callback

    4. Up/Down to select options

    My Fork Adds:

    1. minLength parameter- aka jquery ui autocomplete. Requires textbox to have a set length before calling source.

    2. By default no options are selected. If you tab while no option is selected it will leave your textbox value as is.

    3. Up/Down - when it reaches the start or finish the next press will deselect all options, then next press will start scroll over options again. This is needed to allow tab off input.

    4. Left/Right no longer refresh datasource. There will probably be other keys we want to ignore.

    ### For example, process typeahead list asynchronously and return objects
  6. @thorst thorst revised this gist Jun 20, 2012. 1 changed file with 14 additions and 1 deletion.
    15 changes: 14 additions & 1 deletion README.markdown
    Original file line number Diff line number Diff line change
    @@ -1,6 +1,19 @@
    # This is a fork (of a fork) of Bootstrap Typeahead that adds minimal but powerful extensions.
    Original :: http://twitter.github.com/bootstrap/assets/js/bootstrap-typeahead.js
    Fork ::

    Fork :: https://gist.github.com/1866577

    Original Fork adds:
    1. Ajax source
    2. Source as an object (with property parameter to determine which value in object to use)
    3. Onselect callback
    4. Up/Down to select options

    My Fork Adds:
    1. minLength parameter- aka jquery ui autocomplete. Requires textbox to have a set length before calling source.
    2. By default no options are selected. If you tab while no option is selected it will leave your textbox value as is.
    3. Up/Down - when it reaches the start or finish the next press will deselect all options, then next press will start scroll over options again. This is needed to allow tab off input.
    4. Left/Right no longer refresh datasource. There will probably be other keys we want to ignore.

    ### For example, process typeahead list asynchronously and return objects

  7. @thorst thorst revised this gist Jun 20, 2012. 1 changed file with 4 additions and 6 deletions.
    10 changes: 4 additions & 6 deletions README.markdown
    Original file line number Diff line number Diff line change
    @@ -1,4 +1,6 @@
    # This is a fork of Bootstrap Typeahead that adds minimal but powerful extensions.
    # This is a fork (of a fork) of Bootstrap Typeahead that adds minimal but powerful extensions.
    Original :: http://twitter.github.com/bootstrap/assets/js/bootstrap-typeahead.js
    Fork ::

    ### For example, process typeahead list asynchronously and return objects

    @@ -54,8 +56,4 @@
    })
    ```

    Note that onselect works without source as a function and vice versa. Events may be a cleaner solution to passing callbacks and using bind all over the place, but I tried to strike a balance between modifying the core source too much and adding functionality, so until further improvements on the original Typeahead source I think these additions are very helpful.

    **Update 02/23/2012: Fixed a bug**

    ### Gudbergur Erlendsson, reach me here or gudbergur at gmail
    Note that onselect works without source as a function and vice versa. Events may be a cleaner solution to passing callbacks and using bind all over the place, but I tried to strike a balance between modifying the core source too much and adding functionality, so until further improvements on the original Typeahead source I think these additions are very helpful.
  8. @thorst thorst revised this gist Jun 20, 2012. 2 changed files with 51 additions and 116 deletions.
    59 changes: 51 additions & 8 deletions bootstrap-typeahead.js
    Original file line number Diff line number Diff line change
    @@ -160,7 +160,7 @@
    return i[0]
    })

    items.first().addClass('active')
    //items.first().addClass('active')
    this.$menu.html(items)
    return this
    }
    @@ -169,21 +169,46 @@
    var active = this.$menu.find('.active').removeClass('active')
    , next = active.next()

    /*
    if (!next.length) {
    next = $(this.$menu.find('li')[0])
    }
    next.addClass('active')
    */
    //console.log(active.length);

    //If there arent any selected, select the first one
    if (!active.length) {
    next = $(this.$menu.find('li')[0])
    }

    //If this is the end, dont select another
    if (!next.length) {
    return false;
    }

    next.addClass('active');
    }

    , prev: function (event) {
    var active = this.$menu.find('.active').removeClass('active')
    , prev = active.prev()

    /*
    if (!prev.length) {
    prev = this.$menu.find('li').last()
    }

    */
    //If there arent any selected, select the last one
    if (!active.length) {
    prev = this.$menu.find('li').last()
    }

    //If this is the start, dont select another
    if (!prev.length) {
    return false;
    }

    prev.addClass('active')
    }

    @@ -207,24 +232,35 @@
    e.preventDefault()

    switch(e.keyCode) {
    case 40: // down arrow
    case 38: // up arrow
    case 40: // up/down/left/right arrow
    case 39:
    case 38:
    case 37:
    break

    case 9: // tab
    case 13: // enter
    if (!this.shown) return
    this.select()
    var active = this.$menu.find('.active');
    if (active.length) {
    this.select()
    } else {
    return true;
    }

    break

    case 27: // escape
    this.hide()
    break

    default:
    this.lookup()
    if($(this.$element).val().length >=this.options.minLength) {
    this.lookup();
    } else {
    this.hide()
    }
    }

    }

    , keypress: function (e) {
    @@ -233,6 +269,12 @@

    switch(e.keyCode) {
    case 9: // tab
    var active = this.$menu.find('.active');
    if (active.length) {
    e.preventDefault()
    }
    break;

    case 13: // enter
    case 27: // escape
    e.preventDefault()
    @@ -291,6 +333,7 @@
    , item: '<li><a href="#"></a></li>'
    , onselect: null
    , property: 'value'
    , minLength: 2
    }

    $.fn.typeahead.Constructor = Typeahead
    108 changes: 0 additions & 108 deletions gistfile1.diff
    Original file line number Diff line number Diff line change
    @@ -1,108 +0,0 @@
    /* =============================================================
    * bootstrap-typeahead.js v2.0.0
    * http://twitter.github.com/bootstrap/javascript.html#typeahead
    * =============================================================
    * Copyright 2012 Twitter, Inc.
    @@ -29,6 +29,8 @@
    this.highlighter = this.options.highlighter || this.highlighter
    this.$menu = $(this.options.menu).appendTo('body')
    this.source = this.options.source
    + this.onselect = this.options.onselect
    + this.strings = true
    this.shown = false
    this.listen()
    }
    @@ -38,8 +40,17 @@
    constructor: Typeahead

    , select: function () {
    - var val = this.$menu.find('.active').attr('data-value')
    - this.$element.val(val)
    + var val = JSON.parse(this.$menu.find('.active').attr('data-value'))
    + , text
    +
    + if (!this.strings) text = val[this.options.property]
    + else text = val
    +
    + this.$element.val(text)
    +
    + if (typeof this.onselect == "function")
    + this.onselect(val)
    +
    return this.hide()
    }

    @@ -68,6 +79,25 @@
    var that = this
    , items
    , q
    + , value
    +
    + this.query = this.$element.val()
    +
    + if (typeof this.source == "function")
    + value = this.source(this, this.query)
    + if (value)
    + this.process(value)
    + else
    + this.process(this.source)
    + }
    +
    + , process: function (results) {
    + var that = this
    + , items
    + , q
    +
    + if (results.length && typeof results[0] != "string")
    + this.strings = false

    this.query = this.$element.val()

    @@ -75,7 +105,9 @@
    return this.shown ? this.hide() : this
    }

    - items = $.grep(this.source, function (item) {
    + items = $.grep(results, function (item) {
    + if (!that.strings)
    + item = item[that.options.property]
    if (that.matcher(item)) return item
    })

    @@ -97,10 +129,14 @@
    , caseSensitive = []
    , caseInsensitive = []
    , item
    + , sortby

    while (item = items.shift()) {
    - if (!item.toLowerCase().indexOf(this.query.toLowerCase())) beginswith.push(item)
    - else if (~item.indexOf(this.query)) caseSensitive.push(item)
    + if (this.strings) sortby = item
    + else sortby = item[this.options.property]
    +
    + if (!sortby.toLowerCase().indexOf(this.query.toLowerCase())) beginswith.push(item)
    + else if (~sortby.indexOf(this.query)) caseSensitive.push(item)
    else caseInsensitive.push(item)
    }

    @@ -117,7 +153,9 @@
    var that = this

    items = $(items).map(function (i, item) {
    - i = $(that.options.item).attr('data-value', item)
    + i = $(that.options.item).attr('data-value', JSON.stringify(item))
    + if (!that.strings)
    + item = item[that.options.property]
    i.find('a').html(that.highlighter(item))
    return i[0]
    })
    @@ -251,6 +289,8 @@
    , items: 8
    , menu: '<ul class="typeahead dropdown-menu"></ul>'
    , item: '<li><a href="#"></a></li>'
    + , onselect: null
    + , property: 'value'
    }

    $.fn.typeahead.Constructor = Typeahead
  9. @gudber gudber revised this gist Feb 23, 2012. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion README.markdown
    Original file line number Diff line number Diff line change
    @@ -1,4 +1,4 @@
    # This is an extension to Bootstrap Typeahead that adds minimal but powerful extensions.
    # This is a fork of Bootstrap Typeahead that adds minimal but powerful extensions.

    ### For example, process typeahead list asynchronously and return objects

  10. @gudber gudber revised this gist Feb 23, 2012. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion README.markdown
    Original file line number Diff line number Diff line change
    @@ -56,6 +56,6 @@

    Note that onselect works without source as a function and vice versa. Events may be a cleaner solution to passing callbacks and using bind all over the place, but I tried to strike a balance between modifying the core source too much and adding functionality, so until further improvements on the original Typeahead source I think these additions are very helpful.

    Update 02/23/2012: Fixed a bug
    **Update 02/23/2012: Fixed a bug**

    ### Gudbergur Erlendsson, reach me here or gudbergur at gmail
  11. @gudber gudber revised this gist Feb 23, 2012. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion README.markdown
    Original file line number Diff line number Diff line change
    @@ -56,6 +56,6 @@

    Note that onselect works without source as a function and vice versa. Events may be a cleaner solution to passing callbacks and using bind all over the place, but I tried to strike a balance between modifying the core source too much and adding functionality, so until further improvements on the original Typeahead source I think these additions are very helpful.

    02/23/2012: Fixed a bug
    Update 02/23/2012: Fixed a bug

    ### Gudbergur Erlendsson, reach me here or gudbergur at gmail
  12. @gudber gudber revised this gist Feb 23, 2012. 2 changed files with 6 additions and 4 deletions.
    2 changes: 2 additions & 0 deletions README.markdown
    Original file line number Diff line number Diff line change
    @@ -56,4 +56,6 @@

    Note that onselect works without source as a function and vice versa. Events may be a cleaner solution to passing callbacks and using bind all over the place, but I tried to strike a balance between modifying the core source too much and adding functionality, so until further improvements on the original Typeahead source I think these additions are very helpful.

    02/23/2012: Fixed a bug

    ### Gudbergur Erlendsson, reach me here or gudbergur at gmail
    8 changes: 4 additions & 4 deletions bootstrap-typeahead.js
    Original file line number Diff line number Diff line change
    @@ -83,12 +83,12 @@

    this.query = this.$element.val()

    if (typeof this.source == "function")
    if (typeof this.source == "function") {
    value = this.source(this, this.query)
    if (value)
    this.process(value)
    else
    if (value) this.process(value)
    } else {
    this.process(this.source)
    }
    }

    , process: function (results) {
  13. @gudber gudber revised this gist Feb 20, 2012. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion README.markdown
    Original file line number Diff line number Diff line change
    @@ -56,4 +56,4 @@

    Note that onselect works without source as a function and vice versa. Events may be a cleaner solution to passing callbacks and using bind all over the place, but I tried to strike a balance between modifying the core source too much and adding functionality, so until further improvements on the original Typeahead source I think these additions are very helpful.

    - Gudbergur Erlendsson
    ### Gudbergur Erlendsson, reach me here or gudbergur at gmail
  14. @gudber gudber revised this gist Feb 20, 2012. 1 changed file with 2 additions and 0 deletions.
    2 changes: 2 additions & 0 deletions README.markdown
    Original file line number Diff line number Diff line change
    @@ -55,3 +55,5 @@
    ```

    Note that onselect works without source as a function and vice versa. Events may be a cleaner solution to passing callbacks and using bind all over the place, but I tried to strike a balance between modifying the core source too much and adding functionality, so until further improvements on the original Typeahead source I think these additions are very helpful.

    - Gudbergur Erlendsson
  15. @gudber gudber revised this gist Feb 20, 2012. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions README.markdown
    Original file line number Diff line number Diff line change
    @@ -1,6 +1,6 @@
    # This is an extension to Bootstrap Typeahead that adds minimal but powerful extensions.

    ### For example, process typeahead list asynchronously, return objects and then fire a callback when one is selected
    ### For example, process typeahead list asynchronously and return objects

    ```coffeescript
    # This example does an AJAX lookup and is in CoffeeScript
    @@ -23,7 +23,7 @@
    )
    ```

    ### For example, process typeahead list synchronously and call a function on selection
    ### For example, process typeahead list synchronously and fire a callback on selection

    ```javascript
    // This example is in Javascript, collects html in some li's and returns it
  16. @gudber gudber revised this gist Feb 20, 2012. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion README.markdown
    Original file line number Diff line number Diff line change
    @@ -54,4 +54,4 @@
    })
    ```

    Note that onselect works without source as a function and vice versa. Events may be a cleaner solution to passing callbacks and using bind all over the place, but I think these additions are very helpful.
    Note that onselect works without source as a function and vice versa. Events may be a cleaner solution to passing callbacks and using bind all over the place, but I tried to strike a balance between modifying the core source too much and adding functionality, so until further improvements on the original Typeahead source I think these additions are very helpful.
  17. @gudber gudber revised this gist Feb 20, 2012. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion README.markdown
    Original file line number Diff line number Diff line change
    @@ -1,4 +1,4 @@
    # This is an extension to Bootstrap Typeahead that adds three minimal but powerful extensions.
    # This is an extension to Bootstrap Typeahead that adds minimal but powerful extensions.

    ### For example, process typeahead list asynchronously, return objects and then fire a callback when one is selected

  18. @gudber gudber revised this gist Feb 19, 2012. 1 changed file with 9 additions and 0 deletions.
    9 changes: 9 additions & 0 deletions README.markdown
    Original file line number Diff line number Diff line change
    @@ -45,4 +45,13 @@
    })
    ```

    ### and a very simple example, showing you can pass list of objects as source, and get that object via onselect
    ```javascript
    $('.typeahead').typeahead({
    // note that "value" is the default setting for the property option
    source: [{value: 'Charlie'}, {value: 'Gudbergur'}, ...],
    onselect: function(obj) { console.log(obj) }
    })
    ```

    Note that onselect works without source as a function and vice versa. Events may be a cleaner solution to passing callbacks and using bind all over the place, but I think these additions are very helpful.
  19. @gudber gudber created this gist Feb 19, 2012.
    48 changes: 48 additions & 0 deletions README.markdown
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,48 @@
    # This is an extension to Bootstrap Typeahead that adds three minimal but powerful extensions.

    ### For example, process typeahead list asynchronously, return objects and then fire a callback when one is selected

    ```coffeescript
    # This example does an AJAX lookup and is in CoffeeScript
    $('.typeahead').typeahead(
    # source can be a function
    source: (typeahead, query) ->
    # this function receives the typeahead object and the query string
    $.ajax(
    url: "/lookup/?q="+query
    # i'm binding the function here using CoffeeScript syntactic sugar,
    # you can use for example Underscore's bind function instead.
    success: (data) =>
    # data must be a list of either strings or objects
    # data = [{'name': 'Joe', }, {'name': 'Henry'}, ...]
    typeahead.process(data)
    )
    # if we return objects to typeahead.process we must specify the property
    # that typeahead uses to look up the display value
    property: "name"
    )
    ```

    ### For example, process typeahead list synchronously and call a function on selection

    ```javascript
    // This example is in Javascript, collects html in some li's and returns it
    $('.typeahead').typeahead({
    source: function (typeahead, query) {
    var return_list = []
    $("li").each(function(i,v){
    return_list.push($(v).html())
    })
    // here I'm just returning a list of strings
    return return_list
    },
    // typeahead calls this function when a object is selected, and
    // passes an object or string depending on what you processed, in this case a string
    onselect: function (obj) {
    alert('Selected '+obj)
    }

    })
    ```

    Note that onselect works without source as a function and vice versa. Events may be a cleaner solution to passing callbacks and using bind all over the place, but I think these additions are very helpful.
    311 changes: 311 additions & 0 deletions bootstrap-typeahead.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,311 @@
    /* =============================================================
    * bootstrap-typeahead.js v2.0.0
    * http://twitter.github.com/bootstrap/javascript.html#typeahead
    * =============================================================
    * Copyright 2012 Twitter, Inc.
    *
    * Licensed under the Apache License, Version 2.0 (the "License");
    * you may not use this file except in compliance with the License.
    * You may obtain a copy of the License at
    *
    * http://www.apache.org/licenses/LICENSE-2.0
    *
    * Unless required by applicable law or agreed to in writing, software
    * distributed under the License is distributed on an "AS IS" BASIS,
    * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    * See the License for the specific language governing permissions and
    * limitations under the License.
    * ============================================================ */

    !function( $ ){

    "use strict"

    var Typeahead = function ( element, options ) {
    this.$element = $(element)
    this.options = $.extend({}, $.fn.typeahead.defaults, options)
    this.matcher = this.options.matcher || this.matcher
    this.sorter = this.options.sorter || this.sorter
    this.highlighter = this.options.highlighter || this.highlighter
    this.$menu = $(this.options.menu).appendTo('body')
    this.source = this.options.source
    this.onselect = this.options.onselect
    this.strings = true
    this.shown = false
    this.listen()
    }

    Typeahead.prototype = {

    constructor: Typeahead

    , select: function () {
    var val = JSON.parse(this.$menu.find('.active').attr('data-value'))
    , text

    if (!this.strings) text = val[this.options.property]
    else text = val

    this.$element.val(text)

    if (typeof this.onselect == "function")
    this.onselect(val)

    return this.hide()
    }

    , show: function () {
    var pos = $.extend({}, this.$element.offset(), {
    height: this.$element[0].offsetHeight
    })

    this.$menu.css({
    top: pos.top + pos.height
    , left: pos.left
    })

    this.$menu.show()
    this.shown = true
    return this
    }

    , hide: function () {
    this.$menu.hide()
    this.shown = false
    return this
    }

    , lookup: function (event) {
    var that = this
    , items
    , q
    , value

    this.query = this.$element.val()

    if (typeof this.source == "function")
    value = this.source(this, this.query)
    if (value)
    this.process(value)
    else
    this.process(this.source)
    }

    , process: function (results) {
    var that = this
    , items
    , q

    if (results.length && typeof results[0] != "string")
    this.strings = false

    this.query = this.$element.val()

    if (!this.query) {
    return this.shown ? this.hide() : this
    }

    items = $.grep(results, function (item) {
    if (!that.strings)
    item = item[that.options.property]
    if (that.matcher(item)) return item
    })

    items = this.sorter(items)

    if (!items.length) {
    return this.shown ? this.hide() : this
    }

    return this.render(items.slice(0, this.options.items)).show()
    }

    , matcher: function (item) {
    return ~item.toLowerCase().indexOf(this.query.toLowerCase())
    }

    , sorter: function (items) {
    var beginswith = []
    , caseSensitive = []
    , caseInsensitive = []
    , item
    , sortby

    while (item = items.shift()) {
    if (this.strings) sortby = item
    else sortby = item[this.options.property]

    if (!sortby.toLowerCase().indexOf(this.query.toLowerCase())) beginswith.push(item)
    else if (~sortby.indexOf(this.query)) caseSensitive.push(item)
    else caseInsensitive.push(item)
    }

    return beginswith.concat(caseSensitive, caseInsensitive)
    }

    , highlighter: function (item) {
    return item.replace(new RegExp('(' + this.query + ')', 'ig'), function ($1, match) {
    return '<strong>' + match + '</strong>'
    })
    }

    , render: function (items) {
    var that = this

    items = $(items).map(function (i, item) {
    i = $(that.options.item).attr('data-value', JSON.stringify(item))
    if (!that.strings)
    item = item[that.options.property]
    i.find('a').html(that.highlighter(item))
    return i[0]
    })

    items.first().addClass('active')
    this.$menu.html(items)
    return this
    }

    , next: function (event) {
    var active = this.$menu.find('.active').removeClass('active')
    , next = active.next()

    if (!next.length) {
    next = $(this.$menu.find('li')[0])
    }

    next.addClass('active')
    }

    , prev: function (event) {
    var active = this.$menu.find('.active').removeClass('active')
    , prev = active.prev()

    if (!prev.length) {
    prev = this.$menu.find('li').last()
    }

    prev.addClass('active')
    }

    , listen: function () {
    this.$element
    .on('blur', $.proxy(this.blur, this))
    .on('keypress', $.proxy(this.keypress, this))
    .on('keyup', $.proxy(this.keyup, this))

    if ($.browser.webkit || $.browser.msie) {
    this.$element.on('keydown', $.proxy(this.keypress, this))
    }

    this.$menu
    .on('click', $.proxy(this.click, this))
    .on('mouseenter', 'li', $.proxy(this.mouseenter, this))
    }

    , keyup: function (e) {
    e.stopPropagation()
    e.preventDefault()

    switch(e.keyCode) {
    case 40: // down arrow
    case 38: // up arrow
    break

    case 9: // tab
    case 13: // enter
    if (!this.shown) return
    this.select()
    break

    case 27: // escape
    this.hide()
    break

    default:
    this.lookup()
    }

    }

    , keypress: function (e) {
    e.stopPropagation()
    if (!this.shown) return

    switch(e.keyCode) {
    case 9: // tab
    case 13: // enter
    case 27: // escape
    e.preventDefault()
    break

    case 38: // up arrow
    e.preventDefault()
    this.prev()
    break

    case 40: // down arrow
    e.preventDefault()
    this.next()
    break
    }
    }

    , blur: function (e) {
    var that = this
    e.stopPropagation()
    e.preventDefault()
    setTimeout(function () { that.hide() }, 150)
    }

    , click: function (e) {
    e.stopPropagation()
    e.preventDefault()
    this.select()
    }

    , mouseenter: function (e) {
    this.$menu.find('.active').removeClass('active')
    $(e.currentTarget).addClass('active')
    }

    }


    /* TYPEAHEAD PLUGIN DEFINITION
    * =========================== */

    $.fn.typeahead = function ( option ) {
    return this.each(function () {
    var $this = $(this)
    , data = $this.data('typeahead')
    , options = typeof option == 'object' && option
    if (!data) $this.data('typeahead', (data = new Typeahead(this, options)))
    if (typeof option == 'string') data[option]()
    })
    }

    $.fn.typeahead.defaults = {
    source: []
    , items: 8
    , menu: '<ul class="typeahead dropdown-menu"></ul>'
    , item: '<li><a href="#"></a></li>'
    , onselect: null
    , property: 'value'
    }

    $.fn.typeahead.Constructor = Typeahead


    /* TYPEAHEAD DATA-API
    * ================== */

    $(function () {
    $('body').on('focus.typeahead.data-api', '[data-provide="typeahead"]', function (e) {
    var $this = $(this)
    if ($this.data('typeahead')) return
    e.preventDefault()
    $this.typeahead($this.data())
    })
    })

    }( window.jQuery );
    108 changes: 108 additions & 0 deletions gistfile1.diff
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,108 @@
    /* =============================================================
    * bootstrap-typeahead.js v2.0.0
    * http://twitter.github.com/bootstrap/javascript.html#typeahead
    * =============================================================
    * Copyright 2012 Twitter, Inc.
    @@ -29,6 +29,8 @@
    this.highlighter = this.options.highlighter || this.highlighter
    this.$menu = $(this.options.menu).appendTo('body')
    this.source = this.options.source
    + this.onselect = this.options.onselect
    + this.strings = true
    this.shown = false
    this.listen()
    }
    @@ -38,8 +40,17 @@
    constructor: Typeahead

    , select: function () {
    - var val = this.$menu.find('.active').attr('data-value')
    - this.$element.val(val)
    + var val = JSON.parse(this.$menu.find('.active').attr('data-value'))
    + , text
    +
    + if (!this.strings) text = val[this.options.property]
    + else text = val
    +
    + this.$element.val(text)
    +
    + if (typeof this.onselect == "function")
    + this.onselect(val)
    +
    return this.hide()
    }

    @@ -68,6 +79,25 @@
    var that = this
    , items
    , q
    + , value
    +
    + this.query = this.$element.val()
    +
    + if (typeof this.source == "function")
    + value = this.source(this, this.query)
    + if (value)
    + this.process(value)
    + else
    + this.process(this.source)
    + }
    +
    + , process: function (results) {
    + var that = this
    + , items
    + , q
    +
    + if (results.length && typeof results[0] != "string")
    + this.strings = false

    this.query = this.$element.val()

    @@ -75,7 +105,9 @@
    return this.shown ? this.hide() : this
    }

    - items = $.grep(this.source, function (item) {
    + items = $.grep(results, function (item) {
    + if (!that.strings)
    + item = item[that.options.property]
    if (that.matcher(item)) return item
    })

    @@ -97,10 +129,14 @@
    , caseSensitive = []
    , caseInsensitive = []
    , item
    + , sortby

    while (item = items.shift()) {
    - if (!item.toLowerCase().indexOf(this.query.toLowerCase())) beginswith.push(item)
    - else if (~item.indexOf(this.query)) caseSensitive.push(item)
    + if (this.strings) sortby = item
    + else sortby = item[this.options.property]
    +
    + if (!sortby.toLowerCase().indexOf(this.query.toLowerCase())) beginswith.push(item)
    + else if (~sortby.indexOf(this.query)) caseSensitive.push(item)
    else caseInsensitive.push(item)
    }

    @@ -117,7 +153,9 @@
    var that = this

    items = $(items).map(function (i, item) {
    - i = $(that.options.item).attr('data-value', item)
    + i = $(that.options.item).attr('data-value', JSON.stringify(item))
    + if (!that.strings)
    + item = item[that.options.property]
    i.find('a').html(that.highlighter(item))
    return i[0]
    })
    @@ -251,6 +289,8 @@
    , items: 8
    , menu: '<ul class="typeahead dropdown-menu"></ul>'
    , item: '<li><a href="#"></a></li>'
    + , onselect: null
    + , property: 'value'
    }

    $.fn.typeahead.Constructor = Typeahead