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

HTML5, JavaScript, Knockout, JQuery, Guide for Recovering Silverlight/WPF/C# Addicts. Part 2 - Solar System Animation built with SVG, Knockout and MVVM Pattern.

4.98/5 (32 votes)
26 Sep 2012CPOL16 min read 82.9K   1.4K  
Creating Solar System animation in HTML5/JavaScript

Important Note

Friends, if you like the article, please vote for it. Also I'd appreciate a couple of lines about what you think can be improved and what else you want to hear. Thanks!

Introduction

This is part 2 of HTML5 series. Part 1 can be accessed at HTML5, JavaScript, Knockout, JQuery, Guide for Recovering Silverlight/WPF/C# Addicts. Part 1 - JavaScript and DOM.

Several years ago I came across Bea Stollnitz's (Costa's) example converting a usual ListView into a planetary system and was fascinated by it. You can find her article and code at The power of Styles and Templates in WPF. Even though the MVVM pattern, had not been formalized at that time, she was adhering to a strict separation between the view model and the view's templates and styles.

I decided to build a similar application in HTML5/JavaScript also strictly adhering to the MVVM pattern. You can see the result at Solar System Demo. Make sure to view the demo using the HTML5 compatible browsers. If you use Chrome or Firefox you can even see that the planets are moving around the sun (IE 9 does not support SVG animations, so if you use IE 9, the planets won't move). The speed of the planet images in the demo is chosen randomly and has nothing to do with the actual planet speed. I'll talk more about the demo below.

In order to achieve the separation between visual and non-visual components I use Knockoutjs framework. Knockoutjs is an excellent open source framework giving developers ability to bind properties and events within HTML to JavaScript's entities representing the View Model. The Knockoutjs bindings are similar to those of WPF and Silverlight. From my point of view, Knockoutjs is a must for building an HTML web site that has a considerable amount of business logic.

This article is by no means a detailed tutorial on Knockoutjs, even though I'll try to present some its most important features. Perhaps, in one of the subsequent articles, I'll give more information on Knockoutjs, but in the meanwhile Knockoutjs website has great documentation and tutorials. Also there are two excellent Pluralsite courses on Knockoutjs: Building HTML5 and JavaScript Apps with MVVM and Knockout by John Papa as well as Knockout Fundamentals by Steve Michelotti. (You have to subscribe to pluralsite.com in order to be able to access the tutorials).

In order to draw the non-textual visuals, e.g. the orbits and in order do animations I am using SVG. SVG is part of HTML5 spec and it provides HTML tags to create various shapes use transforms on them and create animations. SVG is quite similar to WPF/Silverlight Shape/Path, transforms and animations functionality. Again, this article is not a tutorial on SVG, it just demonstrates some SVG concepts. Perhaps in the future articles I'll talk about SVG in more detail.

The Demo

As was stated above, the demo can be viewed at AWebPros.com/#Demos.HTML5Demos. You need a browser that supports HTML5 in order to view the demo and in order to see the planets rotating around the Sun, you should not use IE9 (since it does not support the SVG animations). In particular I tested it with Chrome and Firefox. Here is the snapshot of the demo:
Image 1

Toggle Animation button at the top of the page allows the user to stop and restart the animation. Clicking on any Solar System body will select it: it will get a yellow circle around it and its description will appear at the top of the screen to the right of the Toggle Animation button. The image above contains Earth as the selected Solar System body: it is surrounded by a yellow circle and at the top of the screen, you can see its description: "Earth: Earth, our home planet, is the only planet in our solar system known to harbor life".

Let us look a brief look at the code (we'll have a much closer look at the code later once Knockoutjs functionality is explained using simpler examples). The code for the demo is located under SolarSystem project.

First, take a look at the Scripts folder. You can see that we have JQuery and Knockout libraries installed. How to install a JavaScript library as a NuGet package was described in Part 1 of the series: HTML5, JavaScript, Knockout, JQuery, Guide for Recovering Silverlight/WPF/C# Addicts. Part 1 - JavaScript and DOM. This project only uses JQuery in order to detect when the document is loaded, Knockoutjs is, however, used extensively to bind the HTML View to the View Model.

There are 3 custom JavaScript files:

  • SolarSystem.js contains the View Model for the demo. The View Model consists of an array solarSystemObjects. This array contains objects corresponding carrying information about individual Solar System bodies. The View Model also has couple of public functions: toggleAnimation() and setSelected() (there are more functions within the VM but they are not used outside of it). I copied all of the Solar System body parameters and descriptions for Bea's WPF project and just added random speed for the animations (please note that the planet speed within the animation has nothing to do with real planet speed).
  • AnimationControlBinding.js and XlinkHrefCustomBinding.js contain some custom Knockoutjs binding which we are going to discuss later.

SolarSystem.html file under HTML folder is the main HTML file containing the HTML/SVG markup code, references to the required scripts and a small amount of JavaScript whose purpose is to detect if the browser supports SVG and SVG animations and to bind the HTML View to the View Model.

Finally, there is a bunch of planet images under Images folder (also copied from Bea's WPF demo).

Pay attention to how small the files are: SolarSystem.htm file (heavily documented and containing thorough browser compatibility checks) takes less than 110 lines; the View Model file SolarSystem.js also containing detailed comments takes less than 70 lines. Both custom binding files are tiny and the application does not contain any style sheets. Also note the separation between the visual and non-visual parts and the overall clarity of the code. This code brevity and clarity would not be possible without Knockoutjs framework.

Brief Overview of Knockoutjs Framework for WPF and Silverlight Developers

In order to understand the code for the demo, let us first discuss the Knockoutjs binding framework and provide some simple samples. For those interested in more in-depth tutorial, I highly recommend the Knockoutjs website and Pluralsite courses referenced above.

Simple Knockout Binding Sample

You can find the code for a simple Knockoutjs example under SimpleKnockoutSample project. If you run the sample, you will see the following screen:
Image 2

If you try to change the planet name or details, you'll be able to observe the full description in blue color also changes right as you click the keys. There are two little custom files that I created for this sample:

  • SolarSystemBodyVM.js under Scripts folder contains the View Model.
  • SolarSystemBody.html under HTML folder contains the View for the demo.

Here is the content of SolarSystemBodyVM.js file:

/// <reference path="knockout-2.1.0.debug.js" />

// setting up the namespace
var solarSystemBodyNmspace = solarSystemBodyNmspace || {};

// set the view models to an empty object
solarSystemBodyNmspace.solarSystemBodyVM = {};

// let us have the solar system object contain only name and details  
// as well as the computed fullDescription field, for the sake of simplicity
solarSystemBodyNmspace.solarSystemBodyVM = {
    name : ko.observable("Earth"),
    details: ko.observable("the planet we live on") 
};

// computed observables should be defined outside
// of JSON object definition in order for 
// 'this' reference to be correct.
// first argument to the ko.computed function is the function 
// for calculating the computed observable property.
// Second argument passes an object that becomes "this" pointer inside the function.
// This is needed, since the "this" pointer in JavaScript is much more fluid than
// "this" pointer or reference in C++ or C#/Java.
solarSystemBodyNmspace.solarSystemBodyVM.fullDescription = ko.computed(function () {
    return this.name() + ": " + this.details();
}, solarSystemBodyNmspace.solarSystemBodyVM);

It defines two observable simple "properties": name and details and one computed observable "property": fullDescription within solarSystemBodyVM View Model. I put the word "property" in quotation marks because, in fact, they are not properties, but functions that return a value when no arguments are passed to them and set the value when an argument is passed. The reason Knockoutjs uses functions instead of properties is because older browsers do not support JavaScript properties. The value passed to the function within the constructor is the default value of the observable property. (Once I explained that observable properties are actually functions, I'll drop the quotation mark around the work property). The reason we need to use observable properties, is that we want Knockoutjs to be able to detect when the property changes and modify the corresponding HTML text or value and vice versa - we want the VM properties to update when HTML property is updated. The observable properties are, thus, equivalent to the WPF properties that fire PropertyChanged event when updated and their binding to HTML attributes is similar to the two-way WPF binding. If we want the HTML only to receive the original values, and do not care about the VM staying in sync with the View, we do not need to create the observable properties - we can simply use JavaScript fields within the VM. This will map into WPF one time binding or binding to simple properties that do not fire PropertyChanged event.

In the View Model code shown above, the fullDescription observable property is computed from name and details observable properties:

solarSystemBodyNmspace.solarSystemBodyVM.fullDescription = ko.computed(function () {
  return this.name() + ": " + this.details();
}, solarSystemBodyNmspace.solarSystemBodyVM);

In WPF, a computed property is a property that usually has only a getter and whose PropertyChanged event fires within the properties that it depends on. Here is a WPF analogue of solarySystemBodyVM containing a computed property FullDescription that depends on Name and Detail properties:

public class SolarSystemBodyVM : INotifyPropertyChanged
{
    #region INotifyPropertyChanged Members

    public event PropertyChangedEventHandler PropertyChanged;

    #endregion


    protected void OnPropertyChanged(string propertyName)
    {
        if (this.PropertyChanged != null)
        {
            this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }


    #region Name Property
    private string _name;
    public string Name
    {
        get
        {
            return this._name;
        }
        set
        {
            if (this._name == value)
            {
                return;
            }

            this._name = value;
            this.OnPropertyChanged("Name");

            // we need to notify that FullDescription 
            // property possibly changed.
            this.OnPropertyChanged("FullDescription");
        }
    }
    #endregion Name Property


    #region Details Property
    private string _details;
    public string Details
    {
        get
        {
            return this._details;
        }
        set
        {
            if (this._details == value)
            {
                return;
            }

            this._details = value;
            this.OnPropertyChanged("Details");

            // we need to notify that FullDescription 
            // property possibly changed.
            this.OnPropertyChanged("FullDescription");
        }
    }
    #endregion Details Property

    // computed property FullDescription dependent 
    // on Name and Details properties.
    #region FullDescription Property
    public string FullDescription
    {
        get
        {
            return _name + ": " + _details;
        }
    }
    #endregion FullDescription Property

}
You can see that in WPF, we have to add calls to OnPropertyChanged("FullDescription"); within the properties that the computed property depends on. In Knockoutjs, however, the framework figures out itself which properties depend on which, thus, allowing not to introduce some unnecessary extra dependencies within the code. The way Knockoutjs tracks the dependencies - the first time, it calculates the computed property no matter what. During its computation, it checks which other observable properties were called and for each one of them sets a subscription so that when that property changes, the computed property is re-calculated.

Now let us turn our attention to the HTML code located within HTML/SolarSystemBody.htm file:

<body>
    <div>
        Enter Solar System Body Name: <input data-bind="value:name, valueUpdate:'afterkeydown'" />
    </div>
    <div>
        Enter Solar System Body Details: <input data-bind="value:details, valueUpdate:'afterkeydown'" />
    </div>
    <div>
        Solar System Body full Description <span style="color:Blue" data-bind="text:fullDescription"></span>
    </div>
</body>
</html>
<script src="../Scripts/jquery-1.8.1.min.js" type="text/javascript"></script>
<script src="../Scripts/knockout-2.1.0.debug.js" type="text/javascript"></script>
<script src="../Scripts/SolarSystemBodyVM.js" type="text/javascript"></script>
<script type="text/javascript">
    $(document).ready(function () {

        /* bind the whole DOM to the view model. If only part of the DOM
           needs to be bound, you can pass the DOM tag as a second parameter
           to ko.applyBindings function */
        ko.applyBindings(solarSystemBodyNmspace.solarSystemBodyVM);
    });
</scrip>

There are two text input fields for entering Name and Details for a Solar System body. They are bound to name and details observable properties on the View Model. There is also a read only text field for displaying the Full Description bound to the fullDescription computed observable property of the View Model.

The bindings between the View and the View Model are created by ko.applyBindings function called after the DOM has been loaded.

Let us take a closer look at the bindings - they are defined within data-bind attributes of the corresponding HTML tags, e.g:

data-bind="value:name, valueUpdate:'afterkeydown'"
In this binding, value is a built-in binding type. There is a built-in binding within Knockoutjs framework called value and it knows how to bind to HTML <input/> elements of type "text" and, perhaps, also to some other HTML elements. Following the colon, name is the name of the observable field from the View Model to bind to. After the comma, we can add other bindings or some attributes of the current binding. In our case, valueUpdate is an attribute of value binding. The value of valueUpdate attribute is set to 'afterkeydown', and this means that the observable property within the View Model should update every time a key is pressed on the keyboard (while, of course, the focus is within the field). WPF analog of valueUpdate:'afterkeydown' would be setting UpdateSourceTrigger=PropertyChanged within a binding for the Text property of a TextBlock. Without valueUpdate:'afterkeydown', the Knockoutjs binding will work the same as WPF Text binding without UpdateSourceTrigger=PropertyChanged, i.e. it will update only on "tab-out" or "enter", not on every key stroke.

There is a subtle, but very important difference between the Knockoutjs and WPF/Silverlight bindings. In WPF, a binding binds two properties - one on the binding source and one on the binding target, while in Knockoutjs, the binding binds an observable property on the source to the target, not to a target property. Binding type determines the changes on the target when the source property fires - for example "value" binding type will change a value of HTML <input/> elements, while "text" binding type will change the text of a <div/> or <span/> elements. One can also come up with binding types that change multiple attributes of HTML elements.

The computed property fullDescription is bound using text built-in Knockoutjs binding to a span element:

<div>
    Solar System Body full Description <span style="color:Blue" data-bind="text:fullDescription"></span>
</div>
Please, note that unlike WPF bindings, the Knockoutjs bindings accept JavaScript expressions as values, e.g. instead of binding to fullDescription property, we could have written:
Solar System Body full Description <span style="color:Blue" data-bind="text:name() + ': ' + details()"></span>
It works great when the expressions are small but for larger ones it is better to create a computed property in JavaScript.

Creating a Selectable List of the Solar Bodies using Observable Arrays

Now we are going to created something more closely related to the final result - a list of Solar System body objects. We will also allow the users to select one of the objects within the list. The selected object will be highlighted and the description of the selected Solar System body will be shown under the list:
Image 3

You can see that on the picture, Earth is selected Solar System body (its background is darker than that of the rest of the items and its foreground color is white). You can also see that the text under the list of solar bodies corresponds to the description of the Earth.

Code for the sample is located under SelectingSolarSystemBodies project. The project has two custom files: Scripts/SolarSystem.js contains the View Model and HTML/SolarSystem.htm contains the HTML View. The View Model is straightforward:

/// <reference path="knockout-2.1.0.js" />

// define namespace
var selectingSolarSystemBodiesNmspace = selectingSolarSystemBodiesNmspace || {};

// define the view model as an empty object
selectingSolarSystemBodiesNmspace.vm = {};

// constructor for solar system objects
var SolarSystemObject = function (name, details) {
    this.name = name;
    this.details = details;
}

// this function creates and populates the observable array of 
// solar system objects
// it also creates selectedSolarSystemBody observable property and
// setSeleced function.
populateSolarSystem = function () {
    var self = selectingSolarSystemBodiesNmspace.vm;

    // populate observable array to contain SolarSystemObjects 
    // corresponding to the sun and the planets within our solar system.
    self.solarSystemObjects =
        ko.observableArray([
            new SolarSystemObject("Sun", "The yellow dwarf star in the center of our solar system."),
            new SolarSystemObject("Mercury", "The small and rocky planet Mercury is the closest planet to the Sun."),
            new SolarSystemObject("Venus", "At first glance, if Earth had a twin, it would be Venus."),
            new SolarSystemObject("Earth", "Earth, our home planet, is the only planet in our solar system known to harbor life."),
            new SolarSystemObject("Mars", "The red planet Mars has inspired wild flights of imagination over the centuries."),
            new SolarSystemObject("Jupiter", "With its numerous moons and several rings, the Jupiter system is a \"mini-solar system.\""),
            new SolarSystemObject("Saturn", "Saturn is the most distant of the five planets known to ancient stargazers."),
            new SolarSystemObject("Uranus", "Uranus gets its blue-green color from methane gas above the deeper cloud layers."),
            new SolarSystemObject("Neptune", "Neptune was the first planet located through mathematical predictions."),
            new SolarSystemObject("Pluto", "Long considered to be the smallest, coldest, and most distant planet from the Sun."),
            ]);

    // create an observable property for a selected solar system body          
    self.selectedSolarSystemBody = ko.observable("");

    // create a function setSelected that sets the selectedSolarSystemBody
    // observable property
    self.setSelected = function (selectedItem) {
        self.selectedSolarSystemBody(selectedItem);
    };
} ();
The View Model has an observable array of SolarSystemObject objects. The View will bind to it displaying a green rectangle for each one of the objects within the array. Here is the code for the View:
<head>
    <!-- get the styles from styles.css file -->
    <link href="styles.css" rel="stylesheet" type="text/css" />
    <title></title>
</head>
<body>

<!-- move slightly down from the top of the browser -->
<div style="height:20px"></div>

<!-- foreach built-in binding will bind to an array of objects -->
<div data-bind="foreach:solarSystemObjects">
    <!-- whatever is placed within the tag bound with foreach binding, will 
         serv as a template for individual items within the list -->
    <!-- for each item within the list we use
            text binding to show the name of the solar system body
            click binding to call function setSelected on the parent view model to select a solar system body
            css binding to choose the selectedItem style in case the solar system body is selected  
    -->
    <span class="selectableItem" data-bind="text:name, click:$parent.setSelected, css:{selectedItem: ($parent.selectedSolarSystemBody()) && (name === $parent.selectedSolarSystemBody().name)}" style="padding:20px 20px 20px 20px;">
    </span>
</div>
<br />
<div>
    <!-- we show the description of the selected solar system body under the list of solar bodies
         via using text binding -->
    <span data-bind="text:selectedSolarSystemBody().details" />
</div>

</body>
</html>
<script src="../Scripts/jquery-1.8.1.min.js" type="text/javascript"></script>
<script src="../Scripts/knockout-2.1.0.debug.js" type="text/javascript"></script>
<script src="../Scripts/SolarSystem.js" type="text/javascript"></script>

<script type="text/javascript">
    $(function () {
        // create the bindings between the DOM elements and 
        // the View Model
        ko.applyBindings(selectingSolarSystemBodiesNmspace.vm);
    });
</script>

We are using foreach binding to bind to an observable array:

<div data-bind="foreach:solarSystemObjects">
    <span class="selectableItem" data-bind="text:name, click:$parent.setSelected, css:{selectedItem: ($parent.selectedSolarSystemBody()) && (name === $parent.selectedSolarSystemBody().name)}" style="padding:20px 20px 20px 20px;">
    </span>
</div>

This binding considers everything within its element to be the template for individual items (in our case the span element is repeated for every item within the observable array).

In WPF, there is no need for a special binding to bind to an observable collection - just all the visuals derived from ItemsControl assume that their ItemsSource property is bound to a collection object. In Knockoutjs, however, "foreach" binding binds to a collection.

Just like in case of WPF, the data context of the individual view items within a list corresponds to the individual objects within the observable array. In our case, it means that each span element is connected to the corresponding SolarSystemObject object. Unlike, in WPF, however, the Knockoutjs allows to reference the parent View Model within the binding by using $parent notation. In our case, $parent within the individual list items refers to the selectingSolarSystemBodiesNmspace.vm View Model.

Here is how we bind each individual item within the list:

data-bind="text:name, click:$parent.setSelected, css:{selectedItem: ($parent.selectedSolarSystemBody()) && (name === $parent.selectedSolarSystemBody().name)}"
There are three binding involved:
  • "text" binding is bound to name property of the View Model. This means that the name of the Solar system body will be displayed within the span element.
  • "click" binding is bound to setSelected() function on the parent View Model. That means that when the user mouse clicks on the span element, function setSelected will be called on the selectingSolarSystemBodiesNmspace.vm View Model.
  • "css" binding is the most complex one. It means that the span's class will change to selectedItem once the condition ($parent.selectedSolarSystemBody()) && (name === $parent.selectedSolarSystemBody().name) is satisfied. This condintion means that selectedSolarSystemBody observable is defined on the View Model and its name is the same as the name of the current item.

File HTML/styles.css contains very simple styles for the demo:

/* sets the background for the items that are not selected */
.selectableItem
{
	background-color:#33CC33;
}

/* sets the background style for an item that are not selected
	but has the mouse pointer hovering over it */
.selectableItem:hover
{
	background-color:rgba(0,255,0,0.5);
}

/* sets the background to be darker and foreground
	to be white for a selected item */
.selectedItem {
	background-color: #3AAA3A;
	color: White;
}

/* changes the background to get a little lighter for a
	selected item that has the mouse pointer hovering over it */
.selectedItem:hover 
{
	background-color:#5ABB5A;
}

The Main Solar System Application Code

Now we are ready to discuss the code for the main application located under SolarSystem project.

Note that the View Model defined in Scripts/SolarSystem.js file is very similar to the View Model of SelectingSolarSystemBodies project described above. The following functionality was added to the View Model in SolarSystem project:

  • The individual SolarSystemObject objects have more attributes to define radiuses of their orbits, speed of rotation (chosen randomly) and the corresponding image file location.
  • There is a runAnimation observable property defined on the View Model to turn the animation on and off.
  • There is a toggleAnimation method to toggle runAnimation value.
Here is the interesting part of HTML/SolarSystem.htm file:
<div style="position:fixed">

    <!-- button to stop and restart the animation -->
    <button id="toggleAnimationButton" data-bind="click:toggleAnimation">Toggle Animation</button>

     <!-- warning text to show in case the browser does not support SVG animations -->
     <span id="browserDoesNotSupportSVGAnimationsText" style="display:none;color:Red;font-weight:bold; font-size:30px">Your browser does not support SVG animations</span>
     
     <!-- warning text to show in case the browser does not support SVG -->
     <span id="browserDoesNotSupportSVGText" style="display:none;color:Red;font-weight:bold; font-size:30px">Your browser does not support SVG</span>
    
    <!-- displays information about selected solar system body -->
    <span  id="selectedSolarSystemBodyInfo" style="margin:0px 50px;color:White;" >
        <span style="color:Aqua; font-weight:bold" data-bind="text:selectedSolarSystemBody().name"></span>: 
        <span data-bind="text:selectedSolarSystemBody().details"></span>
    </span>
</div>
<svg id="solarSystem" overflow="auto" height="1300" width="1300">

    <!-- foreach binding creates a visual element for each
         item within the View Model's observable array -->
    <svg data-bind="foreach:solarSystemObjects">
        <!-- orbit circle -->
        <!-- fill="none" make it possible to click-through the svg control see point events-->
        <circle cx="500" cy="305" data-bind="attr:{r: radius}" stroke="yellow" stroke-width="1" fill="none"/> 

        <!-- svg group tag (groups element under it). It allows applying transforms and animations to
             all the grouped element at the same time -->
        <g>
            <!-- this is built-in SVG transform animation. It animates rotation (see the "type" attribute).
                 "from" and "to" attributes for "rotation" consist of angle in degrees followed by 
                 the x and y coordinates of the rotation center.
                 "begin" attribute states that the rotation should start right away.
                 "repeatCount" set to "indefinite" means that the rotation should continue for ever.
                 Knockout bindings bind 
                 the rotation duration ("dur" attribute) to "fullCircleDuration" property of the 
                    individual solar system body View Model.
                 Also we use animationControl custom binding connected to "runAnimation" property on
                 the whole solar system View Model to control the animation start and stop -->
            <animateTransform attributeName="transform" 
                              type="rotate"
                              from="0 500 305" 
                              to="360 500 305" 
                              begin="0s"
                              data-bind="attr:{dur: fullCircleDuration}, animationControl: $parent.runAnimation"
                              repeatCount="indefinite"/>

            <!-- this svg tag groups the elements under it and shifts them horizontally by 475 pixels 
                 to align them with the center of the orbits-->
            <svg x="475">
                 <!-- this svg tag groups the elements under it and shifts them vertically by 280 pixels 
                        to align them with the center of the orbits. Also it shifts the individual solar system body 
                        visual objects along axis x. The shift is determined by the radius of its orbit 
                        (see data-bind="attr:{x: radius}") -->
                <svg y="280" data-bind="attr:{x: radius}">
                    <!-- solar system body image to be displayed. -->
                    <image data-bind="click:$parent.setSelected, xlinkHref:imagePath" width="20" height="20" x="15" y="15" />

                    <!-- yellow circle around the selected image. Displays only for the image that has been selected -->
                    <circle data-bind="visible: ($parent.selectedSolarSystemBody()) && (name === $parent.selectedSolarSystemBody().name)" cx="25" cy="25" r="23" stroke="yellow" stroke-width="2" fill-opacity="0" /> 
                </svg>
            </svg>
        </g>

    </svg>
</svg>

For each SolarSystemObject within the View Model, we create an SVG circle for its orbit:

<circle cx="500" cy="305" data-bind="attr:{r: radius}" stroke="yellow" stroke-width="1" fill="none"/>
We use "attr" Knockoutjs binding to bind attribute r of the SVG circle to radius parameter of the corresponding SolarSystemObject.

Take a look at the <image/> element:

<image data-bind="click:$parent.setSelected, xlinkHref:imagePath" width="20" height="20" x="15" y="15" />

Mouse-clicking on the image will result in a call to setSelected() function to set the corresponding View Model object as the selectedSolarSystemBody. In order to set the file path for an SVG <image/>, we need to use SVG <image/> attribute xlink:href. This attribute has a colon inside and because of that if we try to use "attr" Knockoutjs binding for this attribute, it will get confused. So, I created a custom binding "xlinkHref" instead. The code for this binding is located in Scripts/XlinkHrefCustomBinding.js file:

ko.bindingHandlers.xlinkHref = {
    // init function updates a property value on an element only once - after the binding is set on it
    init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
        var value = valueAccessor();

        element.href.baseVal = value;
    }
};

Now let us look at the SVG animation and its bindings:

<animateTransform attributeName="transform"
                             type="rotate"
                             from="0 500 305"
                             to="360 500 305"
                             begin="0s"
                             data-bind="attr:{dur: fullCircleDuration}, animationControl: $parent.runAnimation"
                             repeatCount="indefinite"/>
Type "rotate" of the animationTransform means that the animation will do rotation. Attributes "from" and "to" consist of angle in degrees followed by the x and y coordinates of the rotation center, so the animation will rotate from 0 to 360 degrees with the center of rotation being at (500, 305) point. Attribute "begin" set to 0 seconds, means that the animation begins right away. "repeatCount" set to "indefinite" means that the animation will run for ever (unless, it is explicitly stopped in JavaScript, of course).

The "attr" binding binds "dur" attribute of the <animateTransform/> element to the fullCircleDuration of the corresponding View Model.

I also use custom binding "animationControl" bound to the runAnimation flag on the SolarSystem View Model. Let us talk a little more about "animationControl" custom binding. In WPF if you want something to happen within a control when there is a change in VM, the least invasive way to achieve that, is to create an attached property, which in its callback, will force the change in the control when it changes. This attached property can be bound to a View Model property so that the View Model will have the control over when to trigger the change. A similar non-invasive way to change a behavior of an HTML object can be achieved via Knockoutjs custom bindings.

In particular, here, we want to stop or restart the animation when runAnimation observable property changes. Stopping or restarting the animation can be done in JavaScript by calling element.endElement() function to stop the animation and element.beginElement() to restart the animation where element is the animation element. Custom binding "animationControl" achieves just that:

ko.bindingHandlers.animationControl = {
    // update function executes every time update of the binding is triggered by 
    // a change in the View Model observable property
    update: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
        // element passed here is the SVG animation

        var startOrStop = valueAccessor()();

        // if the browser does not support SVG animations - return
        if (!element.beginElement)
            return;

        if (startOrStop) {
            // if startOrStop flag is true - start the animation
            element.beginElement();
        }
        else {
            // if startOrStop falg is false - stop the animation
            element.endElement();
        }
    }
}; 
It is bound to runAnimation flag. When the flag changes, it triggers the binding's update() function. First parameter passed to this function is the element which defines the binding i.e. SVG <animationTransform/> in our case. Second attribute valueAccessor allows us to pull the changed value of the View Model runAnimation flag. Based on this value we decide whether to call element.endElement() or element.beginElement() function.

Summary

We discussed here using Knockoutjs to turn a View Model containing an array of objects describing Solar System bodies into an animated HTML5 visual representation of the solar system. In the next article, I plan to talk about imitating WPF custom controls in HTML5/JavaScript.

License

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