[Prisjakt – The Story is a series of blog posts about our experiences helping Prisjakt/PriceSpy to write a Windows 8 Metro Style App using HTML/CSS/JavaScript. Today’s post talks implementing the search contract and implementing deep linking from a live tile and is authored by Peter Bryntesson]

Introduction

An important aspect of every Windows 8 Metro Style application is to interface with as many of the Windows 8 contracts available as possible, and to use the tiles on the startup screen creatively. Prisjakt/PriceSpy currently implements the search contract and can pin individual products to the start screen as separate tiles to enable deep linking into the application. This posts talks about the implementation of these two features.

Search contract

In Windows 8 there is a global search functionality accessed from the charms bar, where you can search within not only the system, but also participating applications. This is a powerful feature and will allow the user to search consistently, regardless of which application he currently searches. To implement this we need to register ourselves with the search pane and implement a couple of call-backs to do the search when the user types.

There are two main events fired when the user searches, QuerySubmitted and QueryChanged. QuerySubmitted is fired when the user presses the enter key or clicks on your app’s icon in the search bar. QueryChanged is called whenever the user changes the search string. We would strongly suggest to listening to both events in order to do asynchronous, incremental search. This is the norm in Windows 8 and the user will expect this.

The code to register event handlers for this events are placed in default.js, in app.initialize() like this:

app.initialize = function () { // Localize all resources WinJS.Resources.processAll(); // Obtain the Search Pane object and register for handling search while running as the main application var searchPane = Windows.ApplicationModel.Search.SearchPane.getForCurrentView(); searchPane.addEventListener("querysubmitted", app.querySubmitted, false); searchPane.addEventListener("querychanged", app.queryChanged, false); };

The event handlers are really just wrappers around an internal search function, like this:

app.querySubmitted = function (e) { app.search(e.queryText); }; app.queryChanged = function

(e) { app.search(e.queryText); }; 

We’ll get back to the implementation of app.Search() later. There are two more things we need to add to the code in order to support the search contract. When our app is started and active and the user opens the search bar and searches in our app, the registered event handlers is called and everything works. But the user can initiate a search when our app is not the active app and even when our app isn’t even started, how does it work then? The answer lies in the app.onactivated() function. As one of the parameters in the event detail object, we get an enumeration of ActivationKind to understand why we were activated. In this case we get ActivationKind.Search and the search text is supplied in an other parameter. Fine. But wait, this is really early in the start process, the DOM isn’t loaded and no page stack has been setup. Our main page will be loaded eventually but for now we have nowhere to turn.

Let’s cache it away.

In Windows 8 we have the Windows.Storage.ApplicationData.current.localSettings which essentially is an hierarchical key value store. So we store the search text, and when the main page gets loaded we check it. Here’s the code in app.onactivated():

app.onactivated = function (eventObject) { // are we awakened by a search request? if (eventObject.detail.kind === Windows.ApplicationModel.Activation.ActivationKind.search) { // store this for processing later Windows.Storage.ApplicationData.current.localSettings.values["searchText"

] = eventObject.detail.queryText; } 

And here is the code in the main page ready() function to handle when we are activated through search:

// Are we awakened by deep linking or search? searchText = Windows.Storage.ApplicationData.current.localSettings.values["searchText"]; if (searchText != null && searchText != "") { if (searchText.substring(0, 9) == "PrisJakt.") { // deep linking directly to a product id. move to details page (and user can back to main page) var productID = searchText.substring(9); WinJS.Navigation.navigate("/html/productdetailspage.html", { productID: productID }); } else { // search request, goto productlisting page WinJS.Navigation.navigate("/html/productListingPage.html", { searchString: searchText }); } // reset search text and return (we will not show main page now, anyway) Windows.Storage.ApplicationData.current.localSettings.values["searchText"] = ""; return

; } 

We read the value from local settings and if we find it we checks to see of we are deep linking to a product or doing search and moves to the proper page. We reset the local settings value and return, since we won’t show the main page. By doing this way, the main page is always at the root of the page stack and we can back our way there.

The final thing we have to do is to let Windows know that we support the search contract. This is done in the manifest file package.appxmanifest. Just select Search from the available declarations and click Add.

image

Search implementation details

