Organizing your JavaScript with WinJS

While the JavaScript framework for creating metro style apps is surprisingly flexible, in that it allows you to use pretty much any JavaScript framework that's already out there, there is actually some really useful functionality available in the WinJS library that ships out of the box. And since the JavaScript projection for WinRT itself uses this library it might make sense to use this for our own code as well. In this post I cover some basic ways you can organize your JavaScript code using WinJS.

Namespaces

One common challenge that we tend to encounter with any JavaScript app (whether web or desktop), is the question of being disciplined about introducing symbols into the global namespace. The general guideline is to, well, not do it! Using WinJS it is possible to organize your own classes and functions into well-defined namespaces. Here's an example:

(function () {
    'use strict';

    // export this "enum"
    var ShapeType = Object.freeze({
        None: 0,
        Line: 1,
        Ellipse: 2,
        Rectangle: 3,
        Arc: 4,
        Bezier: 5
    });

    WinJS.Namespace.define("Acme.Shapes", {
        ShapeType: ShapeType
    });

})();

What this does is to basically create only one new symbol in the global namespace called "Acme". All the remaining objects are child properties of this root object - essentially acting like a namespace. The above given code snippet for instance will enable you to access the ShapeType enumeration from pretty much everywhere else like so:

var type = Acme.Shapes.ShapeType.Arc;

You can see how easy it is to extend this to define pretty much your entire project - grouping all your code under a single root namespace. We'll see further examples of how namespaces can be used as we talk about other capabilities that WinJS brings to the table.

Classes

While JavaScript itself is a weakly typed language it is possible to pretend like it isn't and create and use "classes". With WinJS you define a class by calling WinJS.Class.define. This method accepts 3 parameters: a constructor function, a JavaScript object defining the instance members and another JavaScript object defining the static members. Here's an example:

(function () {
    "use strict";

    // constructor function
    function initShape(type) {
        this.id = Acme.Shapes.Shape.newId(); // calling a static member
        if (type) {
            this.type = type;
        }
    }

    var instanceMembers = {
        id: null,
        type: Acme.Shapes.ShapeType.None,
        draw: function () {
            console.log("Shape.draw");
        }
    };

    var staticMembers = {
        // private member
        _counter: 0,
        newId: function () {
            return Acme.Shapes.Shape._counter++;
        }
    };

    var Shape = WinJS.Class.define(initShape, instanceMembers, staticMembers);

    // add to namespace
    WinJS.Namespace.define("Acme.Shapes", {
        Shape: Shape
    });
})();

Now you can create and use instances of Shape like so:

var s = new Acme.Shapes.Shape(); 
s.draw();

The first parameter to WinJS.Class.define is a constructor function. This gets invoked whenever a new instance is created and is the place where you write your object initialization code. The second parameter is an object that describes the runtime shape of the object, i.e. what instance members it will contain. The third parameter defines the set of static members that you can access directly from the class without creating an instance. In the snippet above you'll note that we invoke the static method newId from Shape's constructor.

Finally, you can have static and instance non-enumerable members by prefixing the name with an underscore character. This has the effect of the member not being available when you reflect on the object and try to access its members. In the example above the static member _counteris not intended to be accessed directly by client code. We express this by prefixing an underscore character and WinJS makes sure that it sets the enumerable property descriptor to false when it creates the property. When we call Object.keys for instance, on the Shape constructor, here's what we get:

console.log(Object.keys(Acme.Shapes.Shape));
// prints "newId"

As you can see it does not indicate _counter as being a member of Shape. Having said that if you do go ahead and access _counter anyway, it'll still work! If you want a member to be really inaccessible then you might want to use closures.

Inheriting from classes

You can inherit one class from another by calling WinJS.Class.derive. Here's an example:

(function () {
    'use strict';

    // constructor function
    function initLine() {
        // call the base constructor
        Acme.Shapes.Shape.call(this, Acme.Shapes.ShapeType.Line);
    }

    var instanceMembers = {
        // overriding Shape.draw
        draw: function () {
            // call base
            Acme.Shapes.Shape.prototype.draw.call(this);
            console.log("Line.draw");
        }
    };

    // inherit from Shape
    var Line = WinJS.Class.derive(Acme.Shapes.Shape, initLine, instanceMembers);

    WinJS.Namespace.define("Acme.Shapes", {
        Line: Line
    });
})();

