Convert numbers to words in English and Asian format

Introduction 


I was going through the articles and ran into an article that showed the conversion of number to words, such as 12 to Twelve.  I though let me try same thing but in a different way. I am not a programmer but like to monkey around with codes.
Anyways, the Converter class allows us to convert the numbers into words. Let the picture talk!

 IntroImage.png

Background 


Before digging into the code let’s see how the numbers are read in word
In English Style:
123,123,000 =  One hundred twenty three million one hundred twenty three thousand
123,000,000 =  One hundred twenty three million
      123,000 =                                                 One hundred twenty three thousand
As we can see the three set of numbers are always spelled same way and then the modifier, like million is appended. In English style numbers are aggregated in a group of three. In Asian style it’s little bit different. The last (towards right) three are grouped together and then rests of the numbers are aggregated with two words.

In Asian Style:
12,12,123 = Twelve lakh twelve thousand one hundred twenty three
12,00,000 = Twelve lakh
      12,000 =                        twelve thousand
            123 =                                                          one hundred twenty three

Code  Flow

The absolute common thing between these two systems is the digit. 1 is one, 9 is nine and 20 is twenty in both.  And the difference is how the higher numbers are grouped and read. To facilitate these I created arrays. These arrays are the heart of the conversion. The tricky part is to find the index so that we can use these arrays.
Private SingleDigitStringArray() As String = _
        {"", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten"}
Private DoubleDigitsStringArray() As String = _
        {"", "Ten", "Twenty", "Thirty", "Forty", "Fifty", "Sixty", "Seventy", "Eighty", "Ninety"}
Private TenthDigitStringArray() As String = _
        {"Ten", "Eleven", "Tweleve", "Thirteen", "Fourteen", "Fifteen", "Sixteen", "Seventeen", "Eighteen", "Nineteen"}

Private HigherDigitEnglishStringArray() As String = _
        {"", "", "Hundred", "Thousand", "Million", "Billion", "Trillion", "Quadrillion", "Quintillion"}
Private HigherDigitAsianStringArray() As String = _
        {"", "", "Hundred", "Thousand", "Lakh", "Karod", "Arab", "Kharab"}

Private EnglishCodeArray() As String = {"1", "22", "3"}
Private AsianCodeArray() As String = {"1", "22", "3", "4", "42", "5", "52", "6", "62", "7", "72", "8", "82"} 
The entire process is basically array manipulation. The primary goal is to find the correct index corresponding to the number and its position and then pulling the corresponding word out of the array shown above.

Let’s get the ball rolling.  In English conversion method, we work 3 digits at a time from right side and then affix the thousand/million as necessary and loop until we are done, as shown below:
Do
 concatHigherDigitString = False
 If i > 3 Then concatHigherDigitString = True 'to suffix the Thousand/Million/Billion type of Word


 If newAmount.Length >= 4 Then   'work with 3 right most digits at a time
  newAmount = amountString.Substring(amountString.Length - i, 3)
 End If


 'do the conversion and affix the Thousand/Million/Billion type of word at the end when needed
 If concatHigherDigitString AndAlso CInt(newAmount) <> 0 Then
  result = ThreeDigitsConverter(CInt(newAmount)) & " " & _
                          HigherDigitEnglishStringArray(i / 3 + 1) & " " & result
 Else
  result = ThreeDigitsConverter(CInt(newAmount))
 End If


 workedStringLength += newAmount.Length
 newAmount = amountString.Substring(0, amountString.Length - workedStringLength)
 i += 3
Loop Until amountString.Length <= workedStringLength


In both of the method, we first convert the given number to string and then to an array, for instance, 1234 to “1234” and then to {1, 2, 3, 4}, using following simple code. I believe there must be a better way of doing this…
'convert numbers to array of each digit
Dim amountArray(amountString.Length - 1) As Integer
For i As Integer = amountArray.Length To 1 Step -1
   amountArray(i - 1) = amountString.Substring(i - 1, 1)
Next
Now we work on digit by digit but backwards as the digit in unit place remains in unit place all the time.  Things get little complicated when working with numbers like 12 because it’s not read like onetwo however as Twelve. Similarly, 22 is not twotwo but Twenty two. Likewise, 10001 is not Ten thousand zero hundred one but it’s Ten thousand one. Keeping all these in minds, following loop will find the right index and then from the words library (fields array that we talked about earlier) we convert the digits to word.

For i As Integer = amountArray.Length To 1 Step -1
  j = amountArray.Length - i
  digit = amountArray(j)
  
  codeIndex = EnglishCodeArray(i - 1)
  higherDigitEnglishString = HigherDigitEnglishStringArray(CInt(codeIndex.Substring(0, 1)) - 1)
  
  
  If codeIndex = "1" Then 'Number [1 9]
     result = result & separator & SingleDigitStringArray(digit)
  
  ElseIf codeIndex.Length = 2 And digit <> 0 Then 'Number in tenth place and skip if digit is 0
  
  If digit = 1 Then   'Number [Eleven, Twelve,...., Nineteen]
     Dim suffixDigit As Integer = amountArray(j + 1)
     result = result & separator & TenthDigitStringArray(suffixDigit) & " " & higherDigitEnglishString
     i -= 1  'Skip the next round as we already looked at it

  Else    'Number [tenth] and [20+]  
     result = result & separator & DoubleDigitsStringArray(digit) & " " & higherDigitEnglishString
  End If
  
  ElseIf digit <> 0 Then  'Standard Number like 100, 1000, 1000000 and skip if digit is 0
     result = result & separator & SingleDigitStringArray(digit) & " " & higherDigitEnglishString
  End If
  
  separator = " "
Next 
During the process a space or two get attached in between the words so for the cleanup I use the RegEx as:
Private Function RemoveSpaces(ByVal word As String) As String
  Dim regEx As New System.Text.RegularExpressions.Regex("  ")
  Return regEx.Replace(word, " ").Trim
End Function 
There we go, this will convert the numbers to digits!


Using the code 

Simply create a new instance of the converter and pass the number you want to convert and the conversion style and call the Convert method.
Blocks of code should be set as style "Formatted" like this:
Dim converter As New Converter(number, converter.ConvertStyle.English)
Me.RichTextBox1.Text = converter.Convert

converter = New Converter(number, NumberToWords.Converter.ConvertStyle.Asian)
Me.RichTextBox2.Text = converter.Convert   

Points of Interest

These two arrays are worth mentioning
Private EnglishCodeArray() As String = {"1", "22", "3"}
Private AsianCodeArray() As String = {"1", "22", "3", "4", "42", "5", "52", "6", "62", "7", "72", "8", "82"}
…these two arrays help to determine whether a digit is in unit place or, tenth place and so on… In the above array, double digit like “22” will always represent the 10th place. “4” will represent thousand and “42” will represent ten thousand.
Somewhere I have a feeling I have not done enough testing so comments are actually what I am expecting. Thanks!


A Basic 3D Asteroid Game in openGL with C#

image001.jpg

Introduction

This article is intended for beginners who want to start with 3D game programming and don’t know where to start. It is programmed in Opengl using VS 2008 and a small graphic engine I made myself called Shadowengine. It has the basics of what a game should have: a Score, levels of difficulty and a life counter. All this is written in a small number of code lines, with the objective of being simple and understandable.
The first problem I had to achieve is the scene to look like outer space. For that issue, I set the background color to black. In opengl, it is set this way:
Gl.glClearColor(0, 0, 0, 1);//red green blue alpha 
The other problem were the stars and I solve it by drawing random white points on the screen. The algorithm is more or less this way. I generate a random point and measure the distance to the spaceship, and if it is less than a predefined number, I discard it and repeat the process until it creates the desired stars. Look at the code:
public void CreateStars(int cantidad)
        {
            Random r = new Random();
            int count = 0;
            while (count != cantidad)
            {
                Position p = default(Position);
                p.x = (r.Next(110)) * (float)Math.Pow(-1, r.Next());
                p.z = (r.Next(110)) * (float)Math.Pow(-1, r.Next());
                p.y = (r.Next(110)) * (float)Math.Pow(-1, r.Next());
                if (Math.Pow(Math.Pow(p.x, 2) + Math.Pow(p.y, 2) +
    Math.Pow(p.z, 2), 1 / 3f) > 15)
                {
                    stars.Add(p);
                    count++;
                }
            }
        }
The score is a number which increases over time and it grows faster everytime I pass a level. The level increases every 450 frames.
The last issue I had to address is the problem of asteroid collision. I make for the ship three positions (one for the ship and two for the wings) and every once in a while, I check the distance between all the asteroids and those three positions. If it is under a predefined value, I execute the collision event.
The project has 6 classes:
  • AsteroidGenerator.cs - It handles the creation of asteroids in random places and with random sizes and speeds. It also has the method to query whether or not there has been a collision between the spaceship and an asteroid.
  • Asteroid.cs - It handles all concerning an asteroid such as the movement, texture selection, drawing, among others.
  • Star.cs - It has a method to create random white points and to draw them.
  • Camera.cs - It handles the selection of the user camera and to set the right perspective view of the scene.
  • SpaceShip.cs - This class contains all methods and attributes regarding the spaceship, such as movement, drawing, etc.
  • Controller.cs - This class manages the game creation, game logic and contains all the classes needed to run the game. Here is a portion of code:
using System;
using System.Collections.Generic;
using System.Text;

namespace Naves
{
    public class Controller
    {
        Camera camara = new Camera();
        Star star = new Star();
        SpaceShip spaceShip = new SpaceShip();
        public SpaceShip Nave
        {
            get { return spaceShip; }set { spaceShip = value; }
        }
        public Camera Camara
        {
            get { return camara; }
        }
        public void BeginGame()
        {
            AsteroidGenerator.GenerateAsteroid(35, false);
        }
        public void ResetGame()
        {
            AsteroidGenerator.GenerateAsteroid(35, true);
            spaceShip.Reiniciar();
        }
        public void CreateObjects()
        {
            star.CreateStars(450);
            spaceShip.Create();
            Asteroid.Crear();
        }
        public void DrawScene()
        {
            star.Draw();
            AsteroidGenerator.DrawAsteroids();
            spaceShip.Dibujar();
        }
    }
}
Main.cs - This is the form which contains this visual components of the game. It contains the controller class and gives the player information through proper windows controls. It has a timer to periodically draw the scene and has the code for texture and object loading.
If you want to add sound to the project, uncomment the commented lines and press Ctrl+Alt+E and under managed debugging assistants, uncheck the loaderlock exception.
I am hoping to receive feedback from this example. If you like it, you can visit my blog at www.vasilydev.blogspot.com for more stuff like this. I’ll try to make a version where the spaceship can shoot at the asteroids.

History

  • 28th December, 2011: Initial version

Dynamically creating Excel File, ExcelSheets and Exporting Data from SQL Server using SSIS 2005

 

Introduction

This article is intended primarily for intermediate to advanced SSIS (SQL Server Integration Services) users who are familiar with SQL Server Business Intelligence. Basic understanding of script task, Execute Sql Task, parameters and variables are required, as it is not exactly a tutorial, but a description of the implementation of exporting the database table objects to Excel Objects through SSIS without the need of any templates and much of programming.This package will help to migrate data in a simple and efficient way.The Goal of this article is to help the users by providing the concept to create a Excel file with unlimited sheets with all the data from the SQL Sever database.
This DTSX package is basically to demonstate a method of data migration to export SQL Server tables to Excel file with multiple sheets. After reading many blogs about people having problems in generating excel files and sheets dynamically from SQL Server 2000/2005 through SSIS (SQL Server Integration Services). Here is the simple DTSX package which creates a excel file on the fly and dumps the data on the sheets.
Currently Most of the Microsoft Dynamics ERP Software Products like (NAV, Axapta, Great Plains, Solomon, CRM and etc) are using the SQL Server and Excel for data migration.
Little bit of tweaks in the below code in the tasks could help many users to achieve their goals in a easiest way.
Below is the demonstration of the steps to achieve this.
SSIS_Excel.jpg
The dtsx package should look like the above Image.

Background

Excel file treats each sheet as a table.Excel-95/2003 generates only 65335 rows for sheet and 255 sheets maximum if its Excel-95, There is no rows or sheets limitation if you are using Excel 2007 and above. There are certain limitations to Excel previous versions. Read online documentation for more information.

Using the Code

These options need to be configured 'Show Advanced Options' and 'Ad Hoc Distributed Queries' on the SQL Server before running the package. The package will fail without these Options. And importantly the database login should have admin privileges to execute the package tasks successfully. If you want to run this code, then better check with your DBA's regarding permissions you require.
/*-- This has to executed on the server before running the package.
  -- These Options are required to run the OPENROWSET Function.                                                      This has to be just executed once.*/

SP_CONFIGURE 'Show Advanced Options', 1
GO
RECONFIGURE
GO
SP_CONFIGURE 'Ad Hoc Distributed Queries', 1
GO
RECONFIGURE
GO
[Get List of Tables to Process] Execute SQL Task: The query is to retrieve all the table names which are less then 65335 rows. ( Customize this query as per your requirement.)
/********************************************************************** 
 CREATED BY      : Al-Salman Rehman
 CREATED ON      : This is created and tested in SQL SERVER 2000 and SQL SERVER 2005.
 PURPOSE      : This Object is get the List of Tables from the Database.
 COMMENTS     : So that it can be used for creating objects in xls file via SSIS.
                Tested on SQL Server 8.00.194 - RTM (Desktop Engine) ,SQL Server 9.00.4035.00 - SP3 (Developer Edition)
 SSIS TASK    : [Get List of Tables to Process] SSIS Execute SQL Task   
 **********************************************************************/

DECLARE @Server varchar(50) 
SET @Server = @@SERVERNAME 
-- This ServerOption Should be Turned on 
EXEC sp_serveroption  @Server,'DATA ACCESS','TRUE'

SELECT [TableName] = so.name 
                FROM sysobjects so, sysindexes si 
                WHERE so.xtype = 'U' AND si.id = OBJECT_ID(so.name)
/*Comment the following line if you are using Excel 2007.*/
                AND si.rows < 65335 
/*Uncomment the following code if you want to avoid the table names where you dont have data in it.*/
                --AND si.rows > 0
                GROUP BY so.name 
                ORDER BY 1 DESC
[Excel Script Generator]Execute SQLTask:The query in this task builds the table structure with the data types required for the Excel.(Most of the SQL data types have been handled here. If you want add any other specific data type then you need to modify this.)
 /********************************************************************** 
 CREATED BY      : AL-Salman Rehman 
 CREATED ON      : This is created and tested in SQL SERVER 2000 and SQL SERVER 2005.
 PURPOSE      : This Object is Created for Generating a Script Table for Excel.
 COMMENTS     : So that it can be used for creating objects in xls file via SSIS.
                Tested on SQL Server 8.00.194 - RTM (Desktop Engine) ,SQL Server 9.00.4035.00 - SP3 (Developer Edition) 
 SSIS TASK    : [Excel Script Generator] Execute SQL Task 
 **********************************************************************/
DECLARE @vsTableName VARCHAR(100)  
SET @vsTableName =?
DECLARE @vsSQL VARCHAR(8000)  
 
BEGIN  
  /* EXCEL TABLE SCRIPT GENERATOR 
     Handled Most of the regular SQL data types below. */
  SELECT @vsSQL = 'CREATE TABLE ' + '`' + @vsTableName + '`' + CHAR(10) + '(' + CHAR(10)  
  --SELECT @vsSQL = @vsSQL + '`' + RTRIM(sc.Name) + '` ' +  
  SELECT @vsSQL = @vsSQL + '[' + sc.Name + '] ' +  
  CASE WHEN st.Name IN ('nvarchar','ntext','text','varchar','varchar','char','nchar','xml','uniqueidentifier') THEN  'LongText'  
       WHEN st.Name IN ('tinyint','int','smallint','bigint','float','numeric','decimal','money','smallmoney','bit') THEN  'Long'
       WHEN st.Name IN ('date','datetime','timestamp') THEN  'datetime'
  ELSE ' ' END + ',' + CHAR(10) 
  FROM sysobjects so  
  JOIN syscolumns sc ON sc.id = so.id  
  JOIN systypes st ON st.xusertype = sc.xusertype  
  WHERE so.name = @vsTableName 
  /* Avoiding the following data types here... */
  AND st.Name not in ( 'image','sysname','binary','varbinary','xml','uniqueidentifier') 
  ORDER BY  
  sc.ColID  
  
  SELECT SUBSTRING(@vsSQL,1,LEN(@vsSQL) - 2) + CHAR(10) + ')'  AS ExcelTableName   
 END
Change the database connection to SQL Server 2000/2005 and point to the database which you want to export (currently it is pointed to master database). Change the Variable DestExcelFilePath Value from 'C:\SSIS' for creating a file at the desired location. Package should work properly if all the Table names and Column names are followed as per the microsoft naming standards.  
SSIS_Excel1.jpg
[Insert Script Generation] ScriptTask:This script task code in vb.net is to build a Query using SQL Server OPENROWSET function which finally creates the ExcelSheets in the destination file with the Name (DynamicExcelFileDDMMYYYY.xls). For more information on the OPENROWSET functionality refer online documentation.
Imports System
Imports System.Data
Imports System.Math
Imports Microsoft.SqlServer.Dts.Runtime

Public Class ScriptMain

    Public Sub Main()
        ' CREATED BY      : VENKAT CHAITANYA KANUMUKULA 
        ' Insert Script Generation

        '
        Dim excelFilePath As String = CStr(Dts.Variables("User::DestExcelFilePath").Value) + CStr(Dts.Variables("DestExcelFileName").Value)
        Dim TableName, ExcelTable As String
        TableName = CStr(Dts.Variables("User::ExcelTable").Value)

        If TableName.Length > 30 Then
            ExcelTable = Strings.Left(TableName, 31)
            'Excel sheet Name can only be 30 characters length.
        Else
            ExcelTable = TableName
        End If
       'Retrieve the ExcelColumnNames from the Variable and build the String Here.
        Dim ColumnNames As String = CStr(Dts.Variables("User::ExcelColumns").Value)
        Dim strCn As String = " Insert into OPENROWSET('Microsoft.Jet.OLEDB.4.0'," & _
                "'Excel 8.0;Database=" + excelFilePath + "','SELECT " + ColumnNames + " FROM [" + ExcelTable + "$]') SELECT " + ColumnNames + " FROM [" + TableName + "]"

        'Uncomment the following message to check the query.
        'MsgBox(strCn)
        Dts.Variables("User::InsertScripter").Value = strCn
        Dts.TaskResult = Dts.Results.Success
    End Sub

End Class

Conclusion

The above mentioned demonstration of steps determine that excel file with multiple sheets along with data migration from SQL Server can be dynamically created using SSIS 2005.
SSIS_Excel2.jpg
Hence it can be concluded that exporting the database table objects to a Excel file with multiple sheets is achieved through SSIS without the need of any templates, DLL's and much of programming.
This Article is basically intended to demonstrate the concept of creating sheets and table structures in a excel file with SSIS and to understand the excel functionalities. Its not a good Idea to export a huge database with the above package where it might end up using the system memory and other resources.
There are many articles on creating & handling excel files including formatting the cells, inserting formulas and manipulating the excel objects which can be achieved through various programming languages. But this article is intended for the  SQL Server Integration Services users who are aware of its limitations.
Hope this article will serve the purpose. Any suggestions or corrections are most welcome.



DONWLOAD EXAMPLE

AutoComplete Textbox

This article helps to create a suggestion list with autocomplete facility, add/remove operation is also discussed.

Introduction

During work on a project, I needed to use a control having auto complete facility in it as user types. I realized that we can make use of the textbox control or combobox control for this purpose. Both controls can be used to filter records as a drop down list to show the best matches. I will demonstrate it using VB.NET. If someone needs a C# version, an online converter can be used. I will be discussing the addition and removal of item from the drop down list.

Prerequisites

Before reading this article, we need to be aware of two properties and an enumeration introduced by Microsoft in the .NET Framework 2.0 which is AutoCompleteCustomSource property, AutoCompleteMode property and AutoCompleteSource enumeration.
We should also have a look at AutoCompleteStringCollection class.

Design

An auto complete source is binded to the textbox’s AutoCompleteCustomSource property. It will help to filter the best fit records in the suggestion list.
'Item is filled either manually or from database
Dim lst As New List(Of String)

'AutoComplete collection that will help to filter keep the records.
Dim MySource As New AutoCompleteStringCollection()

Implementation

I have filled the source from the list named ‘lst’ at Form1_Load event. This list can be populated by the database as well.
Private Sub Form1_Load(ByVal sender As System.Object, _
 ByVal e As System.EventArgs) Handles MyBase.Load

    'Manually added some items
    lst.Add("apple")
    lst.Add("applle")
    lst.Add("appple")
    lst.Add("appplee")
    lst.Add("bear")
    lst.Add("pear")

    'Records binded to the AutocompleteStringCollection.
    MySource.AddRange(lst.ToArray)

    'this AutocompleteStringcollection binded to the textbox as custom
    'source.
    TextBox1.AutoCompleteCustomSource = MySource

    'Auto complete mode set to suggest append so that it will sugesst one
    'or more suggested completion strings it has bith ‘Suggest’ and
    '‘Append’ functionality
    TextBox1.AutoCompleteMode = AutoCompleteMode.SuggestAppend

    'Set to Custom source we have filled already
    TextBox1.AutoCompleteSource = AutoCompleteSource.CustomSource
End Sub

Operations on AutoCompleteSource

As I have discussed earlier that we will see how to add/ remove entry from the source, we have binded to the Textbox’s source.
The event uses this task to achieve is KeyDown event.
Here is the source with explanation:
Private Sub TextBox1_KeyDown(ByVal sender As System.Object, _
ByVal e As System.Windows.Forms.KeyEventArgs) Handles TextBox1.KeyDown

If e.KeyCode = Keys.Enter Then   ' On enter I planned to add it the list
     If Not lst.Contains(TextBox1.Text) Then  ' If item not present already
        ' Add to the source directly
         TextBox1.AutoCompleteCustomSource.Add(TextBox1.Text)
     End If
ElseIf e.KeyCode = Keys.Delete Then 'On delete key, planned to remove entry

' declare a dummy source
Dim coll As AutoCompleteStringCollection = TextBox1.AutoCompleteCustomSource

' remove item from new source
coll.Remove(TextBox1.Text)

' Bind the updates
TextBox1.AutoCompleteCustomSource = coll

' Clear textbox
TextBox1.Clear()

End If                   ' End of ‘KeyCode’ condition

End Sub

Conclusion

There are more details as to how the whole thing works. I feel this is a viable solution for an Auto-Complete TextBox and I hope you find it interesting.

How to Create a Visual Library of Images in HTML5 Canvas

As a user interface fan, I could not miss the opportunity to develop with HTML5 Canvas. It unlocks a whole new set of ways to visualize images and data on the web. In this tutorial, I’ll walk you through how to create one for your site.

Application Overview

We will produce an application that will let us display a Magic the Gathering © (courtesy of www.wizards.com/Magic) cards collection. Users will be able to scroll and zoom using the mouse (like Bing Maps, for example).
Note: Image and data visualization is hardware intensive.  Learn about HTML5 hardware acceleration and why it’s important here.
image002.png
You can see the final result here: http://bolaslenses.catuhe.com
The project source files can be downloaded here: http://www.catuhe.com/msdn/bolaslenses.zip
Cards are stored on Windows Azure Storage and use the Azure Content Distribution Network (CDN : a service that deploys data near the final users) in order to achieve maximum performances. An ASP.NET service is used to return cards list (using JSON format).
image003.png

Tools

To write our application, we will use Visual Studio 2010 SP1 with Web Standards Update. This extension adds IntelliSense support in HTML5 page (which is a really important thing ).
So, our solution will contain an HTML5 page side by side with .js files (these files will contain JavaScript scripts). About debug, it is possible to set a breakpoint directly in the .js files under Visual Studio. Try using the F12 Developer tools in Internet Explorer 9.
image005.png
Debug with Visual Studio 2010
image006.png
Debug with Internet Explorer 9 (F12/Developer bar)
So, we have a modern developer environment with IntelliSense and debug support. Therefore, we are ready to start and first of all, we will write the HTML5 page.
image007.png

The HTML5 Page

Our page will be built around an HTML5 canvas which will be used to draw the cards:
1.  <!DOCTYPE html>
2.  <html>
3.  <head>
4.  <meta charset="utf-8" />
5.  <title>Bolas Lenses</title>
6.  <link href="Content/full.css" rel="stylesheet" type="text/css" />
7.  <link href="Content/mobile.css" rel="stylesheet" type="text/css" media="screen and (max-width: 480px)" />
8.  <link href="Content/mobile.css" rel="stylesheet" type="text/css" media="screen and (max-device-width: 480px)" />
9.  <script src="Scripts/jquery-1.5.1.min.js" type="text/javascript"></script>
10.</head>
11.<body>
12.<header>
13.<div id="legal">
14.Cards scanned by <a href="http://www.slightlymagic.net/">MWSHQ Team</a><br />
15.Magic the Gathering official site : <a href="http://www.wizards.com/Magic/TCG/Article.aspx?x=mtg/tcg/products/allproducts">
16.http://www.wizards.com/Magic</a>
17.<div id="cardsCount">
18.</div>
19.</div>
20.<div id="leftHeader">
21.<img id="pictureCell" src="http://www.codeproject.com/Content/MTG Black.png" alt="Bolas logo" id="bolasLogo" />
22.<div id="title">
23.Bolas Lenses
24.</div>
25.</div>
26.</header>
27.<section>
28.<img src="Content/Back.jpg" style="display: none" id="backImage" alt="backImage"
29.width="128" height="128" />
30.<canvas id="mainCanvas">
31.Your browser does not support HTML5 canvas.
32.</canvas>
33.<div id="stats" class="tooltip">
34.</div>
35.<div id="waitText" class="tooltip">
36.Loading data...
37.</div>
38.</section>
39.<!--Scripts-->
40.<script src="Bolas/bolasLenses.animations.js" type="text/javascript"></script>
41.<script src="Bolas/bolasLenses.mouse.js" type="text/javascript"></script>
42.<script src="Bolas/bolasLenses.cache.js" type="text/javascript"></script>
43.<script src="Bolas/bolasLenses.js" type="text/javascript"></script>
44.</body>
45.</html>
If we dissect this page, we can note that it is divided into two parts:
  • The header part with the title, the logo and the special mentions
  • The main part (section) holds the canvas and the tooltips that will display the status of the application. There is also a hidden image (backImage) used as source for not yet loaded cards.
To build the layout of the page, a style sheet (full.css) is applied. Style sheets are a mechanism used to change the tags styles (in HTML, a style defines the entire display options for a tag):
1.  html, body
2.  {
3.  height: 100%;
4.  }
5.   
6.  body
7.  {
8.  background-color: #888888;
9.  font-size: .85em;
10.font-family: "Segoe UI, Trebuchet MS" , Verdana, Helvetica, Sans-Serif;
11.margin: 0;
12.padding: 0;
13.color: #696969;
14.}
15. 
16.a:link
17.{
18.color: #034af3;
19.text-decoration: underline;
20.}
21. 
22.a:visited
23.{
24.color: #505abc;
25.}
26. 
27.a:hover
28.{
29.color: #1d60ff;
30.text-decoration: none;
31.}
32. 
33.a:active
34.{
35.color: #12eb87;
36.}
37. 
38.header, footer, nav, section
39.{
40.display: block;
41.}
42. 
43.table
44.{
45.width: 100%;
46.}
47. 
48.header, #header
49.{
50.position: relative;
51.margin-bottom: 0px;
52.color: #000;
53.padding: 0;
54.}
55. 
56.#title
57.{
58.font-weight: bold;
59.color: #fff;
60.border: none;
61.font-size: 60px !important;
62.vertical-align: middle;
63.margin-left: 70px
64.}
65. 
66.#legal
67.{
68.text-align: right;
69.color: white;
70.font-size: 14px;
71.width: 50%;
72.position: absolute;
73.top: 15px;
74.right: 10px
75.}
76. 
77.#leftHeader
78.{
79.width: 50%;
80.vertical-align: middle;
81.}
82. 
83.section
84.{
85.margin: 20px 20px 20px 20px;
86.}
87. 
88.#mainCanvas{
89.border: 4px solid #000000;
90.}
91. 
92.#cardsCount
93.{
94.font-weight: bolder;
95.font-size: 1.1em;
96.}
97. 
98..tooltip
99.{
100.      position: absolute;
101.      bottom: 5px;
102.      color: black;
103.      background-color: white;
104.      margin-right: auto;
105.      margin-left: auto;
106.      left: 35%;
107.      right: 35%;
108.      padding: 5px;
109.      width: 30%;
110.      text-align: center;
111.      border-radius: 10px;
112.      -webkit-border-radius: 10px;
113.      -moz-border-radius: 10px;
114.      box-shadow: 2px 2px 2px #333333;
115.      }
116.       
117.      #bolasLogo
118.      {
119.      width: 64px;
120.      height: 64px;
121.      }
122.       
123.      #pictureCell
124.      {
125.      float: left;
126.      width: 64px;
127.      margin: 5px 5px 5px 5px;
128.      vertical-align: middle;
129.      }
Thus, this sheet is responsible for setting up the following display:
image008.png
Style sheets are powerful tools that allow an infinite number of displays. However, they are sometimes complicated to setup (for example if a tag is affected by a class, an identifier and its container). To simplify this setup, the development bar of Internet Explorer 9 is particularly useful because we can use it to see styles hierarchy that is applied to a tag.
For example let’s take a look at the waitText tooltip with the development bar. To do this, you must press F12 in Internet Explorer 9 and use the selector to choose the tooltip:
image009.png
Once the selection is done, we can see the styles hierarchy:
image010.png
Thus, we can see that our div received its styles from the body tag and the .tooltip entry of the style sheet.
With this tool, it becomes possible to see the effect of each style (which can be disabled). It is also possible to add new style on the fly.
Another important point of this window is the ability to change the rendering mode of Internet Explorer 9. Indeed, we can test how, for example, Internet Explorer 8 will handle the same page. To do this, go to the [Browser mode] menu and select the engine of Internet Explorer 8. This change will especially impact our tooltip as it uses border-radius (rounded edge) and box-shadow that are features of CSS 3:
image011.png image012.png
Internet Explorer 9 Internet Explorer 8
Our page provides a graceful degradation as it still works (with no annoying visual difference) when the browser does not support all the required technologies.
Now that our interface is ready, we will take a look at the data source to retrieve the cards to display.

