May 232011
 

At the recently concluded I/O developer conference, Google made an much awaited announcement – The Google Places API has been opened to everyone (was in beta testing for some time). For the uninitiated, Google Places is a Google application for searching local businesses like hotels, ATMs etc. Places fits in beautifully with Google maps both on the web and the Android.

Now with the API being opened up, Its also possible for any location aware tools and websites to make uses of Places search option to add the functionality to your own sites. Check out the documentation here. I integrated the Places API search in my previous geolocation example explained in a blog post here. The application returns 20 places near the user’s current location and adds markers to the map for each.

Web Workers

Web workers are one of the most interesting concepts of HTML5. They are a standard based on running JS scripts on a background thread rather than the main UI thread. This is extremely important since the more time consuming scripts (like complex mathematical calculations) can be offloaded to a secondary thread rather than freezing up your application, having huge applications in graphics intensive work. I used Web Workers in the current application to work call a server side method which in turn calls the Google places API to search for a list of places near the user’s application.

Here are some limitations of Web Workers in their current implementation:-

  • Not supported on all browsers (Most notably Internet Explorer).
  • We cannot access any DOM object in the Web Worker script. All communication needs to be to the main thread using the postMessage function.
  • Because we cannot access DOM objects, it also doesn’t allow any script to be loaded which refers to DOM, which renders most JS libraries like JQuery and Prototype unusable.

In this example, I following components.

  • An ASP.NET MVC server side in order to call the Google places API. Its a controller method which calls the API url and a method which holds the data. On client side we have a similar method in JSON with same properties. The ASP.NET MVC Model binder converts the JSON object to a CLR object and passes it to the Action method. Client Side Javascript cannot call the Google places API directly because it would be a cross site request and not allowed by Google. Hence the Server’s broker method becomes necessary here
  • Client side main script which uses Geolocation to determine the user’s location. It then passes this location to the MVC Action. Before calling the action, it checks whether the browser supports Web Workers – If so they are offloaded to secondary thread. Else called on the main thread itself.
  • Worker script which makes the AJAX call to the Server using xmlHttpRequest object (Jquery cannot be used here) 🙁

Here is the code.

ASP.NET Server Side

        public ActionResult GoogleSearchAPI(SearchQuery query)
        {
            //Base URL for calling the Google Places API
            string BaseAPIURL = String.Format("https://maps.googleapis.com/maps/api/place/search/xml?location={0},{1}&radius={2}", query.Latitude, query.Longitude, query.Radius);
            if (!string.IsNullOrEmpty(query.Name))
                //Append the name parameter only if data is sent from Client side.
                BaseAPIURL = String.Concat(BaseAPIURL, String.Format("&name={0}", query.Name));
            //Include the API Key which is necessary
            BaseAPIURL = String.Concat(BaseAPIURL, String.Format("&sensor=false&key={0}", GetAPIKey()));
            //Get the XML result data from Google Places using a helper method whichc makes the call.
            string _response = MakeHttpRequestAndGetResponse(BaseAPIURL);
            //Wrap XML in a ContentResult and pass it back the Javascript
            return Content(_response);
        }

        //Helper method to call the URL and send the response back.
        private string MakeHttpRequestAndGetResponse(string BaseAPIURL)
        {
            var request = (HttpWebRequest)WebRequest.Create(BaseAPIURL);
            request.Method = WebRequestMethods.Http.Get;
            request.Accept = "application/json";
            string text;
            var response = (HttpWebResponse)request.GetResponse();

            using (var sr = new StreamReader(response.GetResponseStream()))
            {
                text = sr.ReadToEnd();
            }

            return text;
        }

    ///
    /// Our data object  which sends the object with data from client side to
    /// server. The Model binder takes care of conversion between JSON and
    /// CLR objects.
    ///
    public class SearchQuery
    {
        public string Latitude { get; set; }
        public string Longitude { get; set; }
        public string Radius { get; set; }
        public string Type { get; set; }
        public string Name { get; set; }
    }

Client Side Main script

Most of the Geolocation code is the same as my previous example. This is the additional code written after the geolocation data is found and the coordinates is passed on to another method which uses it to retrieive places data and mark it on map

var spawnWorkerThread = function (position) {
    //This is executed if the getPosition is successfull. Moving the map to the user's location
    map.panTo(new google.maps.LatLng(position.coords.latitude, position.coords.longitude));
    var coordinates = new google.maps.LatLng(position.coords.latitude, position.coords.longitude);
    //Create a JSON object with the details of search query.
    var placesQuery = { Latitude: position.coords.latitude, Longitude: position.coords.longitude, Type: "establishment", Radius: "500" };
    //Check if the browser supports WEbWorkers
    if (Modernizr.webworkers) {
        printMsg("Web Workers are supported on your browser. Searching for places nearby your location");
        //Load the Worker Script
        var myWorker = new Worker("/files/webworkersmvc/Scripts/worker.js");
        //Send the JSON object to the Worker thread after serializing it to string
        myWorker.postMessage(JSON.stringify(placesQuery));
        // receive a message from the worker
        myWorker.onmessage = function (event) {
            //Send the returned data to the processPlacesData method
            processPlacesData(event.data);
        };
    }
    else {
    //Make the call in a standard way and not using Web Workers
        printMsg("Web Workers isnt supported on your browser.Calling Places API the conventional way");
        var xhr = new XMLHttpRequest();
        //Calling the controller method.
        xhr.open("POST", "http://www.ganeshran.com/Files/webworkersmvc/Home/GoogleSearchAPI");
        xhr.setRequestHeader("Content-Type", "application/json");
        xhr.onreadystatechange = function () {
            if (xhr.readyState == 4 && (xhr.status == 200 || xhr.status == 0)) {
                processPlacesData(xhr.responseText);
            }
        }
        xhr.send(JSON.stringify(placesQuery));
    }

};

