Controls are the basic blocks of any application, you use controls everywhere and also HTML Metro apps are part of the game, you add a control inside a HTML Metro page just placing a <div> with a special “data-win-control” attribute in it:
here’s an example:
<div data-win-control="WinJS.UI.SemanticZoom" >
</div>
if you have pages (even in different applications) that need some special kind of functionality paired with some custom UI, custom controls allows you to reach the “write once, use everywhere” paradigm (unless you like reinventing the wheel of course…)
Let’s see how you can create your own custom control and use it inside a HTML Metro application, simulating a simple custom countdown control.
Step1: Design the user interface
While not strictly a requirement I personally like to start from the appeareance of my control, so I fire up Expression Blend and start design it:
image-1
I’ve highlighted the HTML representing my control’s UI and, on right side, associated CSS,
question now is: how do we turn it in a WinJS custom control?
Step2: Define public interface
The countdown control will expose:
-
initialValue property: will be initialized with countdown initial value
-
start method: begins countdown
-
countdownexpired and coundownstarted events
Step3: The code sss
Below is the control’s code contained in a separate countdown.js file:
//Countdown control
(function (winJs) {
var _events = ["countdownexpired", "coundownstarted"];
WinJs.Namespace.define("MyApp.Controls", {
Countdown: WinJs.Class.define(function (element, options) {
if (!element) throw "element must be provided";
options = options || {};
this._element = element;
this._element.winControl = this;
this._buildVisualTree();
winJs.UI.setOptions(this, options);
},
{
//Private members
_element: null,
_cdn_host: null,
_cdn_content: null,
_timerId: 0,
_progress: 0,
_buildVisualTree: function () {
this._cdn_host = document.createElement("div");
this._cdn_host.className = "cdn-host";
this._cdn_content = document.createElement("span");
this._cdn_content.className = "cdn-content win-type-xx-large";
this._cdn_host.appendChild(this._cdn_content);
this._element.appendChild(this._cdn_host);
},
_onTick: function () {
this._progress--;
this._cdn_content.innerText = this._progress;
if (this._progress === 0) {
this.dispatchEvent("countdownexpired");
clearInterval(this._timerId);
this._timerId = 0;
}
},
//Public members
element: {
get: function () { return this._element; }
},
initialValue: {
get: function () { return this._cdn_content.innerText; },
set: function (value) {
this._cdn_content.innerText = value;
}
},
//Play advertising
start: function () {
if (this._timerId === 0) {
this._progress = this.initialValue;
this._timerId = setInterval(this._onTick.bind(this), 1000);
this.dispatchEvent("coundownstarted");
}
}
})
});
winJs.Class.mix(MyApp.Controls.Countdown, winJs.Utilities.eventMixin);
winJs.Class.mix(MyApp.Controls.Countdown, winJs.Utilities.createEventProperties(_events));
}(WinJS))
Let’s dissect it:
A WinJS custom control is just a class that has a constructor accepting two parameters (if ‘class’ sounds weird to you see my previous post)
Our control will be contained inside a MyApp.Controls namespace.
Inside constructor I associate passed element with control's element property and I also add associate control’s instance with a conventionally named winControl property appended to passed element so that it can easily accessible from page’s javascript code.
The buildVisualTree function uses javascript to recreate control’s DOM and appends it to passed element while setOptions is a WinJS helper method that associates passed options to control so that it can be correctly initialized.
Since the control exposes events we finally use a couple of WinJS helper methods to ‘mix’ control’s class with a class defined inside WinJS framework to add all stuff required to handle and raise events (like onXYZ methods or addEventListener/removeEventListener support)
The rest of the code is just countdown code implementation, nothing really new here.
Step4: Using the control
To embed the code the control inside a HTML page we use exactly the same approach used for any WinJS control, add both required javasript and css references and decorate placeholder div with appropriate attributes, here’s the complete HTML page content:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>DemoCustomControl</title>
<!-- WinJS references -->
<link href="//Microsoft.WinJS.1.0.RC/css/ui-dark.css" rel="stylesheet" />
<script src="//Microsoft.WinJS.1.0.RC/js/base.js"></script>
<script src="//Microsoft.WinJS.1.0.RC/js/ui.js"></script>
<!-- DemoCustomControl references -->
<link href="/css/default.css" rel="stylesheet" />
<link href="/countdown.css" rel="stylesheet" type="text/css">
<script type="text/javascript" src="js/controls/countdown.js"></script>
<script src="/js/default.js"></script>
<script>
WinJS.UI.processAll();
WinJS.Utilities.ready(function () {
var countdown = document.getElementById("countdown1");
countdown.winControl.addEventListener("countdownexpired", function () {
var msg = document.getElementById("result");
msg.textContent = "done!";
}, false);
document.getElementById("start").addEventListener("click", function () {
countdown.winControl.start();
}, false);
}, true).done();
</script>
</head>
<body>
<div id="countdown1" data-win-control="MyApp.Controls.Countdown" data-win-options="{initialValue:4}">
</div>
<button id="start">Start</button>
<div id="result"></div>
</body>
</html>
A couple of notes:
-
we invoke WinJS.UI.processAll() method to force ‘transformation’ of ‘countdown1’ div into countdown control.
-
we use WinJS.Utilities.ready() function to be sure that DOM tree is available when code runs (something that JQuery users know quite well).
-
note how countdown control is initialized using data-win-options attribute to 4 seconds.
Pressing start you will now see countdown starting and a message appearing at the end:
I found little documentation about creating custom control, that’s the reason of this post, if you want to know more I recommend this Build session: http://channel9.msdn.com/Events/BUILD/BUILD2011/APP-846T