Data Gathering

The server provides the cards list using JSON format on this URL:
http://bolaslenses.catuhe.com/Home/ListOfCards/?colorString=0
It takes one parameter (colorString) to select a specific color (0 = all).
When developing with JavaScript, there is a good reflex to have (reflex also good in other languages too, but really important in JavaScript): one must ask whether what we want to develop has not been already done in an existing framework.
Indeed, there is a multitude of open source projects around JavaScript. One of them is jQuery which provides a plethora of convenient services.
Thus, in our case to connect to the URL of our server and get the cards list, we could go through a XmlHttpRequest and have fun to parse the returned JSON. Or we can use jQuery . So we will use the getJSON function which will take care of everything for us:
1.  function getListOfCards() {
2.  var url = "http://bolaslenses.catuhe.com/Home/ListOfCards/?jsoncallback=?";
3.  $.getJSON(url, { colorString: "0" }, function (data) {
4.  listOfCards = data;
5.  $("#cardsCount").text(listOfCards.length + " cards displayed");
6.  $("#waitText").slideToggle("fast");
7.  });
8.  }
As we can see, our function stores the cards list in the listOfCards variable and calls two jQuery functions:
  • text that change the text of a tag
  • slideToggle that hides (or shows) a tag by animating its height
The listOfCards list contains objects whose format is:
  • ID: unique identifier of the card
  • Path: relative path of the card (without the extension)