var processPlacesData = function (data) {
    //Parse the XML result into an xml obect
    var places = $.parseXML($.trim(data));
    var xmldoc = $(places);
    var resultstring = "";
    //Iterate through each result object
    $("result", places).each(function () {
        var typestring = "";
        $("type", this).each(function () {
            typestring += $(this).text() + " , ";
        });
        //Create a MapResult object for each result.
        var resObj = new mapResult($("name", this).text(),
                                      $('vicinity', this).text(),
                                      typestring,
                                      $('lat', this).text(),
                                      $('lng', this).text(),
                                      $('icon', this).text());
        //Create a Google Maps Marker and use the result object's latitude and
        //longitude.
        var marker = new google.maps.Marker({
            position: new google.maps.LatLng(resObj.latitude, resObj.longitude),
            title: resObj.name,
            animation: google.maps.Animation.DROP
        });
        //If the screen is smaller then zoom lesser else zoom more closer.
        //This is to make the markers visible
        if (screen.width < 1000) {
            map.setZoom(12);
        }
        else {
            map.setZoom(15);
        }
        //Set each marker on the map
        marker.setMap(map);
        //this is for the window to show information when the marker is clicked.
        //A single infowindow is reused in order to display only one
        google.maps.event.addListener(marker, 'click', function () {
            infowindow.setContent(resObj.getMarkerHTML());
            infowindow.open(map, marker);
        });
    });

};

//Javascript object to hold the map data and the get the HTML required for the marker.
function mapResult (name, vicinty, types, latitude, longitude, icon) {
    this.name = name;
    this.vicinity= vicinty;
    this.types = types;
    this.latitude = latitude;
    this.longitude = longitude;
    this.iconpath = icon;
}
//Prototype method to avoid creating seperate copies of the method
//for each object
mapResult.prototype.getMarkerHTML = function () {
var htmlstring = "
<div style="color: blue; font-weight: bold;">";
    htmlstring += "Name: " + this.name + "";
    htmlstring += "Types: " + this.types + "";
    htmlstring += "Location: " + this.latitude + "," + this.longitude + "";
    htmlstring += "Vicinity: " + this.vicinity +"</div>";
    return htmlstring;
};

Worker Side Script

The worker side script is pretty straightforward. Just calls the controller and passes on the data to the main thread using the PostMessage function

// receive a message from the main JavaScript thread
onmessage = function (event) {
    // do something in this worker
    var info = event.data;
    var xhr = new XMLHttpRequest();
    xhr.open("POST", "http://www.ganeshran.com/Files/webworkersmvc/Home/GoogleSearchAPI");
    xhr.setRequestHeader("Content-Type", "application/json");
    xhr.onreadystatechange = function () {
        if (xhr.readyState == 4 && (xhr.status == 200 || xhr.status == 0)) {
            //Send message back to Main thread
            postMessage(xhr.responseText);
        }
    }
    xhr.send(info);
};

Demo Page

http://www.ganeshran.com/files/webworkersmvc/

Demo Pics

On PC

On Android

May 122011
 

Gone are the days when the web was a one-size-fits-all kind of global information vending machine. In the past few years, continuous efforts are being made towards making the web as local as possible, with websites being able to present content suited to the users browsing it.

One huge component of this new wave are the development of location aware websites. Websites which give relevant information without the user having to explicitly search for it. For e.g. A movie ticket booking website which can be used to book tickets in theatres all over the country. Very few people, if any would travel more than 20 km to watch a movie. Hence it makes business sense to display the theatres in the 20 km radius of the users location. Instead of subjecting the user to an information overload, the website is smart enough to simplify the entire process and cut down the time taken.

Geolocation isnt a new concept. Previously, websites used the IP address and did a lookup to get a rough idea of the user’s location. There were other methods like the locale setting too. So while framing the HTML5 spec, the W3C decided to arrive at a standard for providing the client’s location to the server which abstracts out the actual method used by the browser to determine it (Cell tower triangulation, IP address, GPS etc). Since its a privacy concern to reveal one’s location, the whole geolocation API depends on explicit permission separately provided for each website.

I created an example page which integrates Google Maps API and geolocation and displays a map as per the user’s location. Since not every browser would be able to support geolocation yet, I used the JS library Modernizr which detects whether the feature is supported by the end browser without us having to do the browser sniffing using the user agent. Here is the script code.

/************************************
 * Title: Geolocation API access
 * Author: Ganesh Ranganathan
 ************************************/