The first parameter is a reference to the base class, followed by the constructor function and the instance members. Just like class definition, you can also pass static members if need be as the fourth argument. Note that when you inherit from another class, you do not inherit the static members. In the example above you cannot call newId via Line. You'll also note that we call base class methods via Function.call instead of directly invoking them, i.e. we do not, for instance, do the following from Line.draw.

Acme.Shapes.Shape.prototype.draw(); // DO NOT do this

This won't work because in this case the "this" pointer inside Shape.draw will point to the Shape's prototype and not to the Line instance on which Line.draw was called. We will therefore need to explicitly specify the context for the call via Function.call.

Mixing functionality in

Let's say you find this awesome piece of code somewhere that you think would be a great fit for one of the classes you are defining and it'd simply be too much trouble to manually copy and paste all of that code into your class definition (besides being error prone and quite simply a bad idea). In this case you can choose to "mix-in" that functionality into your existing class by calling WinJS.Class.mix. Here's an example:

// mix-in some functionality from somewhere; pretend
// that we got this by including a JS or something
var SuperDrawModule = {
    superDraw: function() {
        console.log("SuperDrawModule.superDraw");
    }
};

Line = WinJS.Class.mix(Line, SuperDrawModule);

Now the method superDraw becomes a part of Line which means I can do the following now:

var l = new Acme.Shapes.Line();
l.draw();

// call mixin method
l.superDraw();

Adding eventing capabilities

WinJS ships with a useful little mixin object that automatically adds the ability to call addEventListener, removeEventListener and dispatchEvent on your own objects. The mixin in question is WinJS.Utilities.eventMixin. Here's how you can use it:

(function () {
    "use strict";

    // constructor function
    function initShape(type) {
        this.id = Acme.Shapes.Shape.newId(); // calling a static member
        if (type) {
            this.type = type;
        }
    }

    var instanceMembers = {
        id: null,
        type: Acme.Shapes.ShapeType.None,
        draw: function () {
            // fire event "draw" via "dispatchEvent"
            this.dispatchEvent("draw", { source: this });
            console.log("Shape.draw");
        }
    };

    var staticMembers = {
        // private member
        _counter: 0,
        newId: function () {
            return Acme.Shapes.Shape._counter++;
        }
    };

    var Shape = WinJS.Class.define(initShape, instanceMembers, staticMembers);

    // add eventing capability to Shape
    Shape = WinJS.Class.mix(Shape, WinJS.Utilities.eventMixin);

    // add to namespace
    WinJS.Namespace.define("Acme.Shapes", {
        Shape: Shape
    });
})();

Take note of the lines in bold. We add the event mixin to Shape like any other mixin and call eventMixin.dispatchEvent to actually fire the event. Client code that handles the event would look like any other event handling code that you might have written to handle DOM events.

var l = new Acme.Shapes.Line();
l.addEventListener("draw", function () {
    console.log("Line was drawn.");
});
l.draw();

Note that stuff that you add to a base class via a mixin does in fact get inherited in sub-classes. In the sample snippet above, though the event mixin was added to Shape I am able to call addEventListener on the sub-class Line.

Turning your events into properties

WinJS.Utilities.eventMixin allowed us to make our classes act like DOM objects in supporting the registering of event handlers and in dispatching events. We can take things to the next level and add properties to our classes and add an alternative mechanism for registering event handlers. Our Shape class for instance, supports the raising of the "draw" event. Wouldn't it be nice if it supported a property called ondraw to which we can assign a callback function and have it invoked whenever the event is raised? Turns out that is exactly the sort of thing that WinJS.Utilities.createEventProperties enables. Here's an example:

// add "onevent" properties for all events we support
var eventPropMixin = WinJS.Utilities.createEventProperties("initialized", "draw", "disposed");
Shape = WinJS.Class.mix(Shape, eventPropMixin);

WinJS.Utilities.createEventProperties basically creates a mixin object that contains properties which have the same name as the strings passed to it as parameters except for the prefixing of the word "on" to them. This function can accept a variable number of parameters - one for each event that your class supports. With this in place, now you can write code such as the following:

var l = new Acme.Shapes.Line();
l.ondraw = function () {
    console.log("Line was drawn.");
};
l.draw();

One benefit of using WinJS is that you automatically get intellisense support in VisualStudio for stuff you add to your classes using it. Here's a screen shot:

p1

That's pretty much all I wanted to talk about. With these few simple methods from WinJS it is quite possible to be fairly disciplined about your JavaScript code and believe me, JavaScript being JavaScript, you're going to need all the help you can get! Winking smile I have created a small project with all of the code snippets given above here.

comments powered by Disqus