It should be noted that the URL of the server is called with the “?jsoncallback=?” suffix. Indeed, Ajax calls are constrained in terms of security to connect only to the same address as the calling script. However, there is a solution called JSONP that will allow us to make a concerted call to the server (which of course must be aware of the operation). And fortunately, jQuery can handle it all alone by just adding the right suffix.
Once we have our cards list, we can set up the pictures loading and caching.

Cards Loading & Cache Handling

The main trick of our application is to draw only the cards effectively visible on the screen. The display window is defined by a zoom level and an offset (x, y) in the overall system.
1.  var visuControl = { zoom : 0.25, offsetX : 0, offsetY : 0 };
image013.png
The overall system is defined by 14819 cards that are spread over 200 columns and 75 rows.
Also, we must be aware that each card is available in three versions:
  • High definition: 480x680 without compression (.jpg suffix)
  • Medium definition: 240x340 with standard compression (.50.jpg suffix)
  • Low definition: 120x170 with strong compression (.25.jpg suffix)
Thus, depending on the zoom level, we will load the correct version to optimize networks transfer.
To do this we will develop a function that will give an image for a given card. This function will be configured to download a certain level of quality. In addition it will be linked with lower quality level to return it if the card for the current level is not yet uploaded:
1.  function imageCache(substr, replacementCache) {
2.  var extension = substr;
3.  var backImage = document.getElementById("backImage");
4.   
5.   
6.  this.load = function (card) {
7.  var localCache = this;
8.   
9.  if (this[card.ID] != undefined)
10.return;
11. 
12.var img = new Image();
13.localCache[card.ID] = { image: img, isLoaded: false };
14.currentDownloads++;
15. 
16.img.onload = function () {
17.localCache[card.ID].isLoaded = true;
18.currentDownloads--;
19.};
20. 
21.img.onerror = function() {
22.currentDownloads--;
23.};
24. 
25.img.src = "http://az30809.vo.msecnd.net/" + card.Path + extension;
26.};
27. 
28.this.getReplacementFromLowerCache = function (card) {
29.if (replacementCache == undefined)
30.return backImage;
31. 
32.return replacementCache.getImageForCard(card);
33.};
34. 
35.this.getImageForCard = function(card) {
36.var img;
37.if (this[card.ID] == undefined) {
38.this.load(card);
39. 
40.img = this.getReplacementFromLowerCache(card);
41.}
42.else {
43.if (this[card.ID].isLoaded)
44.img = this[card.ID].image;
45.else
46.img = this.getReplacementFromLowerCache(card);
47.}
48. 
49.return img;
50.};
51.}
An ImageCache is built by giving the associated suffix and the underlying cache.
Here you can see two important functions:
  • load: this function will load the right picture and will store it in a cache (the msecnd.net url is the Azure CDN address of the cards)
  • getImageForCard: this function returns the card picture from the cache if already loaded. Otherwise it requests the underlying cache to return its version (and so on)
