Click here to Skip to main content
15,867,308 members
Articles / Web Development / HTML5

A Stock Portfolio Application Using KnockoutJS

Rate me:
Please Sign up or sign in to vote.
5.00/5 (16 votes)
13 Oct 2012CPOL25 min read 97.1K   5.1K   111   16
An article that shows how to implement MVVM apps with KnockoutJS and custom controls.

application screenshot

Introduction

As tablets and SmartPhones become more popular, developers face increased demand to write applications that can run on those devices as well as on desktops. HTML5 and JavaScript can help developers achieve this goal with a single code base.

Until recently, writing applications using JavaScript and HTML was quite difficult, because developers had to create all the business logic, the user interface, and then connect all these elements using JavaScript code and the HTML document object model. By comparison, writing web applications in Silverlight was much easier, because the logic and the user interface could be implemented largely independently, and later connected using declarative bindings.

This situation started to change with the introduction of JavaScript libraries such as jQuery, jQuery UI, and KnockoutJS. These libraries introduce standard ways to manipulate the HTML document (jQuery), to add user interface widgets to HTML documents (jQueryUI), and to bind the business logic to the UI declaratively (KnockoutJS).

In parallel with the introduction of these popular JavaScript libraries, most popular browsers are gradually adding support for HTML5 features such as local storage, geo-location, rich media, and others. These features also contribute to make the HTML/JavaScript combination a great platform for web applications.

This article describes the implementation of Invexplorer, a stock portfolio application similar to Google finance. The application was written in HTML5 and JavaScript, using the libraries mentioned above.

To run the application in your browser, click here.

Users can add stocks to the portfolio using a smart auto-complete textbox. If they type "ford mot" for example, the textbox will show a list with two symbols that correspond to the "Ford Motor Company". They can then select one of the symbols and click the "Add to Portfolio" button. Once items have been added to the portfolio, users can chart their price history by clicking a checkbox, and can edit the trading information by typing directly into the grid.

Portfolios are automatically persisted to local storage. When users open the application, their portfolio is loaded automatically.

The application follows the MVVM pattern. The view model classes were implemented using the KnockoutJS library. They can be tested independently of the view, and could be re-used in case we wanted to create new views for tablets or phones. Properties are implemented as KnockoutJS "observable" and "observableArray" objects, which allows them to be used as sources and targets in KnockoutJS bindings.

The view was implemented using HTML5 and two custom controls: an auto-search box from the jQueryUI library and a chart control from the Wijmo library. Both libraries have extensions that support KnockoutJS and both can be used simply by including the appropriate links in your HTML pages (no download/install is required).

Shortly before this article was finished, CodeProject published an excellent article by Colin Eberhardt entitled "KnockoutJS vs. Silverlight". Colin focuses on KnockoutJS, and uses a simple sample that requires no custom controls. The article is excellent - definitely recommended reading. You can find it here.

If you would like to see a comparison between Silverlight and KnockoutJS implementations of the Invexplorer application, along with a quick introduction to JavaScript and MVVM, you can find that here.

The latest revision of this article added a TypeScript version of the Invexplorer application. The TypeScript version is discussed in the last section of the article.  

Important disclaimer:

Invexplorer is a sample application designed to show how to use KnockoutJS and custom controls. It uses financial data from Yahoo Finance, which is not a free service. If you would like to use the code provided as a basis for actual applications, you must contact Yahoo or some other financial data provider to obtain the licenses required.

Very Brief 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.

The diagram below illustrates the relationship between elements in MVVM applications (the diagram was taken from a good MVVM introduction that can be found here.):

MVVM diagram

Very Brief Introduction to KnockoutJS

KnockoutJS enables MVVM development by providing two main elements:

  • JavaScript classes such as observable and observableArray, which are used to implement ViewModel variables that issue notifications when their value changes (similar to INotifyPropertyChanged in .NET development).
  • HTML markup extensions that refer to these observables and automatically update the page when their values change. The markup extensions are very rich. In addition to showing values such as numbers and strings, they can be used to customize styles, to enable or disable UI elements, and to elements that represent collections such as lists, grids, or charts. The markup extensions are similar to Binding objects in XAML development.

To develop applications using KnockoutJS, you start by creating ViewModel classes that contain the application logic and expose an object model built of observable objects. These classes can be tested before moving on to the next step, which is creating the View.

The View is created using HTML and css. The only difference is the addition of markup extensions such as "data-bind: text" (to show static content) or "data-bind: value" (to create two-way bindings in elements such as text boxes).

Finally, the ViewModel and the View are connected with a single call to the KnockoutJS applyBindings method, which takes the object model as a parameter and actually builds the bindings.

This is KnockoutJS in a nutshell. In my opinion, this conceptual simplicity is one of the library's main strengths. But there's a lot more to it. The official KnockoutJS site has many samples, tutorials, and great documentation. You can get all the details at knockoutjs.com.

The Invexplorer Sample Application

The Invexplorer application was loosely based on the Google Finance site. It allows users to select stocks and add them to portfolios. Once a stock is added to the portfolio, the application retrieves historical price data and shows the evolution of the prices on a chart. The user may select which stocks should be charted and over what period. The user may also enter the prices he paid for the stocks and the amount purchased. If this information is provided, the application calculates the return on each item.

