Knockout & Twitter Typehead

by Farzad Cyrus

HTML

<script src="//cdn.jsdelivr.net/knockout/3.1.0/knockout.js"></script>
<script src="//netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js"></script>
<script src="//cdn.jsdelivr.net/typeahead.js/0.10.1/typeahead.bundle.min.js"></script>
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css">
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/font-awesome/3.2.1/css/font-awesome.min.css">
<!-- Add your site or application content here -->
<div class="container">
    <div class="row">
        <div class="col-md-12">
            <section id="typeahead">
                <div class="page-header">
                     <h1>Typeahead</h1>

                </div>
                 <h2>Examples</h2>

                <p>Typeahead input with a data source as an observable array. Pass a reference to the <code>data-bind="typeahead:"</code> binding and the items from that observable array will be shown as suggestions in the input box. To show the binding nature of this feature the example allows you to add and remove items from the array.
                    <div class="bs-docs-example">
                        <div class="row">
                            <div class="col-md-4">
                                <form>
                                    <label>Javascript Frameworks</label>
                                    <ul id="selected-tags" data-bind="foreach: selectedItems">
                                        <li data-bind="text: $data"></li>
                                    </ul>
                                    <input name="jsFrameworks" id="jsFrameworks" type="text" data-bind="typeahead: { source: jsFrameworks }" />
                                </form>
                                <form data-bind="submit: addFramework">
                                    <label>Add a framework</label>
                                    <input type="text" data-bind="value: frameworkToAdd, valueUpdate:'afterkeydown'" />
 ...

CSS

.tags {
    margin-left: -4px;
    margin-right: -4px;
    padding: 16px;
}
.tags-item {
    display: inline-block;
    padding:0;
    
    width: auto;
    height: auto;
}
.tags-item a {
    display: block;
    line-height: 16px;
    padding: 4px 8px;
    background-color: #8899aa;
    color: #ffffff;
    text-decoration: none;
    cursor: pointer;
    margin: 0 4px 8px;
}
.tags-item:hover a {background-color:#667788;}

JavaScript

var ExampleViewModel = function () {
    var self = this;

    self.jsFrameworks = ko.observableArray([
        'Angular',
        'Canjs',
        'Batman',
        'Meteor',
        'Ember',
        'Backbone',
        'Knockout',
        'Knockback',
        'Spine',
        'Sammy',
        'YUI',
        'Closure',
        'jQuery']);

    self.frameworkToAdd = ko.observable("");
    self.addFramework = function () {
        self.jsFrameworks.push(self.frameworkToAdd());
    };

    self.removeFramework = function (framework) {
        self.jsFrameworks.remove(framework);
    };
    
    self.selectedItems = ko.observableArray([
        'Angular',
        'Canjs'
    ]);
    
    self.addTag = function() {
        self.selectedItems.push(this);
    }
};

$(function () {
    var viewModel = new ExampleViewModel();

    ko.bindingHandlers.typeahead = {
        init: function (element, valueAccessor, bindingAccessor) {
            var substringMatcher = function (strs) {
                return function findMatches(q, cb) {
                    var matches, substringRegex;

                    // an array that will be populated with substring matches
                    matches = [];

                    // regex used to determine if a string contains the substring `q`
                    substrRegex = new RegExp(q, 'i');

                    // iterate through the pool of strings and for any string that
                    // contains the substring `q`, add it to the `matches` array
                    $.each(strs, function (i, str) {
                        // console.log(str);
                        if (substrRegex.test(str)) {
                            // the typeahead jQuery plugin expects suggestions to a
                            // JavaScript object, refer to typeahead docs for more info
                            matches.push({
                                value: str
                            });
                        }
                   ...