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
© Copyright 2024