So to handle our 3 levels of caches, we have to declare three variables:
1.  var imagesCache25 = new imageCache(".25.jpg");
2.  var imagesCache50 = new imageCache(".50.jpg", imagesCache25);
3.  var imagesCacheFull = new imageCache(".jpg", imagesCache50);
Selecting the right cover is only depending on zoom:
1.  function getCorrectImageCache() {
2.  if (visuControl.zoom <= 0.25)
3.  return imagesCache25;
4.   
5.  if (visuControl.zoom <= 0.8)
6.  return imagesCache50;
7.   
8.  return imagesCacheFull;
9.  }
To give a feedback to the user, we will add a timer that will manage a tooltip that indicates the number of images currently loaded:
1.  function updateStats() {
2.  var stats = $("#stats");
3.   
4.  stats.html(currentDownloads + " card(s) currently downloaded.");
5.   
6.  if (currentDownloads == 0 && statsVisible) {
7.  statsVisible = false;
8.  stats.slideToggle("fast");
9.  }
10.else if (currentDownloads > 1 && !statsVisible) {
11.statsVisible = true;
12.stats.slideToggle("fast");
13.}
14.}
15. 
16.setInterval(updateStats, 200);
Again we note the use of jQuery to simplify animations.
We will now discuss the display of cards.

