Download

You will learn

How to implement the builder pattern for a Windows Store app built using JavaScript.

How to implement the query object pattern for a Windows Store app built using JavaScript.

How to prefetch properties to optimize performance.

How to create and execute a file system query.

Applies to

Windows Runtime for Windows 8

Windows Library for JavaScript

JavaScript

The builder pattern

The builder pattern is an object creation pattern in which you provide input to construct a complex object in multiple steps. Depending on input, the object may vary in its representation. In Hilo, we construct a query builder object whose only function is to build a query object, which encapsulates a query. All of the pages in Hilo need access to the local picture folder to display images, so the query builder settings are particular to a Windows file system query.

When we create the query builder, default builder options specific to a file system query are set in the constructor. After you create a query builder, you can call its particular members to set options that are specific to the page and query. The following code specifies that the query builder object will create a query object that

The chained functions in the preceding code demonstrate the fluent coding style, which is supported because the builder pattern used in Hilo implements the fluent interface. The fluent interface enables a coding style that flows easily, partly through the implementation of carefully named methods. In the query builder object, each function in the chain returns itself (the query builder object). This enables the fluent coding style. For information on the fluent interface, see FluentInterface.

The query object pattern

In the query object pattern, you define a query by using a bunch of properties, and then execute the query. You can change options on the query builder and then use the builder to create a new query object. The following code shows the constructor function for the internally-defined query object. The passed-in settings are used to build a file query (_buildQueryOptions and _buildFileQuery).

function QueryObjectConstructor(settings) {
// Duplicate and the settings by copying them// from the original, to a new object. This is// a shallow copy only.//// This prevents the original queryBuilder object// from modifying the settings that have been// sent to this query object.var dupSettings = {};
Object.keys(settings).forEach(function (key) {
dupSettings[key] = settings[key];
});
this.settings = dupSettings;
// Build the query options.var queryOptions = this._buildQueryOptions(this.settings);
this._queryOptionsString = queryOptions.saveToString();
if (!this.settings.folder.createFileQueryWithOptions) {
var folder = supportedFolders[this.settings.folderKey];
if (!folder) {
// This is primarily to help any developer who has to extend Hilo.// If they add support for a new folder, but forget to register it// at the head of this module then this error should help them// identify the problem quickly.thrownew Error("Attempted to deserialize a query for an unknown folder: " + settings.folderKey);
}
this.settings.folder = folder;
}
this.fileQuery = this._buildFileQuery(queryOptions);
},

The query object pattern also makes it easy to serialize and deserialize the query. You do this by using methods on the query object, typically with corresponding names. For general info on the query object pattern, see Query Object.

In developing Hilo, we changed our initial implementation of the repository pattern to the query object pattern. The main difference between the repository pattern and the query object pattern is that in the repository pattern you create an abstraction of your data source and call methods such as getImagesByType. Whereas, with the query object pattern, you set properties on the query builder, and then simply build a query based on the current state of the builder.

We changed to the query object pattern mainly for two reasons. First, we found we were building different query objects with a lot of the same code and default values in different pages. Second, we were adding repository functions that were no longer generic. The following code shows an example of one specialized repository function that we removed from Hilo.

After you create a query builder, you can set additional options on the builder to match the desired query. For example, in the hub page, we set options in the following code. These options specify the type of builder as bindable, set a prefetch option for the item date (System.ItemDate), and set the count to six. The count value indicates that the hub page will display only six images total.

When we call bindable, we simply add a new "bindable" setting to the array of the query builder’s settings. We will use this property later to create bindable objects that wrap the returned file system objects. For more info, see Making the file system objects observable.

