Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / HTML

How to Write a WinJS Custom Control

5.00/5 (3 votes)
3 Oct 2012CPOL4 min read 27.7K   205  
The article will teach you how to write a simple WinJS custom control.

Introduction

Lately, I was involved in a few Windows 8 projects. One of the questions that I am being asked from time to time is how to write your own Windows Store app custom control with the WinJS library. This article will try to give you a head start for your WinJS custom control library. During the article you will build an autocomplete control and learn a few WinJS concepts on the way. So let's get started.

The WinJS.Namespace object

The first thing to do when you write your own controls is to put them inside their own namespace. Namespaces help to group a set of identifiers into a logical group. Those identifiers can be classes, functions, or any other development language structures that you wish to group. For more details about how to create JavaScript namespaces, go to the following link.

WinJS includes a Namespace object that can help you define namespaces. You will use the WinJS.Namespace.define function which receives two parameters: the namespace name and an object populated with the namespace’s identifiers. The following example defines a new namespace called MyApp.UI:

JavaScript
WinJS.Namespace.define("MyApp.UI", {
   // identifiers
});

The WinJS.Namespace object also includes a defineWithParent function to define namespace hierarchies but in this example I don’t use it.

Defining the control class

So now we have a namespace, what's next? Creating a control class.

Since JavaScript doesn’t include a class concept you can mimic that behavior using constructor functions and prototypical inheritance. Luckily, WinJS includes a Class object that exposes functions to define, derive, or mix JavaScript “classes”.

In order to define a class, you will use the WinJS.Class.define function. The function receives three parameters: a constructor function to initialize the objects, an instance member object to add to every created instance, and a static member object which will be added to the prototype of the class. Let's define the Autocomplete class inside the previously created namespace:

JavaScript
WinJS.Namespace.define("MyApp.UI", {
        Autocomplete: WinJS.Class.define(function (element, options) {
            // constructor function body
        }, 
        { // instance members },
        { // static members });
});

As you can see, the define function gets all the three parameters. In the constructor function you will get two parameters which are the elements for the control and an options object to configure the control. The two parameters are mandatory for building a WinJS control since they are mapped to the data-win-control and data-win-options HTML attributes of the element when the processAll function is called.

Adding control functionality

Now that we have our template for the control class, let's create its functionality. Before we write any code, let's understand what we expect from the control. The control needs to create on the fly a datalist element and attach it to an input type (if you want to read about the new HTML5 datalist element, you can go to the following link). The control also needs to get as input an option list that will be shown as the autocomplete list. So let's write the code:

JavaScript
(function (WinJS) {
    WinJS.Namespace.define("MyApp.UI", {
        Autocomplete: WinJS.Class.define(function (element, options) {              
            if (!element || element.tagName.toLowerCase() !== "input")
                    throw "input type must be provided";
            options = options || {};
            this._setElement(element);
            this._setOptionList(options.optionList);
            this._element.winControl = this;
            WinJS.UI.setOptions(this, options);
            this._createDataList();            
        },
            {
                //Private members
                _element: null,
                _optionList: null,
                _setElement: function (element) {
                    this._element = element;
                },
                _setOptionList: function (optionList) {
                    optionList = optionList || [];
                    this._optionList = optionList;
                },
                _createDataList: function () {
                    var i = 0,
                        len = this._optionList.length,
                        dl = document.createElement('datalist');
                    dl.id = 'dl' + this._element.id;
                    this._element.setAttribute("list", dl.id);
                    for (; i < len; i += 1) {
                        var option = document.createElement('option');
                        option.value = this._optionList[i];
                        dl.appendChild(option);
                    }
                    document.body.appendChild(dl);
                },

                //Public members
                element: {
                    get: function () {
                        return this._element;
                    }
                }
            })
    });
}(WinJS));

As you can see, the constructor function first checks whether the element is an input type. It also sets the element and the option list which is supposed to be supplied in the option object. The last thing it does is to create the datalist element. In the instance member object you divide the functions and properties to private and public member sections. This division is only logical (and marked with comments) and as you can see, private members get the underscore character before their name. The main functionality here is the _createDataList function which handles the creation of the datalist and sets the input type list attribute to the list ID. All the other functions are straightforward.

Using the control

Now that we have the control, let's use it. In the example app, I added a data.js file under the js folder and wrote the following code:

JavaScript
(function () {
    "use strict";

    WinJS.Namespace.define("Data", {
        cities: ["Seattle", "Las Vegas", "New York", "Salt lake City"]
    });
})();

I could get any data from any other data source (cloud, service, storage) but I wanted the example to be as simple as possible.

In the home.html file, located in the pages/home folder, I added a text input type which is configured using the data-win-control and data-win-options attributes:

HTML
<input type="text" name="txtCities" id="txtCities" 
   data-win-control="WinJS.UI.Autocomplete" 
   data-win-options="{ optionList: Data.cities }"/>

Don’t forget to add the script tags for the autocomplete.js file and data.js file or else the example won't work.

Now everything is set and you can run the app and see the result:

Image 1

Summary

WinJS comes with a lot of built-in controls such as the FlipView, ListView, and ToggleSwitch. On the other hand, sometimes it is necessary to build your own custom controls to add your own functionality. In this article you learned the basics of creating your own WinJS custom control. You started with a namespace and finished with an autocomplete control. By using the same technique, you will be able to create your own controls and incorporate them in your Windows Store apps.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)