MVVM in Silverlight and in HTML5/JavaScript Contents

Transcription

MVVM in Silverlight and in HTML5/JavaScript Contents
MVVM in Silverlight and in
HTML5/JavaScript
Contents
Introduction .................................................................................................................................................. 2
JavaScript Really Quick Start for Silverlight Developers ............................................................................... 2
Introduction to MVVM.................................................................................................................................. 4
MVVM Basics in Silverlight............................................................................................................................ 4
MVVM Basics in JavaScript ........................................................................................................................... 6
GeoDashboard/Silverlight Version.............................................................................................................. 12
The Application ....................................................................................................................................... 12
ViewModel Implementation (C#) ........................................................................................................... 13
View Implementation (XAML)................................................................................................................. 17
GeoDashboard/JavaScript........................................................................................................................... 25
Application Layout/User Interface .......................................................................................................... 25
ViewModel Implementation (JavaScript) ............................................................................................... 26
View Implementation (HTML/CSS) ......................................................................................................... 31
Comparison: Silverlight vs. HTML5/JavaScript ............................................................................................ 38
Conclusion ................................................................................................................................................... 40
Resources .................................................................................................................................................... 40
GeoDashboard, Silverlight Version ......................................................................................................... 40
GeoDashboard, HTML5/JavaScript Version ............................................................................................ 41
Suggested Reading .................................................................................................................................. 41
Introduction
This article describes how you can use the MVVM pattern to develop applications that are easy to test
and maintain, using Silverlight and HTML5/JavaScript.
The article was written for two groups of developers:
1) Silverlight developers interested in learning more about HTML5 and JavaScript. The article covers
basic aspects of JavaScript programming such as object-oriented patterns and common mistakes made
by .NET programmers learning JavaScript.
2) JavaScript developers interested in learning more about the MVVM pattern that has become the defacto standard for XAML development, and is gaining popularity in JavaScript development after the
introduction of the KnockoutJS library (http://knockoutjs.com/).
The article describes the MVVM pattern in Silverlight and in JavaScript (using the KnockoutJS library). It
illustrates the main concepts walking through the implementation of a map-based demographic
dashboard application implemented in Silverlight and in HTML5/JavaScript.
Shortly before this article was finished, CodeProject published an excellent article by Colin Eberhardt
entitled “KnockoutJS vs. Silverlight”. The articles talk about the same subject, but present it in different
ways and focus on different aspects of the problem. Colin’s article focuses on KnockoutJS, and uses a
relatively simple sample that requires no custom controls. The article is very well-written and is
definitely recommended reading.
Here is a link to Colin’s “KnockoutJS vs. Silverlight” article:
http://www.codeproject.com/Articles/365120/KnockoutJS-vs-Silverlight.
JavaScript Really Quick Start for Silverlight Developers
If you are a Silverlight developer who uses C#, learning JavaScript will be fairly easy (if you are a VB
developer, it will be a little harder).
If you never created a JavaScript project, and don’t know how to start, here are two options to get you
started quickly:
Creating a JavaScript project using Visual Studio:
1. Create a new project using the “Empty ASP.NET Application” template.
2. Right-click the project and select “Add | New Item…”
3. Select “HTML Page”
4. Open the page and add the text shown below in bold face:
<!DOCTYPE html PUBLIC "…">
<head>
<title></title>
<script type="text/javascript">
alert('hello world');
</script>
</head>
<body>
</body>
</html>
5. Press F5 to run the project.
That’s all there is to it. The JavaScript code in the script tag will be executed when the page loads, and it
will show a dialog. The script tag can also contain function definitions, or include other JavaScript files.
Creating a JavaScript project using Notepad:
This is even easier. Just open Notepad, copy the text above into a new document, and save it with an
“html” extension. Then double-click the new file to open it in the browser.
Regardless of how you created the file, once it is open in the browser you can to bring up the browser
debugging tools (in IE and Chrome, simply press F12). The debugging tools show you the source code,
allow you to add break points, inspect variables and page elements, and much more. Most importantly,
they will show you any errors that the browser will not.
Although you can create JavaScript applications in Notepad and use the browser to debug them, you
probably don’t want to do that. Visual Studio is a great development environment for JavaScript
applications too. Most of the functionality you are used to when developing C# applications is there.
Except of course JavaScript is interpreted, so you won’t get compile-time errors. IntelliSense is also quite
limited.
JavaScript has a syntax that is fairly similar to C#. There are a few exceptions that might be fairly
confusing at first, but once you write one or two simple applications things should become pretty easy.
JavaScript has been gaining tremendous popularity, and there is a wealth of information available on the
web. A good introduction can be found here:
https://developer.mozilla.org/en/JavaScript/A_re-introduction_to_JavaScript
Introduction to MVVM
The MVVM pattern (Model/View/ViewModel) was introduced by Microsoft as a variation of the more
traditional MVC pattern (Model/View/Controller).
MVVM encapsulates the application logic in a set of ViewModel classes that expose an object model
that is View-friendly. Views typically focus on the user interface, and rely on bindings to connect UI
elements to properties and methods in the ViewModel. This separation between logic and markup
brings the following important benefits:
1) Testability: The ViewModel does not contain any user interface elements, and is therefore easy to
test using unit tests.
2) Separation of Concerns: Business logic is written using programming languages such as C# or
JavaScript. User interfaces are written using markup languages such as XAML or HTML. The skills
required and tools used for each type of development are fundamentally different. Separating these
elements makes team development simpler and more efficient.
3) Multi-Target Applications: Encapsulating an application’s business logic into ViewModel classes
makes it easier to develop several versions of an application, targeting multiple devices. For example,
one could develop a single ViewModel and different views for desktop, tablet, and phone devices.
MVVM Basics in Silverlight
MVVM is not new in Silverlight (or WPF). Anyone who has written XAML-based applications is familiar
with the Binding class which allows developers to bind UI elements defined in views to logic elements
defined in the ViewModel.
For example, the XAML snippet below binds a TextBox control to the FirstName property on a
ViewModel object:
<UserControl.Resources>
<local:ViewModel x:Key="_vm" />
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="White"
DataContext="{StaticResource _vm}">
<TextBox Text="{Binding FirstName}" />
</Grid>
In the example above, an instance of the ViewModel class is created as a resource and used as the
DataContext for the page.
The page contains a TextBox element with the Text property bound to the “FirstName” property of the
ViewModel. The binding is two-way, meaning the TextBox shows the current value of the ViewModel
property and allows users to modify it.
In order for this to work, the ViewModel object must implement the INotifyPropertyChanged interface
and it must raise the PropertyChanged event when property values change. This is the mechanism that
allows the bindings to update the UI to reflect the current state of the ViewModel.
In addition to simple bindings such as the one displayed above, Silverlight supports MVVM with two
special types of binding:
1) Binding to ICollectionView objects: UI elements are often bound to collections of objects such as
customer or product lists. ViewModel objects usually expose collections as ICollectionView objects,
which send notifications when the collection changes (items are added, removed, etc.). ICollectionView
objects also support the concept of “currency”, which means one of the items in the collection is the
“current” item. ICollectionView objects are often bound to controls such as ListBox or DataGrid through
their ItemsSource property.
For example, the XAML below causes a ListBox control to display the Customers property defined in the
ViewModel (the Customers property is of type ICollectionView):
<UserControl.Resources>
<local:ViewModel x:Key="_vm" />
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="White"
DataContext="{StaticResource _vm}">
<ListBox ItemsSource="{Binding Customers}" />
</Grid>
2) Binding to ICommand objects: This type of binding connects certain UI elements to methods in the
ViewModel (rather than properties). The ICommand interface includes a method to call (with optional
parameters) and also a method that determines whether the command can currently be executed. One
typically binds ICommand objects to the Command property of controls such as buttons or menu items.
The UI will then enable or disable the UI element depending on the state of the ViewModel, and clicking
the button or menu item will invoke the command in the ViewModel object.
For example, the XAML below binds a Button control to the SaveChanges property defined in the
ViewModel (the SaveChanges property is of type ICommand):
<UserControl.Resources>
<local:ViewModel x:Key="_vm" />
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="White"
DataContext="{StaticResource _vm}">
<Button Content="Save Changes" Command="{Binding SaveChanges}" />
</Grid>
Notice how in the examples above, the View code consists of pure XAML. There are no event handlers
and no code behind. All the business logic is contained in the ViewModel class, implemented in C# or in
Visual Basic.
The Binding class in Silverlight and WPF is mature and quite rich. For example, it allows you to specify
how values should be formatted for display (Binding.StringFormat property) and how to convert value
types to make the binding compatible (Binding.Converter property).
The Binding class is fundamental to MVVM because it is the main mechanism used to connect elements
in the View to properties and methods in the ViewModel.
MVVM Basics in JavaScript
MVVM in JavaScript became possible recently, with the introduction of the KnockoutJS library
(http://knockoutjs.com/). KnockoutJS introduces observable and observableArray classes which play
the role of INotifyPropertyChange and ICollectionView in Silverlight. It also introduces HTML markup
extensions which play the role of the Binding class.
The HTML snippet below is a simple but complete example of an MVVM application written using
KnockoutJS:
<head>
<!-- load KnockoutJS library-->
<script
src=http://cloud.github.com/downloads/SteveSanderson/knockout/knockout-2.0.0.js
type="text/javascript" ></script>
<!-- ViewModel -->
<script type="text/javascript" >
// ViewModel class
function ViewModel() {
this.firstName = ko.observable("Bert")
};
// create and bind ViewModel when document loads
onload = function() {
ko.applyBindings(new ViewModel());
};
</script>
</head>
<body>
<p>First name: <input data-bind="value: firstName" ></b>.</p>
<p>Welcome to MVVM, <b data-bind="text: firstName" ></b>!</p>
</body>
The code starts with a script block that loads the KnockoutJS library and defines a ViewModel class. This
ViewModel has a single property, an observable called “firstName” initialized to the value “Bert”.
The code attaches a handler to the page’s onload event which calls the KnockoutJS applyBindings. This
method performs the actual binding between ViewModel properties and HTML elements that have a
“data-bind” attribute.
The HTML below the script block contains the View, which consists of plain HTML with some “data-bind”
tags.
If you save this snippet to a file and open it in a browser, you will see this:
If you type a new name into the input field and hit the tab key, you will see that the paragraph below
the input field changes to show the new name. That happens because the first binding is two-way,
meaning the View can not only show, but also update the value in the ViewModel.
.NET Developers:
The code defines the ViewModel class as a function, and adds a property by assigning it a value using
the “this” keyword. For details on JavaScript and object-oriented programming, please see
https://developer.mozilla.org/en/Introduction_to_Object-Oriented_JavaScript
Recall that Silverlight provides special bindings for collections (ICollectionView) and commands
(ICommand). These are also supported by KnockoutJS.
The observableArray class plays the role of ICollectionView in Silverlight. It notifies listeners when items
are added or removed from the collection, which causes the view to refresh its content. For example:
// Customer class
function Customer(firstName, lastName) {
this.firstName = ko.observable(firstName);
this.lastName = ko.observable(lastName);
}
// ViewModel class
function ViewModel() {
this.customers = ko.observableArray([
new Customer("Isaac", "Newton"),
new Customer("James", "Maxwell")]);
// create and bind ViewModel when document loads
onload = function() {
ko.applyBindings(new ViewModel());
};
The code defines a ViewModel class with a single property, an observableArray called “customers”. The
array is initialized with two customers, which are objects that in turn contain observable properties.
To bind a View to this ViewModel, you would use the KnockoutJS “foreach” binding. For example:
<table>
<tbody data-bind="foreach: customers">
<tr>
<td data-bind="text: firstName"></td>
<td data-bind="text: lastName"></td>
</tr>
</tbody>
</table>
This creates an HTML table to display the content of the customers observable array as rows that
contain the firstName and lastName properties of each customer.
Because the customers property is an observable array, the table will be updated automatically if
customers are added or removed from the array. Also, since the properties in the customer objects are
observable, the content of the table cells will be updated automatically if the first or last names of a
customer change.
KnockoutJS also provides a way to bind UI elements to commands, which can be enabled or disabled
depending on the ViewModel state. For example:
// ViewModel class
function ViewModel() {
// properties
this.firstName = ko.observable("Bert");
this.hasChanges = ko.observable(true);
// save changes command
var self = this;
this.saveChanges = function () {
alert('saved changes');
self.hasChanges(false);
};
// update hasChanges when a property changes value
this.firstName.subscribe(function () {
self.hasChanges(true);
});
};
.NET Developers:
The ViewModel constructor declares a variable named self and sets it to the value of the built-in this.
This is a common JavaScript construct and is required whenever local functions need to refer to the
main object. Within the local functions, the variable this refers to the inner function itself, not to the
outer object.
This ViewModel has a command called “saveChanges”, and a property called “hasChanges”. The
“hasChanges” property is set to true when the value of the “firstName” property changes, and is set to
false when the changes are saved.
The HTML below shows how you could bind this ViewModel to a View:
<p>First name: <input data-bind="value: firstName" ></b>.</p>
<p>Welcome to MVVM, <b data-bind="text: firstName" ></b>!</p>
<button data-bind="click: saveChanges, enable: hasChanges">
Save Changes
</button>
Notice how the button’s click event is bound to the “saveChanges” method and the button’s enable
property is bound to the “hasChanges” property.
If you save these changes to a file and open it in a browser, you will see this:
If you click the “Save Changes” button, a message box is displayed and the button is disabled indicating
that the changes were applied and there are no further changes to save. If you then type a new name
into the input field and click tab, the new value will be applied to the “firstName” property, and the save
button will become enabled again.
One important missing topic is formatting. We have seen how KnockoutJS bindings allow us to display
ViewModel properties on a page, but in most cases numeric and date values have to be formatted,
perhaps with thousand separators, month abbreviations, etc.
Fortunately, there is a globalization library for JavaScript called Globalize that handles international
formatting and follows the formatting conventions used in .NET.
Globalize can be invoked directly from the View, playing the role of the Binding.Stringformat property
in Silverlight. It has a format function that can be used to localize numbers (including currency and
percentages) as well as dates. The library currently supports 350 cultures and the default is en-US. You
can find details on the globalize library here: https://github.com/jquery/globalize.
The snippet below includes the Globalize library and defines a ViewModel with an “amount” property:
<head>
<!-- load KnockoutJS library-->
<script src=http://cloud.github.com/downloads/SteveSanderson/knockout/knockout-2.0.0.js
type="text/javascript" ></script>
<!-- load globalize library-->
<script src=http://cdn.wijmo.com/jquery.wijmo-open.all.2.0.5.min.js
type="text/javascript"></script>
<script type="text/javascript" >
// define ViewModel class
function ViewModel() {
this.amount = ko.observable(1234.5678);
};
// create and bind ViewModel when document loads
onload = function() {
ko.applyBindings(new ViewModel());
};
</script>
</head>
The View defined below shows the value formatted in different ways:
<body>
<p>Unformatted amount (regular binding):</p>
<ul>
<li data-bind="text: amount" ></li>
</ul>
<p>Formatted amounts (call Globalize from binding tag):</p>
<ul>
<li>c0: <span data-bind="text: Globalize.format(amount(), 'c0')"</span></li>
<li>c2: <span data-bind="text: Globalize.format(amount(), 'c2')"></span></li>
</ul>
</body>
If you save this snippet to a file and open it in a browser, you will see this:
This View formats the amounts using format strings compatible with the ones typically used in Silverlight
applications. The example shows only formatting, not globalization. The globalize library supports
hundreds of different cultures, but those must be downloaded separately.
These simple examples demonstrate how KnockoutJS can be used to provide functionality similar to
what is available in Silverlight and WPF. KnockoutJS is a rich library, and their site provides excellent
help and interactive tutorials that cover many interesting and useful scenarios: http://knockoutjs.com.
GeoDashboard/Silverlight Version
This section describes the implementation of GeoDashboard, a demographics dashboard application in
Silverlight. In the next section, we will describe the implementation of the same app in
HTML5/JavaScript. Of course, both implementations follow the MVVM pattern.
The Application
The GeoDashboard application shows a scrollable map with a crosshair pointer over it. Users can select
locations by dragging points in the map under the crosshair, or they can click a link to automatically
select their current location.
When a location is selected, demographic information for the selected location is displayed on several
tiles that appear below the map.
The geographic information is obtained using Esri’s ArcGIS Online Map Services. The Esri services
provide several types of information about the US population, including average household size, median
age, population density, retail spending potential, and more: http://www.esri.com/data/freedata/index.html) .
The diagram below shows the application’s general layout:
{Selected Location Name}
{Select Current Location}
GeoDashboard
Median Age
Median Income
Median Home Value
42
$47k
$142k
Other Tiles…
The top panel contains the application title, the name of the location that is currently selected, and a
link to scroll the map to the user’s current location.
The center panel contains the map with the selected location indicator (crosshair). Users can drag the
map around to select new locations, and they can zoom in or out to set the level of detail they are
interested in (States, Counties, or Census Tracts).
The bottom panel contains a series of tiles, each showing one type of demographic information. The
tiles should always contain a title describing the type of information that is being displayed. Other than
that, each tile shows its information in the most adequate format, using combinations of UI elements
such as text, charts, grids, gauges, sub-maps, etc.
In future versions of the app, perhaps users will be allowed to select the type of tiles they are interested
in, the order in which they appear, the color scheme used to show the information, etc. But in this first
version, all those elements are fixed.
ViewModel Implementation (C#)
The ViewModel for the GeoDashboard application has a simple object model with the following main
properties:

Extent Property: This property gets or sets the selected location on the map. It represents a
rectangular area centered at the crosshairs. It is the main input property of the ViewModel.

Changing the value of the Extent property causes the ViewModel to update the demographic
information.
Sources Property: This is a dictionary where the keys are information type identifiers (e.g. “age”,
“income”, etc.) and the values are objects of type InfoSource, which provide tile-specific
information. The Sources dictionary contains 10 InfoSource objects. This dictionary is loaded
from a resource file when the ViewModel is created, and it does not change (only the data
contained in the InfoSource objects change).
InfoSource class: This class contains the following main properties:




Name: The name of this InfoSource object. This is the key into the ViewModel’s Sources
property mentioned above.
Dictionary: A dictionary where keys are strings that identify a specific unit of information (for
example, number of people between ages 5 and 10), and the values are objects of type
InfoValue, which is basically a name-value pair. This dictionary is also loaded from a resource file
when the ViewModel is created, and it does not change (only the data contained in the
InfoValue objects change).
List: A list of related values that can be used to create a chart. Typically, these values represent
ranges and counts (such as number of people between ages 5 and 10, or number of homes
worth between $100k and $200k). Not all InfoSource objects contain lists. This list contains
InfoValue objects that are also present in the Dictionary.
ShortList: A condensed version of the full list containing only three ranges. This is useful for
creating summary charts in small tiles.
The class diagram below summarizes our ViewModel class:
We will not list the whole source code here, but we will highlight the main points.
The implementation of the Extent property is as follows:
/// <summary>
/// Gets or sets the area for which we are providing information.
/// </summary>
public Envelope Extent
{
get { return _extent; }
set
{
if (value != _extent)
{
_extent = value;
OnPropertyChanged("Extent");
UpdateValues();
}
}
}
When the value of the Extent property changes, the ViewModel raises the PropertyChanged event and
calls the UpdateValues method which updates the InfoSource objects with demographic information for
the new extent. The UpdateValues method is implemented as follows:
// refresh the detail information for the currently selected
void UpdateValues()
{
// can't query without an extent
if (_extent != null)
{
// update info scale
InfoScale = _extent.Width < 2e5 ? InfoScale.Tract :
_extent.Width < 6e6 ? InfoScale.County :
InfoScale.State;
// perform queries for each infoType in dictionary
foreach (var source in _sources.Values)
{
if (source.HasListeners)
{
source.UpdateValues();
}
}
}
}
The code checks for a valid Extent and calculates the level of detail based on the extent (whether to
retrieve information for states, counties, or tracts). It then loops through the InfoSource objects in the
dictionary and calls the UpdateValues method on each InfoSource that has listeners attached to it. This
check is performed to ensure that information is retrieved only for sources that are actually being used.
For example, if the application is configured to show home values and population age information only,
then there is no reason to retrieve spending data.
The UpdateValues method on the InfoSource class is responsible for performing the actual queries.
Here is a slightly simplified version of its implementation:
/// <summary>
/// Perform a new query based on the current extent and
/// update all values in the Dictionary.
/// </summary>
public void UpdateValues()
{
// create query
var query = new Query();
foreach (var iv in Dictionary)
{
query.OutFields.Add(iv.Key);
}
// specify the geometry to use for this spatial query
// (instead of a WHERE clause)
query.Geometry = ViewModel.Extent.GetCenter();
This part of the code creates a Query object and populates it with the types of information required. The
values are the keys of the InfoValue objects in the InfoSource dictionary. The Geometry property is set
to the center of the current Extent, and defines the region of interest.
Once the query is ready, it is executed as follows:
// execute query
var url = GetQueryUrl();
var queryTask = new QueryTask(url);
queryTask.ExecuteCompleted += (s, e) =>
{
if (e.FeatureSet != null &&
e.FeatureSet.Features.Count > 0 &&
e.FeatureSet.Features[0].Attributes.Count > 0)
{
var atts = e.FeatureSet.Features[0].Attributes;
ViewModel.SelectedLocation = string.Format("{0}, {1}",
atts["NAME"], atts["ST_ABBREV"]);
foreach (var kv in atts)
{
// get information name, key, and value
var infoValue = Dictionary[kv.Key];
infoValue.Value = kv.Value;
}
}
};
queryTask.Failed += (s, e) =>
{
MessageBox.Show(e.Error.Message);
};
// start query
queryTask.ExecuteAsync(query);
}
The method creates a QueryTask object and defines the method that will be executed when the query
task is complete. The code iterates through the FeatureSet object returned by the service, looks up the
corresponding InfoValue in the dictionary, and updates the InfoValue’s Value property. This assignment
will cause a PropertyChanged event to fire, so any listeners will be notified of the update.
The last line is the one that actually invokes the service.
This is the core of our application’s ViewModel.
View Implementation (XAML)
The View is implemented as follows:
<UserControl.Resources>
<local:ViewModel x:Key="_vm" />
</UserControl.Resources>
<Grid x:Name="LayoutRoot" DataContext="{StaticResource _vm}">
<Grid.RowDefinitions>
<RowDefinition Height="auto" /> <!-- app title -->
<RowDefinition MinHeight="150"/> <!-- map -->
<RowDefinition />
<!-- info tiles -->
</Grid.RowDefinitions>
The View starts by declaring a ViewModel resource named “_vm”, which is immediately assigned to the
DataContext property of the layout root. This allows any object on the page to bind to the ViewModel’s
properties.
Next, the code adds three rows to the layout root. These will hold the application title bar, the map, and
the info tiles.
The title bar is implemented as follows:
<!-- app title bar -->
<Grid Background="Black" >
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="auto" />
</Grid.RowDefinitions>
<TextBlock
Text="ComponentOne GeoDashboard"
Foreground="White" FontSize="28" FontFamily="Segoe UI Light"
Grid.RowSpan="2" Margin="8" VerticalAlignment="Center" />
<TextBlock
Text="{Binding SelectedLocation}"
HorizontalAlignment="Right" Foreground="White" FontSize="20" />
<HyperlinkButton
Name="_goToCurrentLocation" Grid.Row="1"
Content="Go to current location"
HorizontalAlignment="Right" Foreground="White" />
</Grid>
The title bar is implemented as a grid that contains three elements. The first shows the application title.
The second is bound to the ViewModel’s SelectedLocation property, and is updated automatically as the
user zooms and pans the map.
The last element on the title bar is a hyperlink that uses HTML5 location services to navigate to the
user’s current location. This is an important point. Silverlight does allow you to interact with the browser
and leverage HTML5 features in your applications. Here is the code-behind that executes when the user
clicks that hyperlink:
// go to the user's current location when user clicks the link
_goToCurrentLocation.Click += (s, e) =>
{
System.Windows.Browser.HtmlPage.Window.Invoke(
"getLocation", new Action<string>(result =>
{
var lonLat = result.Split(',');
if (lonLat.Length == 2)
{
var pt = new Point(double.Parse(lonLat[0]), double.Parse(lonLat[1]));
_map.PanTo(GeoToMap(pt));
}
else
{
MessageBox.Show(result);
_goToCurrentLocation.IsEnabled = false;
}
}));
};
The code uses HtmlPage.Window.Invoke to call the getLocation method, which is part of the HTML5
(actually, it is currently a W3C recommendation, supported in all modern browsers). The method
executes asynchronously and returns a string that contains the user’s latitude and longitude. These
values are then parsed and converted into projection coordinates that can be used with the Esri map
control. The conversion is done using the following GeoToMap utility method:
// convert geo coordinates (lat/lon) to and from map coordinates
// (assuming WebMercator projection).
static WebMercator _wm = new WebMercator();
MapPoint GeoToMap(Point point)
{
var mapPoint = new MapPoint(point.X, point.Y);
return _wm.FromGeographic(mapPoint) as MapPoint;
}
This is all it takes to obtain the user’s current coordinates and adjust the map Extent. Later we will see
that this change will update the ViewModel which in turn will cause all the info tiles to show the
information for the user’s current location.
Below the title bar, we have a row that shows the map and a crosshair display that indicates the
selected location:
<!-- map -->
<esri:Map
Name="_map" Grid.Row="1" WrapAround="True"
Extent="-9120937, 4724643, -8695365, 5142031">
<esri:ArcGISTiledMapServiceLayer
Url="http://services.arcgisonline.com:80/ArcGIS/rest/services/NatGeo_World_Map/MapServer" />
<esri:ArcGISTiledMapServiceLayer
ID="_demographics"
Url=http://services.arcgisonline.com:80/ArcGIS/rest/services/NatGeo_World_Map/MapServer
Opacity=".4" Visible="False"/>
</esri:Map>
The map defines two layers. The first shows the background imagery. The second shows demographic
information in a semi-transparent overlay. The second layer is not visible initially; it is shown only when
an info tile is clicked.
<!-- crosshair -->
<Grid IsHitTestVisible="False" Grid.Row="1" >
<Ellipse Width="200" Height="200" Stroke="#ff5f2588" StrokeThickness="2" />
<Ellipse Width="100" Height="100" Stroke="#ff5f2588" StrokeThickness="2" />
<Rectangle HorizontalAlignment="Center" VerticalAlignment="Stretch"
Fill="#ff5f2588" Width="2" />
<Rectangle HorizontalAlignment="Stretch" VerticalAlignment="Center"
Fill="#ff5f2588" Height="2" />
</Grid>
This code adds a crosshair display that indicates the exact center of the map. To select a location, users
drag the map until the desired location is under the cross-hair.
When the user zooms or pans the map, the following code-behind is used to update the ViewModel:
// update ViewModel when the map extent changes
void _map_ExtentChanged(object sender, ESRI.ArcGIS.Client.ExtentEventArgs e)
{
_sb.Stop();
_sb.Seek(TimeSpan.Zero);
_sb.Begin();
}
void _sb_Completed(object sender, EventArgs e)
{
var model = Resources["_vm"] as ViewModel;
if (model != null)
{
model.Extent = _map.Extent;
}
}
In case you were wondering why we didn’t simply bind the map’s Extent property to the Extent
property on the ViewModel, the code should give you a hint. Notice that it does not update the
ViewModel immediately after each change. Rather, it uses a StoryBoard to create a delay. When the
user drags the map, updates are deferred until the changes stop for a specified interval (about 200 ms).
The last element in the View is the most important: the information tiles. These are implemented as
UserControl objects, and are included in the main page as follows:
<!-- info tiles -->
<ScrollViewer Grid.Row="2" >
<c1:C1WrapPanel Name="_gTiles" Background="Transparent" >
<local:TapestryTile />
<local:SexTile />
<local:AgeTile />
<local:IncomeTile />
<local:NetWorthTile />
<local:HomeValueTile />
</c1:C1WrapPanel>
</ScrollViewer>
The info tiles are laid out in a C1WrapPanel control, so when the user resizes the browser window they
automatically reflow.
We will show the implementation of two tiles, the “Median Net Worth” and “Home Value Distribution”.
The other tiles follow the same pattern, except they have different layouts and use different controls to
show specific types of information.
The “Median Net Worth” tile looks like this:
It is implemented in pure XAML, no code behind is required:
<UserControl x:Class="GeoDashboard.NetWorthTile"
xmlns=http://schemas.microsoft.com/winfx/2006/xaml/presentation
xmlns:x=http://schemas.microsoft.com/winfx/2006/xaml
xmlns:c1=http://schemas.componentone.com/winfx/2006/xaml
Width="230" Height="165" Margin="6"
FontFamily="Segoe UI" FontSize="24" FontWeight="Light" >
<Grid x:Name="LayoutRoot" Background="White"
DataContext="{Binding Path=Sources[USA_Median_Net_Worth]}" >
<StackPanel Margin="12">
<TextBlock Text="Median Net Worth" FontSize="12" />
<TextBlock Text="{Binding Dictionary[MEDNW_CY].Value, StringFormat=c0}"
FontWeight="Bold" Foreground="#ff5f2588"/>
<c1:C1RadialGauge
Minimum="0" Maximum="300000" Background="Transparent"
Width="200" Height="200" Margin="80 -20 0 0"
StartAngle="-90" SweepAngle="90" BorderBrush="Transparent"
PointerFill="Black" PointerStrokeThickness="0"
PointerCapFill="Black" PointerCapStrokeThickness="0"
Value="{Binding Dictionary[MEDNW_CY].Value}">
<c1:C1GaugeRange
Fill="LightGray" From="0" To="300000"
Width=".2" Location=".7" Opacity=".4"/>
<c1:C1GaugeRange
Fill="#ff5f2588" From="0" To="{Binding Dictionary[MEDNW_CY].Value}"
Width=".2" Location=".7" Opacity=".4"/>
<c1:C1GaugeLabel Interval="100000" Location="1.1" Format="#,##0,k"
FontFamily="Segoe UI" FontSize="10" FontWeight="Bold" />
</c1:C1RadialGauge>
</StackPanel>
</Grid>
</UserControl>
The XAML starts by setting the tile’s DataContext to the InfoSource that contains net worth information.
This is just a convenience. It limits the data scope within the tile and makes the inner bindings more
concise.
Two text blocks are used to show the tile title and core information, the median net worth for the
selected region. This value is bound to the ViewModel and is automatically updated when the user
scrolls the map.
Finally, the tile uses a C1RadialGauge control to provide a graphical display of the median net worth on
a fixed scale. The median net worth value is bound to the gauge’s Value property and also to the To
property of a gauge range. When the user selects a new location, the gauge’s pointer and range are
automatically updated.
The “Home Value Distribution” tile looks like this:
And the implementation is as follows:
<UserControl x:Class="GeoDashboard.HomeValueHistogramTile"
xmlns=http://schemas.microsoft.com/winfx/2006/xaml/presentation
xmlns:x=http://schemas.microsoft.com/winfx/2006/xaml
xmlns:c1=http://schemas.componentone.com/winfx/2006/xaml
xmlns:local="clr-namespace:GeoDashboard"
Width="520" Height="360" Margin="6"
FontFamily="Segoe UI" FontSize="24" FontWeight="Light" >
<Grid
x:Name="LayoutRoot"
Background="White"
DataContext="{Binding Path=Sources[USA_Median_Home_Value]}" >
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition />
</Grid.RowDefinitions>
<StackPanel Margin="12">
<TextBlock Text="Home Value Distribution" FontSize="12" />
<TextBlock Text="{Binding Dictionary[NAME].Value}"
FontWeight="Bold" Foreground="{StaticResource _brResult}" />
</StackPanel>
The first block of XAML sets the DataContext for the tile content and shows a header with the name of
the selected location. This is similar to the XAML used in the previous time.
The next block of XAML defines the chart:
<c1:C1Chart ChartType="Column" Grid.Row="1" Margin="12">
<!-- chart data -->
<c1:C1Chart.Data>
<c1:ChartData ItemsSource="{Binding List}" >
<c1:DataSeries ValueBinding="{Binding Value}"
SymbolFill="{StaticResource _brResult}" >
<!-- chart element tooltip -->
<c1:DataSeries.PointTooltipTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding DataObject.Value,
StringFormat=' {0:n0} homes in the range '}"
HorizontalAlignment="Center" FontSize="18"/>
<TextBlock Text="{Binding DataObject.Name}"
HorizontalAlignment="Center" FontSize="18"/>
</StackPanel>
</DataTemplate>
</c1:DataSeries.PointTooltipTemplate>
</c1:DataSeries>
</c1:ChartData>
</c1:C1Chart.Data>
This block of XAML creates the Chart control and sets its ItemsSource property to the List property of
the current InfoSource. The list contains a series of InfoValue objects where the names are price ranges
and the values are counts of homes in that range.
The code then creates a DataSeries object and sets its ValueBinding property to the “Value” property of
the items in the ItemsSource. In this case, these are the home counts.
Finally, the code adds a PointTooltipTemplate definition that shows information about each column
when the user moves the mouse over the chart. In this case, the tooltip shows the number of homes in
the range and the range definition.
The final block of XAML customizes the chart axes:
<!-- chart axes (hide lines, show annotations)-->
<c1:C1Chart.View>
<c1:ChartView>
<!-- X-axis -->
<c1:ChartView.AxisX>
<c1:Axis Foreground="Transparent" MajorGridStroke="Transparent" />
</c1:ChartView.AxisX>
<!-- Y-axis -->
<c1:ChartView.AxisY>
<c1:Axis Foreground="Transparent" MinorTickStroke="Transparent" >
<c1:Axis.AnnoTemplate>
<DataTemplate >
<TextBlock Text="{Binding Value, StringFormat=n0}" FontSize="12" />
</DataTemplate>
</c1:Axis.AnnoTemplate>
</c1:Axis>
</c1:ChartView.AxisY>
</c1:ChartView>
</c1:C1Chart.View>
</c1:C1Chart>
</Grid>
</UserControl>
The code hides the X-axis and the associated gridlines by setting their color to transparent.
It also hides the Y-axis and uses the AnnoTemplate property to customize the appearance and color of
the annotation elements. This is required because the annotations are normally displayed using the
same color as the axis itself, which in this case is transparent.
As you can see, adding and customizing a chart to the view requires a fair amount of XAML. This is to be
expected, because chart controls typically have a lot of flexibility and rich object models. We decided to
include the full XAML for the chart here so it can be compared to the JavaScript chart definition which
we will show later.
The final version of the Silverlight application looks like this:
To see it live, follow this link:
http://demo.componentone.com/Silverlight/GeoDashboard/
To download or browse the source code, follow this link:
http://our.componentone.com/samples/silverlight-geodashboard
GeoDashboard/JavaScript
This section describes the HTML5/JavaScript implementation of the GeoDashboard application.
Application Layout/User Interface
The HTML5/JavaScript version of the application has the same layout and functionality as the Silverlight
version described above.
ViewModel Implementation (JavaScript)
In the HTML5/JavaScript version of the application, the ViewModel is implemented in JavaScript. The
object model is analogous to the ViewModel class implemented in C# and described above. The main
elements of the ViewModel are as follows:


extent Property: As in the previous version, this property gets or sets the selected location on
the map. It represents a rectangular area centered at the crosshairs. It is the main input
property of the ViewModel. Changing the value of the extent property causes the ViewModel
to update the demographic information.
In the JavaScript version, the extent property is implemented as a KnockoutJS observable. The
ViewModel class itself monitors changes to this property and updates the demographic
information when it changes.
sources Property: As in the previous version, this is a dictionary where the keys are information
type identifiers (e.g. “age”, “income”, etc.) and the values are objects of type InfoSource, which
provide tile-specific information. The sources dictionary is initialized by the constructor of the
ViewModel class.
The code below summarizes the code in the JavaScript version of the ViewModel constructor:
/*******************************************************************
* ViewModel class.
* This class provides various types of demographic information for
* a given location.
* The information is exposed as a 'sources' property, and the
* location is exposed as an 'extent' property.
* @constructor
*/
function ViewModel() {
// information sources available to views
this.sources = {};
this.sources.age = new InfoSource(this, "USA_Median_Age", …
this.sources.householdIncome = new InfoSource(this, …
this.sources.netWorth = new InfoSource(this, …
this.sources.homeValue = new InfoSource(this, …
// other info sources declared here…
// name of the location currently selected
this.selectedLocation = ko.observable("");
// location to retrieve information for
this.extent = ko.observable(null);
var self = this;
this.extent.subscribe(function (newExtent) {
self.updateValues();
});
}
Notice that JavaScript objects are ‘associative arrays’, which is the same as a dictionary or hash table.
The constructor populates the sources variable by creating properties on the fly and assigning them new
values. The following lines of code are equivalent:
// add a property called ‘age’ to the ‘sources’ object
this.sources.age = new InfoSource(this, "USA_Median_Age", …
// different syntax, same effect:
this.sources["age"] = new InfoSource(this, "USA_Median_Age", …
Notice also how the ViewModel constructor declares the extent property as a KnockoutJS observable,
then subscribes to it by specifying a function that should be called whenever the value of the property
changes. This is equivalent to attaching a handler to a PropertyChanged event in .NET.
.NET Developers:
Notice once again the use of the self variable to access the ViewModel class from within the local
function. In this scope, the variable this refers to the inner function itself, not to the ViewModel.
The updateValues function is implemented as follows:
/**
* Updates the information in all InfoValues that have subscribers
* attached to them based on the location defined by a given map extent.
*/
ViewModel.prototype.updateValues = function () {
// update info scale
this.infoScale(4); // state level
if (this.extent()) {
if (this.extent().getWidth() < 6e6) {
this.infoScale(3); // county level
}
if (this.extent().getWidth() < 2e5) {
this.infoScale(2); // tract level
}
}
// update all infoValues
for (member in this.sources) {
var source = this.sources[member];
source.updateValues();
}
}
As in the .NET version, the updateValues function updates the scale variable (that determines whether
the user is looking at States, Counties, or Tracts), then loops through the members of the sources
property and calls the updateValues method on those objects. The variable member in the for loop
takes on the values of the property names in the sources property, and sources[member] returns the
associated InfoSource object. This syntax is slightly different from C#, where a foreach loop would
return key-value pairs instead of the keys.
The JavaScript version of the InfoSource class is declared as follows (this is a slightly simplified version):
/**********************************************************************
* InfoSource class.
* The InfoSource belongs to a ViewModel and provides information about
* a specific demographic for the parent ViewModel's current extent.
* @constructor
*/
function InfoSource(model, shortUrl, keys, trimName) {
this.viewModel = model;
// owner ViewModel
this.shortUrl = shortUrl;
// url used to retrieve data/tiles
this.trimName = trimName;
// remove this part from item names
this.values = {};
// object that contains the values
// keys used to query/store values
keys = "NAME,ST_ABBREV," + keys;
var arr = keys.split(',');
for (var i = 0; i < arr.length; i++) {
var key = arr[i];
// add to values object
var infoValue = new InfoValue(key);
this.values[key] = infoValue;
}
}
The code stores a reference to the parent ViewModel and populates a values dictionary with InfoValue
objects that contain the key, a description, and the actual values. As in the .NET version, the dictionary
does not change while the application executes. Only the values within the InfoValue objects change.
Here is the code that updates the InfoSource objects when the extent property changes:
/**
* Updates the information in this InfoValue if it has subscribers.
*/
InfoSource.prototype.updateValues = function () {
// see if our values have any subscribers
var subscribers = 0;
for (var key in this.values) {
var infoValue = this.values[key];
subscribers += infoValue.value.getSubscriptionsCount();
}
// if there are subscribers, go update the values
if (subscribers > 0) {
if (this.viewModel.extent()) {
// build query/fields to retrieve
// NOTE: the query class is part of the Esri SDK for JavaScript
var query = new esri.tasks.Query();
query.geometry = this.viewModel.extent().getCenter();
query.outFields = [];
for (var key in this.values) {
query.outFields.push(key);
}
// run query
// NOTE: the queryTask class is part of the Esri SDK for JavaScript
var url = this.getQueryUrl();
var queryTask = new esri.tasks.QueryTask(url);
var self = this;
dojo.connect(queryTask, "onComplete",
function (featureSet) { self.gotInfoValues(featureSet); });
queryTask.execute(query);
}
}
}
The code starts by checking whether there are any subscribers to this InfoSource’s values property. This
is done for performance: there is no reason to update values that are not being used in the UI.
KnockoutJS makes this easy by providing the observable class with a getSubscriptionsCount method.
If there are subscribers, the code proceeds to build a query object populated with the key of the values
we are interested in. It then creates a queryTask and invokes it, passing as a parameter the function to
be executed when the task returns with the data. The function is called gotInfoValues and is
implemented as follows:
/**
* After getting values from Esri service, store them in this InfoSource.
*/
InfoSource.prototype.gotInfoValues = function (featureSet) {
if (featureSet.features.length > 0) {
// get attributes
var atts = featureSet.features[0].attributes;
// update selected location in parent ViewModel
this.viewModel.selectedLocation(atts["NAME"] + ", " + atts["ST_ABBREV"]);
// update values in our data object
for (var key in this.values) {
var infoValue = this.values[key];
infoValue.value(atts[key]);
}
} else {
// no data available: clear values
this.viewModel.selectedLocation("Please select a location within the USA.");
for (key in this.values) {
var infoValue = this.values[key];
infoValue.value(null);
}
}
}
The service returns the data requested in an attributes dictionary. The code loops through that object
and stores the returned values in the value property of the InfoValue objects in the InfoSource. Since
these are observable items, any subscribers will receive the proper notifications when the values change.
This is the code of the ViewModel class, this time in JavaScript. As you can see, it is very similar to the C#
version. The main differences are that the JavaScript version uses KnockoutJS observables instead of
properties that raise PropertyChanged events, and it leverages the associative arrays that are built into
JavaScript instead of the dictionary classes used in the C# version.
This ViewModel implementation can be tested even without a view. We can simply instantiate a
ViewModel object, set its extent property, then read the values in the source objects. We could also test
the bindings with a simplified View built using HTML and some KnockoutJS binding extensions.
In addition to the ViewModel, the application includes an esri-map.js file that contains some mapping
utilities. These are responsible for initializing the Esri map control, for handling resize events to update
its layout, and for updating the ViewModel’s extent property on a timer, so the application does not
update the information continuously as the user drags the map.
The esri-map.js file also contains a utility function used to select the user’s current location. This is
implemented as follows:
// center the map on the user's current location
function gotoCurrentLocation() {
if (navigator.geolocation) {
navigator.geolocation.watchPosition(function (result) {
// convert location to web mercator coordinates
var sr = new esri.SpatialReference({ wkid: 102113 });
var ptGeo = new esri.geometry.Point(
result.coords.longitude, result.coords.latitude, sr);
var pt = esri.geometry.geographicToWebMercator(ptGeo);
// create new extent centered at current location
var w = map.extent.getWidth();
var h = map.extent.getHeight();
var newExtent = new esri.geometry.Extent({
"xmin": pt.x - w / 2, "ymin": pt.y - h / 2,
"xmax": pt.x + w / 2, "ymax": pt.y + h / 2,
"spatialReference": sr
});
// apply new extent to map
map.setExtent(newExtent);
});
} else {
alert("Sorry, location services are not available.");
}
}
As in the C# version of the application, the code uses the browser’s geoLocation services to obtain the
user’s current location as a latitude/longitude pair of coordinates. It converts those into map
coordinates and centers the map on the point. This will trigger a notification that will update the
ViewModel automatically.
View Implementation (HTML/CSS)
The View implementation starts with a block of include directives that ensure all the libraries needed are
loaded. This is similar to adding references in .NET projects. Our application requires the following
libraries:




jQuery: utilities for manipulating the DOM and calling web services.
Wijmo: custom controls used to build the user interface.
Esri SDK: map control and geographic information queries.
KnockoutJS: data binding for JavaScript applications.
In addition to these includes, the View includes its own style sheet, the ViewModel, and the mapping
utilities mentioned above:
<!-- geoDashboard CSS and script -->
<link type="text/css" href="styles/style.css" rel="stylesheet" />
<script type="text/javascript" src="scripts/esri-map.js"></script>
<script type="text/javascript" src="scripts/view-model.js"></script>
Finally, the View contains a small script that executes when the main page finishes loading:
<script type="text/javascript">
// when document loads, create map, ViewModel, and apply bindings
// NOTE: the dojo library is included with the Esri SDK
dojo.addOnLoad(function () {
createMap();
vm = new ViewModel();
ko.applyBindings(vm);
});
</script>
The createMap function is defined in the “esri-map.js” file. It uses the Esri SDK for JavaScript to create a
map control and add it to the view:
var map;
function createMap() {
// create map with initial extent (continental USA)
var initExtent = new esri.geometry.Extent({
"xmin": -9120937, "ymin": 4724643,
"xmax": -8695365, "ymax": 5142031,
"spatialReference": { "wkid": 102100 }
});
map = new esri.Map("map", {
extent: initExtent,
wrapAround180: true,
navigationMode: "css-transforms"
});
// add background imagery layer
var url = "http://services.arcgisonline.com:80/ArcGIS/rest/services/NatGeo_World_Map/MapServer";
var layer = new esri.layers.ArcGISTiledMapServiceLayer(url);
map.addLayer(layer);
// when the extent changes, update ViewModel to match
var extentChangedTimer;
map.onExtentChange = function (extent) {
clearTimeout(extentChangedTimer);
extentChangedTimer = setTimeout(function () {
if (this.vm != null) {
this.vm.extent(map.extent);
}
}, 250);
};
}
The code creates a map control, initializes its properties (including the initial extent), and adds the map
to the view by specifying the id of the element that will hold the map (in this case, the “map” <div>). The
code also adds a background layer to show the map imagery.
Finally, the code adds a handler to the map’s onExtentChange event. The handler updates the extent
property on the ViewModel to match the map. Notice the use of a time-out to avoid redundant
notifications. The ViewModel is updated only when the map extent changes and then remains constant
for 250ms.
Once these elements are in place, we can start working on the actual View. Notice how the HTML below
is similar to the XAML used in the Silverlight version of the application:
<!-- title -->
<div class="black-background ui-helper-clearfix">
<div class="float-left">
<a href="http://wijmo.com"><img src="img/wijmoLogo_48.png" /></a>
</div>
<div class="float-left">
<div class="app-title">
ComponentOne GeoDashboard</div>
</div>
<div class="float-right">
<div class="app-subtitle" data-bind="text: selectedLocation">
Selected Location</div>
<div class="app-link" onclick="gotoCurrentLocation()">
Go to current location</div>
</div>
</div>
As in the Silverlight version of the application, the title bar contains three elements. The first shows the
application title. The second is bound to the ViewModel’s selectedLocation property, and is updated
automatically as the user zooms and pans the map (notice the data-bind attribute in the markup). The
last element is a hyperlink that calls the gotoCurrentLocation method described above.
The next element is the map and the crosshair display. This is implemented as follows:
<!-- map -->
<div id="map" class="map">
<!-- crosshairs -->
<div class="abs-center"
style="width: 100px; height: 100px;
margin-left: -50px; margin-top: -50px;
border-radius: 50px; border: solid 2px #5f2588;">
</div>
<div class="abs-center"
style="width: 200px; height: 200px;
margin-left: -100px; margin-top: -100px;
border-radius: 100px; border: solid 2px #5f2588;">
</div>
<div class="abs-center"
style="width: 2px; height: 100%; background: #5f2588; top: 0px;">
</div>
<div class="abs-center"
style="width: 100%; height: 2px; background: #5f2588; left: 0px;">
</div>
</div>
The “map” <div> above will host the map control. This is done by the call to createMap in our start-up
script. The cross-hair display is accomplished using absolute positioning and some css.
Notice that most css in the application is stored in the style.css file. The cross-hair display is an exception
because the styles used here are specialized and will not be re-used elsewhere on the page.
The last elements on the View are the actual information tiles.
As in the Silverlight section, we will show the implementation of two tiles, the “Median Net Worth” and
“Home Value Histogram”. The other tiles follow the same pattern, except they have different layouts
and use different controls to show specific types of information.
The HTML5/JavaScript version of the “Median Net Worth” looks like this:
If you remember the Silverlight version of the application, this tile should look familiar. It is implemented
using a Wijmo radial gauge control, as shown below:
<!-- net worth tile -->
<div class="tile" data-bind="with: sources.netWorth" >
<div class="tile-caption">
Median Net Worth</div>
<div class="tile-value"
data-bind="text: Globalize.format(values.MEDNW_CY.value(), 'c0')">
</div>
<div class="radial-gauge"
data-bind="wijradialgauge: {
value: values.MEDNW_CY.value,
ranges: [{
startWidth: 24, endWidth: 24, startValue: 0, endValue: 300000,
startDistance: 0.5, endDistance: 0.5,
style: { fill: '#ccc', stroke: 'none', opacity: 0.4}
},{
startWidth: 24, endWidth: 24, startValue: 0,
endValue: values.MEDNW_CY.value, startDistance: 0.5, endDistance: 0.5,
style: { fill: '#5f2588', stroke: 'none', opacity: 0.4}
}]}">
</div>
</div>
Notice that the outer <div> element contains a data-bind attribute that specifies the data source for the
whole <div> content. Inside the tile, there are three elements:
1) A static title showing the text “Median Net Worth”.
2) A <div> bound to the “MEDNW_CY” value, which represents the Median Net Worth for the
currently selected location on the map. The value is formatted as a currency with no decimals
using the Globalize library. Notice how the KnockoutJS bindings allow you to call JavaScript
methods directly from within the binding expression. In Silverlight, we would have to use a
StringFormat to accomplish this. The JavaScript approach is a lot more powerful and flexible in
this case.
3) The last div in the tile is bound to the same value, but displays it as a wijradialgauge control.
This control is part of the Wijmo library included earlier. The control is implemented in
JavaScript and supports KnockoutJS bindings. In this case, it binds the gauge pointer to the
“MEDNW_CY” value. It also specifies two ranges, one of which is also bound to the “MEDNW_CY”
value. The bound range creates the purple part of the gauge, and is update automatically along
with the pointer. Notice how the object model and bindings are similar to those used in the
Silverlight gauge control we used earlier.
The HTML5/JavaScript version of the “Home Value Histogram” looks like this:
<!-- home value histogram -->
<div class="tile tile-big" data-bind="with: sources.homeValue"
onclick="showTiles('homeValue')">
<div class="tile-caption">
Home Value Distribution</div>
<div>
<span class="tile-value" data-bind="text: values.NAME.value"></span>
<div class="bar-chart"
data-bind="wijbarchart: { seriesList: formatChartSeriesList(list) }">
</div>
</div>
</div>
If you remember the markup required to create the Silverlight version of the tile, you can see this
version is a lot more concise. This is possible because instead of setting the chart properties in the
markup, those are set in the “wijmo-defaults.js” file. Using this technique, you can specify defaults that
apply to all charts in the view.
The actual binding is specified by the “data-bind” attribute, which leverages the Wijmo Knockout
extensions to create a wijbarchart control and to populate the chart via its seriesList property. The
formatChartSeriesList function is a simple utility that converts the InfoView objects provided by the
ViewModel into the format required by the wijbarchart. This step is conceptually similar to using a
converter in XAML applications.
The formatChartSeriesList function is implemented as follows:
/***********************************************************************************
* Extracts values from a list and builds a 'seriesList' that can be used as a
* data source for a wijmo chart control.
*/
function formatChartSeriesList(list) {
var seriesList = [];
var xData = [];
var yData = [];
for (var i = 0; i < list.length; i++) {
xData.push(list[i].name());
yData.push(list[i].value());
}
seriesList.push({
label: "Homes",
data: { x: xData, y: yData }
});
return seriesList;
}
Notice that the yData array contains observable objects. When these values change, the chart is
automatically updated.
The use of Wijmo controls in the View is one of the most important points in this article. Wijmo controls
are implemented in pure JavaScript, using the standard jQuery and Globalize libraries. The controls fully
support KnockoutJS bindings, making it possible for the UI developer to create views that include
gauges, charts, sliders, and other rich UI controls in addition to standard HTML elements. The result is
applications with professional, rich user interfaces that can be developed quickly and deployed to any
device, including iPads, iPhones, and Android tablets and phones.
The remaining tiles follow a similar approach. They use the same CSS classes to achieve a consistent look
that can be updated easily, and use binding tags to link the View to the ViewModel.
The final version of the HTML5/JavaScript application looks like this:
To see it live, follow this link:
http://demo.componentone.com/Wijmo/GeoDashboard/
To download or browse the source code, follow this link:
http://our.componentone.com/samples/html5-geodashboard
As you can see, the HTML5/JavaScript application looks almost exactly the same as the Silverlight one.
The implementation is also virtually identical. Both applications follow the MVVM pattern and
completely separate logic from UI.
Of course, because this is a pure HTML5/JavaScript application, it also runs on mobile devices:
Comparison: Silverlight vs. HTML5/JavaScript
The table below shows the elements that compose a HTML5/ JavaScript application and the
corresponding elements in Silverlight applications:
Programming Language
Markup Language
Styling and Layout
Binding
UI/Controls
Data Access/LOB Apps
Tools
Targets
HTML/JavaScript applications
JavaScript
HTML/HTML5
CSS/HTML
KnockoutJS library
jQueryUI, Wijmo, Third-Party
Web services/JSON, Upshot?
Visual Studio, Various?
Desktop, Tablet, Phone
Silverlight/XAML applications
C# or VB
XAML
XAML/Resources
XAML/Binding class
System, Third-Party, Custom
RIA Services, OData, etc.
Visual Studio, Expression Blend
Desktop, Windows Phone
Programming Language: JavaScript is the programming language of choice for HTML applications. With
Windows 8 Metro applications, it may start gaining ground on the desktop as well. JavaScript is a simple
but powerful interpreted language, with some object-oriented features. I believe most developers
would agree that C# is a more powerful language, with features that make it suitable for large-scale
development. But JavaScript can hold its own, especially for small or distributed applications.
Markup Language: HTML and CSS are well-known to everyone and dispense introductions. XAML has
more powerful layout features (with its flexible Grid panel), but HTML and CSS are catching up quickly.
Modern browsers can render gradients, effects, rounded rectangles, and even arbitrary content in
HTML5 canvas elements. Designing rich page layouts with HTML is still much harder than with XAML.
Perhaps the main advantage of HTML over XAML is the fact that it can be searched easily and is SEOfriendly.
Binding: This is an item where JavaScript simply did not have a story at all. This changed with the release
of the KnockoutJS library, which represents a huge step in bringing balance to our table. KnockoutJS is
definitely comparable to the native binding mechanisms in XAML. The fact that a library such as
KnockoutJS could be written in JavaScript is a perfect illustration of the power and flexibility of the
language.
UI Controls: Silverlight and XAML have a solid advantage in this item. In addition to the powerful
controls that ship with Visual Studio, Silverlight developers can easily buy, download, or create their own
controls. On the HTML side of the table, there used to be only the standard HTML controls (buttons,
textboxes, lists and combos). That started to change a few years ago when jQuery became the de-facto
standard JavaScript library for interacting with the DOM, and the jQueryUI library established a de-facto
standard for creating re-usable, styleable, and semantically correct controls. Since then jQueryUI and
jQueryMobile have been gaining popularity, and a rich eco-system is developing around these
technologies. (Creating new controls in JavaScript remains however much harder than creating them in
Silverlight).
Data Access: Virtually every application needs to retrieve and show data. JavaScript is well-equipped for
asynchronously retrieving data from web services. The JSON format automatically converts the raw data
into regular JavaScript objects. But Silverlight applications have similar mechanisms and a lot more.
Silverlight applications can use RIA services to support Entity Framework models. This means easy and
standard integration with Sql Server databases, a common data platform for LOB application
development. Also in this area, JavaScript is making quick progress. The Upshot.js library is a good
example of this.
Tools: The last item on the list are the development tools used with each platform. Visual Studio can be
used to develop HTML5/JavaScript applications. There is an HTML designer, CSS tooltips, and a
JavaScript debugger with some IntelliSense. Although JavaScript is an interpreted language, there are
‘minifiers’ that can reduce code size by removing comments and whitespace, and renaming variables.
Many minifiers also perform code checks and can help detect errors at design-time. But C# has a huge
advantage here as well. The compiler can catch a lot more mistakes before the app even runs. Also,
IntelliSense is more extensive and accurate. Finally, you can automatically re-factor code, which makes
tasks such as renaming variables and methods easy and safe.
Target Devices: This is clearly the main advantage of the HMTL/JavaScript platform. It holds the promise
of being able to run everywhere, from desktop systems to tablets to all kinds of smart phones. Naturally,
most applications must be adapted to run on different devices. They have to account for differences in
screen size, input methods, and system-specific capabilities. But the changes are relatively simple,
especially if you de-couple logic from presentation using MVVM or a similar pattern. Also, sometimes
compromises have to be made if you want to target older desktop browsers such as IE8. But overall it is
fair to say that HMTL/JavaScript applications do run everywhere. Silverlight/XAML applications, in
contrast, can be deployed to most desktop machines (PCs and Macs) and also to Windows Phones. But
they do not run on tablets or non-Windows phones (e.g. iPhones, Android). If your app must run on an
iPhone or iPad, HMTL/JavaScript is clearly the better choice.
Conclusion
Some people say Silverlight is dead. That seems to be true in the sense that Microsoft is no longer rolling
out new releases every four months. But that means simply that Silverlight has reached maturity, and
developers no longer have to worry about constantly rebuilding (and re-testing) their applications
against the latest bits. Silverlight remains one of the best platforms for web development, and that
ensures it has a long life ahead. Microsoft is a good example: they chose Silverlight to implement the
new Azure SDK and the LightSwitch development tool, both extremely popular and important products.
HTML5 and JavaScript hold promise to be the platform of the future, mainly because they run on every
device. However, Silverlight development is still much easier and faster, especially for business
applications. The tools are more mature and complete, and the ability to detect errors at compile time is
a huge bonus.
HTML5 and JavaScript have come a long way over the last few months. First, jQuery brought browserindependence and easy DOM manipulation. At the same time, new browsers started to support HTML5
features such as geo-location, isolated storage, and the super-flexible canvas element. Then came
KnockoutJS, which makes it possible to separate the HTML (View) from the JavaScript (ViewModel). This
separation makes creating and debugging JavaScript applications much easier, and helps close the gap
between HTML5/JavaScript and Silverlight.
The main piece still missing on the HTML5/JavaScript stack is a rich, business-ready data layer. Silverlight
has had this for a long time in the form of RIA services. JavaScript still does not have it. But that is
coming and will be here soon (the Upshot library is a promising effort in this direction).
What does all this mean? Simple: more choices and better tools for all developers. Learn the tools and
select the best one for each job.
Resources
GeoDashboard, Silverlight Version
Run the application
http://demo.componentone.com/Silverlight/GeoDashboard/
Source Code (browse and download)
http://our.componentone.com/samples/silverlight-geodashboard
Pre-requisites for building the application
http://www.componentone.com/SuperProducts/StudioSilverlight/ (ComponentOne Silverlight controls)
http://help.arcgis.com/en/webapi/silverlight/index.html (Esri Silverlight SDK)
GeoDashboard, HTML5/JavaScript Version
Run the application
http://demo.componentone.com/Wijmo/GeoDashboard/
Source Code (browse and download)
http://our.componentone.com/samples/html5-geodashboard
Recommended downloads
http://wijmo.com/ (ComponentOne Wijmo controls)
http://www.esri.com/software/arcgis/web-mapping/javascript.html (Esri JavaScript SDK)
Suggested Reading
JavaScript and Object-oriented programming:
https://developer.mozilla.org/en/JavaScript/A_re-introduction_to_JavaScript
https://developer.mozilla.org/en/Introduction_to_Object-Oriented_JavaScript
JavaScript binding with KnockoutJS:
http://knockoutjs.com/
http://www.knockmeout.net/
jQuery library:
http://jquery.com/
JavaScript/jQuery localization and formatting:
http://www.ibm.com/developerworks/opensource/library/os-jquerynewpart1/index.html
http://www.codeproject.com/Articles/152732/Exploring-Globalization-with-jQuery
https://github.com/jquery/globalize
JavaScript/jQuery layout:
http://layout.jquery-dev.net/index.cfm
CodeProject Articles:
http://www.codeproject.com/Articles/365120/KnockoutJS-vs-Silverlight
http://www.codeproject.com/Articles/219370/From-Silverlight-To-HTML5