var map; //global variable for the map object
$(document).ready(function(){
	//this is the default latitude /longitude to be set on the map
	var latlng = new google.maps.LatLng(-34.397, 150.644);
	var myOptions = {zoom: 8,
			center: latlng,
			mapTypeId: google.maps.MapTypeId.ROADMAP};

	//Map constructor which creates the map
	map = new google.maps.Map($('#map_canvas')[0],myOptions);

	//Modernizr is a JS library which lets us check if
	//the target browser supports these features
	if(Modernizr.geolocation)
	{
		printMsg('Waiting for permission from you',false);
		//The getCurrentPosition takes in two callbacks - one for
		//success and one for error. Both functions are defined anonymously
		//inside the method
		navigator.geolocation.getCurrentPosition(function(position){
			printMsg('Permission Granted: Your Coordinates are '+position.coords.latitude+
					 ' & '+position.coords.longitude);
			//This is executed if the getPosition is successfull. Moving the map to the user's location
			map.panTo(new google.maps.LatLng(position.coords.latitude,position.coords.longitude));
		},function(error){
			//This is executed if the getPosition is unsucesfull.
			//Show the error reason to the user
			switch(error.code)
			{
			case error.TIMEOUT:
				printMsg ("Error: Timeout",false);
				break;
			case error.POSITION_UNAVAILABLE:
				printMsg ("Error: Position unavailable",false);
				break;
			case error.PERMISSION_DENIED:
				printMsg ("Error: Permission denied",false);
				break;
			case error.UNKNOWN_ERROR:
				printMsg ("Error: Unknown error",false);
				break;
			}
		});
	}
	else //Print this message if GeoLocation itself isnt supported by the browser
		printMsg("geolocation is not supported on your browser",false)
});

//Helper function to set the status message to
//the span value
var printMsg = function(txt,append){
	if(!append)
		$('#statusMsg').text(txt);
	else{
		var existingText = $('#statusMsg').text();
		$('#statusMsg').text(existingText+'  '+txt);
	}
};

The getCurrentPosition method of the navigator.geolocation object takes in two functions as the parameters. First is the SuccessCallback and second is the errorCallback. The success callback is passed the position object which contains the location information like latitude and longitude. This is the simple markup for the page. Please note that the Modernizr, Google Maps, Jquery scripts are referenced in the order they are needed.


<script src="Scripts/modernizr-1.7.min.js" type="text/javascript"><!--mce:0--></script>
<script src="Scripts/jquery-1.6.min.js" type="text/javascript"><!--mce:1--></script>
<script src="http://maps.google.com/maps/api/js?sensor=false" type="text/javascript"><!--mce:2--></script>
<script src="Scripts/scripts.js" type="text/javascript"><!--mce:3--></script>
<h1>Geolocation API with HTML5</h1>
<div id="status">
 <span id="statusMsg"> </span></div>

The demo page for this application can be seen at – http://www.ganeshran.com/Files/geolocationtry

Here is the application when its run for the first time in Chrome. As you can see it asks for permission from the user. Meanwhile the map is centered towards the default location.

If we allow the application to access our location, then the map moves to the user’s coordinates – Bangalore in my case. Here is a screenshot – this time in Firefox

Since its a standardized API, the same code works for mobile browsers as well. Here are some screenshots of the page running on the Android stock browser which is also geolocation compliant.

After granting permission

May 112011
 

I came across this amazingly useful jquery feature at Microsoft Tech-ed 2011 at a talk by Fritz Onion. Microsoft for a long time now has been favoring jQuery instead of the in house developed Ajax Control Toolkit. Though both of them coexist for now in some of the ASP.NET Visual studio templates, I believe its only a matter of time before ACT is totally discarded as jQuery has proven to be the defacto web standard for Javascript programming.

Its also heartening to know that not only are Microsoft planning to adopt jQuery as the standard in their frameworks, they are also going to actively contribute code to the jQuery project. (To clarify, Its not a fork from the original project and jQuery will continue to be licensed under the MIT-PL). One such Microsoft initiative is the jQuery plugin – Templates. Its so useful and simple to use that I am amazed no one thought of this before.

jQuery Templates rids us the need for using DOM manipulation code while adding elements on the fly. It acts like a placeholder for certain pieces of data, the values of which are dynamically substituted by the plugin and also acts as a sort of repeater for multiple elements following the same data structure. I would call it a cross between the .NET String.Format() and HTML repeater control. For e.g. below is a sample template tag

There are two things that stand out in the above script tag, one is an unfamiliar type – x-jquery-tmpl and the other are the $ signs. Apart from that, the rest of the stuff is pretty much run-of-the-mill markup. The x-jquery-tmpl type is used to make the browser ignore the content between the script tag and not try to parse it. This is necessary since the parsing would need to be done by the plugin. The $ is used to mark the member name of the binding data structure. For e.g. ${genre} indicates that this value would be replaced by the genre property of the object that is bound to the template tag.

This is the script code which gets data from the server (an Xml file which contains a catalog root node with 12 book nodes and details like name, author, price etc) through an ajax call and then binds it to the template script tag which we described in the previous paragraph. The template needs an array of objects which have to bound to the individual tags. Any markup will be repeated the number of times as there are elements in the array.

/***************************************
* Description: JQuery Templating feature
* Author : Ganesh Ranganathan 
*****************************************/
$(document).ready(function () {
    //Method to be called when all DOM elements are loaded
    makeAjaxCall();
});