bindable: function (bindable) {
// `!!` is a JavaScript coersion trick to convert any value// in to a true boolean value. //// When checking equality and boolean values, JavaScript // coerces `undefined`, `null`, `0`, `""`, and `false` into // a boolean value of `false`. All other values are coerced // into a boolean value of `true`.//// The first ! then, negates the coerced value. For example,// a value of "" (empty string) will be coerced in to `false`.// Therefore `!""` will return `true`. //// The second ! then inverts the negated value to the// correct boolean form, as a true boolean value. For example,// `!!""` returns `false` and `!!"something"` returns true.this._set("bindable", !!bindable);
returnthis;
},

When you perform file operations with the Windows Runtime, it can be helpful to instruct Windows Runtime to optimize the retrieval of specific file properties. You do this by setting prefetch options on a query. Here, we take the passed in System.ItemDate parameter and set the query builder’s prefetchOption property to the same value. Initially, we just set the prefetch properties on the query builder, as shown here. We will use this value later when we build the query.

function QueryObjectConstructor(settings) {
// Duplicate and the settings by copying them// from the original, to a new object. This is// a shallow copy only.//// This prevents the original queryBuilder object// from modifying the settings that have been// sent to this query object.var dupSettings = {};
Object.keys(settings).forEach(function (key) {
dupSettings[key] = settings[key];
});
this.settings = dupSettings;
// Build the query options.var queryOptions = this._buildQueryOptions(this.settings);
this._queryOptionsString = queryOptions.saveToString();
if (!this.settings.folder.createFileQueryWithOptions) {
var folder = supportedFolders[this.settings.folderKey];
if (!folder) {
// This is primarily to help any developer who has to extend Hilo.// If they add support for a new folder, but forget to register it// at the head of this module then this error should help them// identify the problem quickly.thrownew Error("Attempted to deserialize a query for an unknown folder: " + settings.folderKey);
}
this.settings.folder = folder;
}
this.fileQuery = this._buildFileQuery(queryOptions);
},

From the constructor, the query object calls _buildQueryOptions. This function turns the query builder settings into a valid Windows.Storage.Search.QueryOptions object. It is here that we set prefetch options to optimize performance. For the Hub page, we pass the ItemDate property as the second parameter in setPropertyPrefetch.

The prefetched item date is not used directly in the hub view. The hub view doesn’t care about dates, and just displays the first six images. But in hub.js we will use the item date to set the correct index value on an image before passing to other pages, such as the details page. Windows Runtime handles retrieval of the item date on demand in a separate asynchronous operation. This is a relatively slow operation that is performed one file at a time. By prefetching the item dates before we get the actual images, we could improve performance.

In the execute call, we use getFilesAsync to actually retrieve the images. We then check whether the bindable property was set in the query. If bindable is set, this means we need to wrap returned StorageFile objects to make them usable for binding to the UI. For info on wrapping the file system object for binding to the UI, see Making the file system objects observable.

When the query result is returned in the completed promise, we call bindImages. The main job of this function is to associate the ungrouped index value for each returned image to a group index value that is month-based. It then requests binding to the UI by using setDataSource.

Making the file system objects observable

Objects returned from the file system, aren’t in a format that’s bindable (observable) to controls such as ListView and FlipView. This is because Windows Runtime is not mutable. There is a WinJS utility for making an object observable, but this utility tries to change the object by wrapping it in a proxy. When returned StorageFile objects need to be bound to the UI, they are set as "bindable" in the query builder. For bindable objects, the execute function in the query object calls _createViewModels. This maps the file system objects to Hilo.Picture objects.

The code here shows the constructor function for a Picture object. In this function, string values are added to the object as properties. After the properties are set, they can be used for binding to the UI. For example, the url property is set by calling the app’s URL cache (not shown). The cache calls URL.createObjectURL. createObjectURL takes as input a blob of binary data (a passed-in thumbnail) and returns a string URL.

Tip Before navigating to a new page, you need to release objects that you created by using URL.createObjectURL. This avoids a potential memory leak. You can do this by using URL.revokeObjectURL, which in Hilo is called in urlCache.js (not shown). For info on finding memory leaks and other performance tips, see Improving performance and Analyzing memory usage data.