Cards Display

To draw our cards, we need to actually fill the canvas using its 2D context (which exists only if the browser supports HTML5 canvas):
1.  var mainCanvas = document.getElementById("mainCanvas");

2.  var drawingContext = mainCanvas.getContext('2d');
The drawing will be made by processListOfCards function (called 60 times per second):
1.  function processListOfCards() {
2.   
3.  if (listOfCards == undefined) {
4.  drawWaitMessage();
5.  return;
6.  }
7.   
8.  mainCanvas.width = document.getElementById("center").clientWidth;
9.  mainCanvas.height = document.getElementById("center").clientHeight;
10.totalCards = listOfCards.length;
11. 
12.var localCardWidth = cardWidth * visuControl.zoom;
13.var localCardHeight = cardHeight * visuControl.zoom;
14. 
15.var effectiveTotalCardsInWidth = colsCount * localCardWidth;
16. 
17.var rowsCount = Math.ceil(totalCards / colsCount);
18.var effectiveTotalCardsInHeight = rowsCount * localCardHeight;
19. 
20.initialX = (mainCanvas.width - effectiveTotalCardsInWidth) / 2.0 - localCardWidth / 2.0;
21.initialY = (mainCanvas.height - effectiveTotalCardsInHeight) / 2.0 - localCardHeight / 2.0;
22. 
23.// Clear
24.clearCanvas();
25. 
26.// Computing of the viewing area
27.var initialOffsetX = initialX + visuControl.offsetX * visuControl.zoom;
28.var initialOffsetY = initialY + visuControl.offsetY * visuControl.zoom;
29. 
30.var startX = Math.max(Math.floor(-initialOffsetX / localCardWidth) - 1, 0);
31.var startY = Math.max(Math.floor(-initialOffsetY / localCardHeight) - 1, 0);
32. 
33.var endX = Math.min(startX + Math.floor((mainCanvas.width - initialOffsetX - startX * localCardWidth) / localCardWidth) + 1, colsCount);
34.var endY = Math.min(startY + Math.floor((mainCanvas.height - initialOffsetY - startY * localCardHeight) / localCardHeight) + 1, rowsCount);
35. 
36.// Getting current cache
37.var imageCache = getCorrectImageCache();
38. 
39.// Render
40.for (var y = startY; y < endY; y++) {
41.for (var x = startX; x < endX; x++) {
42.var localX = x * localCardWidth + initialOffsetX;
43.var localY = y * localCardHeight + initialOffsetY;
44. 
45.// Clip
46.if (localX > mainCanvas.width)
47.continue;
48. 
49.if (localY > mainCanvas.height)
50.continue;
51. 
52.if (localX + localCardWidth < 0)
53.continue;
54. 
55.if (localY + localCardHeight < 0)
56.continue;
57. 
58.var card = listOfCards[x + y * colsCount];
59. 
60.if (card == undefined)
61.continue;
62. 
63.// Get from cache
64.var img = imageCache.getImageForCard(card);
65. 
66.// Render
67.try {
68. 
69.if (img != undefined)
70.drawingContext.drawImage(img, localX, localY, localCardWidth, localCardHeight);
71.} catch (e) {
72.$.grep(listOfCards, function (item) {
73.return item.image != img;
74.});
75. 
76.}
77.}
78.};
79. 
80.// Scroll bars
81.drawScrollBars(effectiveTotalCardsInWidth, effectiveTotalCardsInHeight, initialOffsetX, initialOffsetY);
82. 
83.// FPS 
84.computeFPS();
85.}
This function is built around many key points:
  • If the cards list is not yet loaded, we display a tooltip indicating that download is in progress::