Back to the implementation of search. Prisjakt/Pricespy consist of four main pages:

  • The main or root page, where we should the spotlighted products and the root categories. This is the landing page.
  • The categories page, which is shown while browsing the categories tree.
  • The product listing page, where we should a list of products, either from search or from a subcategory.
  • The product details page, where we show all the information for a single product.

Search is the main feature of this app, and it is so prominent that we wanted to be able to search in the app as well, and not just in the search bar. Also, we wanted to be able to search from all pages mentioned above except the details page, and we wanted the search experience to be consistent. The solution we ended up with was to have an appbar in each of those pages, have a search text box placed consistently in those appbars and navigate to the product listing page as soon as the user clicks in the search box. That way all searching is done in one page with a consistent behaviour. 

We also wanted the search to be contextually aware, that is if the user has browsed into a subcategory, the search should only search within that category and not all products. This is also a behaviour we recommend, regardless of if you search from the search bar or from inside your app.

Back to code. The code for app.search() is as follows:

app.search = function (searchString) { // check where we are, we should navigate to product listing if we are not already there if (WinJS.Navigation.location == "/html/productlistingpage.html" && productListingPage != null) { // let's submit new search string here, do new search inline productListingPage.onAppSearch(searchString, null); } else if (WinJS.Navigation.location == "/html/categorypage.html" && categoryPage != null) { // find out current category and supply that to narrow the search var category = categoryPage.getCategory(); // move to productlistingpage and supply the searchstring WinJS.Navigation.navigate("/html/productlistingpage.html", { searchString: searchString, category: category }); } else { // move to productlistingpage and supply the searchstring WinJS.Navigation.navigate("/html/productlistingpage.html",

 { searchString: searchString }); } } 

Here we can see that we check on which type of page we are right now, and supply contextual information to product listing page. The code in product listing page’s ready() function looks like this:

 

// do we have a search string? this.searchString = (options.searchString != null ? options.searchString : ""); if (app.getCurrentSearchString() != null) //search string saved away for return from details page, override when backing this.searchString = app.getCurrentSearchString(); if (this.searchString != null && this.searchString != "") { // assign this to the search box in the appbar var searchCtl = document.getElementById("productlistingpageappbar-search"); searchCtl.value = this.searchString; }

 // other initialization omitted here 

// initiate a search this.startSearch();

 

Here we collect the incoming parameters, assign them to the search box (for a consistent search interface) and then we call startSearch() which will initiate a asynchronous, interruptible search against the web site.

Deep Linking

Deep linking is the concept of using a tile on the start screen to not only link to your app, but to a specific place in your app. Think of it as a way of adding favourites or bookmarks in your app, on the start screen. When used from inside the app, the operation is commonly known as pin to start. In Prisjakt/PriceSpy we added this feature to the product details page, in order to link directly to your favourite products from the start page. Let’s us go through what has to be done in the code.

Pinning

We have chosen to use the string “Prisjakt.” + the product id to uniquely identify a product. To work with tiles, you use the Windows.UI.StartScreen.SecondaryTile class. To find out if we already have a tile for a specific product, we do the following call:

 if 

(Windows.UI.StartScreen.SecondaryTile.exists("PrisJakt." + productID))

We have added an appbar command in the app bar which either shows “Pin” or “Unpin” depending on if the tile already exists. The code for handling click on this command goes here:

function handlePin() { var that = this; // do we have something to pin/unpin? document.getElementById("productdetailspageappbar").winControl.sticky = true; // Check whether you've received a pin or unpin command. if (Windows.UI.StartScreen.SecondaryTile.exists("PrisJakt." + productID)) { // unpinByElementAsync is an asynchronous, app-defined, // helper function. that.unpinByElementAsync(document.getElementById("cmdPin"), "PrisJakt." + productID).then(function (isDeleted) { that.enableAppBarCommands(); }); } else { // pinByElementAsync is an asynchronous, app-defined, // helper function. that.pinByElementAsync(document.getElementById("cmdPin"), "PrisJakt." + productID, productName, productName).then(function (isCreated) { that.enableAppBarCommands(); }); } }

Basically the code checks to see if we have a secondary tile already, and calls pinByElementAsync or unpinByElementAsync. The reason these helepr functions end with Async is that they are just that, asynchronous functions. The reason for this is that we need to ask the user for permission (via a popup on-the-screen), we cannot add/remove tiles programmatically without the user’s consent.

If we take a look inside pinByElementAsync, we will find the following:

function pinByElementAsync(element, newTileID, newTileShortName, newTileDisplayName) { // setup the graphical properties var uriLogo = new Windows.Foundation.Uri("ms-appx:///images/logo150x150.png"); var uriSmallLogo = new Windows.Foundation.Uri("ms-appx:///images/logo150x150.png"); var tile = new Windows.UI.StartScreen.SecondaryTile(newTileID, newTileShortName, newTileDisplayName, newTileID, Windows.UI.StartScreen.TileOptions.showNameOnLogo, uriLogo); tile.foregroundText = Windows.UI.StartScreen.ForegroundText.light; tile.smallLogo = uriSmallLogo; // Let's place the focus rectangle near the button, just like we did in pinByElement var selectionRect = element.getBoundingClientRect(); // Now let's try to pin the tile. return new WinJS.Promise(function (complete, error, progress) { tile.requestCreateAsync({ x: selectionRect.left, y: selectionRect.top }).then(function (isCreated) { if (isCreated) { complete(true); } else { complete(false

); } }); }); } 

