Chapter 12
Contracts

Recently I discovered a delightfully quirky comedy called Interstate 60 that is full of delightfully quirky characters. One of them, played by Chris Cooper, is a former advertising executive who, having discovered he was terminally ill with lung cancer, decided to make up for a career built on lies by encouraging others to be more truthful. As such, he was very particular about agreements and contracts, especially those in writing.

We really get to see the character’s quirkiness in a scene at a gas station. He’s approached by a beggar with a sign, “Will work for food.” Seeing this, he offers the man an apple in exchange for cleaning his car’s windshield. But when the man refuses to honor the written contract on his sign, Cooper’s character gets increasingly upset over the breach…to the point where he announces his terminal illness, rips open his shirt, and reveals the dynamite wrapped around his body and the 10-second timer that’s already counting down!

In the end, he drives away with a clean windshield and the satisfaction of having helped someone—in his delightfully quirky way—to fulfill their part of a written contract. And he reappears later in the movie in a town that’s 100% populated with lawyers; I’ll leave it to you to imagine the result, or at least enjoy the film.

Setting the dynamite aside, agreements between two parties are exceptionally important in a civil society as they are in a well-running computer system. Agreements are especially important where apps provide extensions to the system and where apps written by different people at different points in time cooperate to fulfill certain tasks.

Such is the nature of various contracts within Windows 8, which as a whole is perhaps one of the most powerful features of the entire system. The overarching purpose of contracts has been describes as “launching apps for a purpose and with context.” That is, instead of just starting apps in isolation, contracts make it possible to start them in relationship to other apps and in the context of those other apps. Information can then be shared directly between those apps for a real purpose, rather than through the generic intermediary of the file system where such context is lost.

With any given contract, one party is the consumer or receiver of information that’s managed through the contract. The other party is then the source or provider of that information. The contract itself is then generic: neither party needs any specific knowledge of the other, just knowledge of their side of the contract. It might not sound like much, but what this allows is a degree of extensibility that gets richer and richer as more apps that support those contracts are added to the system. I figure that when users really start to experience what these contracts provide, they’ll more and more look for and choose apps from the Windows Store that use contracts to enrich their system and create increasingly powerful user experiences.

Within the apps themselves, consuming contracts typically happens through an API call, such as the file pickers, or is already built into the system through UI like the Charms bar. Providing information for a contract is often the more interesting part, because an app needs to respond to specific events (when running), or announce the capability through its manifest and then handle different contract activations.

The table below summarizes all the contracts and other extensions in Windows 8 (in alphabetical order), some of which serve to allow apps to work together while others serve to allow apps to extend system functionality. Full descriptions can be found on App contracts and extensions. Those that are covered in this chapter are colored in green: share, search, file type and URI scheme associations, file pickers, cached file updater, and contacts (people). Others contracts have been or will be covered in the chapters indicated, and a few I’ve simply left for you to explore through the linked documentation and samples.

Tip For a comparison of the different options for exchanging data—the share contract, the clipboard, and the file save picker contract—see Sharing and exchanging data on the Windows Developer Center. It outlines different scenarios for each option and when you might implement more than one in the same app.

Also note that there are many WinRT events involved in these different contracts, so be mindful of the need to call removeEventListener as described in Chapter 3, “App Anatomy and Page Navigation,” in the section “WinRT Events and removeEventListener.”

Images

Images

Share

Though Search appears at the top of the Charms bar, the first contract I want to look at in depth is Share—after all, it’s one of the first things you learn as a child! In truth, I’m starting with Share because we’ve already seen the source side of the story starting back in Chapter 2, “Quickstart,” with the Here My Am! app, and our coverage here will also include a brief look at the age-old clipboard at the end of this section.

Here’s what we’ve already learned about Share, with the more complete process illustrated in Figure 12-1:

• An app with sharable content listens for the datarequested event from the object returned by Windows.ApplicationModel.DataTransfer.DataTransferManager.getForCurrent-View(). This WinRT event (for which you should be mindful of using removeEventListener) is fired whenever the user invokes the Share charm. Note that if an app doesn’t listen for this event at all, the Share charm will show a default “unable to share” message (one that is certain to be disappointing to users!).

• In its event handler, the app determines whether it has anything to share in its current state. If it does, it populates the Windows.ApplicationModel.DataTransfer.DataPackage provided in the event. (This can vary with the selection or lack thereof; if the user needs to make a selection for share to work, the app can display a message to that effect.)

• Based on the data formats in the package, Windows—that is, the share broker who manages the contract—determines the share target apps to display to the user. The user can also control which apps are shown through Change PC Settings > Share.

• When the user picks a target, the associated app is activated and receives the data package to process however it wants.

Images

FIGURE 12-1 Processing the Share contract as initiated by the user’s selection of the Share charm.

This whole process provides a very convenient shortcut for users to take something they love in one app and get it into another app with a simple edge gesture and target app selection. It’s like a semantically rich clipboard in which you don’t have to figure out how to get connected to other apps. What’s very cool about the Share contract, in other words, is that the source doesn’t have to care what happens to the data—its only role is to provide whatever data is appropriate for sharing at the moment the user invokes the Share charm (if, in fact, there is appropriate data—sometimes there isn’t). This liberates source apps from the burden of having to predict, anticipate, or second-guess what users might want to do with the data. Perhaps they want to email it, share it via social networking, drop it into a content management app…who knows?

Well, only the user knows, so what the share broker does with that data is let the user decide! Given the data package from the source, the broker matches the formats in that package to target apps that have indicated support for those formats in their manifests. The broker then displays that list to the user. That list can contain apps, for one, but also something called a quicklink (a Windows.ApplicationModel.-DataTransfer.ShareTarget.Quicklink object, to be precise), which is serviced by some app but is much more specific. For instance, when an email app is shown as an option for sharing, the best it can do is create a new message with no particular recipients. A quicklink, however, can identify specific email addresses, say, for a person or persons you email frequently. The quicklink, then, is essentially an app plus specific configuration information.

Whatever the case, some app is launched when the user selects a target. With the Share contract, the app is launched with an activation kind of shareTarget. This tells it to not bring up its default UI but to rather show a specific share pane (with light-dismiss behavior) in which the user can refine exactly what is being shared and how. A share target for a social network, for instance, will often provide a place to add a comment on the shared data before posting it. An email app would provide a means to edit the message before sending it. A front-end app for a photo service could allow for adding a caption, specifying a location, identifying people, and so on. You get the idea. All of this combines together to provide a smooth flow from having something to share to an app that facilitates the sharing and allows the user to add customizations.

Overall, then, the Share contract gets apps connected to one another for this common purpose without either one having to know anything about the other. This creates a very extensible and scalable experience: since all the potential target choices appear only in the Share charm pane, they never need to clutter a source app as we see happening on many web pages. This is the “content before chrome” design principle in action.

Source apps also don’t need to update themselves when a new target becomes popular (e.g., a new kind of social network): all that’s needed is a single target app. As for those target apps, they don’t have to evangelize themselves to the world: through the contract, source apps are automatically able to use any target apps that come along in the future. And from the end user’s point of view, their experience of the Share charm gets better and better as they acquire more Share-capable apps.

At the same time, it is possible for the source app to know something about how its shared data is being used. Alongside the datarequested event, the DataTransferManager also fires a targetApplicationChosen event to those sources who are listening. The eventArgs in this case contain only a single property: applicationName. This isn’t really useful for any other WinRT APIs, mind you, but is something you can tally within your own analytics. Such data can help you understand whether you’d provide a better user experience by sharing richer data formats, for example, or, if common target apps also support custom formats that you can supportin future updates.

Source Apps

Let’s complete our understanding of source apps now by looking at a number of details we haven’t fully explored yet, primarily around how the source populates the data package and the options it has for handling the request. For this purpose, I suggest you obtain and run both the Sharing content source app sample and the Sharing content target app sample. We’ll be looking at both of these, and the latter provides a helpful way to see how a target app will consume the data package created in the source.

The source app sample provides a number of scenarios that demonstrate how to share different types of data. They also show how to programmatically invoke the Share charm. This isn’t typically recommended, but it is possible. If it really fits in your app scenario, here’s how:

Windows.ApplicationModel.DataTransfer.DataTransferManager.showShareUI();

Calling this will, as when the user invokes the charm, trigger the datarequested event where eventArgs.request object is a Windows.ApplicationModel.DataTransfer.DataRequest object. This request object contains two properties and two methods:

data is the DataPackage to populate. It contains methods to make various data formats available, though it’s important to note that not all formats will be immediately rendered. Instead, they’re rendered only when a share target asks for them.

deadline is a Date property indicating the time in the future when the data you’re making available will no longer be valid (that is, will not render). This recognizes that there might be an indeterminate amount of time between when the source app is asked for data and when the target actually tries to use it. With delayed rendering, as noted above for the data property, it’s possible that some transient source data might disappear after some time. By indicating that time in deadline, rendering requests that occur past the deadline will be ignored.

failWithDisplayText is a method to tell the share broker that sharing isn’t possible right now, along with a string that will tell the user why (perhaps the lack of a usable selection). You call this when you don’t have appropriate data formats or an appropriate selection to share, or if there’s an error in populating the data package for whatever reason. The text you provide will then be displayed in the Share charm (and thus should be localized). Scenario 8 of the source app sample shows the use of this in the simple case when don’t provide data in response to the datarequested event..

getDeferral provides for async operations you might need to perform while populating the data package (just like other deferrals elsewhere in the WinRT API). Do note that datarequested has a 200ms timout period, after which time the Share charm will display “can’t share right now”. Requesting a deferral does not change that timeout; it only prevents datarequested from assuming that the data package is ready once you return from your handler.

The basic structure of a datarequested handler, then, will attempt to populate the minimal properties of eventArgs.request.data and call eventArgs.request.failWithDisplayText when errors occur. We see this structure in most of the scenarios in the sample:

var dataTransferManager =
    Windows.ApplicationModel.DataTransfer.DataTransferManager.getForCurrentView();
// Remove this listener as required
dataTransferManager.addEventListener("datarequested", dataRequested);

function dataRequested(e) {
    var request = e.request;

    // Title is required

    var dataPackageTitle = document.getElementById("titleInputBox").value;

    if ( /* Check if there is appropriate data to share */ ) {
        request.data.properties.title = dataPackageTitle;

        // The description is optional.
        var dataPackageDescription = document.getElementById("descriptionInputBox").value;
        request.data.properties.description = dataPackageDescription;

        // Call request.data.setText, setUri, setBitmap, setData, etc.
    } else {
        request.failWithDisplayText(/* Error message */);
    }
}

As we see here, the request.data.properties object (of type DataPackagePropertySet) is where you set things like a title and description for the data package. Other properties are as follows:

applicationListingUri The URI of your app’s page in the Windows Store, which should be set to the return value of Windows.ApplicationModel.Store.CurrentApp.linkUri57)

applicationName A string, which helps share targets gather the same kind of information that the source can obtain from the targetApplicationChosen event

fileTypes A string vector, where strings should come from the StandardDataFormats enumeration but can also be custom formats

size The number of items when the data in the package is a collection, e.g., files

thumbnail A stream containing a thumbnail image; obtaining this image is typically why you’d use the DataRequest.getDeferral method).

Beyond this the data.properties object also supports custom properties through its insert, remove, and other methods. This makes is possible for the source app to pass custom properties along with custom formats, making all of this extensible if new data formats are widely adopted in the future.

Within this code structure above, the primary job of the source app is to populate the data package by calling the package’s various set* methods. For standard formats, which are again those described in the StandardDataFormats enumeration, there are discrete methods: setText, setUri, setHtmlFormat, setRtf (rich text format, a comparably ancient precursor to HTML), setBitmap, and setStorageItems (for files and folders). All of these except for setRtf are represented in the source app sample as follows.

Sharing text—Scenario 1 (js/text.js):

var dataPackageText = document.getElementById("textInputBox").value;
request.data.setText(dataPackageText);

Sharing a link—Scenario 2 (js/link.js), which can be used for local and remote content alike:

request.data.setUri(new Windows.Foundation.Uri(document.getElementById("linkInputBox").value));

Sharing an image and a storage item—Scenario 3 (js/image.js):

var imageFile; // A StorageFile obtained through the file picker

// In the data requested event
var streamReference =
    Windows.Storage.Streams.RandomAccessStreamReference.createFromFile(imageFile);
request.data.properties.thumbnail = streamReference;

// It's recommended to always use both setBitmap and setStorageItems for sharing a
// single image since the Target app may only support one or the other

// Put the image file in an array and pass it to setStorageItems
request.data.setStorageItems([imageFile]);

// The setBitmap method requires a RandomAccessStreamReference
request.data.setBitmap(streamReference);

Sharing files—Scenario 4 (js/file.js):

var selectedFiles; // A collection of StorageFile objects obtained through the file picker

// In the data requested event
request.data.setStorageItems(selectedFiles);

As for sharing HTML, this can be quite simple if you just have HTML in a string:

request.data.setHtmlFormat(someHtml);

For this purpose you might find the Windows.ApplicationModel.DataTransfer.-HtmlFormatHelper object, well, helpful, as it provide methods to build properly formatted markup. What’s also true with HTML is that it often refers to other content like images that aren’t directly contained in the markup. So how do you handle that? Fortunately, the designers of this API thought through this need: you employ the data package’s resourceMap property to associate relative URIs in the HTML with an image stream. We see this in Scenario 6 of the sample (js/html.js):

var path = document.getElementById("htmlFragmentImage").getAttribute("src");
var imageUri = new Windows.Foundation.Uri(path);
var streamReference =
   Windows.Storage.Streams.RandomAccessStreamReference.createFromUri(imageUri);
request.data.resourceMap[path] = streamReference;

The other interesting part of Scenario 6 is that it actually replaces the data package in the eventArgs with a new one that it creates as follows:

var range = document.createRange();
range.selectNode(document.getElementById("htmlFragment"));
request.data = MSApp.createDataPackage(range);