The portfolio information is persisted so when users quit the application so their edits are not lost and can be re-loaded automatically when the application runs again.

The original version of the Invexplorer application was written in Silverlight, using the MVVM pattern (you can see the Silverlight version here). When KnockoutJS was introduced, we thought it would be an ideal candidate for a port to HTML5/JavaScript.

Porting the application took only a couple of days. Most of the work went into re-writing the view model classes in JavaScript, using the KnockoutJS library to implement properties as observable objects. Writing the view was very easy, because the controls we needed were easy to find: the auto-search box used to select new stocks is part of the jQueryUI library, and the chart we used is part of the Wijmo library. Both control libraries support KnockoutJS, and both are stable, powerful, and easy-to-use.

View Model Implementation (JavaScript)

The class diagram below shows the classes that implement the view model:

ViewModel class diagram

The main properties in each class are described below:

ViewModel class main properties:

  • companies: An array of Company objects that contains the complete list of ticker symbols, company names, and historical price data. The list is populated with symbols and names when the model is created, and the price data is retrieved on demand.
  • portfolio: A Portfolio object that contains a list of portfolio items.
  • chartSeries, chartStyles: Two observableArray objects that contain data prepared for display on a line chart control. The chartSeries array contains price variations rather than absolute values, so series for different companies can be compared easily. The chartStyles array contains CSS style definitions used to specify the color to be used for each data series.
  • minDate: An observable date object that defines the starting date for the chart. This allows users to compare different stocks over specific periods such as this year, last 12 months, etc.
  • updating, chartVisible: Two observable objects that expose the state of the view model and enable the view to provide user feedback while data is being loaded and to hide chart-related elements when the chart is empty.

Company class main properties:

  • symbol: The trading symbol for this company.
  • name: The full company name.
  • prices: An observable containing an array of date/price objects. Note that this is not an observableArray object; rather it is a regular observable object whose value is a regular array. This array is filled on demand, one time per company.
  • chartSeries: An observable containing a custom object used to define a single series for the chart control. This custom object specifies the series x and y values as well as the series label, markers, and whether it should be displayed on the chart legend. This array is created and refreshed when the prices array is filled and also when the minDate property of the parent ViewModel changes value.

Portfolio class main properties:

  • items: An observableArray containing PortfolioItem objects. Each portfolio item refers to a company, and may include transaction data such as the number of shares purchased and the purchase price.
  • newSymbol: An observable containing a stock symbol to be added to the portfolio. This value is bound to an auto-search textbox in the View which allows users to select stocks they want to add to the portfolio.
  • canAddNewSymbol: An observable containing a Boolean value that determines whether the newSymbol property currently contains a valid symbol that is not already included in the current portfolio. This variable is used to enable or disable the UI element used to add new items to the items collection.

PortfolioItem class main properties:

  • symbol: Trading symbol of the company that this item represents. This value is used as a key into the ViewModel’s companies array.
  • company: The Company object that contains the company information including name and price history.
  • lastPrice, change: These are observable objects that are initially set to null, and are updated when the price history for the company becomes available.
  • shares, unitCost: These are observable objects that can be edited by the user. These values represent the user transactions, and are used to calculate the cost of each portfolio item. These values belong to the portfolio item itself.
  • chart: An observable Boolean variable that determines whether this item should be included in the chart.
  • value, cost, gain, etc: Several computed objects that provide values that are calculated based on other properties. For example, value = shares * lastPrice.

The constructor of the ViewModel class is implemented as follows:

JavaScript
/***********************************************************************************
* ViewModel class.
* @constructor
*/
function ViewModel() {
  var self = this;

  // object model
  this.companies = [];
  this.updating = ko.observable(0);
  this.minDate = ko.observable(null);
  this.chartSeries = ko.observable([]);
  this.chartStyles = ko.observable([]);
  this.chartHoverStyles = ko.observable([]);
  this.chartVisible = ko.observable(false);
  this.setMinDate(6); // chart 6 months of data by default
  this.minDate.subscribe(function () { self.updateChartData() });
  this.portfolio = new Portfolio(this);

  // create color palette
  this.palette = ["#FFBE00", "#C8C800", …];

The first part of the constructor declares the properties that were described above. Notice that the minDate property is an observable and the ViewModel class subscribes to it. When the value of the minDate property changes, the ViewModel calls updateChartData so the chart is re-generated to reflect the date range requested by the user.

The palette property contains an array with colors used to create the chart series for each item. The colors are also displayed in the grid, which acts as a legend for the chart. Adding this type of purely UI-related elements to ViewModel classes is fairly common. After all, ViewModels exist to drive views (that is why they called "ViewModels", and not just "Models").

Once the properties have been declared, the constructor populates the companies array as follows:

JavaScript
// populate companies array
$.get("StockInfo.ashx", function (result) {
      var lines = result.split("\r");
      for (var i = 0; i < lines.length; i++) {
          var items = lines[i].split("\t");
          if (items.length == 2) {
              var c = new Company(self, $.trim(items[0]), $.trim(items[1]));
              self.companies.push(c);
          }
      }

      // load/initialize the portfolio after loading companies
      self.portfolio.loadItems();
});

The code uses the jQuery get method to invoke a service called "StockInfo.ashx", which is part of the Invexplorer application. The service executes asynchronously and returns list of company symbols and names which is parsed and added to the companies array.

.NET Developers: Notice the use of the "self" variable to access the ViewModel class from within local functions. In this scope, the variable this refers to the inner function itself, not to the ViewModel. This is a common JavaScript technique.

After the company data has been loaded, the constructor calls the portfolio’s loadItems method to load the last saved portfolio from local storage. In order for this to work, the portfolio must be saved when the user closes the application. This is done in the last block of the constructor:

JavaScript
// save portfolio when window closes
  $(window).unload(function () {
      self.portfolio.saveItems();
  });
}

