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:
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:
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:
var solarSystemBodyNmspace = solarSystemBodyNmspace || {};
solarSystemBodyNmspace.solarSystemBodyVM = {};
solarSystemBodyNmspace.solarSystemBodyVM = {
name : ko.observable("Earth"),
details: ko.observable("the planet we live on")
};
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");
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");
this.OnPropertyChanged("FullDescription");
}
}
#endregion Details Property
#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 () {
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:
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:
var selectingSolarSystemBodiesNmspace = selectingSolarSystemBodiesNmspace || {};
selectingSolarSystemBodiesNmspace.vm = {};
var SolarSystemObject = function (name, details) {
this.name = name;
this.details = details;
}
populateSolarSystem = function () {
var self = selectingSolarSystemBodiesNmspace.vm;
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."),
]);
self.selectedSolarSystemBody = ko.observable("");
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 () {
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:
.selectableItem
{
background-color:#33CC33;
}
.selectableItem:hover
{
background-color:rgba(0,255,0,0.5);
}
.selectedItem {
background-color: #3AAA3A;
color: White;
}
.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 (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 (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
var startOrStop = valueAccessor()();
if (!element.beginElement)
return;
if (startOrStop) {
element.beginElement();
}
else {
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.