function makeAjaxCall() {
    $.ajax({
        url: 'ajax/catalog.xml', //Make an ajax call to the server
        datatype: 'xml',
        success: function (data) {
            // Callback function on successfull call
            var books = [];
            //Iterate through the xml file and apply function on each 'book' node
            $(data).find('book').each(function () {
                //books is an object array where we create an object for each xml node
                //and assign values to the properties. These property names are bound in
                //the templates
                books.push({ bookName: $(this).find('title').text(),
                author: $(this).find('author').text(),
                genre: $(this).find('genre').text(),
                price: $(this).find('price').text(),
                description: $(this).find('description').text()
                 });
            });

        //This is the templating call where the data structure is bound to the id
        //of the script tag. The tmpl() call returns the DOM element containing the HTML
        // which is then appended to the mainDiv which is the holder element
            $('#books').tmpl(books).appendTo('#mainDiv');
        }
    });
}

This is the minimal markup that needs to be used for the page. The important part is the the script files – jquery.1.4.4.min.js and jquery.tmpl.js that can be downloaded from its repo on github or you could add a NuGet Package reference as well which auto adds all the dependencies and saves a lot of time.

<head runat="server">
    <title></title>
    <script src="Scripts/jquery-1.4.4.min.js" type="text/javascript"></script>
    <script src="Scripts/jQuery.tmpl.js" type="text/javascript"></script>
    <script src="Scripts/script.js" type="text/javascript"></script>
    <link href="Styles/styles.css" rel="stylesheet" type="text/css" />
</head>

<body>
    <script id="books" type="text/x-jquery-tmpl">
    <div id="bookHolder">Name: ${bookName} <br />
    Author: ${author} <br />
    Genre: ${genre} <br />
    Price: ${price} 
    </div>
    </script>
    <form id="form1" runat="server">
    <div id="heading">Books Catalog</div>
    <div id="mainDiv">
    </div>
    </form>
</body>
</html>

With some ugly basic styling, here is what the final page looks like. Please note that there was absolutely no DOM manipulation in the Javascript code. Conventional approaches would have required iterating through the xml file, creating DOM elements, assigning their node value, taking care of their hierarchy etc. JQuery’s templating plugin rids us of most of this additional plumbing code and makes the script files more concise and readable.

Oct 262010
 

Closures are one of the most powerful features of Javascript. They are also one of the easiest features to mess up if you don’t have a proper understanding of them. I took to studying them better after encountering a strange bug in my application. After delving deeper, it finally made sense.

Simply put, a closure is a feature which allows local variables in a function to be alive even after the function which contained them has gone out of scope. Those of us who come from the strict world of Java/.NET might find this truly unnerving – How can a local variable be still alive even after its container function goes off the stack? But closures are possible in the .NET and Java worlds too. Here is a great article on the subject by the guru Jon Skeet himself.

In Javascript the easiest way to create a closure is to define a function inside another function. The inner function still retains access to all the local variables of its parent function. Keep in mind though, that this access isnt by value but rather by reference. This was the exact situation which happened to me. In the sample code below, I iterate through an array of country names to create a dynamic list of divs. Clicking the country name’s div would trigger an alert message with the country’s name. Seems quite simple to do. Here is the code.

var countries = ["India", "USA", "Brazil", "Netherlands", "China"];

var styleObject = {
    'width': '150px',
    'background-color': '#66CCFF',
    'border': 'solid 1px black',
    'padding': '2px',
    'margin': '10px'
};

$(document).ready(function () {
    wrongUse();
});

var wrongUse = function () {

    for (var i = 0; i < countries.length; i++) {
        var countryName = countries[i];
        var countryDiv = $('<div>' + countryName + '</div>').css(styleObject);
        countryDiv.click(function () {
            alert("The Country you clicked on is " + countryName);
        });
        $(countryDiv).appendTo($('#canvas'));
    }

};

Surprisingly however, irrespective of whatever div was clicked – the alert message always showed “China”. This was because the inner click function was accessing the local variable called countryName through reference and not value. Since for every iteration this variable was changed, only the last value which was China, remained. The interesting part is that the click function is defined in the document.ready(), but is invoked much later. Because of the closure, each click event still maintained a link to the outer function’s local variable whose value was now “China” because of subsequent iterations.

To avoid the closure problem, we can use the Javascript Function constructor (Note the capital ‘F’). This constructor is used to create an anonymous function type and return an object of it. The advantage of this is that the Function constructor doesn’t create a closure with its enclosing type.

var countries = ["India", "USA", "Brazil", "Netherlands", "China"];

var styleObject = {
    'width': '150px',
    'background-color': '#66CCFF',
    'border': 'solid 1px black',
    'padding': '2px',
    'margin': '10px'
};

$(document).ready(function () {
    rightUse();
});

var rightUse = function () {
    for (var i = 0; i < countries.length; i++) {
        var countryName = countries[i];
        var countryDiv = $('<div>' + countryName + '</div>').css(styleObject);
        countryDiv.click(new Function("alert('The Country you clicked on is " + countryName + ".')"));

        $(countryDiv).appendTo($('#canvas'));
    }
};

We see that we get the desired result

As usual, JQuery manages to give us an even easier way to achieve this – the each() function. The each function operates on array types and executes a similar function on each element in the collection. The advantage is that there is no sharing of the scope between the various executions which eliminates the risk of shared variables being modified.

var countries = ["India", "USA", "Brazil", "Netherlands", "China"];

var styleObject = {
    'width': '150px',
    'background-color': '#66CCFF',
    'border': 'solid 1px black',
    'padding': '2px',
    'margin': '10px'
};