As you can see, the MSApp.createDataPackage method takes a DOM range (in this case a portion of the current page) and creates a data package from it, where the package’s setHtmlFormat method is called in the process (which is why you don’t see that method called explicitly in Scenario 6). For what it’s worth, there is also MSApp.createDataPackageFromSelection that does the same job with whatever is currently selected in the DOM. You would obviously use this if you have editable elements on your page from which you’d like to share.

Sharing Multiple Data Formats

As shown in Scenario 3, it is certainly allowable—and encouraged!—to share data in as many formats as makes sense, thereby enabling more potential targets. All this means is that you call all the set* methods that make sense within your datarequested handler. This includes calling setData for custom formats and setDataProvider for deferred rendering, as described in the next two sections.

Custom Data Formats: schema.org

Long ago, I imagine, API designers decided it was an exercise in futility to try to predict every data format that apps might want to exchange in the future. The WinRT API is no different, so alongside the format-specific set* methods of the DataPackage we find the generic setData method. This takes a format identifier (a string) and the data to share. This is illustrated in Scenario 7 of the sample using the format “http://schema.org/Book” and data in a JSON string (js/custom.js):

request.data.setData(dataFormat, JSON.stringify(book));

Since the custom format identifier is just a string, you can literally use anything you want here; a very specific format string might be useful, for example, in a sharing scenario where you want to target a very specific app, perhaps one that you authored yourself. However, unless you’re very good at evangelizing your custom formats to the rest of the developer community (and have a budget for such!), chances are that other share targets won’t have any clue what you’re talking about.

Fortunately, there is a growing body of conventions for custom data formats maintained by http://schema.org. This site is the point of agreement where custom formats are concerned, so we highly recommend that you draw formats from it. See http://schema.org/docs/schemas.html for a complete list.

Here’s the JSON book data used in the sample:

var book = {
    type: "http://schema.org/Book",
    properties: {
        image: "http://sourceuri.com/catcher-in-the-rye-book-cover.jpg",
        name: "The Catcher in the Rye",
        bookFormat: "http://schema.org/Paperback",
        author: "http://sourceuri.com/author/jd_salinger.html",
        numberOfPages: 224,
        publisher: "Little, Brown, and Company",
        datePublished: "1991-05-01",
        inLanguage: "English",
        isbn: "0316769487"
    }
};

You can easily express this same data as plain text, as HTML (or RTF), as a link (perhaps to a page with this information), and an image (of the book cover). This way you can populate the data package with all the standard formats alongside specific custom formats.

Deferrals and Delayed Rendering

Deferrals, as mentioned before, are a simple mechanism to delay completion of the datarequested event until the deferral’s complete method is called within an async operation’s completed handler. The documentation for DataRequest.getDeferral shows an example of using this when loading an image file:

var deferral = request.getDeferral();

Windows.ApplicationModel.Package.current.installedLocation.getFileAsync(
    "images\\smalllogo.png")
    .then(function (thumbnailFile) {
        request.data.properties.thumbnail = Windows.Storage.Streams.
            RandomAccessStreamReference.createFromFile(thumbnailFile);
        return Windows.ApplicationModel.Package.current.installedLocation.getFileAsync(
            "images\\logo.png");
    })
    .done(function (imageFile) {
        request.data.setBitmap(
            Windows.Storage.Streams.RandomAccessStreamReference.createFromFile(imageFile));
        deferral.complete();
    });

Delayed rendering is a different matter, though the process typically employs the deferral. The purpose here is to avoid rendering the shared data until a target actually requires it, sometimes referred to as a pull operation. The set* methods that we’ve seen so far all copy the full data into the package. Delayed rendering means calling the data package’s setDataProvider method with a data format identifier and a function to call when the data is needed. Here’s how it’s done in Scenario 5 of the source app sample where imageFile is selected with a file picker (js/image.js):

// When sharing an image, don't forget to set the thumbnail for the DataPackage
var streamReference =
    Windows.Storage.Streams.RandomAccessStreamReference.createFromFile(imageFile);
request.data.properties.thumbnail = streamReference;
request.data.setDataProvider(
    Windows.ApplicationModel.DataTransfer.StandardDataFormats.bitmap,
    onDeferredImageRequested);

As indicated in the comments, it’s a really good idea to provide a thumbnail with delayed rendering so the target app has something to show the user. Then, when the target needs the full data, the data provider function gets called—in this case, onDeferredImageRequsted:

function onDeferredImageRequested(request) {
    if (imageFile) {
        // Here we provide updated Bitmap data using delayed rendering
        var deferral = request.getDeferral();

        var imageDecoder, inMemoryStream;

        imageFile.openAsync(Windows.Storage.FileAccessMode.read).then(function (stream) {
            // Decode the image
            return Windows.Graphics.Imaging.BitmapDecoder.createAsync(stream);
        }).then(function (decoder) {
            // Re-encode the image at 50% width and height
            inMemoryStream = new Windows.Storage.Streams.InMemoryRandomAccessStream();
            imageDecoder = decoder;
            return Windows.Graphics.Imaging.BitmapEncoder.createForTranscodingAsync(
                inMemoryStream, decoder);
        }).then(function (encoder) {
            encoder.bitmapTransform.scaledWidth = imageDecoder.orientedPixelWidth * 0.5;
            encoder.bitmapTransform.scaledHeight = imageDecoder.orientedPixelHeight * 0.5;
            return encoder.flushAsync();
        }).done(function () {
            var streamReference = Windows.Storage.Streams.RandomAccessStreamReference
                .createFromStream(inMemoryStream);
            request.setData(streamReference);
            deferral.complete();
        }, function (e) {
            // didn't succeed, but we still need to release the deferral to avoid
            //a hang in the target app
            deferral.complete();
        });
    }
}

Note that this function receives a simplified hybrid of the DataRequest and DataPackage objects: a DataProviderRequest that contains deadline and formatId properties, a getDeferral method, and a setData method through which you provide the data that matched formatId. The deadline property, as you can guess, is the same as what the datarequested handler might have stored in the DataRequest object.

Target Apps

Looking back to Figure 12-1, we can see that while the interaction between a source app and the share broker is driven by the single datarequested event, the interaction between the broker and a target app is a little more involved. For one, the broker needs to determine which apps can potentially handle a particular data package, for which purpose each target app includes appropriate details in its manifest. When an app is selected, it gets launched with an activation kind of shareTarget, in response to which it should show a specific share UI rather than the full app experience.

Let’s see how all this works with the Sharing content target app sample whose appearance is shown in Figure 12-2 (borrowing from Figure 2-22 we saw ages ago). Be sure to run this app once in Visual Studio so that it’s effectively installed and it will appear on the list of apps when we invoke the Share charm.

Images

FIGURE 12-2 The appearance of the Sharing content target app sample (the right-hand nonfaded part).

The first step for a share target is to declare the data formats it can accept in the Declarations section of its manifest, along with the page that will be invoked when the app is selected as a target. As shown in Figure 12-3, the target app sample declares it can handle text, URI, bitmap, html, and the http://schema.org/Book formats, and it also declares it can handle whatever files might be in a data package (you can indicate specific file types here). Way down at the bottom it then points to target.html as its Share target page.

Images

FIGURE 12-3 The Share content target app sample’s manifest declarations.

The Share start page, target.html, is just a typical HTML page with whatever layout you require for performing the share task. This page typically operates independently of your main app: when your app is chosen through Share, this page is loaded and activated by itself and thus has an entirely separate script context. This page should not provide navigation to other parts of the app and should thus load only whatever code is necessary for the sharing task. (The Executable and Entry Point options are not used for apps written in HTML and JavaScript; those exist for apps written in other languages.)

Much of this structure is built for you automatically through the Share Target Contract item tem-plate provided by Visual Studio and Blend, as shown in Figure 12-4; the dialog appears when you right-click your project and select Add > New Item or select the Project > Add New Item menu command.

Images

FIGURE 12-4 The Share Target Contract item template in Visual Studio and Blend.

This item template will give you HTML, JS, and CSS files for the share target page and will add that page to your manifest declarations along with text and URI formats. So you’ll want to update those as appropriate.

Before we jump into the code, a few notes about the design of a share target page, summarized from Guidelines for sharing content:

• Maintain the app’s identity and its look and feel, consistent with the primary app experience.

• Keep interactions simple to quickly complete the share flow: avoid text formatting, tagging, and setup tasks, but do consider providing editing capabilities especially if posting to social networks or sending a a message. (See Figure 12-5 from the Mail app for an example.) A social networking target app would generally want to include the ability to add comment; a photo-sharing target would probably include the ability to add captions.

• Avoid navigation: sharing is a specific task flow, so use inline controls and inline errors instead of switching to other pages. Another reason to avoid this is that the share page of the target app runs in its own script context, so being able to navigate elsewhere in the app within a separate context could be very confusing to users.

• Avoid links that would distract from or take the user away from the sharing experience. Remember that sharing is a way to shortcut the oft-tedious process of getting data from one app to another, so keep the target app focused on that purpose.

• Avoid light-dismiss flyouts since the Share charm already works that way.

• Acknowledge user actions when you start sending the data off (to an online service, for example) so that users know something is actually happening.

• Put important buttons within reach of the thumbs on a touch device; refer to Windows 8 Touch Posture topic in the documentation for placement guidance.

• Make previews match the actual content—in other words, don’t play tricks on the user!

With this page design, it’s good to know that you do not need to worry about different view states—this page really just has one state (and as a flyout, it cannot be snapped). It does need to adapt itself well to varying dimensions, mind you, but not different view states. Basing the layout on a CSS grid with fractional rows and columns is a good approach here.

Caution Because a target app can receive data from any source app, it should treat all such content as untrusted and potentially malicious, especially with HTML, URIs, and files. The target app should avoid adding such HTML or file contents to the DOM, executing code from URIs, navigating to the URI or some other page based on the URI, modifying database records, using eval with the data, and so on.

Images

FIGURE 12-5 The sharing UI of the Windows Mail app (the bottom blank portion has been cropped); this UI allows editing of the recipient, subject, and message body, and sending an image as an attachment or as a link to SkyDrive.

Let’s now look at the contents of the template’s JavaScript file as a whole, because it shows us the basics of being a target. First, as you can see, we have the same structure as a typical default.js for the app, using the WinJS.Application object’s methods and events.

(function () {
    "use strict";

    var app = WinJS.Application;
    var share;

    function onShareSubmit() {
        document.querySelector(".progressindicators").style.visibility = "visible";
        document.querySelector(".commentbox").disabled = true;
        document.querySelector(".submitbutton").disabled = true;

        // TODO: Do something with the shared data stored in the 'share' var.

        share.reportCompleted();
    }
    // This function responds to all application activations.
    app.onactivated = function (args) {
        var thumbnail;

        if (args.detail.kind ===
            Windows.ApplicationModel.Activation.ActivationKind.shareTarget) {
            document.querySelector(".submitbutton").onclick = onShareSubmit;
            share = args.detail.shareOperation;

            document.querySelector(".shared-title").textContent =
                share.data.properties.title;
            document.querySelector(".shared-description").textContent =
                share.data.properties.description;

            thumbnail = share.data.properties.thumbnail;
            if (thumbnail) {
                // If the share data includes a thumbnail, display it.
                args.setPromise(thumbnail.openReadAsync().then(
                    function displayThumbnail(stream) {
                        document.querySelector(".shared-thumbnail").src =
                            window.URL.createObjectURL(stream, { oneTimeOnly: true });
                    }));
            } else {
                // If no thumbnail is present, expand the description and
                // title elements to fill the unused space.
                document.querySelector("section[role=main] header").style
                   .setProperty("-ms-grid-columns", "0px 0px 1fr");
                document.querySelector(".shared-thumbnail").style.visibility = "hidden";
            }
        }
    };

    app.start();
})();

When this page is loaded and activated, during which time the app’s splash screen will appear, its WinJS.Application.onactivated event will fire—again independently of your app’s main activated handler that’s typically in default.js. As a share target you just want to make sure that the activation kind is shareTarget, after which your primary responsibility is to provide a preview of the data you’ll be sharing along with whatever UI you have to edit it, comment on it, and so forth. Typically, you’ll also have a button to complete or submit the sharing, on which you tell the share broker that you’ve completed the process.

The key here is the args.detail.shareOperation object provided to the activated handler. This is a Windows.ApplicationModel.DataTransfer.ShareTarget.ShareOperation object, whose data property contains a read-only package called a DataPackageView from which you obtain all the goods:

• To check whether the package has formats you can consume, use the contains method or the availableFormats collection.

• To obtain data from the package, use its get* methods such as getTextAsync, getBitmap-Async, and getDataAsync (for custom formats). When pasting HTML you can also use the getResourceMapAsync method to get relative resource URIs. The view’s properties like the thumbnail are also useful to provide a preview of the data.

As you can see, the Share target item template code above doesn’t do anything with shared data other than display the title, description, and thumbnail; clearly your app will do something more by requesting data from the package, like the examples we see in the share target sample. Its js/target.js file contains an activated handler for the target.html page (in the project root), and it also displays the thumbnail in the data package by default. It then looks for different data formats and displays those contents if they exist:

if (shareOperation.data.contains(Windows.ApplicationModel.DataTransfer.StandardDataFormats.
    text)) {
    shareOperation.data.getTextAsync().done(function (text) {
        displayContent("Text: ", text, false);
    });
}

The same kind of code appears for the simpler formats. Consuming a bitmap is a little more work but straightforward:

if (shareOperation.data.contains(Windows.ApplicationModel.DataTransfer.StandardDataFormats.
    bitmap)) {
    shareOperation.data.getBitmapAsync().done(function (bitmapStreamReference) {
        bitmapStreamReference.openReadAsync().done(function (bitmapStream) {
            if (bitmapStream) {
                var blob = MSApp.createBlobFromRandomAccessStream(bitmapStream.contentType,
                    bitmapStream);
                document.getElementById("imageHolder").src = URL.createObjectURL(blob,
                    { oneTimeOnly: true });
                document.getElementById("imageArea").className = "unhidden";
            }
        });
    });
}

For HTML, it looks through the markup for img elements and then sets up their src attributes from the resource map (the iframe already has the HTML content from the package by this time):