This code uses jQuery to connect to the window’s unload event, which is called when the user closes the application. At this point, the portfolio’s saveItems method is called and the current portfolio is saved to local storage.

The saveItems and loadItems methods are part of the Portfolio class. Before we show their implementation, here is the Portfolio constructor:

JavaScript
/***********************************************************************************
* Portfolio class.
* @constructor
*/
function Portfolio(viewModel) {
    var self = this;
    this.viewModel = viewModel;
    this.items = ko.observableArray([]);
    this.newSymbol = ko.observable("");
    this.newSymbol.subscribe(function () { self.newSymbolChanged() });
    this.canAddSymbol = ko.observable(false);
}

The constructor keeps a reference to the parent ViewModel, creates the items observable array that will contain the portfolio items, and declares a newSymbol observable. The newSymbol property contains the symbol of a company to be added to the portfolio.

The constructor subscribes to changes in the newSymbol property so whenever its value changes, the newSymbolChanged method is called. The newSymbolChanged method in turn sets the value of the canAddSymbol property to true if the new symbol is valid and does not correspond to any of the items already in the portfolio. The newSymbol and canAddSymbol properties are used by the view to allow users to add items to the portfolio.

And here are the methods that save and load portfolio items from local storage:

JavaScript
// saves the portfolio to local storage
Portfolio.prototype.saveItems = function () {
  if (localStorage != null) {

    // build array with items
    var items = [];
    for (var i = 0; i < this.items().length; i++) {
      var item = this.items()[i];
      var newItem = {
        symbol: item.symbol,
        chart: item.chart(),
        shares: item.shares(), 
        unitCost: item.unitCost()
      };
      items.push(newItem);
    }

    // save array to local storage
    localStorage["items"] = JSON.stringify(items);
  }
}

.NET Developers: Notice the use of the Portfolio.prototype.saveItems syntax used to define the method. The "prototype" keyword attaches the method to every instance of the Portfolio class. This a common way to implement objects in JavaScript.

The method starts by checking that the localStorage object is defined. This is an HTML5 feature available in all modern browsers. Then it builds an array containing the information that should be persisted for each portfolio item (symbol, chart, shares, and unitCost). Finally, the array is converted into a string using the JSON.stringify method and saved to storage under the key "items".

The loadItems method reads this information back from local storage:

JavaScript
// loads the portfolio from local storage (or initializes it with a few items)
Portfolio.prototype.loadItems = function () {

    // try loading from local storage
    var items = localStorage != null ? localStorage["items"] : null;
    if (items != null) {
        try {
            items = JSON.parse(items);
            for (var i = 0; i < items.length; i++) {
                var item = items[i];
                this.addItem(item.symbol, item.chart, item.shares, item.unitCost);
            }
        }
        catch (err) { // ignore errors while loading...
        }
    }

    // no items? add a few now
    if (this.items().length == 0) {
        this.addItem("AMZN", false, 100, 200);
        this.addItem("YHOO", false, 100, 15);
    }
}

The method starts by retrieving the "items" string from localStorage. It uses the JSON.parse method to convert the string into a JavaScript array object and then loops through the items in the array calling the addItem method on each one.

.NET Developers: The JSON.stringify and JSON.parse methods are the JavaScript analogous of .NET serializers. The provide a simple way to convert objects into and from strings.

The addItem method is implemented as follows:

JavaScript
// add a new item to the porfolio
Portfolio.prototype.addItem = function (symbol, chart, shares, unitCost) {
    var item = new PortfolioItem(this, symbol, chart, shares, unitCost);
    this.items.push(item);
}

The PortfolioItem class represents items in the portfolio. Here is the constructor:

JavaScript
/***********************************************************************************
* PortfolioItem class.
* @constructor
*/
function PortfolioItem(portfolio, symbol, chart, shares, unitCost) {
  var self = this;
  this.portfolio = portfolio;
  this.symbol = symbol;

  // observables
  this.lastPrice = ko.observable(null);
  this.change = ko.observable(null);

  // editable values
  this.shares = ko.observable(shares == null ? 0 : shares);
  this.unitCost = ko.observable(unitCost == null ? 0 : unitCost);
  this.chart = ko.observable(chart == null ? false : chart);
  this.shares.subscribe(function () { self.parametersChanged() });
  this.unitCost.subscribe(function () { self.parametersChanged() });
  this.chart.subscribe(function () { self.updateChartData() });

  // find company
  this.company = portfolio.viewModel.findCompany(symbol);
  if (this.company != null) {
    this.company.prices.subscribe(function () { self.pricesChanged() });
    this.pricesChanged();
    this.company.updatePrices();
  }

  // computed observables
  this.name = ko.computed(function() {…}, this);
  this.value = ko.computed(function () {…}, this);
  this.cost = ko.computed(function () {…}, this);
  this.gain = ko.computed(function () {…}, this);
  this.color = ko.computed(this.getColor, this);

  // finish initialization
  this.updateChartData();
  this.parametersChanged();
}

The constructor starts by storing a reference to the parent portfolio and to the company symbol. Then it declares the lastPrice and change properties, two observables which will be obtained later from the web service.

Next, the constructor declares the properties that can be changed by the user: chart, unitCost, and shares. Notice how the constructor also subscribed to these observables so when the user edits any of these values, the portfolio item invokes the methods needed to update the item parameters or the chart data.

The pricesChanged method, invoked when the price history for the item’s company becomes available, is implemented as follows:

JavaScript
PortfolioItem.prototype.pricesChanged = function () {
  var prices = this.company.prices();
  if (prices.length > 1) {
    this.lastPrice(prices[0].price);
    this.change(prices[0].price - prices[1].price);
    if (this.chart()) {
      this.updateChartData();
    }
  }
}

The method retrieves the price history from the company.prices property. If the price history is already available, then the code updates the value of the lastPrice and change properties. If the item is currently configured to appear on the chart, the method calls updateChartData, which calls the updateChartData method on the ViewModel class:

JavaScript
PortfolioItem.prototype.updateChartData = function () {
  var vm = this.portfolio.viewModel;
  vm.updateChartData();
}

Here is the implementation of the updateChartData method:

JavaScript
// update chart data when min date changes
ViewModel.prototype.updateChartData = function () {

  // start with empty lists
  var seriesList = [], stylesList = [], hoverStylesList = [];

  // add series and styles to lists
  var items = this.portfolio.items();
  for (var i = 0; i < items.length; i++) {
      var item = items[i];
      if (item.chart()) {

          var series = item.company.updateChartData();
          seriesList.push(series);

          var style = { stroke: item.getColor(), 'stroke-width': 2 };
          stylesList.push(style);

          var hoverStyle = { stroke: item.getColor(), 'stroke-width': 4 };
          hoverStylesList.push(hoverStyle);
      }
  }

  // update chartVisible property
  this.chartVisible(seriesList.length > 0);

  // update chartSeries and styles
  this.chartStyles(stylesList);
  this.chartHoverStyles(hoverStylesList);
  this.chartSeries(seriesList);
}

The updateChartData data method loops through the portfolio items. For each item with the chart property set to true, the code creates a new chart series, a new chart style, and a new chart 'hover style'. All these objects are added to arrays.

When the loop is complete, the arrays are used to update the chartStyles, chartHoverStyles, and chartSeries properties. These are all observable properties bound to the chart control in the view.

The series variable contains the actual data to be plotted on the chart. It is calculated by the updateChartData method of the Company class:

JavaScript
// update data to chart for this company
Company.prototype.updateChartData = function () {
  var xData = [], yData = [];

  // loop through prices array
  var prices = this.prices();
  for (var i = 0; i < prices.length; i++) {

    // honor min date
    if (prices[i].day < this.viewModel.minDate()) {
        break; 
    }

    // add this point
    xData.push(prices[i].day);
    yData.push(prices[i].price);
  }

  // convert to percentage change from first value
  var baseValue = yData[yData.length - 1];
  for (var i = 0; i < yData.length; i++) {
    yData[i] = yData[i] / baseValue - 1;
  }

  // return series object with x and y values
  var series = {
    data: { x: xData, y: yData },
    label: this.symbol,
    legendEntry: false,
    markers: { visible: false }
  };
  return series;
}

The code converts the historical prices into percentage changes, making it easier to compare the performance of multiple companies shown on the same chart. The values calculated are wrapped into a "series" object that contains properties expected by the Wijmo chart control that will be used to show the data.

The last interesting method in our ViewModel class is the one that loads the historical price data. This method is given below:

JavaScript
// get historical prices for this company
Company.prototype.updatePrices = function () {
  var self = this;

  // don't have prices yet? go get them now
  if (self.prices().length == 0) {

    // go get prices
    var vm = self.viewModel;
    vm.updating(vm.updating() + 1);
    $.get("StockInfo.ashx?symbol=" + self.symbol, function (result) {

      // got them
      vm.updating(vm.updating() - 1);

      // parse result
      var newPrices = [];
      var lines = result.split("\r");
      for (var i = 0; i < lines.length; i++) {
        var items = lines[i].split("\t");
        if (items.length == 2) {
          var day = new Date($.trim(items[0]));
          var price = $.trim(items[1]) * 1;
          var item = { day: day, price: price };
          newPrices.push(item);
        }
      }

      // update properties
      self.prices(newPrices);

      // update chart series data
      self.updateChartData();
    });

  } else {

    // same data, different min date
    self.updateChartData();
  }
}

