Monday, July 2, 2012

Javascript Metro App Search with Web Services

If you are here, it probably means you are not contented with the default search implementation in javascript metro apps. The default search method when you add a search contract is as below

   1: // This function populates a WinJS.Binding.List with search results for the
   2:         // provided query.
   3:         searchData: function (queryText) {
   4:             var originalResults;
   5:             var regex;
   6:             // TODO: Perform the appropriate search on your data.
   7:             if (window.Data) {
   8:                 originalResults = Data.items.createFiltered(function (item) {
   9:                     regex = new RegExp(queryText, "gi");
  10:                     return (item.title.match(regex) || item.subtitle.match(regex) || item.description.match(regex));
  11:                 });
  12:             } else {
  13:                 originalResults = new WinJS.Binding.List();
  14:             }
  15:             return originalResults;
  16:         },




Pay attention to line number 8. The way the search results are populated is by creating a filtered list of Data.items that match the query text. Which means, you should already have items loaded into the app to be able to search from them. But what if, I do not load all the items in the app but want to search for items in my database that I haven’t loaded?


If you haven’t guessed already, that is exactly what this post will show. Be aware that this is not the only implementation that is possible for this. This is just the way I did it. I really did not see any help or a post that shows this, so I thought I will blog about it, just in case somebody is looking for something similar. And just so you know, this was built using Windows 8 Release Preview, so it could be a bit different if you are on Windows 8 CP.


When you add a search contract to your project, there are two methods in searchResults.js that are of interest to us. They are  handleQuery and searchData. You will see that the handleQuery in turn calls searchData and a couple of other methods. The search data method is synchronous because it is only filtering items that have already been fetched. If you want to call a web service when the user searches, then this method has to be asynchronous since all web service calls made using WinJS.Xhr are asynchronous by default.


I am going to add a new method in data.js called searchEventHandler



   1: function searchEventHandler(data) {
   2:     var searchList = new WinJS.Binding.List();
   3:     return new WinJS.Promise(function (c, e, p) {
   4:         WinJS.xhr({ type: "POST", url: localSettings.values["webserviceUri"], headers: { "Content-type": "application/x-www-form-urlencoded" }, data: data }).done(
   5:             function (result) {
   6:                 if (result != null && result.responseText != "") {
   7:                     var jsonData = JSON.parse(result.responseText);
   8:                     for (var i = 0; i < jsonData.length; i++) {
   9:                         //process your results if need be
  10:                         searchList.push(jsonData[i]);
  11:                     }
  12:                     c(searchList);
  13:                 }
  14:             },
  15:             function error(result) {
  16:                 if (result.responseText != "") {
  17:                     var item = new Object();
  18:                     item.title = "Error occurred.";
  19:                     item.group = sampleGroups[0];
  20:                     item.backgroundImage = lightGray;
  21:                     searchList.push(item);
  22:                     e(searchList);
  23:                 }
  24:  
  25:             }
  26:         );
  27:     });



Will take a moment to explain what this method does. It calls the web service passing the query string as a parameter and adds the results to a WinJS binding list. To make this method asynchronous, I have used WinJS.Promise. The method then calls the success or the error call back with the search results as a parameter. With this, we now have an asynchronous method that calls a web service and returns the search results. We now have to call this method from searchResults.js. Add this method to the data namespace, so it can be called from searchResults.



   1: WinJS.Namespace.define("data", {
   2:     items: groupedItems,
   3:     products:productsList,
   4:     groups: groupedItems.groups,
   5:     getItemsFromGroup: getItemsFromGroup,
   6:     searchEventHandler: searchEventHandler
   7: });

The searchData function then needs to be modified as below



   1: // This function populates a WinJS.Binding.List with search results for the
   2:         // provided query.
   3:         searchData: function (queryText) {
   4:             var originalResults;
   5:             //set the criteria to en-us and on-demand
   6:             var criteria = "culture=en-us&eventType=3&kwdAny=" + queryText;
   7:             data.searchEventHandler(criteria).done(function (results) {
   8:                 originalResults = results;
   9:                 searchResults.generateFilters(originalResults);
  10:                 searchResults.populateFilterBar(currentElement, originalResults);
  11:             });
  12:         }



Apart from the obvious change of calling the searchEventHandler, also note that the generateFilters and the populateFilterBar functions are being called from here instead of handleQuery. This is because both those functions depend on the results. I am leaving out more obvious code changes here like making currentElement a class variable.


One last thing that is left to do is to generate the filters. If the filters are generated from the results, then the generateFilters method has to be changed to update your filters after the search is done rather than before. Since it depends on your implementation of the filters, I am not including that code here.


As I stated in the post earlier, there are innumerable variations through which this can be achieved. Please feel free to let me know if you have done this any differently.


Technorati Tags: ,,,