Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / Typescript

Typescript Type Ahead Binding and Filter with Knockout.js and Underscore.js

4.50/5 (5 votes)
29 Nov 2016CPOL2 min read 13.2K  
I did two versions, one with a simple text dump to a span as HTML, and the other to filter contents of a drop down list (select). This article only contains the simpler one. I hope to add the other one depending on how this is received and if I can host code samples demos somewhere.

Introduction

If you're looking for examples using knockout.js and underscore.js together, this should be a good one. The resulting UI is nothing sexy.

A couple of potential annoying gotchas:

The example uses Typscript syntax because that is what I use in my current dev environment.

Please forgive me for not having a hosted demo, I'm coming up to speed on how and where to do that.

Background

While searching for "typeahead" examples, I realized I already did this with knockout.js and underscore.js.

Uses a knockout.js observable with the input filter text, an observable array to hold initialized data, a computed observable with chained underscore.js filter and sort to do the type ahead filtering magic.

For my purposes I'm assuming some basic knowledge and experience with using knockout.js and underscore.js.

Using the Code

A quick typescript class to use:

JavaScript
class myDataModel
  {
    Name = '';
    Type = '';
    DATE: Date = null;
    constructor(pname, ptype, pdate:Date) 
    {
      this.Name = pname;
      this.Type = ptype;
      this.DATE = pdate;
    }
  }

And a quick data mock-up to use.

JavaScript
placeholderData = [
      { Name: 'Bob T. Turner', Type: "M", DATE: new Date('Jan 03 1964') },
      { Name: 'Sarah S. Wilson', Type: "F", DATE: new Date('Apr 16 1956') },
      { Name: 'Frederick R. Flintock', Type: "M", DATE: new Date('Dec 23 1974') },
      { Name: 'Joey P. Ponder', Type: "M", DATE: new Date('Jun 01 1963') },
      { Name: 'Alice C. Reston', Type: "M", DATE: new Date('Aug 28 2000') }
    ];

The Knockout.js observables, including a computed observable which filters and sorts based on UI input.

I will further explain the use of Knockout observables and the Underscore chain() below.

JavaScript
dataFilter = ko.observable('');
modelArray = ko.observableArray(<Array<myDataModel>>[]);

computedHtmlResult = ko.computed(() =>
{
  var filterText = this.dataFilter();
  var baseList = this.modelArray();

  if (!filterText)
    return '';
  else
   var vfilteredSearch =   _(baseList)
      .chain()
      .filter((pmodel) => {
         return (pmodel.Name.toLowerCase()).indexOf(filterText.toLowerCase()) !== -1;
      })
      .sortBy((pfilteredmodel) => {
        return pfilteredmodel.Name;
      })
    .value();

   var vreturnValue = '';

   _.each(vfilteredSearch, (pfsmodel) => {
     var vdateText = pfsmodel.DATE.toLocaleDateString('en-US');
     vreturnValue += pfsmodel.Name + ' ' +
     pfsmodel.Type + ' ' + vdateText + '<br>';
   });

  return vreturnValue;
});

HTML

With Knockout.js binding is accomplished with "data-bind="textInput: dataFilter "...".

HTML
<input type="text" id="dev-settings-res-scratchdiv" 
data-bind="textInput: dataFilter " 
class="input-xxlarge"/>

<span data-bind="html: computedHtmlResult "> </span>

Very clean and simple.

Back to Typescript.

The Knockout.js computed observable computedHtmlResult contains the important type-ahead intelligence: (if you are not used to underscore.js functional chains, this may not be clear at first, but I will explain more).

If there is no filter text input, we just return:

JavaScript
if (!filterText) return '';

Filter the list of models into a new list of models (vfilteredSearch) to only include those where the name contains the filter text:

JavaScript
var vfilteredSearch = _(baseList)

Chain the filter and sort.

JavaScript
.chain()

Filter the "scratch" models to only include what we want. I chose to use toLowerCase to make it case insensitive.

JavaScript
.filter((pmodel) => { return (pmodel.Name.toLowerCase()).indexOf(filterText.toLowerCase()) !== -1; })

Sort by name and add the filtered models to the new filtered array:

JavaScript
.sortBy((pfilteredmodel) => { return pfilteredmodel.Name; }) .value();

Now vfilteredSearch contains the filtered models.

Let's create some simple HTML text to display in the UI. Not very sexy, but demonstrates the concept quite well:

JavaScript
var vreturnValue = '';

_.each(vfilteredSearch, (pfsmodel) => 
{ 
var vdateText = pfsmodel.DATE.toLocaleDateString('en-US'); 
vreturnValue += pfsmodel.Name + ' ' + pfsmodel.Type + ' ' + vdateText + '<br>'; 
});

return vreturnValue;

The return value now contains simple HTML text for only the filtered models, this will be displayed in the span.

If all works well, the text should be blank until a user inputs something in the text input.

An input of "b" or "B" should result in only "Bob T. Turner..." displayed.

An input of "o" or "O" should show all the data.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)