The method starts by checking whether the prices have already been loaded for this company. If they haven’t, the method increments the updating property (so the UI can that there is download activity going on).

The method then calls the "StockInfo.ashx" service again, this time passing the stock symbol as a parameter. When the service returns, the updating property is decremented and the value returned is parsed into a newPrices array. Finally, the method updates the value of the prices property and updates the chart data.

View Implementation (HTML/CSS)

The View is implemented in the default.html file. The file starts with a block of include statements which load the libraries used by the application. This is similar to adding references in .NET projects. The Invexplorer application uses the following libraries:

  • KnockoutJS: data binding for JavaScript applications.
  • jQuery: utilities for manipulating the DOM and calling web services.
  • jQueryUI: UI controls including the auto-search box.
  • Wijmo: UI controls including the line chart control.
  • Knockout-jQuery and Knockout-Wijmo: libraries that add KnockoutJS support to the jQuery and Wijmo control libraries.
  • ViewModel: the Invexplorer view model classes described above.

The Knockout-jQuery library used in this project was written by Mike Edmunds and is available from github. The Knockout-Wijmo library is included with Wijmo and can be included directly from the Wijmo CDN.

In addition to these includes, the view includes a small script block that does two things:

  1. Instantiates the ViewModel and applies the bindings (using the KnockoutJS applyBindings method)
  2. Configures the jQueryUI autoComplete control to show HTML in the dropdown list instead of plain text. This is an optional step, not related to the Invexplorer application. It allows us to highlight partial matches in the auto-complete list.
JavaScript
<script type="text/javascript">

    // initialize application on page load
    $(function () {

        // create ViewModel and apply bindings
        var vm = new ViewModel();
        ko.applyBindings(vm);

        // configure auto-complete control to render html instead of plain text
        // http://stackoverflow.com/questions/3488016/using-html-in-jquery-ui-autocomplete
        $("#autoComplete").autocomplete().data("autocomplete")._renderItem =
    function (ul, item) {
        return $("<li></li>")
        .data("item.autocomplete", item)
        .append("<a>" + item.label + "</a>")
        .appendTo(ul);
    };
    });
</script>

The body of the default.html page starts with a header that displays a title and some information about the application. Below the header comes the portfolio table, which is a standard HTML table element defined as follows:

XML
<!-- portfolio table -->
<table>

  <!-- table header -->
  <thead>
    <tr>
      <th class="left">Name</th>
      <th class="left">Symbol</th>
      <th class="left">Chart</th>
      <th class="right">Last Price</th>
      <th class="right">Change</th>
      <th class="right">Change %</th>
      <th class="right">Shares</th>
      <th class="right">Unit Cost</th>
      <th class="right">Value</th>
      <th class="right">Gain</th>
      <th class="right">Gain %</th>
      <th class="center">Delete</th>
    </tr>
  </thead>

The table header simply specifies the header for each column on the table. The interesting part is the table body, which uses KnockoutJS bindings as follows:

XML
<!-- table body-->
  <tbody data-bind="foreach: portfolio.items">
    <tr>
      <td>
        <span data-bind="style: { backgroundColor: color }">
              </span>
         <span data-bind="text: name"></span></td>
      <td data-bind="text: symbol"></td>
      <td class="center">
        <input data-bind="checked: chart" type="checkbox" /></td>
      <td class="right" data-bind="text: Globalize.format(lastPrice(), 'n2')"></td>
      <td class="right" data-bind="text: Globalize.format(change(), 'n2'),
          style: { color: $root.getAmountColor(change()) }"></td>
      <td class="right" data-bind="text: Globalize.format(changePercent(), 'p2'),
          style: { color: $root.getAmountColor(changePercent()) }"></td>
      <td><input class="numeric" data-bind="value: shares" /></td>
      <td><input class="numeric" data-bind="value: unitCost" /></td>
      <td class="right" data-bind="text: Globalize.format(value(), 'n2')"></td>
      <td class="right" data-bind="text: Globalize.format(gain(), 'n2'),
          style: { color: $root.getAmountColor(gain()) }"></td>
      <td class="right" data-bind="text: Globalize.format(gainPercent(), 'p2'),
          style: { color: $root.getAmountColor(gainPercent()) }"></td>
      <td class="center">
          <a class="hlink" data-bind="click: $root.portfolio.removeItem">x</a></td>
    </tr>
  </tbody>
</table>

The tbody element specifies the data source for the table. In this case, the source is the portfolio items property. Below the tbody element, we specify a single row (tr element) with several cells (td elements). Each cell contains a data-bind attribute that specifies the data it will display and in some cases the formatting and the color to be used for showing the cells.

The most common binding is "data-bind: text", which specifies the content of the cell. The content can be any JavaScript expression, including calls to the Globalize library. Similarly, the "data-bind: value" binding is used to display editable values in text boxes.