1.  var pointCount = 0;
2.   
3.  function drawWaitMessage() {
4.  pointCount++;
5.   
6.  if (pointCount > 200)
7.  pointCount = 0;
8.   
9.  var points = "";
10. 
11.for (var index = 0; index < pointCount / 10; index++)
12.points += ".";
13. 
14.$("#waitText").html("Loading...Please wait<br>" + points);
15.}
  • Subsequently, we define the position of the display window (in terms of cards and coordinates), then we proceed to clean the canvas:
1.  function clearCanvas() {
2.  mainCanvas.width = document.body.clientWidth - 50;
3.  mainCanvas.height = document.body.clientHeight - 140;
4.   
5.  drawingContext.fillStyle = "rgb(0, 0, 0)";
6.  drawingContext.fillRect(0, 0, mainCanvas.width, mainCanvas.height);
7.  }
  • Then we browse the cards list and call the drawImage function of the canvas context. The current image is provided by the active cache (depending on the zoom):
1.  // Get from cache
2.  var img = imageCache.getImageForCard(card);
3.   
4.  // Render
5.  try {
6.   
7.  if (img != undefined)
8.  drawingContext.drawImage(img, localX, localY, localCardWidth, localCardHeight);
9.  } catch (e) {
10.$.grep(listOfCards, function (item) {
11.return item.image != img;
12.});
  • We also have to draw the scroll bar with the RoundedRectangle function that uses quadratic curves:
1.  function roundedRectangle(x, y, width, height, radius) {
2.  drawingContext.beginPath();
3.  drawingContext.moveTo(x + radius, y);
4.  drawingContext.lineTo(x + width - radius, y);
5.  drawingContext.quadraticCurveTo(x + width, y, x + width, y + radius);
6.  drawingContext.lineTo(x + width, y + height - radius);
7.  drawingContext.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
8.  drawingContext.lineTo(x + radius, y + height);
9.  drawingContext.quadraticCurveTo(x, y + height, x, y + height - radius);
10.drawingContext.lineTo(x, y + radius);
11.drawingContext.quadraticCurveTo(x, y, x + radius, y);
12.drawingContext.closePath();
13.drawingContext.stroke();
14.drawingContext.fill();
15.}
1.  function drawScrollBars(effectiveTotalCardsInWidth, effectiveTotalCardsInHeight, initialOffsetX, initialOffsetY) {
2.  drawingContext.fillStyle = "rgba(255, 255, 255, 0.6)";
3.  drawingContext.lineWidth = 2;
4.   
5.  // Vertical
6.  var totalScrollHeight = effectiveTotalCardsInHeight + mainCanvas.height;
7.  var scaleHeight = mainCanvas.height - 20;
8.  var scrollHeight = mainCanvas.height / totalScrollHeight;
9.  var scrollStartY = (-initialOffsetY + mainCanvas.height * 0.5) / totalScrollHeight;
10.roundedRectangle(mainCanvas.width - 8, scrollStartY * scaleHeight + 10, 5, scrollHeight * scaleHeight, 4);
11. 
12.// Horizontal
13.var totalScrollWidth = effectiveTotalCardsInWidth + mainCanvas.width;
14.var scaleWidth = mainCanvas.width - 20;
15.var scrollWidth = mainCanvas.width / totalScrollWidth;
16.var scrollStartX = (-initialOffsetX + mainCanvas.width * 0.5) / totalScrollWidth;
17.roundedRectangle(scrollStartX * scaleWidth + 10, mainCanvas.height - 8, scrollWidth * scaleWidth, 5, 4);
18.}
  • And finally, we need to compute the number of frames per second:
1.  function computeFPS() {
2.  if (previous.length > 60) {
3.  previous.splice(0, 1);
4.  }
5.  var start = (new Date).getTime();
6.  previous.push(start);
7.  var sum = 0;
8.   
9.  for (var id = 0; id < previous.length - 1; id++) {
10.sum += previous[id + 1] - previous[id];
11.}
12. 
13.var diff = 1000.0 / (sum / previous.length);
14. 
15.$("#cardsCount").text(diff.toFixed() + " fps. " + listOfCards.length + " cards displayed");
16.}
Drawing cards relies heavily on the browser's ability to speed up canvas rendering. For the record, here are the performances on my machine with the minimum zoom level (0.05):
image014.png
Browser FPS
Internet Explorer 9M 30
Firefox 5 30
Chrome 12 17
iPad (with a zoom level of 0.8) 7
Windows Phone Mango (with a zoom level of 0.8) 20 (!!)
The site even works on mobile phones and tablets as long as they support HTML5.
Here we can see the inner power of HTML5 browsers that can handle a full screen of cards more than 30 times per second!  This is possible through hardware acceleration.

Mouse Management

To browse our cards collection, we have to manage the mouse (including its wheel).
For the scrolling, we'll just handle the onmouvemove, onmouseup and onmousedown events.

Onmouseup and onmousedown events will be used to detect if the mouse is clicked or not:
1.  var mouseDown = 0;
2.  document.body.onmousedown = function (e) {
3.  mouseDown = 1;
4.  getMousePosition(e);
5.   
6.  previousX = posx;
7.  previousY = posy;
8.  };
9.   
10.document.body.onmouseup = function () {
11.mouseDown = 0;
12.};
The onmousemove event is connected to the canvas and used to move the view:
1.  var previousX = 0;
2.  var previousY = 0;
3.  var posx = 0;
4.  var posy = 0;
5.   
6.  function getMousePosition(eventArgs) {
7.  var e;
8.   
9.  if (!eventArgs)
10.e = window.event;
11.else {
12.e = eventArgs;
13.}
14. 
15.if (e.offsetX || e.offsetY) {
16.posx = e.offsetX;
17.posy = e.offsetY;
18.}
19.else if (e.clientX || e.clientY) {
20.posx = e.clientX;
21.posy = e.clientY;
22.} 
23.}
24. 
25.function onMouseMove(e) {
26.if (!mouseDown)
27.return;
28.getMousePosition(e);
29. 
30.mouseMoveFunc(posx, posy, previousX, previousY);
31. 
32.previousX = posx;
33.previousY = posy;
34.}
This function (onMouseMove) calculates the current position and provides also the previous value in order to move the offset of the display window:
1.  function Move(posx, posy, previousX, previousY) {
2.  currentAddX = (posx - previousX) / visuControl.zoom;
3.  currentAddY = (posy - previousY) / visuControl.zoom;
4.  }
5.  MouseHelper.registerMouseMove(mainCanvas, Move);
Note that jQuery also provides tools to manage mouse events.
For the management of the wheel, we will have to adapt to different browsers that do not behave the same way on this point:
1.  function wheel(event) {
2.  var delta = 0;
3.  if (event.wheelDelta) {
4.  delta = event.wheelDelta / 120;
5.  if (window.opera)
6.  delta = -delta;
7.  } else if (event.detail) { /** Mozilla case. */
8.  delta = -event.detail / 3;
9.  }
10.if (delta) {
11.wheelFunc(delta);
12.}
13. 
14.if (event.preventDefault)
15.event.preventDefault();
16.event.returnValue = false;
17.}
We can see that everyone does what he wants .
The function to register with this event is:
1.  MouseHelper.registerWheel = function (func) {
2.  wheelFunc = func;
3.   
4.  if (window.addEventListener)
5.  window.addEventListener('DOMMouseScroll', wheel, false);
6.   
7.  window.onmousewheel = document.onmousewheel = wheel;
8.  };
And we will use this function to change the zoom with the wheel:
1.  // Mouse
2.  MouseHelper.registerWheel(function (delta) {
3.  currentAddZoom += delta / 500.0;
4.  });
Finally we will add a bit of inertia when moving the mouse (and the zoom) to give some kind of smoothness:
1.  // Inertia
2.  var inertia = 0.92;
3.  var currentAddX = 0;
4.  var currentAddY = 0;
5.  var currentAddZoom = 0;
6.   
7.  function doInertia() {
8.  visuControl.offsetX += currentAddX;
9.  visuControl.offsetY += currentAddY;
10.visuControl.zoom += currentAddZoom;
11. 
12.var effectiveTotalCardsInWidth = colsCount * cardWidth;
13. 
14.var rowsCount = Math.ceil(totalCards / colsCount);
15.var effectiveTotalCardsInHeight = rowsCount * cardHeight
16. 
17.var maxOffsetX = effectiveTotalCardsInWidth / 2.0;
18.var maxOffsetY = effectiveTotalCardsInHeight / 2.0;
19. 
20.if (visuControl.offsetX < -maxOffsetX + cardWidth)
21.visuControl.offsetX = -maxOffsetX + cardWidth;
22.else if (visuControl.offsetX > maxOffsetX)
23.visuControl.offsetX = maxOffsetX;
24. 
25.if (visuControl.offsetY < -maxOffsetY + cardHeight)
26.visuControl.offsetY = -maxOffsetY + cardHeight;
27.else if (visuControl.offsetY > maxOffsetY)
28.visuControl.offsetY = maxOffsetY;
29. 
30.if (visuControl.zoom < 0.05)
31.visuControl.zoom = 0.05;
32.else if (visuControl.zoom > 1)
33.visuControl.zoom = 1;
34. 
35.processListOfCards();
36. 
37.currentAddX *= inertia;
38.currentAddY *= inertia;
39.currentAddZoom *= inertia;
40. 
41.// Epsilon
42.if (Math.abs(currentAddX) < 0.001)
43.currentAddX = 0;
44.if (Math.abs(currentAddY) < 0.001)
45.currentAddY = 0;
46.}
This kind of small function does not cost a lot to implement, but adds a lot to the quality of user experience.

State Storage

Also to provide a better user experience, we will save the display window’s position and zoom. To do this, we will use the service of localStorage (which saves pairs of keys / values ​​for the long term (the data is retained after the browser is closed) and only accessible by the current window object):
1.  function saveConfig() {
2.  if (window.localStorage == undefined)
3.  return;
4.   
5.  // Zoom
6.  window.localStorage["zoom"] = visuControl.zoom;
7.   
8.  // Offsets
9.  window.localStorage["offsetX"] = visuControl.offsetX;
10.window.localStorage["offsetY"] = visuControl.offsetY;
11.}
12. 
13.// Restore data
14.if (window.localStorage != undefined) {
15.var storedZoom = window.localStorage["zoom"];
16.if (storedZoom != undefined)
17.visuControl.zoom = parseFloat(storedZoom);
18. 
19.var storedoffsetX = window.localStorage["offsetX"];
20.if (storedoffsetX != undefined)
21.visuControl.offsetX = parseFloat(storedoffsetX);
22. 
23.var storedoffsetY = window.localStorage["offsetY"];
24.if (storedoffsetY != undefined)
25.visuControl.offsetY = parseFloat(storedoffsetY);
26.}

Animations

To add even more dynamism to our application we will allow our users to double-click on a card to zoom and focus on it.
Our system should animate three values: the two offsets (X, Y) and the zoom. To do this, we will use a function that will be responsible of animating a variable from a source value to a destination value with a given duration:
1.  var AnimationHelper = function (root, name) {
2.  var paramName = name;
3.  this.animate = function (current, to, duration) {
4.  var offset = (to - current);
5.  var ticks = Math.floor(duration / 16);
6.  var offsetPart = offset / ticks;
7.  var ticksCount = 0;
8.   
9.  var intervalID = setInterval(function () {
10.current += offsetPart;
11.root[paramName] = current;
12.ticksCount++;
13. 
14.if (ticksCount == ticks) {
15.clearInterval(intervalID);
16.root[paramName] = to;
17.}
18.}, 16);
19.};
20.};
The use of this function is:
1.  // Prepare animations parameters
2.  var zoomAnimationHelper = new AnimationHelper(visuControl, "zoom");
3.  var offsetXAnimationHelper = new AnimationHelper(visuControl, "offsetX");
4.  var offsetYAnimationHelper = new AnimationHelper(visuControl, "offsetY");
5.  var speed = 1.1 - visuControl.zoom;
6.  zoomAnimationHelper.animate(visuControl.zoom, 1.0, 1000 * speed);
7.  offsetXAnimationHelper.animate(visuControl.offsetX, targetOffsetX, 1000 * speed);
8.  offsetYAnimationHelper.animate(visuControl.offsetY, targetOffsetY, 1000 * speed);
The advantage of the AnimationHelper function is that it is able to animate as many parameters as you wish (and that only with the setTimer function!)

Handling Multi-Devices

Finally we will ensure that our page can also be seen on tablets PC and even on phones.
To do this, we will use a feature of CSS 3: The media-queries. With this technology, we can apply style sheets according to some queries such as a specific display size:
1.  <link href="Content/full.css" rel="stylesheet" type="text/css" />
2.  <link href="Content/mobile.css" rel="stylesheet" type="text/css" 
        media="screen and (max-width: 480px)" />
3.  <link href="Content/mobile.css" rel="stylesheet" type="text/css" 
        media="screen and (max-device-width: 480px)" />
Here we see that if the screen width is less than 480 pixels, the following style sheet will be added:
1.  #legal
2.  {
3.  font-size: 8px; 
4.  }
5.   
6.  #title
7.  {
8.  font-size: 30px !important;
9.  }
10. 
11.#waitText
12.{
13.font-size: 12px;
14.}
15. 
16.#bolasLogo
17.{
18.width: 48px;
19.height: 48px;
20.}
21. 
22.#pictureCell
23.{
24.width: 48px;
25.}
This sheet will reduce the size of the header to keep the site viewable even when the browser width is less than 480 pixels (for example, on a Windows Phone):
image015.png

Conclusion

HTML5 / CSS 3 / JavaScript and Visual Studio 2010 allow to develop portable and efficient solutions (within the limits of browsers that support HTML5 of course) with some great features such as hardware accelerated rendering.
This kind of development is also simplified by the use of frameworks like jQuery.
Also, I am especially fan of JavaScript that turns out to be a very powerful dynamic language. Of course, C# or VB.NET developers have to change theirs reflexes but for the development of web pages it's worth.

In conclusion, I think that the best to be convinced is to try!