Facebook-style Search using Knockoutjs and jQuery

Facebook-style Search with Knockoutjs and JQuery
I recently stumbled across Knockoutjs, a MVVM framework for Javascript that allows us to declaratively add two-way data binding to html elements with minimal markup and no tedious event registration. Now there are plenty of examples of using Knockoutjs on the project website, but what I want to see is if I could create a Facebook style search box and results using some nifty and clean Knockoutjs code.

Now, Knockoutjs isn’t really concerned with the DOM manipulation and event registration of the current generation of javascript frameworks, but that is not to say that it doesn’t play nice. In fact, using Knockoutjs and jQuery together is just a dream.

View Models and Bindings
Our first port of call is to look at how we plan our view model. I can initially see we’re going to need something like:

var viewModel = { query: ko.observable(), results: ko.observableArray() };
Code language: JavaScript (javascript)

This allows us to data bind to the query property and automatically update that as our search box is changed. But the problem we currently have, is when we want to start fetching data from our server, we would have to manually update this observable with our results. To get round this, we can use a nifty extension called ko.mapping. The mapping extension allows us to define a base model and generate a view model from that, e.g.:

var baseModel = { query: "", results: [] } var viewModel = ko.mapping.fromJS(baseModel); var viewModel = ko.mapping.fromJS(baseModel);
Code language: JavaScript (javascript)

The mapping extension will take our base model and generate a view model with observable members within it, essentially our mapped view model works like our original view model. What we can do now though, is when we grab results from our server, we can simple use the mapping extension to update the view model, which in turn updates our UI.

var resultModel = // get from server? ko.mapping.updateFromJS(viewModel, resultModel);
Code language: JavaScript (javascript)

So how does this fit in with our UI?

A simple html markup
Our markup is pretty simple, we have a textbox, a button and a list. Chuck in a few more elements and some styling, we get a nice Facebook-style search box:

<div class="searchBox"> <span class="searchContainer"><input /><button></button></span> <ul class="results"></ul> </div>
Code language: HTML, XML (xml)

Data-binding the view model
What we need to do first, is add a two-way data-binding to our input textbox, but the default binding updates our value when the change event is fired. To allow a search-as-you-type experience, we need to change this to a keyup event. We can do that with the following binding:

<input data-bind="value: query, valueUpdate: 'afterkeyup'" />
Code language: HTML, XML (xml)

Now, let’s jump ahead a bit, and start looking at how our UI will get updated by our view model:

(function($) { var baseModel = { query: "", results: [] }; var viewModel = ko.mapping.fromJS(baseModel); viewModel.doSearch = function() { var $this = this; setTimeout(function() { var resultModel = null; var q = $this.query(); if (q == "") { resultModel = { results: [] }; ko.mapping.updateFromJS(viewModel, resultModel); } else { $.ajax({ url: "json.asp", data: { "query": q }, type: "GET", dataType: "json", success: function(r) { resultModel = r; ko.mapping.updateFromJS(viewModel, resultModel); } }); } }, 1); return true; }; ko.applyBindings(viewModel, $("#search").get(0)); })(jQuery);
Code language: JavaScript (javascript)

I’ve added a function to the view model, doSearch and that is responsible for updating our results. We use jQuery’s ajax function to grab our server data (in JSON format), and then map that result straight into our view model. You’ll notice the setTimeout call, unfortunately the keypress event the doSearch function will be bound to fires before the value is updated, so we need to delay it and allow our value to update before the we try and do our search.

We’ve also only applied this view model in the scope of the search element, this allows us to use multiple view models in a single page, targeting different widgets.

Binding events and templating the results
To get it all working, we need to do a couple of things. Firstly, let’s bind our events, we do this in two places, our textbox and our button. The button will respond to the default click event, but the textbox needs to respond to keyup, so…

Using jQuery’s tmpl plugin, we can easily create client side templates, which we need for our result objects. Now, our results may look like this:

results: [ { type: "header", text: "People" }, { type: "person", name: "Matthew Abbott", imageUrl: "…" } ]
Code language: JavaScript (javascript)

So, in our template, we need to handle both header elements, and people elements. Let’s have a look:

With that template, we add a specific class for styling purposes, based on the result type, and then fill the content using data-bound html elements. Once again, we leave the majority of our data-binding to Knockoutjs, but we let jQuery’s tmpl plugin handle our conditionals.

And this is how we wire it up:

We add our data binding to the list element, specify our template name, and then we toggle the visible binding based on the number of results. Without any results present, the list will automatically be hidden, when we have results, it shows. Here is an example:

Leave a Reply

Your email address will not be published. Required fields are marked *

Related Post

Epic Fan-ArtEpic Fan-Art

Another daily search on Deviantart, and I found two gems, which are pretty damn impressive: SonicSuper_Sonic_by_MRi Source: Sonic by =gureiduson and Super Sonic by *MRi. Damn, if only I could