$(document).ready(function () {
    jQueryWay();
});

var jQueryWay = function () {
    $.each(countries, function (index, value) {
        var countryDiv = $('<div>' + value + '</div>').css(styleObject);
        countryDiv.click(function () {
            alert("The country you clicked on is " + value);
        });

        $(countryDiv).appendTo($('#canvas'));
       
    });
};
Jun 072010
 

A few weeks back, Ibrahim told me about a cool JQuery plugin – Flot which makes charting really easy. So I played around and worked out an example. It plots your total cost versus minutes for both postpaid and prepaid mobile connections. Just enter the plan details for both prepaid and postpaid and you get the graph, which could be used to compare which plan suits your usage range. Postpaid plans usually have a monthly recurring cost but also give away free minutes. So it makes sense to go for it only if your monthly usage exceeds certain number of minutes. This graph will show the minimum usage beyond which its profitable to go for a plan.

You can check out the demo page here. A Screenshot is below

Flot can be downloaded here. You can also check out the API documentation and the examples. The code for the example is given below and you can also download it from the site.

Javascript:-

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
/* ************************************************
* Developer: Ganesh Ranganathan
* Date: 07-June-2010
* Description: This is an example for Flot-  a Jquery plugin
* ***********************************************/
 
//A class which encapsulates the cost for both prepaid and postpaid
function Cost(monthly, free, local, std, sms) {
    this.monthly = monthly;
    this.free = free;
    this.local = local;
    this.std = std;
    this.sms = sms;
 
    //This function calculates  the cost, its supplied with the number of prepaid and post paid
    this.Calculate = function(minutes, stdProp) {
        //If the number of minutes is less than free minutes, just return the monthly recurring and SMS cost
        if (minutes < free)
            return parseInt(monthly) + (parseInt(sms * appVariables["sms"]));
        else
            minutes = minutes - free; // reduce free minutes
        var stdPropMin = minutes * stdProp; // calculate number of STD
        var localPropMin = minutes * (1 - stdProp);
        //return the total cost
        return (parseInt(monthly) + (parseInt(local * localPropMin)) + (parseInt(std * stdPropMin)) + (parseInt(sms * appVariables["sms"])));
    }
}
 
//No Magic numbers anywhere. Any constants/default variable value should be entered here
//and accessed. Customize defaults later using other controls
var appVariables =
{
    minuteRange: 800, //Max minutes per month
    allowedChars: [8, 190], //All allowed character inputs for cost boxes
    serviceTaxPercent: 10.2, //Service tax percentage - not applicable for prepaid
    sms: 50, // Number of SMS sent per month -- **customize later**
    stdProp: 0.5 //Proportion of STD to local -- **customize later**
}
 
//Chart options. Refer API.txt for complete list of
//available options
var chartOptions = {
    yaxis: { min: 0 },
    xaxis: { tickDecimals: 0 },
    series: {
        lines: { show: true }
    }
};
 
//This JSON variable will hold the data. Empty now - data will be calculated dynamically
//based on the cost
var dataset =
{
    "prepaid": {
        label: "prepaid",
        data: [[0,0]]
    },
    "postpaid": {
        label: "postpaid",
        data: [[0,0]]
    }
};
 
//This is the array which holds the data. We will pass
//it to FLOT.
var data = [dataset["prepaid"], dataset["postpaid"]];
 
//This is init function run after DOM loads
$(document).ready(function() {
 
    //Push all the allowed charcters in the array.
    for (var i = 48; i < 58; i++)
        appVariables["allowedChars"].push(i);
 
    //Set all cost boxes to 0
    $('.cost').each(function() {
        $(this).val(0);
    });
 
    //Hooking up event handlers - The keyup event redraws the chart, while keydown event validates data
    //If keydown validates false, it cancels the input
    $('.cost').keydown(checkInput);
    $('.cost').keyup(redrawChart);
 
    //Plot the chart. We pass the placeholder of the chart, the data array
    //and the options array
    var plot = $.plot($("#chart"), data, chartOptions);
 
});
 
//Function to check input. If the entered key isnt in the allowed character array, the input is cancelled.
//Its enough to validate here since keyup will be fired after here
var checkInput = function(event) {
    var found = false;
    for (var i = 0; i < appVariables["allowedChars"].length; i++) {
        if (event.keyCode == appVariables["allowedChars"][i])
            found = true;
    }
    if (!found)
        return false;
};
 
//Function to redraw the chart. IT will be fired once the costboxes's value is changed
var redrawChart = function(event) {
    //First all arrays need to be set to null. This
    //is important since we are pushing the data in
    //the array and not index setting it
    dataset["postpaid"]["data"] = [[]];
    dataset["prepaid"]["data"] = [[]];
    //Call the functions to populate the arrays.
    calculatePostPaid(appVariables["minuteRange"]);
    calculatePrePaid(appVariables["minuteRange"]);
 
    //Plot the chart. We pass the placeholder of the chart, the data array
    //and the options array
    var plot = $.plot($("#chart"), data, chartOptions);
};
 
