Skip to content

Building Great #WinApps: Custom WinJS controls

by Brandon on February 19th, 2014

I like to segment the UI components for my WinJS projects into controls, and organize them in the codebase as separate HTML/JS/CSS files. Controls encapsulate functionality and provide an easy way to reuse code throughout a project (or between projects!).

First, let me describe a few relevant facilities that WinJS provides:

Fragments

The WinJS.UI.Fragments namespace provides utilities for loading fragments of HTML from separate files and rendering them into your DOM tree. Its capabilities include caching fragments in advance of their usage, and rendering copies as needed. On WinRT capable platforms (i.e. Windows 8) it will use an optimized path for loading these fragments versus other cases (i.e. running in IE, or another browser/platform).

Templates

The WinJS.Binding.Template namespace allows you to use a block of HTML as a template for new block. This is similar to the templating facilities in other JS libraries. Rendering a template is a lot like rendering a copy of a fragment and then running WinJS.Binding.processAll on it. However, it’s more efficient than that, especially in WinJS 2.0 with its template compilation feature.

PageControls

WinJS also provides WinJS.UI.Pages.PageControl, which essentially builds on the Fragment Loader and adds some functionality. In particular, it integrates with the navigation functionality from WinJS.Navigation and the Navigation App template. You can also use this to create custom controls, which you can then instantiate programmatically or declaratively.

I find PageControl to be reasonably useful for actual pages, which makes sense given the name. However, MSDN also suggests this is the preferred way to implement a custom WinJS control. This is how I originally implemented controls in my apps (and some less often used ones still are this way). It certainly works, but there are several ways in which it is not ideal.

One nice thing about PageControl is it has a handy VS Item Template, which makes it easy to create a matched set of HTML, JS, and CSS files in one go. And they’re pre-populated with some boilerplate code. As we saw in my last post of this series, most of this is cruft you want to delete anyway.

Downsides (beyond the template cruft) include:

  • There are tricks to getting the Fragment Loader to cache a page fragment (because of differences in the URL format).
  • PageControl doesn’t use the WinJS template infrastructure, and thus misses out on the compilation benefit, which is valuable if you’re using many instances of the control.
  • The PageControl pattern, and the idiosyncrasies of its methods (i.e. render vs ready, when things are sync vs async, etc) can take time to master and even then are sometimes confusing.

My control template

To enable the convenience of creating those 3 files all at once I decided to create my own VS Item Template. I went a bit further though, and replaced all the boilerplate code with my own pattern. It leverages the Fragment Loader and the Template infrastructure, but doesn’t use PageControl at all.

Using the template

Once the template is installed, you use it like this:

1) Right-click on the folder where you want the control files to live.

2) Go to Add -> New Item.

image

3) In the dialog that comes up, select “Bside WinJS Control”.

4) Give the control a name and click “Add”.

Add New Item dialog - B-side control template

What do you get?

When you create a new control using the template, you’ll get three files just as with the Page Control template. In this example case, I’m creating a new “JokeTile” control for use in the Chuck Norris Joke DB app we started creating in the last post.

HTML file

The HTML file is a lot simpler than the one in the Page Control template. There is, however, one action item here. This template doesn’t try to mimic PageControl’s ability to reference the control by the URL of its HTML file, and this means the control’s JS file must be included before you can instantiate the control. The control’s HTML file will have a reference to the JS file, but you’ll want to cut and paste this to either the page(s) referencing the control, or your default.html file. The latter is preferable if the control is used in your usual app startup path.


<link href="JokeTile.css" rel="stylesheet" />
<!-- TODO: Move the below script reference to controls/JokeTile/JokeTile.js from this file to any page that references the control,
           or to default.html if this control is used during normal app startup (to get bytecode caching) -->
<script src="/controls/JokeTile/JokeTile.js"></script>

<div id="JokeTile-template" class="JokeTile win-disposable">
    <p>Control content goes here</p>
</div>

Anything you add to the template div will become part of the control. Any data binding directives you place on it will be populated by values from the “dataSource” property of the options parameter passed to the control’s constructor (or set via data-win-options, if the control is constructed declaratively).

CSS files

Our control has no default styling at all. But presumably you’ll want to add some sort of styling to the control’s root element, so I pre-populate a place for that.

.JokeTile {
}

JS file

The JS file is where things are really interesting. There’s actually a good deal more code here than the built-in Page Control template. But I think you’ll find it useful.

(function () {
    "use strict";

    var controlTemplate = null;
    var templateInitPromise = null;

    // This will load and cache the control's template.
    // This happens the first time an instance of the control is created, or when you call JokeTile.cacheControlTemplate
    function loadControlTemplate() {
        if (!templateInitPromise) {
            var controlFragment = document.createElement("div");
            templateInitPromise = WinJS.UI.Fragments.render("/controls/JokeTile/JokeTile.html", controlFragment).then(function () {
                controlTemplate = new WinJS.Binding.Template(controlFragment.querySelector("#JokeTile-template"));
                controlTemplate._renderImpl = controlTemplate._compileTemplate({ target: "render" });
            });
        }
        return templateInitPromise;
    }

    var JokeTile = WinJS.Class.define(function (element, options) {
        var options = options || {};
        this.element = element || document.createElement("div");
        this.element.winControl = this;
        WinJS.UI.setOptions(this, options);

        this.controlInitialized = false;
        var that = this;
        this.initPromise = loadControlTemplate().then(function () {
            that.element.className = controlTemplate.element.className;
            return controlTemplate.render(options.dataSource, that.element).then(
                function (element) {
                    that._initialize();
                    that.controlInitialized = true;
                });
        });
    }
    , {
        _initialize: function () {
            // TODO: Control initialization
        },

        ensureInitialized: function () {
            return this.initPromise;
        },

        dispose: function () {
            // TODO: Control clean-up
            this._disposed = true;
        },
    },
    {
        cacheControlTemplate: function () {
            return loadControlTemplate();
        },
    });

    WinJS.Namespace.define("Controls", {
        JokeTile: JokeTile,
    });
})();

The gist of all this is that you have a new control which:

  1. Can be instantiated programmatically or declaratively (using data-win-control and WinJS.UI.processAll).
  2. Uses a compiled template, which is particularly valuable if you instantiate many instances of the same control type (i.e. for use as a list item, for example).
  3. Has only useful markup and code.
  4. Implements the dispose pattern.
  5. Allows pre-emptive loading and caching of the control’s files and compiled template.

If you see a good way to improve any of this, please post in the comments section below!

I’ll cover basic usage of the template in the next post, where we’ll implement the JokeTile for our Chuck Norris Jokes app.

Download the template

The template will become part of my own extension library for WinJS, which I’m in the process of putting together. For now, you can get just the template here:

My WinJS Control Template

 

One Comment

Trackbacks & Pingbacks

  1. Weekly Digest for Developers: Feb 16 - Feb 23, 2014 | Super Dev Resources

Comments are closed.