Another common binding is "data-bind: style", which allows you to specify CSS elements to be used when rendering the cell. The table above uses style bindings to show positive amounts in green and negative amounts in red. This is done with a call to the getAmountColor method, which plays the role of a binding converter in XAML.

Finally, the "data-bind: click" is used to create a column with buttons that can be used to remove items from the portfolio. The click event is bound to the portfolio.removeItem method, which is invoked and automatically receives a parameter that specifies the item that was clicked.

Building HTML tables with KnockoutJS is very similar to building data grids in XAML.

Below the portfolio table comes the section that allows users to add items to the portfolio. This is implemented using a jQueryUI auto-complete control and a regular HTML button:

XML
<!-- add symbol -->
<div class="addSymbol">
  Add Symbol: 

  <!-- jQueryUI autocomplete -->
  <input id="autoComplete" type="text" data-bind="
    value: portfolio.newSymbol,
      jqueryui: {
        widget: 'autocomplete',
        options: {

          /* require two characters to start matching */
          minLength: 2, 

          /* use ViewModel's getSymbolMatches to populate drop-down */
          source: function(request, response) {
            response(getSymbolMatches(request)) 
          },

          /* update current portfolio's newSymbol property when drop-down closes */
          close: function() {
            portfolio.newSymbol(this.value)
          }
        }
      }" />

  <!-- add the selected symbol to the portfolio -->
  <button data-bind="
    click: function() { portfolio.addNewSymbol()},
    enable: portfolio.canAddNewSymbol">
    Add to Portfolio
  </button>

  <!-- progress indicator (visible when ViewModel.updating != 0) -->
  <span class="floatRight" data-bind="visible: updating">
    <i> getting data...</i>
  </span>
</div>

The input element gets the auto-complete behavior from the jQueryUI library. We use data binding to specify the list of valid choices and the action to be taken when the user makes a selection.

The source option specifies that the list of valid choices will be provided by the getSymbolMatches method of the ViewModel class. This method takes the input provided by the user (for example "gen mot") and returns a list of companies that have those terms in their name or symbol (in this case, a match would be "General Motors"). The values returned are HTML, so matches are highlighted in the auto-complete drop-down.

The close option specifies a method that is invoked when the user picks an item from the list. In this case, the method sets the value of the portfolio’s newSymbol property. Recall that setting this value will automatically update the value of the canAddNewSymbol property, which is used in the next binding.

Binding controls involves setting options and property values in HTML. This is similar to setting property values in XAML.

Next to the input element there is a button with two bindings: the click binding invokes the addNewSymbol method in the portfolio class; the enable binding ensures the button is enabled only when the canAddNewSymbol property is set to true (which happens when a symbol is selected and if this symbol is not yet included in the portfolio). These bindings play the role of the ICommand interface in XAML.

The last element in this section is a "getting data" message with a visible binding that ensures the message is visible only while the ViewModel is downloading some data.

The next section contains commands used to select the time span shown on the chart:

XML
<!-- links to select time span to be charted -->
<div data-bind="visible: chartVisible">
  <a class="hlink" data-bind="click: function() { setMinDate(6) }">6m</a> 
  <a class="hlink" data-bind="click: function() { setMinDate(0) }">YTD</a> 
  <a class="hlink" data-bind="click: function() { setMinDate(12) }">1y</a> 
  <a class="hlink" data-bind="click: function() { setMinDate(24) }">2y</a> 
  <a class="hlink" data-bind="click: function() { setMinDate(36) }">3y</a> 
  <a class="hlink" data-bind="click: function() { setMinDate(1000) }">All</a> 
</div>

The whole section has a visible binding that ensures it is displayed only if the chart is currently visible. Within the section, there are links with click bindings that invoke the setMinDate method in the ViewModel and pass the desired time span as a parameter.

The final part of the view is the chart control, implemented as follows:

XML
<!-- portfolio chart -->
<div id="chart" data-bind="
  wijlinechart: { 

    /* bind series, styles */
    seriesList: chartSeries,
    seriesStyles: chartStyles,
    seriesHoverStyles: chartHoverStyles,

    /* axis label formats */
    axis: { 
      y: { annoFormatString : 'p0' },
      x: { annoFormatString : 'dd-MMM-yy' } 
    },

    /* series tooltip */
    hint: {
      content: function() {
        return this.label + ' on ' + 
               Globalize.format(this.x, 'dd-MMM-yy') + ':\n' + 
               Globalize.format(this.y, 'p2');
      }
    },

    /* other properties */
    animation: { enabled: false },
    seriesTransition: { enabled : false },
    showChartLabels: false,
    width: 800, height: 250,
  }">

The data-bind attribute is used to specify the chart properties we need. Recall that the seriesList, seriesStyles, and seriesHoverStyles are properties implemented by the ViewModel and tracked by KnockoutJS. Whenever any of these properties change, the chart is refreshed automatically.

The data-bind attribute also initializes chart properties that are not bound, the same way you would set control properties in XAML. In this case, the code sets the format for the axis annotations, adds a tooltip that shows the current symbol, date, and value as the user moves the mouse over the chart, disables animations, and so on.