var calculatePostPaid = function(minuteRange) {
    //Get monthly recurring cost. We need to add the service tax to it
var monthly = parseInt($('#postMonthly').val()) * (1 + appVariables["serviceTaxPercent"] / 100);
    //Get the free minutes, local cost, std cost and sms cost from the text boxes
    var free = $('#postFree').val();
    var local = $('#postLocal').val();
    var std = $('#postStd').val();
    var sms = $('#postSMS').val();
    //Create the cost object and pass all values to its
    //constructor
    var postCost = new Cost(monthly, free, local, std, sms);
    //Push the data into the array. Calculate for each minute the cost using the
    //Cost class's calculate method.
    for (var i = 0; i < minuteRange; i++)
        dataset["postpaid"]["data"].push(new Array(i, postCost.Calculate(i, appVariables["stdProp"])));
};
 
var calculatePrePaid = function(minuteRange) {
//Get the free minutes, local cost, std cost and sms cost from the text boxes
    var monthly = $('#preMonthly').val();
    var free = $('#preFree').val();
    var local = $('#preLocal').val();
    var std = $('#preStd').val();
    var sms = $('#preSMS').val();
    //Create the cost object and pass all values to its
    //constructor
    var preCost = new Cost(monthly, free, local, std, sms);
    //Push the data into the array. Calculate for each minute the cost using the
    //Cost class's calculate method.
    for (var i = 0; i < minuteRange; i++)
        dataset["prepaid"]["data"].push(new Array(i, preCost.Calculate(i,appVariables["stdProp"])));
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<html>
<head>
<title>Flot </title>
<link rel="Stylesheet" type="text/css" href="Styles/layout.css" />
<script language="javascript" type="text/javascript" src="js/jquery.js"></script>
<script language="javascript" type="text/javascript" src="js/jquery-ui-1.7.1.custom.min.js"></script>
<script language="javascript" type="text/javascript" src="js/jquery.flot.js"></script>
<script language="javascript" type="text/javascript" src="js/myscript.js"></script>
</head>
<body>
<div id="chart">
</div>
<div id="optionsHolder" class="divOptions">
<table id="tblOptions">
<caption>Enter the cost</caption>
<tr>
<th scope="col">Charges For</th>
<th scope="col">Prepaid</th>
<th scope="col">PostPaid</th>
</tr>
<tr id="rental">
    <td>Monthly Recurring Cost</td>
    <td><input type="text" id="preMonthly" class="cost" /></td>
    <td><input type="text" id="postMonthly" class="cost"/></td>
</tr>
<tr id="freeMinutes">
    <td>Free Minutes</td>
    <td><input type="text" id="preFree" class="cost" /></td>
    <td><input type="text" id="postFree" class="cost"/></td>
</tr>
<tr id="localCost">
    <td>Local Call: </td>
    <td><input type="text" id="preLocal" class="cost" /></td>
    <td><input type="text" id="postLocal" class="cost"/></td>
</tr>
<tr id="stdCost">
    <td>STD Call: </td>
    <td><input type="text" id="preStd" class="cost" /></td>
    <td><input type="text" id="postStd" class="cost"/></td>
</tr>
<tr id="smsCost">
    <td>SMS: </td>
    <td><input type="text" id="preSMS" class="cost" /></td>
    <td><input type="text" id="postSMS" class="cost"/></td>
</tr>
</table>
</div>
</body>
</html>
Apr 272010
 

Even the simplest data driven applications today are not only about gathering, understanding and digesting data. Its also important to decide how you display it in such a manner that the user is able to get the gist in the least amount of time. A great way to do that is display data as graphically as possible – with Charts, Graphs and maps. For displaying maps there are quite a few APIs available – Google Maps, Yahoo maps and Bing maps.

I have been playing around with the Google Maps API for the last few days and found it an amazingly simple way to display geographical data in your application. All you need to do is to reference the Google maps JavaScript file and a few lines of code would get you started with a basic map.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<html xmlns="http://www.w3.org/1999/xhtml" >
<head>
    <title>Google Maps API</title>
<!-- Google maps Script has to included -->
<script src="http://maps.google.com/maps?file=api&amp;v=2&amp;sensor=false&amp;key=YOUR_API_KEY_HERE" type="text/javascript"> </script>
<script type="text/javascript">
    function initialize() {
        var map = new GMap2(document.getElementById('map_canvas'));
        map.setCenter(new GLatLng(12.98058, 77.58854), 13);
    }
</script>
</head>
<body onload="initialize()" onunload="GUnload()">
<div id="map_canvas" style="height:500px;width:500px" />
</body>
</html>

There are a few things to note here

  • GMap2 is the object which returns the map object. We need to pass the DOM element on which the map has to be drawn. This is called the canvas and can be a div HTML element.
  • map.SetCenter: This method is the first that needs to be called on a map object in order to set its coordinates. In the code we passed the latitude, longitude and the zoom level of the map.
  • GUnload: Most important part of the application. Call this function in the onUnload event handler to avoid any unpleasant memory leaks in the application. This eliminates most of the circular references.

Now only displaying a map is not of much use. We need to be programmatically able to manipulate each part of the map in order to show our data in the canvas. This can be done through the Google Maps API which is an object oriented library representing each graphical part of the map.

For a small working example, I wrote some code to display data from an xml file on the server. Suppose you want to display the hotels on a map in Bangalore, and when the user clicks on the hotel, the details of the hotel should appear in a popup window. Something like this:-

The xml file resides on the server and contains all the details of the hotel including their latitude and longitude. The JavaScript gets the xml through an AJAX call and then iterates through to get the information of the individual hotels. For each one, it creates a marker and adds it to the map. Also using the details it creates the HTML to be displayed in the popup event and hooks up the click event handler to do it.
Below is the code:-

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
/* ************************************************
* Developer: Ganesh Ranganathan
* Date: 26-Apr-2010
* Description: This is a Google Maps API Example.
* ***********************************************/
 
var map1;
 
$(document).ready(function() {
    //Init Method is run on load of Page DOM
    initialize();
    refreshData();
});
 
//GUnload method cleans up any google map1 objects
$(window).unload(function() {
    GUnload();
});
 
//The KeyValue pair for the location
//that we start with. Currently its bangalore
var DEF_LOC = {
'lat': 12.98058,
'lng': 77.58854,
'zoom':14
};
 
function initialize() {
    //Getting the map1 object. The [0] Index is because JQuery
    //returns an array of JavaScript elements from a DOM search
    //and we need only one
    map1 = new GMap2($('#map_canvas')[0]);
    //Setting the map1 to Bangalore and Adding standard map1 controls
    map1.setCenter(new GLatLng(DEF_LOC['lat'], DEF_LOC['lng']), DEF_LOC['zoom']);
 
    $.each([new GLargeMapControl(), new GMapTypeControl(), new GOverviewMapControl()]
    , function(index, value) {
        //Adding all the controls to the map
        map1.addControl(value);
    });
 
    //Setting the icon properties to display
    var hotelIcon = new GIcon();
    hotelIcon.image = "hotel_icon1.gif";
    hotelIcon.shadow = "http://chart.apis.google.com/chart?chst=d_map1_pin_shadow";
    hotelIcon.iconSize = new GSize(25, 34);
    hotelIcon.shadowSize = new GSize(35, 28);
    hotelIcon.iconAnchor = new GPoint(6, 20);
    hotelIcon.infoWindowAnchor = new GPoint(5, 1);
    markerOptions = { icon: hotelIcon };
}
 
function refreshData() {
    $.ajax(
{
    url: 'Hotels_data.xml', // Data Source in XML file
    //No Caching, Asynchronous call and setting the
    //Datatype to XML.
    cache: false,
    async: true,
    datatype: 'xml',
    success: function(xml) {
        //Successful ajax callback function. First clear
        //all existing markers to preserve memory
        map1.clearOverlays();
 
        $(xml).find('hotel').each(function() {
            //Iterating through each hotel tag in the xml
            //and gettiing the lattitude and longitude
            var lat = $(this).find('latitude').text();
            var lng = $(this).find('longitude').text();
            //pass node to helper function to get the markup
            //for the marker.
            var html = getMarkerHTML($(this));
            //Creating a marker object and assigning the click
            //event handler for the map to pan to the center of the
            //canvas. Also the Info window needs to open
            var marker = new GMarker(new GLatLng(lat, lng), markerOptions);
            GEvent.addListener(marker, "click", function() {
                map1.panTo(new GLatLng(lat, lng), DEF_LOC['zoom']);
                marker.openInfoWindowHtml(html);
            });
            //Add the marker to the map
            map1.addOverlay(marker);
        });
    },
    error: function() {
        //Error Callback. Just inform the user
        alert('An error has occured');
    }
});
}
 
//This is a helper function to get the markup
//for the marker's click node.
function getMarkerHTML(xmlNode) {
    //find the text in the childd nodes
    var get = function(tag) {
        return xmlNode.find(tag).text();
    };
 
    //Bolden the Key and append the value
    var decorateBold= function(key,value){
        return '<b>'+key+': </b>'+value+'<br />';
    };
 
    var singleCost = xmlNode.find('single_room').text();
    var doubleCost = xmlNode.find('double_room').text();
    var html = "<div style= 'background-color: #FFCC66;font-family: ";
    html += "Verdana;font-size: 1.0em;padding: 3px'>" + decorateBold('Name', get('name'));
    html += decorateBold('Address', get('address'));
    if(!isNaN(singleCost))
    html += '<b>Single Room: </b>'+ singleCost;
    if(!isNaN(doubleCost))
    html += '<b> Double Room: </b>'+doubleCost+'<br />';
    else
    html += '<br />';
    html += decorateBold('Phone',get('phone_no.'));
    html += decorateBold('Email',get('email'));
    html += decorateBold('Website',get('website'));
    return html+= '</div>';
}

Click here for the demo.

Apr 112010
 

Anyone who has written a fairly complex web application would have experienced the quirks of JavaScript. Though immensely powerful and probably the only way to write good client side code, JavaScript code can get difficult especially while doing complex DOM navigation and including cross browser support (Many browsers see the DOM differently).

The best possible solution to harness the power of JavaScript and making the code fun to write is to use client side libraries which do the dirty work for you. Arguably the most popular one available today is JQuery. It makes code both easy to read and write. Here are some cool things you can do with JQuery. To get started download jQuery at jquery.com, and reference the script file before any your custom scripts.

1
2
3
4
5
<html xmlns="http://www.w3.org/1999/xhtml" >
<head>
<script type="text/javascript" src="Scripts/jquery.js" language="javascript" ></script>
<script type="text/javascript" src="Scripts/myscript.js" language="javascript" ></script>
</head>
  • Code which runs on page load: The classic way to do this was using the window.onload event. However, the onload event waits for all of the page to get loaded, including images. This can make the user wait for a long time before the events fire. jquery has an alternative – document.ready which fires as soon as the DOM gets loaded. In heavy pages, this can dramatically increase user experience
1
2
3
$(document).ready(function() {
    alert("Hello jquery");
});
  • Hooking up dynamic event handlers: Hooking up Javascript events was a pain. Now its surprisingly easy. Note how the document.getElementById has been replaced by the $. In this example we hook up a dynamic event handler for a button click event which gets the value from a textbox.
1
2
3
4
5
6
7
8
$(document).ready(function() {
    //Getting value of button and assigning an event
    //handler using an anonymous method
    $('#btnSayHello').click(function() {
        alert('Hello ' + $('#txtName').val());
    });
 
});
  • Animation made easy: Animation has always difficult in Javascript and pushed people towards Flash/Silverlight. JQuery’s animate API makes things almost too easy. In the example below, a div’s size is increased, its moved towards the left, made slightly transparent
1
2
3
4
<input type="button" id="btnAnimate" value="Animate" />
<div id="containerDiv" >
This is the text which would be animated
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$(document).ready(function() {
    //The animation is hooked on to
    //the Animate Button's click handler,
    //A callback function is specified to alert
    //when the animation is over
    $('#btnAnimate').click(function() {
        $('#containerDiv').animate({
            opacity: 0.6,
            marginLeft: '+=2in',
            fontSize: '3em'
        }, 1000, function() {
            alert('animation complete');
        });
    });
});
  • AJAXify your application almost instantly: Writing raw AJAX code was just too much of a hassle considering the time you spent on ironing out the browser differences rather than your core application logic. JQuery makes AJAX extremely simple and takes care of the background work of creating the xmlhttp object, making the call and giving you back the result. In the below example, I created a simple autocomplete textbox that makes the suggestions dynamically based on what you type. This would have taken atleast 150 lines if written in plain-ol javascript.

HTML Code

1
2
3
<span>Enter Country Name</span><br />
<input type="text" id="txtCountry" style="width:250px" /><br />
<span id="autoList" style="width:250px;display:block" />

Javascript code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
$(document).ready(function() {
    //Hooking the event handler to the keyup function
    $('#txtCountry').keyup(function() {
        //Only call AJAX function if atleast 3 characters are there
        $('#autoList').empty();
        if ($('#txtCountry').val().length > 2) {
            $.ajax( //Jquery AJAX api
        {
        url: "GetCountriesSuggestion.aspx",
        //Send in data as a query string
        data: "country=" + $('#txtCountry').val(),
        cache: false,
        async: true, //Make an asynchronous req
        datatype: "xml",
        //This is a callback function
        success: function(xml) {
            $('#autoList').empty();
            if ($(xml).find('Country').length > 0) {
                var ul = $('<ul></ul');
                //Creating a border and removing the bullets
                //that appear in an unordered list by default
                ul.css('border', 'solid 1px black').css('list-style', 'none');
                ul.css('left', '0px');
                //Find the country element and iterate
                //through each element
                $(xml).find('Country').each(function() {
                    //Creating a list item element and hooking up
                    //its click handler
                    var li = $('<li>' + $(this).text() + '</li>').css('cursor', 'pointer')
                    li.css('left', '0px');
                    li.hover(function() {
                        //This changes the color and background color
                        //of the suggestion box when the mouse is taken over it
                        //The hover method takes in two functions- one invoked on
                        //mouseover and one on mouseout
                        $(this).css('color', '#FFFFFF');
                        $(this).css('background-color', '#0000FF')
                    }, function() {
                        $(this).css('color', '#000000');
                        $(this).css('background-color', '#FFFFFF')
                    });
                    li.click(function() {
                        //Set the clicked item to textbox text and empty the
                        //Collecttion list
                        $('#txtCountry').val($(this).text());
                        $('#autoList').empty();
                    });
                    ul.append(li);
                });
                $('#autoList').append(ul);
            }
        }
    });
        }
    });
});

ASP.NET Code Behind in C#

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
protected void Page_Load(object sender, EventArgs e)
        {
            if (!String.IsNullOrEmpty(Request.QueryString["country"]))
            {
                Response.Write(GetCountriesList(Request.QueryString["country"]));
            }
        }
        private string GetCountriesList(string countryName)
        {
            //creating a string builder object
            StringBuilder _response = new StringBuilder();
            XmlDocument _xDoc = new XmlDocument();
            //Load the xml file with all the countries
            _xDoc.Load(Server.MapPath("Countries.xml"));
            //Get all country names in the xml file
            XmlNodeList _allCountries = _xDoc.GetElementsByTagName("Entry");
            //Writing out xml declaration and root tag
            _response.Append("<?xml version=\"1.0\" encoding=\"ISO-8859-1\" standalone=\"yes\"?><Countries>");
            //Iterating through each country
            foreach (XmlNode _country in _allCountries)
            {
                //If what user entered matches the country name,
                //create a dynamic xml node and append
                if ((countryName.Length <= _country.FirstChild.InnerText.Length) &&( countryName.ToUpper().Equals(_country.FirstChild.InnerText.Substring(0, countryName.Length))))
                    _response.Append("<Country>" + _country.FirstChild.InnerText + "</Country>");
            }
            //Append closing root tag
            return _response.Append("<Countries>").ToString();
        }
    }