Lately, I was involved in a few Windows 8 projects. One of the questions that I am being asked from time to time was how to write your own Windows Store app custom control with WinJS library. This post will try to give you a head start for your custom control library. During the post you will build an autocomplete control step by step and learn a few WinJS concepts on the way. So let 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 inside the namespace. For more details about 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:
WinJS.Namespace.define("MyApp.UI", {
// identifier
});
WinJS.Namespace object also include 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 gets three parameters: a constructor function to initialize objects, an instance members object to add to every created instance and a static members object which will be added to the prototype of the class. Lets define the Autocomplete class inside the previously created namespace:
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 element 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, lets create its functionality. Before we write any code, lets understand what we expect from the control. The control needs to create a datalist element on the fly 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 lets write the code:
(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 a private and public member section. This division is only logical (and marked with comments) and as you can see private members gets the underscore character before their name. The main functionality here is the _createDataList function which handle the creation of the datalist and sets the input type list attribute to the list id. All the other functions are straight forward.
Using the Control
Now that we have the control, lets use it. In the example app I added a data.js file under the js folder and wrote the following code inside of it:
(function () {
"use strict";
WinJS.Namespace.define("Data", {
cities: ["Seattle", "Las Vegas", "New York", "Salt lake City"]
});
})();
I could get any data from any other source (cloud, service, storage) but I wanted the example to be simple.
In the home.html file located in pages/home folder I added a text input type which is configured using the data-win-control and data-win-options attributes:
<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 in order for the example to work.
Now everything is set and you can run the app and see the result:
The full example can be downloaded from here.
Summary
WinJS comes with a lot of built-in controls such as the FlipView, ListView ToggleSwitch and many more. On the other hand, sometimes it is necessary to build your own custom controls. In this post you learned the basics of creating your own WinJS custom control.
Add comment
Originally published at blogs.microsoft.co.il on September 24, 2012.