WebService Implementation (C#)

Recall that our view model classes use a "StockInfo.ashx" service to retrieve company names and historical price data. This service is part of the Invexplorer application. It is a "Generic Handler" implemented as follows: 

C#
// StockInfo returns two types of information:
// 
// Stock Prices:
// If the request contains a 'symbol' parameter, StockInfo returns a string
// with a list where each line contains a date and the closing value for the
// stock on that day. Dates are between 1/1/2008 and today.
// Values are obtained from the Yahoo finance service.
// 
// Company Names and Symbols:
// If the request does not contain a 'symbol' parameter, StockInfo returns
// a string with a list where each line contains company symbols and names.
// Values are loaded from resource file 'resources/symbolnames.txt'.
public class StockInfo : IHttpHandler
{
  public void ProcessRequest(HttpContext context)
  {
    string symbol = context.Request["symbol"];
    string content = string.IsNullOrEmpty(symbol)
      ? GetSymbols()
      : GetPrices(symbol);

    context.Response.ContentType = "text/plain";
    context.Response.Write(content);
  }
  // implementations of GetSymbols and GetPrices methods follow…

The service checks to see if the request has a "symbol" parameter. If it does, the content is obtained by calling the GetPrices method. Otherwise, the content is obtained by calling GetSymbols. Both methods return strings containing the information requested, with line breaks between items and tabs between values.

The GetSymbols method simply reads a resource file that contains the company names and symbols. The GetPrices method calls Yahoo finance services as the Silverlight application did. We will not show the implementation of those methods here, but they are included in the source code in case you want to check them out. Remember that the Yahoo financial data is not free; if you want to use it in commercial application, you will need to contact Yahoo to obtain a license.

The services return tab-separated items instead of JSON to reduce the size of the download. Each of these calls returns thousands of items, and parsing them in this case is very easy.

Conclusion

HTML5 and JavaScript have come a long way over the last couple of years. First, jQuery brought browser-independence and easy DOM manipulation. At the same time, new browsers started to support HTML5 features such as geo-location, isolated storage, and the flexible canvas element. Then KnockoutJS and other similar libraries made it easy to separate the HTML (View) from the JavaScript (ViewModel). This separation makes creating and maintaining JavaScript applications much easier.

Finally, some popular control libraries have added support for KnockoutJS, making development in HTML5 and JavaScript about as easy as it is in Silverlight.

In my opinion, the main pieces still missing on the HTML5/JavaScript stack are:

  1. A 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 hopefully that will change in the near future.
  2. Better support for extending and creating re-usable custom controls (including layout elements such as the XAML Grid element).
  3. More advanced development tools, with built-in error-checking, refactoring support, and IntelliSense.

What does all this mean? Is the HTML/JavaScript platform ready to replace Silverlight? In my opinion, the answer is it depends on the complexity of the application and on whether the application must be able to run on tablets and phones.

The Invexplorer application is relatively simple. It does not require database updates, validation, or complex data types. The page layout is also simple. This makes it an ideal candidate for an HTML5/JavaScript implementation. 

TypeScript Version 

The second revision of the Invexlorer application incorporates TypeScript (http://www.typescriptlang.org/).

TypeScript is an open source project headed by Anders Hejlsberg. TypeScript adds optional types, classes, and modules to JavaScript. It compiles to readable, standards-based JavaScript. In addition to these great extensions, the TypeScript compiler integrates with Visual Studio and provides automatic compilation, static error-checking, and IntelliSense. It goes a long way towards fixing the limitations listed in item 3 in the previous section. 

TypeScript is still quite new, but it is already popular. In fact, there are already at least two CodeProject articles that describe it very nicely: Getting Started With TypeScript and An Introduction to TypeScript.

You can download and install TypeScript from CodePlex at http://typescript.codeplex.com/. 

Note that after installing it, you may have to run the vsix file manually in order to complete the installation (I spent a few hours figuring this out). The vsix can usually be found here: 

  c:\Program Files (x86)\Microsoft SDKs\TypeScript\0.8.0.0\TypeScriptLanguageService.vsix 

Once you've installed it, you can create new TypeScript projects in Visual Studio using the File | New Project menu, then selecting the Visual C# node and picking the "HTML Application with TypeScript" option (not very intuitive). 

Converting the original ViewModel from plain JavaScript to TypeScript was very easy. The process consisted of the following steps:

1) Breaking up the original "js" file into several "ts" files (one per class)

2) Adding external variable declarations to the top of each "ts" file. These declarations instruct the compiler to ignore names defined in external files. The Invexplorer project requires these declarations:

JavaScript
// declare externally defined objects (to keep TypeScript compiler happy)
declare var $; // jQuery
declare var ko; // KnockoutJS
declare var Globalize; // Globalize plug-in 

3) Adding "reference path" statements that allow the TypeScript compiler to find objects defined in other files within the same project. For example, our ViewModel class references the Portfolio and Company classes, so it needs these references:  

JavaScript
///<reference path='portfolio.ts'/>
///<reference path='company.ts'/> 

4) Adding class, member, constructor, and method declarations to the classes and their elements. For example (just to give you a flavor of the syntax): 

JavaScript
class PortfolioItem {

