WinJS: Using the Windows Store Library for JavaScript
Part of the advantage of ramping up to write Windows Store apps is that you can utilize your existing knowledge of HTML, CSS and Javascript.
The Windows Library for JavaScript (referred to as WinJS) extends the support for Javascript in Windows Store apps, has a number of helper functions for common app development scenarios. In the article below, we’ll talk about how to get started with WinJS for creating custom types and namespaces, as well as handling and managing event listeners.
Defining and deriving types with WinJS.Class
In the sections below, we’ll talk about how you can use the define and derive methods in the WinJS library to quickly create your own custom types, and specify their property constructors.
WinJS.Class.define
The WinJS.Class.define function is a helper function for defining a JavaScript type. You supply a constructor function, a set of “instance” members (which are defined on the prototype of the type), and a set of “static” members (which are defined on the type itself).
The properties that are added to the prototype of an object appear on any object that is instantiated by calling its constructor function, but the properties added to the type directly belong only to the type itself.
For example, let’s say we want to define a class Robot, which has an instance property name and a static property harmsHumans. The modelName property belongs to the individual objects created with Robot(name) contstructor function, but the harmsHuman property is valid for all Robot objects.
Using WinJS.Class.define
var Robot = WinJS.Class.define( function(name) { this.name = name; }, { modelName: "" }, { harmsHumans: false }); var myRobot = new Robot("Mickey"); myRobot.modelName = "4500"; Robot.harmsHumans = false;
Pure Javascript
function Robot(name) { this.name = name; } Robot.prototype.modelName = '4500'; Robot.harmsHumans = false;
WinJS.Class.define offers helpful shortcuts for defining properties. In JavaScript you can customize the behavior of properties by using a property descriptor. WinJS.Class.define treats a given object as a property descriptor. For example, if you want to use a computed value for modelName, in JavaScript without Windows Library for JavaScript you need the following code:
Using WinJS.Class.define
var Robot = WinJS.Class.define( function (name) { this.name = name; }, { modelName: { get: function () { return this.computeModelName(); } } } );
Pure Javascript
Object.defineProperty(Robot.prototype, "modelName", { get: function () { this.computeModelName(); } });
This way, if you use WinJS to define your custom types in your Windows Store app, you can define the setters and getters of properties within the WinJS.Class.define code block.
WinJS.Class.derive
The WinJS.Class.derive is a similar helper function that you can use to derive one custom Javascript type from another. It behaves much like WinJS.Class.define, except that it uses the prototype that you provide to construct the derived type by calling Object.create.
The following code shows how to derive a SpaceRobot type from the Robot type.
var SpaceRobot = WinJS.Class.derive(Robot, function (name) { this.name = name; }, { airSupply: "" }, { saveSelf: true }); var mySpaceRobot = new SpaceRobot("Myra"); mySpaceRobot.airSupply = "oxygen"; var save = SpaceRobot.saveSelf;
Organizing your code with WinJS.Namespace
In addition to creating custom types, you might want to organize these types into your own custom namespace, so that you can reference them conveniently and consistently through your code. The WinJS.Namespace.define function allows you to create your own namespace as a way of organizing your code.
When you create a type or other element in a namespace, you reference it from outside the namespace by using its qualified name: Namespace.Type.
NOTE: You should define your elements before you define the namespace, and simply add references to these elements inside the namespace definition. If you define your elements inside the namespace definition, you may try to use the this keyword to erroneously refer to other members of the namespace.
In general, the best way to define namespace members is to do so inside a module closure, defining the namespace elements first and then adding them to the namespace definition. The members must be defined before they are added to the namespace definition.
Let’s look at an example below:
function () { var Robot = WinJS.Class.define(function (name) { this.name = name; }, { modelName: "" }, { harmsHumans: false, obeysOrders: true } ); WinJS.Namespace.define("Robotics", { Robot: Robot }); }
As you can see, you should define your elements before you define the namespace, and simply add references to these elements in the namespace definition.
Later, to create a new object of the type Robot, you call the members of the Robotics namespace as follows
var myRobot = new Robotics.Robot("Sam"); var harm = Robotics.Robot.harmsHumans;
You should not to refer to the namespace before it is defined. If you do so, as in the code below, you get the error “namespace name is undefined.”
//Example of namespace defined in the wrong order //first user defines type Robot var Robot = WinJS.Class.define(function (name) { this.name = name; }, { modelName: "" }, { harmsHumans: false, obeysOrders: true } ); //The user calls constructor function in Robotics namespace, without having first defined the namespace var myRobot = new Robotics.Robot("mike"); WinJS.Namespace.define("Robotics", { Robot: Robot }); //Bad robot!
You can also add properties and functions to a namespace multiple times if you need to, i.e. after you’ve already defined the namespace. For example, you might want to define a function in a different file from the one in which the rest of the namespace elements are defined to keep your code organized. You may even want to write that function once and then add it to several namespaces (in the separate file).
In the following code, WinJS.Namespace.define is called twice to add the getAllRobots function and the findRobot function to the Robotics namespace.
var Robot = WinJS.Class.define( function(name) { this.name = name; }); function getAllRobots() { return allRobots; } var allRobots = [ new Robot("mike"), new Robot("ellen") ]; WinJS.Namespace.define("Robotics", { Robot: Robot, getAllRobots: getAllRobots }); function findRobot(robotName) { for (var i in allRobots) { if (allRobots[i].name == robotName) { return allRobots[i]; } } } WinJS.Namespace.define("Robotics", { findRobot: findRobot });
Nesting Namespaces
You can create nested namespaces by defining individual namespaces with dot notation, for example “Robotics.Search”. (However, it’s not a good idea to use a lot of nesting in your code, because nesting can make the code very complex.)
Here’s an example of a nested namespace:
var Robot = WinJS.Class.define( function(name) { this.name = name; }); function findRobot(robotName) { for (var i in allRobots) { if (allRobots[i].name == robotName) { return allRobots[i]; } } } var allRobots = [ new Robot("mike"), new Robot("ellen") ]; function getAllRobots() { return allRobots; } WinJS.Namespace.define("Robotics", { Robot: Robot }); WinJS.Namespace.define("Robotics.Search", { findRobot: findRobot }); var robot = Robotics.Search.findRobot("mike");
The namespace examples above show how you can organize your types and their related functions in different ways using WinJS.Namespace.define
Adding functionality with WinJS mixins
Mixins are objects that implement a certain class of functionality. For example, in the Windows Library for JavaScript there are mixins that manage events and mixins that handle DOM binding. Mixins aren’t designed to be used directly, but you can use them to add functionality to your types. Mixins contain implemented functions that can be added to many types.
Think of a mixin as an abstract base class, which is not intended for direct consumption i.e. instantiation, without subclassing. Additionally, a class or object may “inherit” most or all of its functionality from one or more mixins, therefore mixins can be thought of as a mechanism of multiple inheritance.
The examples below show how to extend our Robot class by creating our own mixins. This code shows how to define a Movable mixin that contains functions goForward, goBackward, turnRight, and turnLeft and use WinJS.Class.mix to add it to the Robot class. (NOTE: You need to add a DIV element with an ID of “div” to make this example work.)
WinJS.Namespace.define("Robotics", { Robot: WinJS.Class.define( function(name) { this.name = name; }, { name: name }, { harmsHumans: false, obeysOrders: true }) }); var Movable = { goForward: function () { document.getElementById("div").innerText = "go forward"; }, goBack: function () { document.getElementById("div").innerText = "go back"; }, turnRight: function () { document.getElementById("div").innerText = "turn right"; }, turnLeft: function () { document.getElementById("div").innerText = "turn left"; }; //here is Where we extend our Robot type be adding the functionality from Movable. WinJS.Class.mix(Robotics.Robot, Movable); var myRobot = new Robotics.Robot("Mickey"); myRobot.goBack();
The example above shows how you can add custom functionality to your classes from mixins that you have defined. The Windows Library for Javascript has several out-of-box mixins which can help you with event management as well as binding to your objects.
You can use WinJS.Utilities.eventMixin and WinJS.UI.DOMEventMixin to add event management functionality, and WinJS.Binding.dynamicObservableMixin to add binding management functionality.
Adding WinJS.Utilities.eventMixin to a type
You can add WinJS.Utilities.eventMixin to any type you define. It contains WinJS.Utilities.eventMixin.addEventListener, WinJS.Utilities.eventMixin.removeEventListener, and WinJS.Utilities.eventMixin.dispatchEvent functions that help you raise and handle custom events that you define on the type.
For example, the following code shows how to define a type Robot with a rename event that is raised whenever the name of the Robot is changed. In this code, you will first mix the Robot type with WinJS.Utilities.eventMixin to get the event listener functionality, and then mix it with the WinJS.Utilities.createEventProperties function to define the event. When you define the name property, you use the WinJS.Utilities.eventMixin.dispatchEvent function in the setter to raise the rename event.
Also, assume that your app has a DIV element with an ID of “report”.
var Robot = WinJS.Class.define(function (thisName) { this.name = thisName; }, { _nameValue : "", name: { get: function () { return _nameValue; }, set: function (val) { _nameValue = val; this.dispatchEvent("rename"); } } }); WinJS.Class.mix(Robot, WinJS.Utilities.eventMixin); WinJS.Class.mix(Robot, WinJS.Utilities.createEventProperties("rename")); var rob = new Robot("bob"); // Add an event listener that writes to the DIV. rob.addEventListener("rename", function (ev) { document.getElementById("report").innerText = "got renamed"; }); // Rename the object. rob.name = "bill";
When you run this code, you should see that the innerText value of the DIV is “got renamed”.
NOTE: You must mix the WinJS.Utilities.eventMixin with the type before you use WinJS.Utilities.createEventProperties.
Adding DOMEventMixin to a type
WinJS.Utilities.eventMixin is useful for defining events on types that are dealt with completely in JavaScript. It isn’t useful when you’re working with events that are associated with DOM elements. Windows Library for JavaScript controls wrap and manipulate HTML elements. These controls often contain events that are based on the DOM events raised from their associated DOM elements. For example, when you use a DatePicker, you find it easier to respond to a change event from the DatePicker control rather than to track individual change events from the three select controls.
Associating events with the control type rather than the DOM element also protects the code from changes, for example if the implementation of the DatePicker changes the controls it contains.
The DOM event model allows for event propagation. DOM events are propagated up the DOM tree and can be handled at any level. This is useful when you have a lot of clickable elements. For example, you
can put a single click handler function on the DIV element that contains the elements and then process all the click events from all the contained elements as they bubble up. For more information about DOM event handling, see Event capture and event bubbling. WinJS.UI.DOMEventMixin contains methods that implement events that propagate up through the DOM tree. It has three functions: addEventListener, removeEventListener, and dispatchEvent.
To add this mixin to a type you have created, you must set a property named _domElement on the type. This is the field that the WinJS.UI.DOMEventMixin implementation looks for when it raises the event. It becomes the target node for the event.
The following code shows how to create a minimal control class on a DIV element and mix WinJS.UI.DOMEventMixin. You can then add an event listener for the click event and respond to it.
For this code to work, you need one DIV that has an ID of “div” and a second DIV that has an ID of “report”.
var MyStar = WinJS.Class.define(function (elem) { this._domElement = elem; elem.winControl = this; this._numberClicks = 0; }, { initialize: function () { WinJS.Utilities.addClass(this._domElement, "win-rating"); this._domElement.innerHTML = " "; var that = this; function clickHandler (ev) { var clicks= ++that._numberClicks; document.getElementById("report").innerText = clicks; } this.addEventListener("click", clickHandler); } }); WinJS.Class.mix(MyStar, WinJS.UI.DOMEventMixin); var star = new MyStar(document.getElementById("div")); star.initialize();
Adding binding functionality with WinJS.Binding.mixin
To bind a user-defined object to an HTML element, you must make it observable, that is, capable of notifying listeners when its properties change. The WinJS.Binding.mixin provides this functionality. For an example, see How to bind a complex object.
Event capture and event bubbling with DOM events
In Windows Library for JavaScript there are several addEventListener methods (for example, WinJS.Application.addEventListener and WinJS.UI.AppBar.addEventListener). They all contain a useCapture parameter, which is set to true if you want event capture or false if you want event bubbling. For more information, see see Event capture and Event bubbling.
In the addEventListener method, if you set the useCapture parameter to true, the order in which events are handled is top-down. That is, if there are event handlers for the specified event that belong to more than one element in the DOM tree, the handler belonging to the highest element in the tree is called first, and any handlers belonging to child elements are called after that. If you set the useCapture parameter to false, the handler that belongs to the target element is called first, and any handlers that belong to parent elements are called after that. In this example, you create a WinJS.UI.DatePicker and add the same change handler function to the WinJS.UI.DatePicker and the document object. If you set the useCapture parameter to true, the handler on the document object is called first, then the handler on the WinJS.UI.DatePicker object.
In an event, the this keyword refers to the target of the event, so you should see “change event for [object Document]” and then “change event for [object HTMLDivElement]”. If you set the useCapture parameter to false, you should see the output for the DatePicker handler first, then the output for the document handler.
... <div id="div"></div> <div id="report"></div> ... var dp = new WinJS.UI.DatePicker(document.getElementById("div")); dp.addEventListener("change", handleChange, true); document.addEventListener("change", handleChange, true); function handleChange(ev) { var elem = document.getElementById("report"); elem.innerHTML = elem.innerHTML + "<br> change event for " + this; }
In the article above, we’ve gone over some of the basics of WinJS (the Windows Library for Javascript), and talked about using WinJS helper methods to create custom types and namespaces, as well as to utilize out-of-box and custom mixins to add functionality to your objects.
This tutorial is brought to you by the team at MSDN. To learn more about coding for Windows Store apps, please visit http://dev.windows.com