First we create an instance of the Windows.UI.StartScreen.SecondaryTile object with our properties set, then we call requestCreateAsync on it (this will show the popup GUI) and return the asynchronous result wrapped in a WinJS.Promise object back to the caller. Likewise, the code for unpinByElementAsync goes like:

function unpinByElementAsync(element, unwantedTileID) { // We'll use the bounding client rectangle of // the element to get those coordinates. var selectionRect = element.getBoundingClientRect(); // We need to construct a SecondaryTile with // that name in order to unpin it. var tileToDelete = new Windows.UI.StartScreen.SecondaryTile(unwantedTileID); // Now we make the delete request, passing a point // built from the bounding client rectangle. // Note that this is an async call, and we'll return a // promise so we can do additional work when it's complete. return new WinJS.Promise(function (complete, error, progress) { tileToDelete.requestDeleteAsync({ x: selectionRect.left, y: selectionRect.top }).then(function (isDeleted) { if (isDeleted) { complete(true); } else { complete(false); } }); }); }

It’s a bit awkward that we have to create the SecondaryTile object just so that we can remove it, but it’s no big deal. Otherwise it follows the same pattern as before by initiating a asynchronous call and wrapping the result into a promise. This is all the code needed for doing pinning/unpinning from the app.

Launch from link

Now, the final missing part of the puzzle is how an app is started from a pinned(secondary) tile. Much of the same functions that we went through when we we’re activated via search is in use here as well. Let’s go through this briefly. When a user launches a secondary tile, app.onactivated() gets called with ActivationKind.Launch. Here we need to check if the app is already running. Then we can process the request directly and navigate to the correct page directly. Otherwise, we store the string in localSettings and process the request in the main page the same we did in search (actually the same code). Here’s the complete code for app.onactivated():

 app.onactivated = function (eventObject) { // are we awakened by a search request? if (eventObject.detail.kind === Windows.ApplicationModel.Activation.ActivationKind.search) { // store this for processing later Windows.Storage.ApplicationData.current.localSettings.values["searchText"] = eventObject.detail.queryText; } else if (eventObject.detail.kind === Windows.ApplicationModel.Activation.ActivationKind.launch) { // okay, was the previous state running? that is is everything setup // and we have a page stack? if (eventObject.detail.previousExecutionState == Windows.ApplicationModel.Activation.ApplicationExecutionState.running) { // yes, parse and see if it's a deep link to a product or search query var text = eventObject.detail.arguments; if (text != null && text != "") { if (text.substring(0, 9) == "PrisJakt.") { // deep link to a product. navigate directly to details page // to not mess up the page stack var productID = text.substring(9); WinJS.Navigation.navigate("/html/productdetailspage.html", { productID: productID }); } else { // deep link to a search query. navigate directly to listing page WinJS.Navigation.navigate("/html/productListingPage.html", { searchString: text }); } } } else { // simply store it in localsettings for later processing in mainpage Windows.Storage.ApplicationData.current.localSettings.values["searchText"] = eventObject.detail.arguments; } } WinJS.UI.processAll(); }; 

So this is the code needed for deep linking.

Conclusion

Interfacing with the contracts Windows 8 and working with secondary tiles is an really important part of an Metro Style app. Implementing the search contract and implement pinning in your app is not too difficult as we’ve illustrated here. What do you think?