  // fields (typed)
  portfolio: Portfolio;
  symbol: string;
  company: Company;
  // ...
  constructor(portfolio: Portfolio, symbol: string, chart = false, shares = 0, unitCost = 0) 
  {
    this.portfolio = portfolio;
    this.symbol = symbol;
    // ...
5) Adding type information to members and method signatures.

This may seem like a lot of work, but it is actually very easy. And once you are done, you will get static error checking and IntelliSense right in your TypeScript files. In fact, it is likely that the compiler will find a few bugs in your project as soon as you finish the conversion. As a C# developer, I really missed this support when I wrote JavaScript code. 

The images below show how the TypeScript compiler integrates with Visual Studio to provide static error-checking and IntelliSense. 

Static Error Checking
Static error-checking provided by TypeScript 

IntelliSense
IntelliSense provided by TypeScript 

Running the project causes the TypeScript compiler to generate the "js" files which contain plain JavaScript. The final project will contain no traces of TypeScript, it's just good old HTML and JavaScript as before. 

I am a fan of TypeScript already, and plan to use it in my future HTML/JS projects. If you develop in JavaScript and haven't tried TypeScript yet, you are in for a treat. 

References and Resources 

  1. http://demo.componentone.com/wijmo/InvExplorer/: Live version of the Invexplorer application described in this article.
  2. http://www.codeproject.com/Articles/365120/KnockoutJS-vs-Silverlight: CodeProject article by Colin Eberhart, compares MVVM development using KnckoutJS and Silverlight. Excellent resource for Silverlight developers getting started with HTML5 and JavaScript.
  3. http://publicfiles.componentone.com/Bernardo/MVVM in Silverlight and in HTML5 IX.pdf: Article that compares Silverlight and JavaScript implementations of the Invexplorer application.
  4. http://knockoutjs.com/: KnockoutJS home page. This is the ultimate resource on KnockoutJS. It contains conceptual information, documentation, samples, and tutorials.
  5. http://jqueryui.com/: jQueryUI home page. The official resource for jQueryUI, including documentation and samples for the controls (widgets), effects, and utilities included with jQueryUI.
  6. http://wijmo.com/: Wijmo home page. The official resource for Wijmo, a control library that includes grid and chart controls that support KnockoutJS. 
  7. http://typescript.codeplex.com/: TypeScript home page. The TypeScript compiler adds type information, static error-checking, and IntelliSense to JavaScript.  

License

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


Written By
Software Developer
Brazil Brazil
Software Architect/Developer with several years experience creating and delivering software.

Full-stack Web development (including React, Firebase, TypeScript, HTML, CSS), Entity Framework, C#, MS SQL Server.

Passionate about new technologies and always keen to learn new things as well as improve on existing skills.

Comments and Discussions

 
QuestionRegarding this article Pin
jitendra prajapat18-Jul-14 3:01
jitendra prajapat18-Jul-14 3:01 
AnswerRe: Regarding this article Pin
Bernardo Castilho21-Jul-14 2:41
professionalBernardo Castilho21-Jul-14 2:41 
QuestionWhen I try to compile the TS version, it fails. Pin
NGErndt6-May-13 15:55
NGErndt6-May-13 15:55 
AnswerRe: When I try to compile the TS version, it fails. Pin
Bernardo Castilho6-May-13 19:27
professionalBernardo Castilho6-May-13 19:27 
GeneralRe: When I try to compile the TS version, it fails. Pin
NGErndt7-May-13 9:11
NGErndt7-May-13 9:11 
GeneralRe: When I try to compile the TS version, it fails. Pin
Bernardo Castilho7-May-13 9:27
professionalBernardo Castilho7-May-13 9:27 
Questionvery polished Pin
Sacha Barber13-Oct-12 4:31
Sacha Barber13-Oct-12 4:31 
AnswerRe: very polished Pin
Bernardo Castilho13-Oct-12 6:05
professionalBernardo Castilho13-Oct-12 6:05 
GeneralRe: very polished Pin
Sacha Barber15-Oct-12 1:38
Sacha Barber15-Oct-12 1:38 
GeneralRe: very polished Pin
Bernardo Castilho15-Oct-12 4:24
professionalBernardo Castilho15-Oct-12 4:24 
GeneralSimply Excellent! Pin
j_corba28-Sep-12 6:16
j_corba28-Sep-12 6:16 
GeneralRe: Simply Excellent! Pin
Bernardo Castilho28-Sep-12 6:18
professionalBernardo Castilho28-Sep-12 6:18 
GeneralMy vote of 5 Pin
Colin Eberhardt1-Aug-12 5:08
Colin Eberhardt1-Aug-12 5:08 
GeneralRe: My vote of 5 Pin
Bernardo Castilho1-Aug-12 9:41
professionalBernardo Castilho1-Aug-12 9:41 
GeneralMy vote of 5 Pin
RabinDl19-Jul-12 22:29
RabinDl19-Jul-12 22:29 
GeneralRe: My vote of 5 Pin
Bernardo Castilho1-Aug-12 9:39
professionalBernardo Castilho1-Aug-12 9:39 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.