var images = iFrame.contentDocument.documentElement.getElementsByTagName("img");
if (images.length > 0) {
    shareOperation.data.getResourceMapAsync().done(function (resourceMap) {
        if (resourceMap.size > 0) {
            for (var i = 0, len = images.length; i < len; i++) {
                var streamReference = resourceMap[images[i].getAttribute("src")];
                if (streamReference) {
                    // Call a helper function to map the image element's src
                    // to a corresponding blob URL generated from the streamReference
                    setResourceMapURL(streamReference, images[i]);
                }
            }
        }
    });
}

The setResourceMapURL helper function does pretty much what the bitmap-specific code did, which is call openReadAsync on the stream, call MSApp.createBlobFromRandomAccessStream, pass that blob to URL.createObjectURL, set the img.src with the result, and close the stream.

After the target app has completed a sharing operation, it should call the ShareOperation.report-Completed method, as shown earlier with the template code. This lets the system know that the data package has been consumed, the share flow is complete, and all related resources can be released. The share target sample does this when you explicitly click a button for this purpose, but normally you automatically call the method whenever you’ve completed the share. Do be aware that calling reportCompleted will close the target app’s sharing UI, so avoid calling it as soon as the target activates: you want the user to feel confident that the operation was carried out.

Long-Running Operations

When you run the share target sample and invoke the Share charm from a suitable source app, there’s a little expansion control near the bottom labeled “Long-running Share support.” If you expand that, you’ll see some additional controls and a bunch of descriptive text, as shown in Figure 12-6. The buttons shown here tie into a number of other methods on the ShareOperation object alongside reportCompleted. These help Windows understand exactly how the share operation is happening within the target: reportStarted, reportDataRetrieved, reportSubmittedBackgroundTask, and reportError. As you can see from the descriptions in Figure 12-6, these generally relate to telling Windows when the target app has finished cooking its meal, so to speak, and the system can clean the dishes and put away the utensils:

reportStarted informs Windows that your sharing operation might take a while, as if you’re uploading the data from the package to another place, or just sending an email attachment with what ends up being large images and such. This specific method indicates that you’ve obtained all necessary user input and that the share pane can be dismissed.

reportDataRetrieved informs Windows that you’ve extracted what you need from the data package such that it can be released. If you’ve called MSApp.createBlobFromRandomAccessStream for an image stream, for example, the blob now contains a copy of the image that’s local to the target app. If you’re using images from the package’s resourceMap, on the other hand, you’ll not want to call reportDataRetrieved unless you explicitly make a copy of those references whose URIs refer to bits inside the data package. In any case, if you need to hold onto the package throughout the operation, you don’t need to call this method as you’ll later call reportCompleted to release the package.

reportSubmittedBackgroundTask tells Windows that you’ve started a background transfer using the Windows.Networking.BackgroundTransfer.BackgroundUploader class. As the sample description in Figure 12-6 indicates, this lets Windows know that it can suspend the target app and not disturb the sharing operation. If you call this method with a local copy of the data being uploaded, go ahead and call reportCompleted method so that Windows can clean up the package; otherwise wait until the transfer is complete.

reportError lets Windows know if there’s been an error during the sharing operation.

Images

FIGURE 12-6 Expanded controls in the Sharing content target app sample for Long-Running Share Support. The Report Completed button is always shown and isn’t specific to long-running tasks despite its placement in the sample’s UI. Don’t let that confuse you!

Quicklinks

The last aspect of the Share contract for us to explore is something we mentioned early on in this section: quicklinks. These serve to streamline the Share process such that users don’t need to re-enter information in a target app. For example, if a user commonly shares data with particular people through email, each contact can be a quicklink for the email app. If a user commonly shares with different people or groups through a social networking app, those people and/or groups can be represented with quicklinks. And as these targets are much more user-specific than target apps in general, the Share charm UI shows these at the top of its list (see Figure 12-7 below).

Each quicklink is associated with and serviced by a particular target app and simply provides an identifier to that target. When the target is invoked through a quicklink, it then uses that identifier to retrieve whatever data is associated with that quicklink and prepopulates or otherwise configures its UI. It’s important to understand that quicklinks contain only an identifier, so the target app must store and retrieve the associated data from some other source, typically local app data where the identifier is a filename, the name of a settings container, etc. The target app could also use roaming app data or the cloud for this purpose, but quicklinks themselves do not roam to another device—they are strictly local. Thus, it makes the most sense to store the associated data locally.

A quicklink itself is just an instance of the Windows.ApplicationModel.DataTransfer.-Quicklink class. You create one with the new operator and then populate its title, thumbnail, supportedDataFormats, supportedFileTypes, and id properties. The data formats and file types are what Windows uses to determine if this quicklink should be shown in the list of targets for whatever data is being shared from a source app (independent of the app’s manifest declarations). The title and thumbnail are used to display that choice in the Share charm, and the id is what gets passed to the target app when the quicklink is chosen.

Tip For the thumbnail, use an image that’s more specifically representative of the quicklink (such as a contact photo) rather than just the target app. This helps distinguish the quicklink from the general use of the target app.

An app then registers a quicklink with the system by passing it to the ShareOperation.report-Completed method. As this is the only way in which a quicklink is registered, it tells us that creating a quicklink always happens as part of another sharing operation. It’s a way to create a specific target that might save the user some time and encourage her to choose your target app again in the future.

Let’s follow the process within the Sharing content target app sample to see how this all works. First, when you invoke the Share charm and choose the sample, you’ll see that it provides a check box for creating a quicklink (Figure 12-7). When you check this, it provide fields in which you can enter an id and a title (the thumbnail just uses a default image). When you press the Report Completed button, it calls reportCompleted and the quicklink is registered. On subsequent invocations of the Share charm with the appropriate data formats from the source app, this quicklink will then appear in the list, as shown in Figure 12-8 where the app servicing the quicklink is always indicated under the provided title.

Images

FIGURE 12-7 Controls to create a quicklink in the Sharing content target app sample.

Images

FIGURE 12-8 A quicklink from the Sharing content target app sample as it appears in the Share charm target list.

Here’s how the share target sample creates the quicklink within the function reportCompleted (js/target.js) that’s attached to the Report Completed button (some error checking omitted):

if (addQuickLink) {
    var quickLink = new Windows.ApplicationModel.DataTransfer.ShareTarget.QuickLink();

    var quickLinkId = document.getElementById("quickLinkId").value;
    quickLink.id = quickLinkId;

    var quickLinkTitle = document.getElementById("quickLinkTitle").value;
    quickLink.title = quickLinkTitle;

    // For quicklinks, the supported FileTypes and DataFormats are set independently

    // from the manifest
    var dataFormats = Windows.ApplicationModel.DataTransfer.StandardDataFormats;
    quickLink.supportedFileTypes.replaceAll(["*"]);
    quickLink.supportedDataFormats.replaceAll([dataFormats.text, dataFormats.uri,
        dataFormats.bitmap,
        dataFormats.storageItems, dataFormats.html, customFormatName]);

    // Prepare the icon for a QuickLink
    Windows.ApplicationModel.Package.current.installedLocation.getFileAsync(
    "images\\user.png").done(function (iconFile) {
        quickLink.thumbnail = Windows.Storage.Streams.RandomAccessStreamReference
            .createFromFile(iconFile);
        shareOperation.reportCompleted(quickLink);
    });

Again, the process just creates the Quicklink object, sets its properties (perhaps settings a more specific thumbnail such as a contact person’s picture), and passes it to reportCompleted. In the share target sample, you can see that it doesn’t actually store any other local app data; for its purposes the properties in the quicklink are sufficient. Most target apps, however, will likely save some app data for the quicklink that’s associated with the quicklink.id property and reload that data when activated later on through the quicklink.

When the app is activated in this way, the eventArgs.detail.shareOperation object within the activated event handler will contain the quicklinkId. The Source target app simply displays this id, but your would certainly use it to load app data and prepopulate your share UI:

// If this app was activated via a QuickLink, display the QuickLinkId
if (shareOperation.quickLinkId !== "") {
    document.getElementById("selectedQuickLinkId").innerText = shareOperation.quickLinkId;
    document.getElementById("quickLinkArea").className = "hidden";
}

Note that when the target app is invoked through a quicklink, it doesn’t display the same UI to create a quicklink, because doing so would be redundant. However, if the user edited the information related to the quicklink, you might provide the ability to update the quicklink, which means to update the data you save related to the id, or to create a new quicklink with a new id.

The Clipboard

Before the Share contract was ever conceived, the mechanism we know as the Clipboard was once the poster child of app-to-app cooperation. And while it may not garner any media attention nowadays, it’s still a tried-and-true means for apps to share and consume data.

For Windows Store apps, clipboard interactions build on the same DataPackage mechanisms we’ve already seen for sharing, so everything we’ve learned about populating that package, using custom formats, and using delayed rendering still apply. Indeed, if you make data available on the clipboard, you should make sure the same data is available for the Share contract!

The question is how to wire up commands like copy, cut, and paste—from the app bar, a context menu, or keystrokes—should an app provide them for its own content (many controls handle the clipboard automatically). For this we turn to the Windows.ApplicationMode.DataTransfer.-Clipboard class.

As shown in the Clipboard app sample, the processes here are straightforward. For copy and cut:

• Create a new Windows.ApplicationModel.DataTransfer.DataPackage (or use MSApp.-createDataPackage or MSApp.createDataPackageFromSelection), and populate it with the desired data.

    var dataPackage = new Windows.ApplicationModel.DataTransfer.DataPackage();
    dataPackage.setText(textValue);
    //...

• (Optional) Set the package’s requestedOperation property to values from DataPackageOperation: copy, move, link, or none (the latter is used with delayed rendering). Note that these values can be combined using the bitwise OR operator, as in:

    var dpo = Windows.ApplicationModel.DataTransfer.DataPackageOperation;
    dataPackage.requestedOperation = dpo.copy | dpo.move | dpo.link;

• Pass the data package to Windows.ApplicationMode.DataTransfer.Clipboard.setContent:

    Windows.ApplicationModel.DataTransfer.Clipboard.setContent(dataPackage);

To perform a paste:

• Call Windows.ApplicationMode.DataTransfer.Clipboard.getContent to obtain a read-only data package called a DataPackageView:

    var dataView = Windows.ApplicationModel.DataTransfer.Clipboard.getContent();

• Check whether it contains formats you can consume with the contains method (alternately, you can check the contents of the availableFormats vector):

    if (dataView.contains(Windows.ApplicationModel.DataTransfer.StandardDataFormats.
        text)) {
    //...}

• Obtain data using the view’s get* methods such as getTextAsync, getBitmapAsync, and getDataAsync (for custom formats). When pasting HTML, you can also use the getResourceMapAsync method to get relative resource URIs. The view’s properties like the thumbnail are also useful, along with the requestedOperation value or values.

    dataView.getTextAsync().done(function (text) {
        // Consume the data
    }

If at any time you want to clear the clipboard contents, call the Clipboard class’s clear method. You can also make sure data is available to other apps even if yours is shut down by calling the flush method (which will trigger any deferred rendering you might have set up).

Apps that use the clipboard also need to know when to enable or disable a paste command depending on available formats. At any time you can get the data package view from the clipboard and use its contains method or availableFormats property and decide accordingly. You should also then listen to the Clipboard object’s contentChanged event (a WinRT event), which will be fired when you or some other app calls the clipboard’s setContent method. At time time you’d again enable or disable the commands. Of course, you won’t receive this event when your app is suspended, so you should refresh the state of those commands within your resuming handler.

Again, the Clipboard app sample provides examples of these various scenarios, including copy/paste of text and HTML (Scenario 1); copy and paste of an image (Scenario 2); copy and paste of files (Scenario 3); and clearing the clipboard, enumerating formats, and handling contentChanged (Scenario 4).

Note, finally, that pasted data can come from anywhere. Apps that consume data from the clipboard should, like a share target, treat the content they receive as potentially malicious and take appropriate precautions.

Search

Search has become such a ubiquitous feature for apps that the designers of Windows 8 decided to provide a system-level keyword search UI (with built-in Input Method Editor support) directly alongside Share, Devices, and Settings in the Charms bar, as shown in Figure 12-9. This means that apps don’t need to (and generally shouldn’t) provide their own UI controls for search, and, by participating in this contract, the user can not only easily search the app that’s in the foreground but also quickly and easily search within other apps without having to go off and start those apps separately. And because those other apps can be searching content that doesn’t necessarily exist on your machine, the Search charm fills the middle ground between material on your file system and the rest of the world. It’s quite powerful in this way!

The Search charm also means that users never need to explicitly start your app to search within it. Simply by changing the search target within the search pane, that target app is launched and asked to perform a search with the current keywords. This is also what makes Search work even if the current foreground app doesn’t support the contract at all—the search target just defaults to the first app in the list.

Tip If you need to know which side of the screen the Search pane appears on, so you can place controls on your results page so they won’t be obscured, check the Windows.UI.Application-Settings.SettingsPane.edge property.

The Search contract that makes this happen is composed of a set of interactions between the Windows-provided Search UI and the search target app. (In this section, when I refer to a target app, I’m referring now to search, not share.) This interaction communicates the keywords (even if empty) to the app when the user presses Enter, clicks the icon to the right of the entry field, or changes apps. The interaction also allows the target app to provide suggested search terms, as well as suggested results (with result-specific graphics) that appear in the search pane directly, as shown in Figure 12-10.

Images

FIGURE 12-9 The Search pane invoked through the Search charm, with results shown in the Games app and the Photos app. As with Share, the user can control which apps are shown through Change PC Settings > Search. That same settings panel also allows the user to clear search history and control a few other aspects of the UI.

Designwise, Search should work with whatever data the app manages, whether local or online (or both); it’s really the primary means to search within everything that the app can access. For this reason, Microsoft highly recommends that apps don’t provide their own search UI (which otherwise distracts from the app’s content) unless it’s really all the app does and where it would need additional search criteria up-front. Otherwise, it’s best to let the user first search through the charm and then filter, sort, and otherwise organize the results within the app through on-canvas or app bar commands. On the flip side, the Search charm is not intended for finding data within a page; that is, it is expected that apps provide their own controls for essentially scanning and highlighting results that are already in view (like the find function in browsers). Many details on such design questions can be found on Guidelines for search.

FIGURE 12-10 Suggested searches (left) and search results (right) from a target app appear directly in the search pane.

Images

Searching within an app effectively navigates the app to its search results page, as we see in Figure 12-9, and thus activates the app in the same script context as when it’s run normally. Again, if the app needs to be launched to service the search contract, it will be launched directly into that page (we’ll see this mechanism shortly). Tapping on a result then navigates the app directly to the details for that result. Of course, if the app was already running when invoked via Search, the result page’s back button should navigate to whatever page the user was on before. Even if the app is launched to service the Search charm, it’s helpful to provide the user with a means to navigate to its home page, especially when there are no results through which to navigate elsewhere.

Let’s now look at the basic search contract interaction, after which we’ll explore the richer aspects of search suggestions, suggested results, and type to search.

Search in the App Manifest and the Search Item Template

An app’s life as a search target begins, as with other contracts, in the app manifest on the Declarations tab, as shown in Figure 12-11.

Images

FIGURE 12-11 The Search declarations page within Visual Studio; typically, the App Settings properties are left blanks in an HTML/JavaScript app.

Since search is not tied to any particular data format (like share is), all you really need to specify here is a Start page, if in fact you want it to be separate from the rest of your app at all. Unlike the share contract, search is much more integrated with in-app navigation: when the user taps a result on your results page, you want to navigate to that page directly as if they’d tapped on the same item in some other list. Similarly, if the user taps the back button in your results page, they should navigate to whatever page they were on when the charm was first invoked. For this reason, then, activation via search typically gets handled by through the app’s main activated event. We’ll get to that in the next section.

An easy way to add the Search contract is through the Search contract item template in Visual Studio and Blend. (You can see this listed back in Figure 12-4 just above the Share target contract.) If you right-click your project and select Add > New Item, or use the Project > Add New Item… menu command, you can choose the Search Contract item in from the list of templates. This will add the Search declaration in your app manifest and add three page control files (.html, .js. and .css) for a search results page. There’s not much exciting to show here visually because the template code very much relies on there being some real data to work with. Nevertheless, the template gives you a great structure to work from, including the recommended UI for providing filters and so forth. Some further details can be found on Adding a Search Contract item template.

Basic Search and Search Activation

The most basic interaction with the Search contract is receiving a query when the app is already running. This is a great example of how search really just triggers navigation in the app. To receive such a query, you need only listen to the querysubmitted event of the Windows.ApplicationModel.-Search.SearchPane object. The exact code looks something like this where searchPageURI identifies the results page:

var searchPane = Windows.ApplicationModel.Search.SearchPane.getForCurrentView();
searchPane.onquerysubmitted = function (eventArgs) {
    WinJS.Navigation.navigate(searchPageURI, eventArgs);
};

The eventArgs object here will be a SearchPaneQuerySubmittedEventArgs that contains just two properties: queryText (the contents of the text box in the search pane) and language (the BCP 47 language tag currently in used). In the code above, these are just passed to the WinJS.-Navigation.navigate method that passes them onto to the results page (whatever searchPageURI contains). From there, that page will just process queryText appropriate to language and populate the page contents with appropriate items. For this purpose an app typically uses a ListView control, as you might expect for a variable-length results collection.

Through the same SearchPane object you can also set the placeholderText property with whatever should appear in the initial search box. Its show method allows you to show the pane programmatically, its visible property and visibilitychanged event will tell you its status, and its queryText property will give you the current contents of the input control.

You can also listen for its querychanged event. This is a precursor to querySubmitted and is appropriate if you have logic you need to run outside of providing suggestions, such as previewing results (the behavior you see on the start screen when searching for apps, also known as word wheeling). Its eventArgs will contain queryText and language properties, as with query-submitted, along with a linguisticDetails property that provides details about text entered through an Input Method Editor (IME), specifically linguistic alternatives. If you expect to have Japanese or Chinese users, it’s highly recommended to also search for these alternatives in response to querychanged and suggestionsrequested (see the next section)

Let’s see how search affects app activation, which again typically comesthrough your default activated handler in the same script context as when the app is run normally.

In this case the activation kind value will be search, a case that you want to handle separately from launch. To see this in action, let’s turn to the Search contract sample. Its activation code is found in js/default.js—code that’s applicable to the entire app:

function activated(eventObject) {
    if (eventObject.detail.kind ===
        Windows.ApplicationModel.Activation.ActivationKind.launch) {
        eventObject.setPromise(WinJS.UI.processAll().then(function () {
            var url = WinJS.Application.sessionState.lastUrl || scenarios[0].url;
            return WinJS.Navigation.navigate(url);
        }));
    } else if (eventObject.detail.kind ===
        Windows.ApplicationModel.Activation.ActivationKind.search) {
        eventObject.setPromise(WinJS.UI.processAll().then(function () {
            if (eventObject.detail.queryText === "") {
                // Navigate to your landing page since the user is pre-scoping to your app.
            } else {
                // Display results in UI for eventObject.detail.queryText and
                // eventObject.detail.language (that represents user's locale).
            }

            // Navigate to the first scenario since it handles search activation.
            var url = scenarios[0].url;
            return WinJS.Navigation.navigate(url, { searchDetails: eventObject.detail });
        }));
    }
}

In the search activation path, it’s clearly good to avoid any processing that isn’t needed by the search page itself, but you still need to be prepared to navigate to other parts of the app when a result is chosen. Also, if the app is being launched in response to a search, be sure to reload both general settings as you would with a normal launch as well as session state when previousExecutionState is terminated. This means, in fact, that the state of a results page is part of the app’s session state; you’ll normally want to save the last search term as part of that state so that you can rehydrate the results page when needed.

The sample doesn’t actually search any real data—it just outputs messages when certain events happen. But you can test this activation path in a couple of ways. First, if the app isn’t running, invoke the search charm, enter some query text, and then select the search sample. You’ll find that it ends up on the page for Scenario 1 and shows the search term right away. This tells you that it processed the activation and picked up the search term from eventObject.detail.queryText, as you can see in the code above. (Look also at js/scenario1.js where it outputs the term in the page’s processed method.)

To step through the same code, set a breakpoint within the searchTarget case of the activated handler and run the app in the Visual Studio debugger. Invoke the search charm, enter a query, select some other app (which will do a search), and then switch back to the sample. You should hit your breakpoint as the activated handler will be called with the activation kind of search.

When activated through search, be sure that the page gets fully processed with calls like WinJS.UI.processAll. (You don’t need to worry if the app is already running; processAll won’t do redundant work.)

It is important when your app is activated—as with handling querysubmitted and/or querychanged events—to note that the queryText might be empty. In this case you can show default results or navigate to your home page if that’s more appropriate. See “Sidebar: Testing Search.”

A number of variations with the Search charm can affect how a search target app is launched and with what parameters. To be sure that you’ve exercised all applicable code paths, be sure to test these conditions:

• App is running and search is invoked with no query text, query text with known results, and query text that returns no results.

• App is not running and is invoked from the search charm, with all the variations on text listed above.

• App is in the snapped state and is invoked as above, in which case Search will go to the Start screen.

• App is suspended and is invoked as above.

You should also be mindful of how you present results, taking care that the primary results are not hidden by the Search pane, which will remain visible until the user dismisses it.

Some types of apps will still maintain their own in-app search UI in addition to using the search pane, or in other ways they might have some kind of search term that would be good to keep in sync with the term shown in the search pane. To do this, the app can ask the search pane for its queryText value and can attempt to set that value through the SearchPane.trySet-QueryText method. This call will fail, mind you, if the app isn’t itself visible or if the search pane is already visible or becoming visible.

Providing Query Suggestions

Using querysubmitted and the activation sequence in the previous section gives you the basic level of search interaction, and Windows will automatically provide a history of the user’s recent searches. Still, with just a little more work you can make the experience much richer. Because writing the code to actually perform the search, process the results, and display them beautifully is the bulk of the work with the Search contract anyway, adding support for query suggestions (this section) and result sugges-tions (next section) is a relatively small investment with a huge impact on the overall user experience.

To go beyond the default search history and provide as-the-user-is-typing query suggestions, which appear to the user as shown on the left side of Figure 12-10, you have two options. Which one you use depends on what you want to suggest and the data that you’re searching.

First, to provide suggestions from folders on the file system, such as the music, pictures, and videos libraries, the search pane provides a built-in implementation through its setLocalContent-SuggestionsSettings method with results like those in Figure 12-12. As shown in Scenario 4 of the sample, you first create a Windows.ApplicationModel.Search.LocalContentSuggestion-Settings object, populate its properties, and then pass that object to setLocalContent-SuggestionsSettings (js/scenario4.js):

var page = WinJS.UI.Pages.define("/html/scenario4.html", {
    ready: function (element, options) {
        var localSuggestionSettings = new
            Windows.ApplicationModel.Search.LocalContentSuggestionSettings();
        localSuggestionSettings.enabled = true;
        localSuggestionSettings.locations.append(Windows.Storage.KnownFolders.musicLibrary);
        localSuggestionSettings.aqsFilter = "kind:=music";

        Windows.ApplicationModel.Search.SearchPane.getForCurrentView()
            .setLocalContentSuggestionSettings(localSuggestionSettings);
    }
});

Images

FIGURE 12-12 Suggestions from local folders as automatically provided by the search pane.

In populating the LocalContentSuggestionSettings properties, be sure first to set enabled to true. The locations collection (a vector) contains one or more StorageFolder objects to indicate where the search should take place. Because enumerating files to provide suggestions requires programmatic access to those folders, you need to make sure your app has the appropriate capabilities set in its manifest, retrieves the folder from the AccessCache, or has obtained programmatic access through the folder picker. In the latter case, the app would provide UI elsewhere to configure the search locations (perhaps through the Settings pane, for instance).

You can also specify an Advanced Query Syntax (AQS) string in the aqsFilter property and/or some number of Windows Properties (like System.Title) within propertiesToMatch (a string vector). This is typically used to filter file types, as when searching a folder, but it can be as specific as you need. For more on AQS, refer to “Rich Enumeration with File Queries” in Chapter 8, “State, Settings, Files, and Documents”; for more on Windows properties, refer to “Media File Metadata” in Chapter 10, “Media.”

As for the second option, LocalContentSuggestionSettings can do a lot for you, but clearly many apps will be searching on some other data source (whether local or online) and will thus need to supply suggestions from those sources. In these cases, listen for and handle the search pane’s suggestionsrequested event. Its eventArgs will contain the queryText, language, and linquistic-Details as always, and in response you populate a collection of up to five suggestions in the eventArgs.request.searchSuggestionCollection (again including the alternatives in the linguistic-Details object if needed). Ideally this takes half a second or less, and it’s important to know that all the results need to be in the collection once you return from your event handler.

Here’s how it’s done in Scenario 2 of the Search contract sample (where suggestionList is just a hard-coded list of city names):

Windows.ApplicationModel.Search.SearchPane.getForCurrentView().onsuggestionsrequested =
    function (eventObject) {
        var queryText = eventObject.queryText;
        var suggestionRequest = eventObject.request;
        var query = queryText.toLowerCase();
        var maxNumberOfSuggestions = 5;
        for (var i = 0, len = suggestionList.length; i < len; i++) {
            if (suggestionList[i].substr(0, query.length).toLowerCase() === query) {
                suggestionRequest.searchSuggestionCollection.appendQuerySuggestion(
                    suggestionList[i]);
                if (suggestionRequest.searchSuggestionCollection.size ===
                    maxNumberOfSuggestions) {
                    break;
                }
            }
    }
};

So if query contains “ba” as it would in Figure 12-10, the first 5 names in suggestionList will be Bangkok, Bangalore, Baghdad, Baltimore, and Bakersfield. Of course, a real app will be drawing suggestions from its own database or from a service (simulated in Scenarios 5 and 6, by the way), but you get the idea. With a service, though, you should also check the suggestionResult.isCanceled property before starting a new request: this flag indicates that the search query hasn’t actually changed from a previous query and it’s not necessary to create new suggestions.

Note When the SearchPane.searchHistoryEnabled property is true (the default), a user’s search history will be automatically tracked with prior searches appearing as suggestions when the search charm is first invoked (before the user types any other characters). Setting this property to false will disable the behavior, in which case an app can maintain its own history of previous queryText values. If an app does this, we recommend providing a means to clear the history through the app’s Settings.

Apps can also use the SearchPane.searchHistoryContext property to create different histories depending on different contexts. When this value is set prior to the search charm being invoked, automatically managed search terms (searchHistoryEnabled is true) will be saved for that context. This has no effect when an app manages its own history, in which case it can manage different histories directly.

Now the eventArgs.request property, a SearchPaneSuggestionsRequest object, has a few features you want to know about. Its searchSuggestedCollection property is unique—it’s not an array or other generic vector but a SearchSuggestionCollection object with a size property and four methods: appendQuerySuggestion (to add a single item to the list, as shown above), appendQuerySuggestions (to add an array of items at once, as you might receive from a query to a service), appendResultSuggestion (see next section) and appendSearchSeparator (which is used to group suggestions). In the latter case, a separator is given a label and appears as follows:

Images

The request object also has a getDeferral method if you need to perform an asynchronous operation to retrieve your suggestions. It works like all other deferral’s we’ve seen: before starting the async operation (like WinJS.xhr), call getDeferral to retrieve the deferral object, start the operation, return from the suggestionsrequested method, and call the deferral’s complete method inside the async completed handler. This is demonstrated again in Scenarios 5 and 6 of the sample since this would clearly be needed when querying a service for this purpose (code here derived from js/scenario5.js):

Windows.ApplicationModel.Search.SearchPane.getForCurrentView().onsuggestionsrequested =
function (eventObject) {
    var queryText = eventObject.queryText;
    var suggestionRequest = eventObject.request;

    var deferral = suggestionRequest.getDeferral();

    // Create request to obtain suggestions from service and supply them to the Search Pane.
    // Depending on design of the service, you might vary URL based on eventObject.language.
    // You might also compose queryText in the URL to let the service do the filtering.
    xhrRequest = WinJS.xhr({ url: /* URL to suggestion service */ });
    xhrRequest.done(
        function (request) {
            if (request.responseText /* or responseXML */) {
                // Populate suggestionRequest.searchSuggestionCollection based on response
            }

            deferral.complete(); // Indicate we're done supplying suggestions.
        },
        function (error) {
            // Call complete on the deferral when there is an error.
            deferral.complete();
        });
};

You can use any JSON or XML response format you want, but since your app is doing the parsing, there are existing standards for returning search suggestions. For JSON, refer to the OpenSearch Suggestions specification and Scenario 5 in the sample where a JSON response can be directly parsed into an array and passed in one call to appendQuerySuggestions. For XML, refer to the XML Search Suggestions Format Specification and Scenario 6. In the latter case, a function named generate-Suggestions provides a generic parser routine for such a response, and although the sample doesn’t demonstrate using separators, URIs, and images in those suggestions, the generateSuggestions function shows how to parse them and send them onto appendQuerySuggestion[s] as well as appendResultSuggestion, which we’ll see next.

Providing Result Suggestions

As shown in Figure 12-10 (on the right side), a search target app can provide suggested results and not just suggested queries. This is also accomplished by handling the search pane’s suggestions-requested event as described in the previous section, only make sure you use suggestion-Request.searchSuggestionCollection.appendResultSuggestion to populate the results and not appendQuerySuggestion[s] (appendSearchSeparator can still be used). You also then need to handle the search pane’s resultSuggestionChosen event to handle the user’s selection as a result and not as a query.

In other words, handling the querysubmitted event means that you’re taking the query text and populating a list of results in your own page. Because of this, you’ll be handling click or tap events for those items directly, navigating to the appropriate details page. The resultSuggestionChosen event tells you that the same thing has happened in the system-owned search pane with results that are shown there from your suggestions. You thus process the resultSuggestionChosen event in the same way that you would handle an item invocation in your own page. The eventArgs.tag property in this case will contain the tag you provide for the suggested result in the appendResultSuggestion call.

This method takes five arguments in this order, and be mindful of any necessary localization here:

text The first line text to show in the search pane (as in Figure 12-10).

detailText The second line of text for a search result (as in Figure 12-10) that is also used for tooltips.

tag The string you want to receive in the resultSuggestionChosen event.

image An IRandomAccessStreamReference for the image to display. The base size of this image is 40x40 for 100% scale, 56x56 for 140%, and 72x72 for 180%. Take these sizes into account if you dynamically generate images for the result suggestions.

imageAlternateText The alt attribute for the image.

As noted in the previous section, the generateSuggestions function found in js/scenario6.js of the sample provides a generic parser that turns XML search suggestions into the appropriate appendResultSuggestion calls, including the use of Windows.Storage.Streams.RandomAccess-StreamReference.createFromUri to convert an image URI to the appropriate stream reference. Typically, such URIs point to a remote source where ideally you’d be able to ask your service for different sized images based on the resolution scaling.

Local ms-appx:// and ms-appdata:// URIs are also allowable using the appropriate .scale-1x0 naming convention. You should always, in fact, have a default image for suggested results in your package (using an ms-appx:// URI to refer to it when necessary); the system will not provide one for you.

Type to Search

The final feature of Search is the ability to emulate the “type to search” behavior of the Windows Start screen, where the user doesn’t explicitly invoke the Search charm. If you haven’t done it before and you have a computer with a physical keyboard, press the Windows key to return to the Start screen, and start typing some app name without invoking the search charm first. Voila! The search charm appears automatically with results immediately filtered and displayed. This is essentially the same behavior that the Start button provided in previous versions of Windows, but it’s now much more visually engaging!

To enable this in your own app, simply set the SearchPane.showOnKeyboardInput property to true. You can enable or disable the behavior at any time through this property. Generally speaking, we recommend providing this behavior on your app’s main page(s) and on search results pages, but not on other subsidiary pages where there can be other input controls, nor on details pages showing content for a single item, nor on pages that support an in-page find capability. For details, see Guidelines for Enabling Type to Search.

Launching Apps: File Type and URI Scheme Associations

Developers of Windows 8 apps have often asked whether it’s possible for one app to launch another. The answer is yes, with some restrictions (aren’t you surprised!). First, apps can be launched only through a file type or URI scheme association, not directly by name or path. To be specific, the only way for a Windows 8 app to launch another app—including desktop applications—is through the Windows.System.Launcher API that provides you with two choices:

launchFileAsync Launches another app associated with a given StorageFile. An optional LauncherOptions object lets you specify a number of details (see below).

launchUriAsync Launches another app associated with a given URI scheme, again with or without LauncherOptions.

Note With both launchFileAsync and launchUriAsync, Windows 8 specifically blocks apps from launching any file or URI scheme that is handled by a system component and for which there is no legitimate scenario for a Windows 8 app to insert itself into that process. The How to handle file activation and How to handle protocol activation topics lists the specific file types and URI schemes in question. The file:// URI scheme is allowed in launchUriAsync, but only for intranet URIs when you have declared the Private Networks (Client & Server) capability in the manifest.

The result of both these async methods, as passed to your completed handler, is a Boolean: true if the launch succeeded, false if not. That is, barring a catastrophic failure such as a low memory condition where the async operation will outright fail, these operations normally report success to your completed handler with a Boolean indicating the outcome. You’ll get a false result, for example, if you try to launch a file that itself contains executable code or other files that are blocked for security reasons.

However, you cannot know ahead of time what the result will be. This is the reason for the LauncherOptions parameter, through which you can provide fallback mechanisms:

• The treatAsUntrusted option (a Boolean, default is false) will display a warning to the user that they’ll be switching apps if they proceed (see image below). This is good to use when you’re unsure about the source of the association, such as launching a URI found inside a PDF or other document, and want to prevent the user from experiencing a classic bait-and-switch!

Images

displayApplicationPicker (a Boolean, default is false) will let the use choose which app to launch as part of the process (see image below). Note that the UI allows the user to change the default app for subsequent invocations. Also, the LauncherOptions.ui property can be used to control the placement of the app picker.

Images

preferredApplicationDisplayName and preferredApplicationPackageFamilyName provide a suggestion to the user to acquire a specific app from the Windows Store if no other app is available to service the request. This is very useful with a particular URI scheme or file type for which you provide an app yourself.

• Similarly, fallbackUri specifies a URI to which the user will be taken if no app can be found to handle the request and you don’t have a specific suggestion in the Windows Store.

• Finally, for launchUriAsync, the contentType option identifies the content type associated with a URI that controls which app is launched. This is primarily useful when the URI doesn’t contain a specific scheme but simply refers to a file on a network using a scheme such as http or file that would normally launch a browser for file download. With contentType, the default app that’s registered for that type, rather than the scheme, will be launched. That app, of course, must be able to them use the URI to access the file. In other words, this option is a way to pass a URI, rather than a whole file, to a handler app that you know can work with that URI.

Scenarios 1 and 2 of the Association launching sample provide a demonstration of using these methods with some of the options so you can see their effects.

On the flip side, as demonstrated in Scenarios 3 and 4 of the same sample, is the question of how an app associates itself with a file type or URI scheme so that it can be launched in these ways. These associations constitute the File Activation contract and the Protocol Activation contract. In both cases the target app must declare the file types and/or URI schemes it wishes to service in its manifest and must then provide for those activation kinds, as we’ll see in the following sections.

Again, file or URI scheme association is the only means through which a Windows 8 app can launch another, so there’s no guarantee that you’ll actually launch a specific app. Of course, the more unique and specific the file type or URI scheme, the less likely it is that a consumer would have multiple apps to handle the association or even that there would be many such apps in the Windows Store. Indeed, designing a unique URI scheme interface, where the scheme is fairly app-specific, is really the best way to have one Windows 8 app launch and delegate a task to another, since all kinds of data can be passed in the URI string itself. The Maps app in Windows 8, for example, supports a bingmaps scheme for accomplishing mapping tasks from other apps. You can imagine the same for a stocks app, a calendar app, an email app (beyond mailto), and so forth. If you create such a scheme and want other apps to use it, you’ll certainly need to provide documentation for its usage details, which means that another app can implement the same scheme and thus offer itself as another choice in the Windows Store. So, there’s no guarantee even with a very specific scheme that you can know for certain that you’ll be launching another known app, but this is about as close as you can get to that capability.58

File Activation

To declare file activation capability, first go to the Declarations section of the manifest and add a “File Type Associations” declaration, the Visual Studio UI for which is shown in Figure 12-13. Each file type can have multiple specific types (notice the Add New button under Supported File Types), such as a JPEG having .jpg and .jpeg file extensions. Note that some file types are disallowed for apps; see How to handle file activation for the complete list.

Under Properties, the Display Name is the overall name for a group of file types (this is optional; not needed if you have only one type). The Name, on the other hand, is required—it’s the internal identity for the file group and one that should remain consistent for the entire lifetime of your app across all updates. In a way, the Name/Display Name properties for the whole group of file types is like your real name, and all the individual file types are nicknames—any of them ultimately refer to the core file type and your app.

Info Tip is tooltip text for when the user hovers over a file of this type and the app is the primary association. The Logo is a little tricky; in Visual Studio here, you simply refer to a base name for an image file, like you do with other images in the manifest. In your actual project, however, you should have multiple files for the same image in different target sizes (not resolution scales): 16x16, 32x32, 48x48, and 256x256. The Association launching sample uses such images with targetsize-* suffixes in the filenames.59 These various sizes help Windows provide the best user experience across many different types of devices.

Images

FIGURE 12-13 The Declarations > File Type Associations UI in the Visual Studio manifest designer.

Under Edit Flags, these options control whether an “Open” verb is available for a downloaded file of this type: checking Open Is Safe will enable the verb in various parts of the Windows UI; checking Always Unsafe disables the verb. Leaving both blank might enable the verb, depending on where the file is coming from and other settings within the system.

At the very bottom of this UI you can also set a discrete start page for handling activations, but typically you’ll use your main activation handler, as shown in js/default.js of the Association launching sample (leading into js/scenario3.js).

There you’ll receive the activation kind of file, in which case eventArgs.detail is a WebUIFile-ActivatedEventArgs: its files property contains the array of StorageFile objects from Windows.-System.Launcher.launchFileAsync, and its verb property will be "open". You respond, of course, by opening and presenting the file contents in whatever way is appropriate to the app.

Of course, since the file might have come from anywhere, treat it as untrusted content, as we mentioned earlier for share targets. Avoid taking permanent actions based on those the file contents.

As with the Search contract, too, be sure to test file activation when the app is already running and when it must be started anew. In all cases be sure to load app settings and restore session state if eventArgs.detail.previousExecutionState is terminated.

Protocol Activation

Creating a URI scheme association for an app is much like a file type association. In the Declarations section of the manifest, add a Protocol declaration, as shown in Figure 12-14.

Images

FIGURE 12-14 The Declarations > Protocol UI in the Visual Studio manifest designer.

Under Properties, the Logo, Display Name, and Name all have the same meaning as with file type associations (see the previous section). Similarly, while you can specify a discrete start page, you’ll typically handle activation in your main activation handler, as demonstrated in again in js/default.js of the Association launching sample (leading into js/scenario4.js).

There you’ll receive the activation kind of protocol, in which case eventArgs.detail is a WebUIProtocolActivatedEventArgs: its uri property contains the URI from Windows.System.-Launcher.launchUriAsync.

Once again be warned that URIs with some unique scheme can come from anywhere, including potentially malicious sources. Be wary of any data or queries in the URI, and avoid taking permanent actions with it. For instance, you can perhaps navigate to a new pagebut don’t modify database records to try to eval anything in the URI.

Nevertheless, protocol associations are a primary way that an app can provide valuable services to others when appropriate. The built-in Maps app, for example, supports a bingmaps:// URI scheme and association, so you can just launch a URI with the appropriate format to show the user a fully interactive map instead of trying to implement such capabilities yourself. This is similar to how you rely on an email client with the mailto: scheme; other kinds of apps can easily create a URI scheme interface for other services and workflows.

Tip To debug protocol activation you need to be able to have the app start directly within the debugger when it’s activated. To do this, open the project’s properties (Project > Properties menu command in Visual Studio), and then under Configuration Properties > Debugging set Launch Application to No.

File Picker Providers

Back in Chapter 8 we looked at how the file/folder picker can be used to reference not only locations on the file system but also content that’s managed by other apps or even created on-the-fly within other apps. Let’s be clear on this point: the app that’s using the file picker is doing so to obtain a StorageFile or StorageFolder for some purpose. But this does not mean that provider apps that can be invoked through the file picker necessary manage their data as files or folders. Their role is to take whatever kind of data they manage and package it up so that it looks like a file/folder to the picker.

In the “Using the File Picker” section of Chapter 8, for instance, we saw how the Windows 8 Camera app can be used to take a photo and return it through the file picker. Such a photo did not exist at the time the target app was invoked; instead, it displayed its UI through which the user could essentially create a file that was then passed back through the file picker. In this way, the Camera app shortcuts the whole process of creating a new picture, providing that function exactly when the user is trying to select a picture file. Otherwise the user would have to start the Camera app separately, take a photo, store it locally, and switch to the original app to invoke the file picker and relocate that new picture.

The file picker is not limited to pictures, of course: it works with any file type, depending on what the caller indicates it wants. One app might let the user go into a music library, purchase and download a track, and then return that file to the file picker. Another app might perform some kind of database query and return the results as a file, and still others might allow the user to browse online databases of file-type entities, again hiding the details of downloading and packaging that data as a file such that the user’s experience of the file picker is seamless across the local file system, online resources, and apps that just create data dynamically. It’s also possible to create an app that generates or acquires file-like data on the fly, such as the Camera app that allows you to take a picture or an audio app that could record a new sound. In such cases, however, note that the file picker contracts are designed for relatively quick in-and-out experiences. For this reason an app should provide only basic editing capabilities (like cropping a photo or trimming the audio) in this context.

As with the Search and Share target contracts, Visual Studio and Blend provide an item template for file picker providers, specifically the File Open Picker contract item in the Add > New Item dialog as we’ve seen before (it’s hiding off the top of the list in Figure 12-4). This gives you a basic selection structure built around a Listview control, but not much else. For our purposes here we won’t be using this template; we’ll draw on samples instead. Generally speaking, when servicing the file picker contracts, an app should use the same views and UI as it does when launched normally, thereby keeping the app experience consistent in both scenarios.

Manifest Declarations

To be a provider for the file picker, an app starts by—what else!—adding the appropriate declaration to its manifest. In this case there are actually three declarations: File Open Picker, File Save Picker, and Cached File Updater, as shown below in Visual Studio’s manifest designer. Each of these declarations can be made once within any given app.

Images

The File Open Picker and File Save Picker declarations are what make a provider app available in the dialogs invoked through the Windows.Storage.Pickers.FileOpenPicker and FileSavePicker API. The calling app in both cases is completely unaware that another app might be invoked—all the interaction is between the picker and the provider app through the contract, with the contract broker being responsible for first displaying a UI through which to select an object and second for returning a StorageFile object for that item.

With both the File Open Picker and File Save Picker contracts, the provider app indicates in its manifest those file types that it can service. This is done through the Add New button in the image below; the file picker will then make that app available as a choice only when the calling app indicates a matching file type. The Supports Any File Type option that you see here will make the app always appear in the list, but this is appropriate only for apps like SkyDrive that provide a general storage location. Apps that work only with specific file types should indicate only those types.

Images

The provider app indicates a Start Page for the open and save provider contracts separately—the operations are distinct and independent. In both cases, as we’ve seen for other contracts, these are the pages that the file picker will load when the user selects this particular provider app. As with Share targets, these pages are typically independent of the main app and will have their own script contexts and activation handlers, as we’ll see in the next section. (Again, the Executable and Entry Point options are there for other languages.)

You might be asking: why are the open and save contracts separate? Won’t most apps generally provide both? Well, not necessarily. If you’re creating a provider app for a web service that is effectively read-only (like the image results from a search engine), you can serve only the file open case. If the service supports the creation of new files and updating existing files, such as a photo or document management service would, then you can also serve the file save case. There might also be scenarios where the provider would serve only the save case, such as writing to a sharing service. In short, Windows cannot presume the nature of the data sources that provider apps will work with, so the two contracts are kept separate.

While the next main section in this chapter covers the Cached File Updater contract, it’s good to know how it relates to the others here. This contract allows a provider app to synchronize local and remote copies of a file, essentially to subscribe to and manage change/access notifications for provided files. This is primarily of use to apps that represent a file repository where the user will frequently open and save files, like SkyDrive or a database app. It’s essentially a two-way binding service for files when either local or remote copies can be updated independently. As such, it’s always implemented in conjunction with the file picker provider contracts.

Tip As noted earlier in this chapter, the Sharing and exchanging data topic on the Windows Developer Center has some helpful guidance as to when you might choose to be a provider for the file save picker contract and when being a share target is more appropriate.

Activation of a File Picker Provider

Demonstrations of the file picker provider contracts—for open and save—are found in the File picker contracts sample, which I’ll refer to as the provider sample for clarity. Declarations for both are included in the manifest with Supports Any File Type, so the sample will be listed with other apps in all file pickers, as shown here:

Images

When invoked, the Start page listed in the manifest for the appropriate contract (open or save) is loaded. These are fileOpenPicker.html and fileSavePicker.html, found in the root of the project. Both of these pages are again loaded independently of the main app and appear as shown in Figures 12-15 and 12-16. Note that the title of the app and the color scheme is determined by the Application UI settings in the provider app’s manifest. In particularly, the text comes from the Display Name field and the colors come from the Foreground Text and Background Color settings under Tile, as shown in Figure 12-17. Note that the system automatically adds the down chevron (v) next to the title in Figures 12-15 and 12-16 through which the user can select a different picker location or provider app.

Images

FIGURE 12-15 The Open UI as displayed by the sample.

Images

FIGURE 12-16 The Save UI as displayed by the sample.

Images

FIGURE 12-17 Application UI settings in the manifest that affect the appearance of the open and save picker UI for a provider app. The gray bars in this image represent other fields that I’ve omitted for brevity.

When you first run this sample, you won’t see either of these pages. Instead you’ll see a page through which you can invoke the file open or save pickers and then choose this app as a provider. You can do this if you like, but I recommend using a different app to invoke the pickers, just so we’re clear on which app is playing which role. For this purpose you can use the sample we used in Chapter 8, the File picker sample (this is the consumer side). You can even use something like the Windows 8 Music app where the Open File command on its app bar will invoke a picker wherein the provider sample will be listed.

Whatever your choice, the important parts of the provider sample are its separate pages for servicing its contracts, which are again fileOpenPicker.html and fileSavePicker.html. In the first case, the code is contained in js/fileOpenPicker.js where we can see the activated event handler with the activation kind of fileOpenPicker:

function activated(eventObject) {
    if (eventObject.detail.kind ===
        Windows.ApplicationModel.Activation.ActivationKind.fileOpenPicker) {
        fileOpenPickerUI = eventObject.detail.fileOpenPickerUI;

        eventObject.setPromise(WinJS.UI.processAll().then(function () {
            // Navigate to a scenario page...
        }));
    }
}

Here eventObject.detail is a WebUIFileOpenPickerActivatedEventArgs object, whose fileOpenPickerUI property (a Windows.Storage.Pickers.Providers.FileOpenPickerUI object) provides the means to fulfill the provider’s responsibilities with the contract.

In the second case, the code is in js/fileSavePicker.js where the activation kind is fileSavePicker:

function activated(eventObject) {
    if (eventObject.detail.kind ===
        Windows.ApplicationModel.Activation.ActivationKind.fileSavePicker) {
        fileSavePickerUI = eventObject.detail.fileSavePickerUI;

        eventObject.setPromise(WinJS.UI.processAll().then(function () {
            // Navigate to a scenario page
        }));
    }
}

where eventObject.detail is a WebUIFileSavePickerActivatedEventArgs object. As with the open contract, the fileSavePickerUI property of this (a Windows.Storage.Pickers.Providers.-FileSavePickerUI object) provides the means to fulfill the provider’s side of the contract.

In both open and save cases, the contents of the contract’s Start page is displayed within the letterboxed area between the system-provided top and bottom bands. If that content overflows the provided space, scrollbars would be provided only within that area—the top and bottom bands always remain in place. In both cases, WinRT also provides the usual features for activation, such as the splashScreen and previousExecutionState properties, just as we saw in Chapter 3, “App Anatomy and Page Navigation,” meaning that you should reload necessary session state and use extended splash screens as needed.

What’s most interesting, though, are the contract-specific interactions that are represented in the different scenarios for these pages (as you can see in Figures 12-15 and 12-16). Let’s look at each.

Note For specific details on designing a file picker experience, see Guidelines for file pickers.

File Open Provider: Local File

The provider for file open works through the FileOpenPickerUI object supplied with the fileOpen-Picker activation kind. Simply said, whatever kind of UI the provider offers to select some file or data will be wired to the various methods, properties, and events of this object.

First, the UI will use the allowedFileTypes property to filter what it displays for selection—clearly, the provider should not display items that don’t match what the file picker is being asked to pick! Next, the UI can use the selectionMode property (a FileSelectionMode value) to determine if the file picker was invoked for single or multiple selection.

When the user selects an item within the UI, the provider calls the addFile method with the StorageFile object as appropriate for that item. Clearly, the provider has to somehow create that StorageFile object. In the sample’s open picker > Scenario 1, this is accomplished with a StorageFolder.getFileAsync (where the StorageFolder is the package location).

Windows.ApplicationModel.Package.current.installedLocation
    .getFileAsync("images\\squareTile-sdk.png").then(function (fileToAdd) {
    addFileToBasket(localFileId, fileToAdd);
}

where addFileToBasket just calls FileOpenPickerUI.addFile and displays messages for the result. That result is a value from Windows.Storage.Pickers.Provider.AddFileResult: added (success), alreadyAdded (redundant operations, so the file is already there), notAllowed (adding is denied due to a mismatched file type), and unavailable (app is not visible). These really just help you report the result to users in your UI. Note also that the canAddFile method might be helpful for enabling or disabling add commands in your UI as well, which will prevent some of these error cases from ever arising in the first place.

The provider app must also respond to requests to remove a previously added item, as when the user removes a selection from the “basket” in the multi-select file picker UI. To do this, listen for the FileOpenPickerUI object’s fileRemoved event, which provides a file ID as an argument. You pass this ID to containsFile followed by removeFile as in the sample (js/fileOpenPickerScenario1.js):

// Wire up the event in the page's initialization code
fileOpenPickerUI.addEventListener("fileremoved", onFileRemovedFromBasket, false);

function removeFileFromBasket(fileId) {
    if (fileOpenPickerUI.containsFile(fileId)) {
        fileOpenPickerUI.removeFile(fileId);
    }
}

If you need to know when the file picker UI is closing your page (such as the user pressing the Open or Cancel buttons as shown in Figure 12-15), listen for the closing event. This gives you a chance to close any sessions you might have opened with an online service and otherwise perform any necessary cleanup tasks. In the eventArgs you’ll find an isCanceled property that indicates whether the file picker is being canceled (true) or if it’s being closed due to the Open button (false). The eventArgs.closingOperation object also contains a getDeferral method and a deadline property that allows you to carry out async operations as well, similar to what we saw in Chapter 3 for the suspending event.

A final note is that a file picker provider should respect the FileOpenPickerUI.settings-Identifier to relaunch the provider to a previous state (that is, a previous picker session). If you remember from the other side of this story, an app that’s using the file picker can use the settings-Identifier to distinguish different use cases within itself—perhaps to differentiate certain file types or feature contexts. The identifier can also differ between different apps that invoke the file picker. By honoring this property, then, a provider app can maintain a case-specific context each time it’s invoked (basically using settingsIdentifier in its appdata filenames and the names of settings containers), which is how the built-in file pickers for the file system works.

It’s also possible for the provider app to be suspended while displaying its UI and could possibly be shut down if the calling app is closed. However, if you manage picker state based on settings-Identifier values, you don’t need to save or manage any other session state where your picker functionality is concerned.

File Open Provider: URI

For the most part, Scenario 2 of the open file picker case in the provider sample is just like we’ve seen in the previous section. The only difference is that it shows how to create a StorageFile from a nonfile source, such as an image that’s obtained from a remote URI. In this situation we need to obtain a data stream for the remote URI and convert that stream into a StorageFile. Fortunately, a few WinRT APIs make this very simple, as shown in js/fileOpenPickerScenario2.js within its onAddFileUri method:

function onAddUriFile() {
   // Respond to the "Add" button being clicked
   var imageSrcInput = document.getElementById("imageSrcInput");

   if (imageSrcInput.value !== "") {
      var uri = new Windows.Foundation.Uri(imageSrcInput.value);
      var thumbnail =
         Windows.Storage.Streams.RandomAccessStreamReference.createFromUri(uri);

      // Retrieve a file from a URI to be added to the picker basket
      Windows.Storage.StorageFile.createStreamedFileFromUriAsync("URI.png", uri,
         thumbnail).then(function (fileToAdd) {
         addFileToBasket(uriFileId, fileToAdd);
      },
      function (error) {
         // ...
      });
   } else {
      // ...
   }
}

Here Windows.Storage.StorageFile.createStreamedFileFromUriAsync does the honors to give us a StorageFile for a URI, and addFileToBasket is again an internal method that just calls the addFile method of the FileOpenPickerUI object.

Note that if you need to perform authentication or take any other special steps to obtain content from a web service, you’ll generally want to use the Windows.Netwoking.BackgroundTransfer API to acquire the content (where you can provide credentials), followed by StorageFile.create-StreamedFile to then serve that file up through the contract. StorageFile.createStreamedFileFromUriAsync does exactly this but doesn’t provide for authentication.

File Save Provider: Save a File

Similar to how the file open provider interacts with a FileOpenPickerUI object, a provider app for saving files works with the specific methods, properties, and events FileSavePickerUI class. Again, the open and save contracts are separate concerns because the data source for which you might create a provider app might or might not support save operations independently of open. If you do support both, you will likely reuse the same UI and would thus use the same Start page and activation path.

Within the FileSavePickerUI class, we first have the allowedFileTypes as provided by the app that invoked the file save picker UI in the first place. As with open, you’ll use this to filter what you show in your own UI so that users can clearly see what items for these types already exist. You’ll also typically want to populate a file type drop-down list with these types as well.

For restoring the provider’s save UI for the specific calling app from a previous session, there is again the settingsIdentifier property.

Referring back to Figure 12-16, notice the controls along the bottom of the screen, the ones that are automatically provided by the file picker UI when the provider app is invoked. When the user changes the filename field, the provider app can listen for and handle the FileSavePickerUI object’s filenameChanged event; in your handler you can get the new value from the fileName property. If the provider app has UI for setting the filename, it cannot write to this property, however. It must instead call trySetFileName, whose return value from the SetFileNameResult enumeration is either succeeded, notAllowed (typically a mismatched file type), or unavailable. This is typically used when the user taps an item in your list, where the expected behavior is to set the filename to the name of that item.

The most important event, of course, happens when the user finally taps the Save button. This will fire the FileSavePickerUI object’s targetFileRequested event. You must provide a handler for this event, in which you must create an empty StorageFile object in which the app that invoked the file picker UI can save its data. The name of this StorageFile must match the fileName property.

The eventArgs for this event is a Windows.Storage.Pickers.Providers.TargetFile-Requested-EventArgs object. This contains a single property named request, which is a TargetFileRequest. Its targetFile property is where you place the StorageFile you create (or null if there’s an error). You must set this property before returning from the event handler, but of course you might need to perform asynchronous operations to do this at all. For this purpose, as we’ve seen many times, the request also contains a getDeferral method. This is used in Scenario 1 of the provider sample’s save case (js/fileSavePickerScenario1.js):

function onTargetFileRequested(e) {
    var deferral = e.request.getDeferral();
    // Create a file to provide back to the Picker
    Windows.Storage.ApplicationData.current.localFolder.createFileAsync(
        fileSavePickerUI.fileName).done(function (file) {
        // Assign the resulting file to the targetFile property and complete the deferral
        e.request.targetFile = file;
        deferral.complete();
    }, function () {
        // Set the targetFile property to null and complete the deferral to indicate failure
        e.request.targetFile = null;
        deferral.complete();
    });
};

In your own app you will, of course, replace the createFileAsync call in the local folder with whatever steps are necessary to create a file or data object. Where remote files are concerned, on the other hand, you’ll need to employ the Cached File Updater contract (see “Cached File Updater” below).

File Save Provider: Failure Case

Scenario 2 of the provider sample’s save UI just shows one other aspect of the process: displaying errors in case there is a real failure to create the necessary StorageFile. Generally speaking, you can use whatever UI you feel is best and consistent with the app in general, to let the user know what they need to do. The sample uses a MessageDialog like so:

function onTargetFileRequestedFail(e) {
    var deferral = e.request.getDeferral();

    var messageDialog = new Windows.UI.Popups.MessageDialog("If the app needs the user to
correct a problem before the app can save the file, the app can use a message like this to
tell the user about the problem and how to correct it.");

    messageDialog.showAsync().done(function () {
        // Set the targetFile property to null and complete the deferral to indicate failure
        // once the user has closed the dialog. This will allow the user to take any
        // necessary corrective action and click the Save button once again.
        e.request.targetFile = null;
        deferral.complete();
    });
};

Cached File Updater

Using the cached file updater contract provides for keeping a local copy of a file in sync with one managed by a provider app on some remote resources. This contract is specifically meant for apps that provide access to a storage location where users regularly save, access, and update files. The SkyDrive app in Windows is a good example of this. In other cases where the user is generally going to pick a file and use it some scenario but not otherwise come back to it, using the file picker contracts is entirely sufficient.

Back in Chapter 8, we saw some of the method calls that are made by an app that uses the file picker: Windows.Storage.CachedFileManager.deferUpdates and Windows.Storage.CachedFileManager.-completeUpdatesAsync. This usage is shown in Scenarios 4 and 6 of the File picker sample we worked with in that chapter. Simply said, these are the calls that a file-consuming app makes if and when it writes to a file that it obtained from a file picker. It does this because it won’t know (and shouldn’t care) whether the file provider has another copy in database, web service, etc., that needs to be kept in sync. If the provider needs to handle synchronization, the consuming app’s calls to these methods will trigger the necessary cached file updater UI of the provider app, which might or might not be shown, depending on the need. Even if the consuming app doesn’t call these methods, the provider app will still be notified of changes but won’t be able to show any UI.

There are two directions with which this contract works, depending on whether it’s needed to update a local (cached) copy of a file or the remote (source) copy. In the first case, the provider is asked to update the local copy, typically when the consuming app attempts to access that file (pulling it from the FutureAccessList or MostRecentlyUsed list of Windows.Storage.AccessCache; it does not explicitly ask for an update). In the second case, the consuming app has modified the file such that the provider needs to propagate those changes to its source copy.

From a provider app’s point of view, the need for such updates comes into play whenever it supplies a file to another app. This can happen through the file picker contracts, as we’ve seen in the previous section, but also through file type associations as well as the share contract. In the latter case a share source app is, in a sense, a file provider and might make use of the cached file updater contract as well. In short, if you want your file-providing app to be able to track and synchronize updates between local and remote copies of a file, this is the contract to use.

Supporting the contract begins with a manifest declaration as shown below, where the Start page indicates the page implementing the cached file updater UI. That page will handle the necessary events to update files and might or might not actually be displayed to the user, as we’ll see later.

Images

The next step for the provider is to indicate when a given StorageFile should be hooked up with this contract. It does so by calling Windows.Storage.Provider.CachedFileUpdater.-setUpdateInformation on a provided file as shown in Scenario 3 of the File picker contracts sample, which I’ll again refer to as the provider sample for simplicity (js/fileOpenPickerScenario3.js):

function onAddFile() {
    // Respond to the "Add" button being clicked

    Windows.Storage.ApplicationData.current.localFolder.createFileAsync("CachedFile.txt",
        Windows.Storage.CreationCollisionOption.replaceExisting).then(function (file) {
        Windows.Storage.FileIO.writeTextAsync(file, "Cached file created...").then(
            function () {
                Windows.Storage.Provider.CachedFileUpdater.setUpdateInformation(
                    file, "CachedFile",
                    Windows.Storage.Provider.ReadActivationMode.beforeAccess,
                    Windows.Storage.Provider.WriteActivationMode.notNeeded,
                    Windows.Storage.Provider.CachedFileOptions.requireUpdateOnAccess);
                addFileToBasket(localFileId, file);
            }, onError);
    }, onError);
};

Note setUpdateInformation is within the Windows.Storage.Provider namespace and is different from the Windows.Storage.CachedFileManager object that’s used on the other side of the contract; be careful to not confuse the two.

The setUpdateInformation method takes the following arguments:

• A StorageFile for the file in question.

• A content identifier string that identifies the remote resource to keep in sync.

• A ReadActivationMode indicating whether the calling app can read its local file without updating it; values are notNeeded and beforeAccess.

• A WriteActivationMode indicating whether the calling app can write to the local file and whether writing triggers an update; values are notNeeded, readOnly, and afterWrite.

• One or more values from CachedFileOptions (that can be combined with bitwise-OR) that describes the ways in which the local file can be accessed without triggering an update; values are none (no update), requireUpdateAccess (update on accessing the local file), useCachedFileWhenOffline (will update on access if the calling app desires, and access is allowed if there’s no network connection), and denyAccessWhenOnline (triggers an update on access and requires a network connection).

It’s through this call, in other words, that the provider specifically controls how and when it should be activated to handle updates when a local file is accessed.

So, together we have two cases where the provider app will be invoked and might be asked to show its UI: one where the calling app updates the file, and another when the calling app attempts to access the file but needs an update before reading its contents.

Before going into the technical details, let’s see how these interactions appear to the user. To see the cached file updater in action using the sample, invoke it by using the file picker from another app. First, then, run the provider sample to make sure its contracts are registered. Then run the aforementioned File picker sample. In the latter, Scenarios 4, 5, and 6 cause interactions with the cached file updater contract. Scenarios 4 and 6 write to a file to trigger an update to the remote copy; Scenario 5 accesses a local file that will trigger a local update as part of the process.

Updating a Local File: UI

In Scenario 5 (updating a local file), start by tapping the Pick Cached File button in the UI shown here:

Images

This will launch the provider sample. In that view, select Scenario 3 so that you see the UI shown in Figure 12-18. This is the mode of the provider sample that is just a file picker provider, (js/fileOpenPickerScenario3.js) where it calls setUpdateInformation. This is not the UI for the cached file updater yet. Click the Add File to Basket button, and tap the Open button. This will return you to the first app (the picker sample in the above graphic) where the Output Latest Version button will now be enabled. Tapping that button will then invoke the provider sample through the cached file updater contract, as shown in Figure 12-19. This is what appears when there’s a need to update the local copy of the cached file.

Images

FIGURE 12-18 The provider sample’s UI for picking a file; the setUpdateInfomation method is called on the provided file to set up the cached file updater relationship.

Images

FIGURE 12-19 The provider sample’s UI for the cached file updater contract on a local file.

Take careful note of the description in the sample. While the sample shows this UI by default, a cached file updater app will not show it unless it’s necessary to resolve conflicts or collect credentials. Oftentimes no such interaction is necessary and the provider silently provides an update to the local file or indicated that the file is current. The sample’s UI here is simply providing both those options as explicit choices (and be sure to choose one of them because selecting Cancel will throw an exception).

Updating a Remote File: UI

In Scenario 6 (updating a remote file) of the file picker sample, we can see the interactions that take place when the consuming app writes changes to its local copy, thereby triggering an update to the remote copy. Start by tapping the Get Save File button in the UI shown next:

Images

In the picker, select the provider sample as the picker source, which invokes the UI of Figure 12-20 through the file save picker contract, implemented through html/fileSavePickerScenario3.html and js/fileSavePickerScenaro3.js. If you look in the JavaScript file, you’ll again see a call to setUpdateInformation that’s called when you enter a file name and tap Save. Doing so also returns you to the picker sample above where Write To File should now be enabled. Tapping Write To File then reinvokes the provider sample through the cached file updater contract with the UI shown in Figure 12-21. This UI is intended to demonstrate how such a provider app would accommodate overwriting or renaming the remote file.

Images

FIGURE 12-20 The provider sample’s UI for saving a file; the setUpdateInfomation method is again called on the provided file to set up the cached file updater relationship.

Images

FIGURE 12-21 The provider sample’s UI for the cached file updater contract on a remote file.

Update Events

Let’s see how the cached file updater contract looks in code. As you will by now expect, the provider app is launched, the Start page (cachedFileUpdater.html in the project root) is loaded, and the activated handler is called with the activation kind of cachedFileUpdater. This will happen for both local and remote cases, and as we’ll see here, you use the same activation code for both. Here eventObject.detail is a WebUICachedFileUpdaterActivatedEventArgs that contains a cachedFileUpdaterUI property (a CachedFileUpdaterUI) along with the usual roster of kind, previousExecutionState, and splashScreen. Here’s how it looks in js/cachedFileUpdater.js of the provider sample:

function activated(eventObject) {
    if (eventObject.detail.kind ===
        Windows.ApplicationModel.Activation.ActivationKind.cachedFileUpdater) {
        cachedFileUpdaterUI = eventObject.detail.cachedFileUpdaterUI;

        cachedFileUpdaterUI.addEventListener("fileupdaterequested", onFileUpdateRequest);
        cachedFileUpdaterUI.addEventListener("uirequested", onUIRequested);

        switch (cachedFileUpdaterUI.updateTarget) {
            case Windows.Storage.Provider.CachedFileTarget.local:
                // Code omitted: configures sample to show cachedFileUpdaterScenario1
                // if needed.
                break;

            case Windows.Storage.Provider.CachedFileTarget.remote:
                // Code omitted: configures sample to show cachedFileUpdaterScenario2
                // if needed.
                break;
        }
    }
}

When the provider app is invoked to update a local file from the remote source, the cachedFileUpdaterUI.updateTarget property will be local, as you can see above. When the app is being asked to update a remote file with local changes, the target is remote. All the sample does in these cases is point to either html/cachedFileUpdaterScenario1.html (Figure 12-19) or html/cachedFile-UpdaterScenario2.html (Figure 12-21) as the update UI.

The UI is not actually shown initially. What happens first is that the CachedFileUpdaterUI object fires its fileUpdateRequested event to attempt a silent update. Here the eventArgs is a File-UpdateRequestedEventArgs object with a single request property (FileUpdateRequest), an object that you’ll want to save in a variable that’s accessible from your update UI.

If it’s possible to silently update a local file, follow these steps:

• Because you’ll likely be doing async operations to perform the update, obtain a deferral from request.getDeferral.

• To update the contents of the local file, use one of these options:

• If you already have a StorageFile with the new contents, just call request.update-LocalFile. This is a synchronous call, in which case you do not need to obtain a deferral.

• The local file’s StorageFile object will be in request.file. You can open this file and write whatever contents you need within it. This will typically start an async operation, after which you return from the event handler.

• To update the contents of a remote file, copy the contents from request.file to the remote source.

• Depending on the outcome of the update, set request.status to a value from FileUpdate-Status: complete (the copies are sync’d), incomplete (sync didn’t work but the local copy is still available), userInputNeeded (the update failed for need of credentials or conflict reso-lution), currentlyUnavailable (source can’t be reached, and the local file is inaccessible), failed (sync cannot happen now or ever, as when the source file has been deleted), and completeAndRenamed (the source version has been renamed, generally to resolve conflicts).

• If you asked for a deferral and processed the outcome within completed and error handlers, call the deferral’s complete method to finalize the update.

Now the provider might know ahead of time that it can’t do a silent update at all—a user might not be logged into the back-end service (or credentials are needed each time), there might be a conflict to resolve, and so forth. In these cases the event handler here should check the value of cachedFile-UpdaterUI.uiStatus (a UIStatus) and set the request.status property accordingly:

• If the UI status is visible, switch to that UI and return from the event handler. Complete the deferral when the user has responded through the UI.

• If UI status is hidden, set request.status to userInputNeeded and return. This will trigger the CachedFileUpdaterUI.onuiRequested event followed by another fileUpdate-Requested event where uiStatus will be visible, in which case you’ll switch to your UI.

• If the UI status is unavailable, set request.status to currentlyUnavailable.

You can see some of this in the sample’s onFileUpdateRequest handler; it really handles only the uiStatus check because it doesn’t attempt silent updates at all (as described in the comments below):

function onFileUpdateRequest(e) {
    fileUpdateRequest = e.request;
    fileUpdateRequestDeferral = fileUpdateRequest.getDeferral();

    // Attempt a silent update using fileUpdateRequest.file silently, or call
    // fileUpdateRequest.updateLocalFile in the local case, setting fileUpdateRequest.status
    // accordingly, then calling fileUpdateRequestDeferral.complete(). Otherwise, if you
    // know that user action will be required, execute the following code.

    switch (cachedFileUpdaterUI.uiStatus) {
        case Windows.Storage.Provider.UIStatus.hidden:
            fileUpdateRequest.status =
        Windows.Storage.Provider.FileUpdateStatus.userInputNeeded;
            fileUpdateRequestDeferral.complete();
            break;

        case Windows.Storage.Provider.UIStatus.visible:
            // Switch to the update UI (configured in the activated event)
            var url = scenarios[0].url;
            WinJS.Navigation.navigate(url, cachedFileUpdaterUI);
            break;

        case Windows.Storage.Provider.UIStatus.unavailable:
            fileUpdateRequest.status = Windows.Storage.Provider.FileUpdateStatus.failed;
            fileUpdateRequestDeferral.complete();
        break;
    }
}

Again, if a silent update succeeds, the provider app’s UI never appears to the user. In the case of the provider sample, since it never attempts to do a silent update, it always does the check on uiStatus. When the app was just launched to service the contract, we’ll end up in the hidden case and return userInputNeeded, as would happen if you attempted a silent update but returned the same status. Either way, the CachedFileUpdateUI object will fire its uiRequested event, telling the provider app that the system is making the UI visible. The app, in fact, can defer initializing its UI until this event occurs because there’s no need to do so for a silent update.

After this, the fileUpdateRequested event will fire again with uiStatus now set to visible. Notice how the code above will have called request.getDeferral in this case but has not called its complete. We save that step for when the UI has done what it needs to do (and, in fact, we save both the request and the deferral for use from the UI code).

The update UI is responsible for gathering whatever user input is necessary to accomplish the task: collecting credentials, choosing which copy of a file to keep (the local or remote version), allowing for renaming a conflicting file (when updating a remote file), and so forth. When updating a local file, it writes to the StorageFile within request.file or calls request.updateLocalFile; in the remote case it copies data from the local copy in request.file.

To complete the update, the UI code then sets request.status to complete (or any other appropriate code if there’s a failure) and calls the deferral’s complete method. This will change the status of the system-provided buttons along the bottom of the screen, as you can see in Figure 12-19 and Figure 12-21—enabling the OK button and disabling Cancel. In the provider sample, both buttons just execute these two lines for this purpose:

fileUpdateRequest.status = Windows.Storage.Provider.FileUpdateStatus.complete;
fileUpdateRequestDeferral.complete();

All in all, the interactions between the system and the app for the cached file updater contract are simple and straightforward in themselves: handle the events, copy data around as needed, and update the request status. The real work with this contract is first deciding when to call setUpdateInfor-mation and then providing the UI to support updates of local and remote files under the necessary circumstances. This will, of course, involve more interactions with your backend storage system.

Contacts

The last contract we’ll explore in this chapter (whew!) is that of the contact picker. We haven’t seen this feature of Windows 8 in action yet. Let’s take a look at it first and then explore how the picker is used from one side of the contract and how an provider app fulfills the other side.

A contact, as you probably expect, is information about a person that includes details like name, phone numbers, email addresses, and so forth. An obvious place you’d need a contact is to compose an email, as shown in Figure 12-22. Here, tapping the + controls to the right of the To and Cc fields will open the contact picker, which defaults to the Windows 8 People app, as shown in Figure 12-23 (its splash screen) and Figure 12-24 (its multiselect picker view, where I have blurred my friends’ identities so that they don’t start blaming me for unwanted attention!). As we saw with the File Picker UI, the provider app supplies the UI for the middle portion of the screen while Windows supplies the top and bottom bars, the header, and the down-arrow menu control using information from the provider app’s manifest. (Refer back to Figure 12-17.) Figure 12-25 shows the appearance of the Contact Picker app sample in its provider mode, as well as the menu that allows you to select a different provider (those who have declared themselves as a contact provider).

When I select one or more contacts in any provider app and press the Select button along the bottom of the screen, those contacts are then brought directly back to the first app—Mail in this case. Just as the file picker contract allowed the user to navigate into data surfaced as files by any other app, the contact contract (say that ten times fast!) lets the user easily navigate to people you might select from any other source.

Images

FIGURE 12-22 The Mail app uses the contact picker to choose a recipient.

Images

FIGURE 12-23 The People app on startup when launched as a contact provider.

Images

FIGURE 12-24 The picker UI within the People app, shown for multiple selection (with my friends blurred because they’re generally not looking for fame amongst developers). The selections are gathered along the bottom in the basket.

Images

FIGURE 12-25 The Contact Picker sample’s UI when used as a provider, along with the header flyout menu allowing selection of a picker provider.

Using the Contact Picker

Contacts as a whole work with the API in the Windows.ApplicationModel.Contacts namespace. An app that consumes contacts sees each one represented by an instance of the Contact-Information class, whose properties like name, phoneNumbers, locations, emails, instant-Messages, and customFields give you the contact information, along with the getThumbnailAsync and queryCustomFields methods.

Choosing a contract happens through a picker UI much like the file picker, invoked through Windows.ApplicationModel.Contacts.ContactPicker. After creating an instance of this object, you can set the commitButtonText for the first (left) button in the picker UI (as with “Select” in the earlier figures). You can also set the selectionMode property to a value from the Contact-SelectionMode enumeration: either contact (the default) or fields. In the former case, the whole contact information is returned; in the latter, the picker works against the contents of the picker’s desiredFields. Refer to the documentation on that property for details.

When you’re ready to show the UI, call the picker’s pickSingleContactAsync or pickMultipleContactsAsync methods. These provide your completed handler with a single ContactInformation object or a vector of them, respectively. As with the file picker, note that these APIs will throw an exception if called when the app is in snapped view, so you’ll want to avoid this case.

Picking a single contact and displaying its information is demonstrated in Scenario 1 of the Contact Picker app sample (js/scenarioSingle.js):

var picker = new Windows.ApplicationModel.Contacts.ContactPicker();
picker.commitButtonText = "Select";

// Open the picker for the user to select a contact
picker.pickSingleContactAsync().done(function (contact) {
    if (contact !== null) {
        // Consume the contact information...
    }
});

Choosing multiple contacts (Scenario 2, js/scenarioMultiple.js) works the same way, just using pickMultipleContactsAsync. In either case, the calling app then applies the Contact-Information data however it sees fit, such as populating a To or Cc field like the Mail app. However, other than the name property in that object, which is just a string, its properties have a little more structure, as shown in the following table.

Images

Accordingly, the sample consumes a ContactInformation object as follows, first extracting the individual vector properties:

appendFields("Emails:", contact.emails, contactElement);
appendFields("Phone Numbers:", contact.phoneNumbers, contactElement);
appendFields("Addresses:", contact.locations, contactElement);

and then enumerating the contents of those vectors and in this case creating elements with their contents. Other apps will, of course, transfer the values to appropriate fields or other parts of the app UI—what’s shown here demonstrates processing of the different categories:

function appendFields(title, fields, container) {
    // Creates UI for a list of contact fields of the same type, e.g. emails or phones
    fields.forEach(function (field) {
        if (field.value) {
            // Append the title once we have a non-empty contact field
            if (title) {
                container.appendChild(createTextElement("h4", title));
                title = "";
            }

            // Display the category next to the field value
            switch (field.category) {
                case Windows.ApplicationModel.Contacts.ContactFieldCategory.home:
                    container.appendChild(createTextElement("div",
                        field.value + " (home)"));
                    break;
                case Windows.ApplicationModel.Contacts.ContactFieldCategory.work:
                    container.appendChild(createTextElement("div",
                        field.value + " (work)"));
                    break;
                case Windows.ApplicationModel.Contacts.ContactFieldCategory.mobile:
                    container.appendChild(createTextElement("div",
                        field.value + " (mobile)"));
                    break;
                case Windows.ApplicationModel.Contacts.ContactFieldCategory.other:
                    container.appendChild(createTextElement("div",
                        field.value + " (other)"));
                    break;
                case Windows.ApplicationModel.Contacts.ContactFieldCategory.none:
                default:
                    container.appendChild(createTextElement("div", field.value));
                    break;
            }
        }
    });
}

Contact Picker Providers

On the provider side, which is also demonstrated in the Contact Picker app sample, we see the same pattern as for file picker providers. First, a provider app needs to declare the Contact Picker contract in its manifest, where it indicates the Start page to load within the context of the picker. In the sample, the Start page is contactPicker.html that in turn loads html/contactPickerScenario.html (with their associated JavaScript files):

Images

As with the file picker, having a separate Start page means having a separate activated handler, and in this case it looks for the activation kind of contactPicker (js/contactPicker.js):

function activated(eventObject) {
    if (eventObject.detail.kind ===
        Windows.ApplicationModel.Activation.ActivationKind.contactPicker) {
        contactPickerUI = eventObject.detail.contactPickerUI;
        eventObject.setPromise(WinJS.UI.processAll().then(function () {
            // ...
        }));
    }
}

The eventObject.detail here is a ContactPickerActivatedEventArgs (these names are long, but at least they’re predictable!). As with all activations, it contains kind, previous-ExecutionState, and splashScreen properties for the usual purposes. Its contactPickerUI property, a ContactPickerUI, then contains the information specific for the contact picker contract:

• The selectionMode and desiredFields properties as supplied by the calling app.

• Three methods—addContact, removeContact, and containsContact—for managing what’s returned to the calling app. These methods correspond to the actions of a typical selection UI.

• One event, contactsRemoved, which informs the provider when the user removes an item from the basket along the bottom of the screen. (Refer back to Figure 12-24.)

Within a provider, each contact is represented by a Windows.ApplicationModel.Contacts.Contact object. A provider will create an object for each contact it supplies. In the sample (js/contactPickerScenario.js), there’s an array called sampleContacts that simulates what would more typically come from a database. That array just contains JSON records like this:

{
    name: "David Jaffe",
    homeEmail: "david@contoso.com",
    workEmail: "david@cpandl.com",
    workPhone: "",
    homePhone: "248-555-0150",
    mobilePhone: "",
    address: {
        full: "3456 Broadway Ln, Los Angeles, CA",
        street: "",
        city: "",
        state: "",
        zipCode: ""
    },
    id: "761cb6fb-0270-451e-8725-bb575eeb24d5"
},

Each record is shown as a check box in the sample’s UI (generated in the createContactUI function), which is a quick and easy way to show a selectable list of items! Of course, your own provider app will likely use a ListView for this purpose; the sample is just trying to keep things simple so that you can see what’s happening with the contract itself.

When a contact is selected, the sample’s addContactToBasket function is called. This is the point at which we create the actual Contact object and call ContactPickerUI.addContact. The process here for each field follows a chain of other function calls, so let’s see how it works for the single homeEmail field in the source record, starting with addContactToBasket (again in js/contactPicker-Scenario.js). The rest of the field values are handled pretty much the same way:

function addContactToBasket(sampleContact) {
    var contact = new Windows.ApplicationModel.Contacts.Contact();
    contact.name = sampleContact.name;

    appendEmail(contact.fields, sampleContact.homeEmail,
        Windows.ApplicationModel.Contacts.ContactFieldCategory.home);

    // Add other fields...

    // Add the contact to the basket
    switch (contactPickerUI.addContact(sampleContact.id, contact)) {
        // Show various messages based on the result, which is of type
        // Windows.ApplicationModel.Contacts.Provider.AddContactResult
    }
}

As you can see, the homeEmail field is passed to a function called appendEmail, where the first argument is the vector (Contact.fields) in which to add the field and the third parameter is the category (home). These are then passed through to another generic function, appendField, where the type of the field has been thrown into the mix, all of which is used to create a ContactField object and add it to the contact:

function appendEmail(fields, email, category) {
    // Adds a new email to the contact fields vector
    appendField(fields, email,
        Windows.ApplicationModel.Contacts.ContactFieldType.email, category);
}

function appendField(fields, value, type, category) {
    // Adds a new field of the desired type, either email or phone number
    if (value) {
        fields.append(new Windows.ApplicationModel.Contacts.ContactField(value,
            type, category));
    }
}

In short, this is essentially how all the fields in a contact are assembled, one bit at a time.

Now, when an item is unselected in the list, it needs to be removed from the basket:

function removeContactFromBasket(sampleContact) {
   // Programmatically remove the contact from the basket
       if (contactPickerUI.containsContact(sampleContact.id)) {
           contactPickerUI.removeContact(sampleContact.id);
   }
}

Similarly, when the user removes an item from the basket, the contact provider needs to update its selection UI by handling the contactremoved event:

contactPickerUI.addEventListener("contactremoved", onContactRemoved, false);

function onContactRemoved(e) {
    // Add any code to be called when a contact is removed from the basket by the user
    var contactElement = document.getElementById(e.id);
    var sampleContact = sampleContacts[contactElement.value];
    contactElement.checked = false;
}

You’ll notice that we haven’t said anything about closing the UI, and in fact the ContactPickerUI object does not have an event for this. Simply said, when the user selects the commit button (with whatever text the caller provided), it gets back whatever the provider has added to the basket. If the user taps the cancel button, the operation returns a null contact. In both cases, the provider app will be suspended and, if it wasn’t running prior to being activated for the contact,close automatically.

Do note that as with file picker providers, a contact provider needs to be ready to save its session state when suspended such that it can restore that state when relaunched with previousExecution-State set to terminated. Although not demonstrated in the sample, a real provider app should save its current selections and viewing position within its list, along with whatever else, to session state and restore that in its activated handler when necessary.

What We’ve Just Learned

• Contracts in Windows 8 provide the ability for any number of apps to extend system functionality as well as extend the functionality of other apps. Through contracts, installing more apps that support them creates a richer overall environment for users.

• The Share contract provides a shortcut means through which data from one app can be sent to another, eliminating many intermediate steps and keeping the user in the context of the same app. A source app packages data it can share when the Share charm is invoked; target apps consume that data, often copying it elsewhere as in an email message, text message, social networking service, and so forth.

• The Share target provides for delayed rendering of items (such as graphics), for long-running operations (such as when it’s necessary to upload large data files to a service), and for providing quicklinks to specific targets within the same app (such as frequent email recipients).

• The Search contract provides integration between an app and the Search charm. From the charm users can search the current app as well as any others that support the contract, easily viewing results from other apps without having to manually launch them or switch to them. The search contract allows apps to also provide query suggestions and result suggestions.

• File type and URI scheme associations are how apps can launch other apps. an app’s associa-tions are declared in its manifest allowing it to be launched to service those associations. URI scheme associations are an excellent means for an app to provide workflow services to others.

• Apps that implement the provider side of the file picker contract appear as choices within the file picker UI. This is how apps can present data sources they manage as if they were part of the local file system, even though they might exist in databases, online services, or other such locations. To the user, the necessary transport considerations are transparent, and through the cached file updater contract a provider app can also handle synchronization of local and remote copies of the file.

• The contract for Contacts works similarly to the file picker but with information about people. A consuming app can easily invoke the contact picker UI and any number of provider apps can implement the other side of the contract to surface an address book, database, or other source through that UI.