Feb 152010
 

The Vancouver Olympics has been generating considerable bit of interest with everyone speculating on the country which bags the most number of golds. To make it easier to keep up to date with the medals tally, I wrote a Chrome extension which displays the top 10 countries. It can be downloaded at the chrome site here.

As with the Twitter Trends, I wasn’t able to find any RSS feed which gives the details of the medals. So I had to scrape the HTML page for the data and then dynamically create the table and display it. The manifest.json file gives the details of the gadget.

{
  "name": "Vancouver Medals Tally",
  "version": "1.0",
  "description": "Displays latest medals tally from Vancouver 2010 olympics",
  "icons": { "128": "icon.png" },
  "browser_action": {
    "default_title": "Vancouver Medals Tally",
    "default_icon": "icon.png",
    "popup": "Background.html"
  },
  "permissions": [
    "tabs",
    "http://www.vancouver2010.com/*"
  ]
}

The stylesheet gives the basic styling of the table and the banner above it. The banner has two states, one where the gadget is loading and hence its smaller in size. After the gadget increases in size, the banner must also be made to fit. This is done in CSS. The switching is done through 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
  body {
  font-family: helvetica, arial, sans-serif;
  font-size: 12px;
  overflow: hidden;
  }
 
  #title
  {
      font-size:14px;
      overflow:hidden;
  }
 
  #mainDiv
  {
      min-width:205px;
      margin-top:5px;
  }
 
th {
	font: bold 11px "Trebuchet MS", Verdana, Arial, Helvetica,
	sans-serif;
	color: #6D929B;
	border-right: 1px solid #C1DAD7;
	border-bottom: 1px solid #C1DAD7;
	border-top: 1px solid #C1DAD7;
	letter-spacing: 2px;
	text-transform: uppercase;
	text-align: left;
	padding: 6px 6px 6px 12px;
	background: #CAE8EA;
}
  td
  {
    border-right: 1px solid #C1DAD7;
	border-bottom: 1px solid #C1DAD7;
	background: #fff;
	padding: 5px 5px 5px 10px;
	color: #6D929B;
  }
 
   #bannerDiv.expanded
   {
       font-size: 18px;
       vertical-align:middle;
       padding-bottom:10px;
       text-align:center;
       height:30px;
   }
 
   #bannerDiv.collapsed
   {
       font-size: 12px;
       vertical-align:middle;
       padding-bottom:5px;
       text-align:center;
       height:15px;
   }
 
   #bannerDiv.collapsed img
   {
        height: 10px;
        width: 20px;
   }
 
    #bannerDiv.expanded img
   {
        height: 28px;
        width: 56px;
   }

In the JavaScript, we retrieve the HTML table from the website, create a table using DOM and populate it with the data and append it to the main div. The challenge here was to parse the HTML page into the XML DOM structure to allow JavaScript manipulation. Chromer and Firefox support a class called DOMParser which can be used to construct a DOM structure from the AJAX response text. However, this was giving an error because of the script tags. After much googling, I found a solution at Stack Overflow where the user had removed the data between script tags and then parsed the document. This worked!! and once we the data could be manipulated, all that remained was creating a HTML table and appending the proper rows to the table.

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
/* Developer  : Ganesh Ranganathan
    * Date        : 02/15/2010
    * Description : This is a Google Chrome Extension created to display latest medals
                    tally for Vancouver olympic games 2010
    * Disclaimer  : I have checked that this extension doesnt violate any copyrights
                    However, if you feel otherwise please point me towards the documentation
                    and I will take corrective measures */
 
var req;
var _feedURL = 'http://www.vancouver2010.com/olympic-medals/';
 
function main() {
    //Call the loading method after a slight delay to allow
    //Chrome to load the extension body. Else it looks ugly with
    //the pressed button waiting for the feed to load.
    setTimeout("delayedCall()", 20);
}
 
function delayedCall() {
    _req = new XMLHttpRequest();
    //Hooking up the readystate changed event handler.
    _req.onreadystatechange = function() {
        if (_req.readyState == 4 && _req.status == 200) {
            //Come here only if all data is loaded.
            var doc = _req.responseText;
 
            //This workaround is for parsing the HTML string into
            //an xml Dom. The script tags are removed from the HTML
            //since they cause a lot of parser errors
            var _tempDiv = document.createElement('div');
             _tempDiv.innerHTML = doc.replace(/<script (.|\s)*?\/script>/g, '');
 
            //Get medals table - There is one 1
            var medalsTable = _tempDiv.getElementsByTagName('table');
            var tableRows = medalsTable[0].getElementsByClassName('sortMe');
 
            var mainDiv = document.getElementById('mainDiv');
            mainDiv.innerHTML = ""; //resetting existing html to null
            var table = document.createElement('table');
            table.cssClass = 'medalsTally'; //Setting CSS Class
            var tbody = document.createElement('tbody');
            table.appendChild(LoadHeader());
 
            for (var i = 0; i < tableRows.length; i++) {
                //Iterate through each of the table nodes 
                //and create a new table for displaying in the extension
                var _childNodes = new Array(5);
                _childNodes[0] = createTableCell(tableRows[i].getElementsByClassName('c2')[0].firstChild.nextSibling.nodeValue, 'td');
                _childNodes[0].width = '40%';
                _childNodes[1] = createTableCell(tableRows[i].getElementsByClassName('c3')[0].firstChild.nodeValue, 'td');
                _childNodes[1].width = '15%';
                _childNodes[2] = createTableCell(tableRows[i].getElementsByClassName('c4')[0].firstChild.nodeValue, 'td');
                _childNodes[2].width = '15%';
                _childNodes[3] = createTableCell(tableRows[i].getElementsByClassName('c5')[0].firstChild.nodeValue, 'td');
                _childNodes[3].width = '15%';
                _childNodes[4] = createTableCell(tableRows[i].getElementsByClassName('c6')[0].firstChild.nodeValue, 'td');
                _childNodes[4].width = '15%';
                //Appending everything in the array to the row
                var _tempRow = AppendAllData(_childNodes);
                tbody.appendChild(_tempRow);
            }
            //append body to table
            table.appendChild(tbody);
            //Changing Banner size
            document.getElementById('bannerDiv').className = 'expanded';
            mainDiv.appendChild(table);
        }
    }
    //Without these two the AJAX request would never get sent.
    _req.open("GET", _feedURL, true);
    _req.send(null);
}
 
//Helper function ofr loading the header cells
function LoadHeader() {
    var thead = document.createElement('thead');
    var headerRow = document.createElement('tr');
    var countryName = createTableCell('CountryName','th');
    var gold = createTableCell('Gold','th');
    var silver = createTableCell('Silver','th');
    var bronze = createTableCell('Bronze','th');
    var total = createTableCell('Total','th');
    headerRow.appendChild(countryName);
    headerRow.appendChild(gold);
    headerRow.appendChild(silver);
    headerRow.appendChild(bronze);
    headerRow.appendChild(total);
    thead.appendChild(headerRow);
    return thead;
 
}
 
function createTableCell(name,tag) {
    var _tempCell = document.createElement(tag);
    var _txtNode = document.createTextNode(name);
    _tempCell.appendChild(_txtNode);
    return _tempCell;
}
 
 
function AppendAllData(_childNodes) {
var _tempRow = document.createElement('tr');
    for (var i = 0; i < _childNodes.length; i++)
        _tempRow.appendChild(_childNodes[i]);
    return _tempRow;
}</script></script>

The HTML is just two div tags – one for the content and one for the banner. Please download the extension and let me know your valuable feedback

Feb 122010
 

Google Chrome recently opened up their extensions API. Though Chrome had supported extensions quite early on in its lifecycle, the API was still a work in progress and one needed to open Chrome in the developer mode to play around. A few weeks back, Chrome announced that extensions are now enabled in normal mode as well.

So I decided to poke around the API and write a simple extension. Though it doesnt compare to FireFox yet with much of the browser still not accessible, but I am sure future versions will bring much better customization capability. The extension I wrote is very simple and just displays the latest trending topics from twitter. The source can be downloaded here and the packaged version here.

Lets see the anatomy of a Chrome Extension. An Extension consists of three main parts

  • Manifest
  • UI
  • All your other files like scripts, stylesheets, images, etc

The manifest is a json file which contains the metadata about your extension. Details like Title, Author etc

{
  "name": "Twitter Trends",
  "version": "1.0",
  "description": "Displays Current Trending Topics",
  "icons": { "128": "icon.png" },
  "browser_action": {
    "default_title": "Twitter Trends",
    "default_icon": "icon.png",
    "popup": "Background.html"
  },
  "permissions": [
    "tabs",
    "http://twitter.trends.free.fr/*"
  ]
}

As you can see, it contains basic details like name, version, icon etc. The popup attribute tells us the page to open when the browser button is clicked. As you can see I have specified a normal HTML page here. A popup extension is nothing but a simple HTML page opened when the toolbar button is clicked. Another interesting part is the permissions section. Here we can specify what all websites the extension can make AJAX calls to retrieve data. Since it matches based on wildcards, a * denotes all possible subdirectories under the website.

Now the HTML part. Since its a normal HTML page, you can include references to scripts, images, style sheets etc. The extension directory works as the root and all paths must be relative to it. Since its a simple page, I included all the scripts and styling information in a single page.

First the styling information. Always helps to keep this separate.

  body {
  font-family: helvetica, arial, sans-serif;
  font-size: 12px;
  overflow: hidden;
  background-color:#ddeef6;
  }
 
  #title
  {
      font-size:14px;
      overflow:hidden;
  }
 
  #mainDiv
  {
      min-width:175px;
      margin-top:5px;
  }
 
  .topicDisplayClass
  {
      display:block;
     min-height:2.2em;
     cursor:pointer;
  }
 
  .linkClass
  {
      color:#2276bb;
      text-decoration:none;
      vertical-align:middle;
      padding-left:2px;
  }
 
  .topicDisplayClass:Hover
  {
      background-color:#edfeff;
  }

Next the javascript. This is the most important part of the extension. What we do here is retrieve the RSS feed for the trending topics and parse it as an xmlDocument. After iterating thorough this xml, a span element is dynamically created for each item and it is appended into our main content holder. This main function call is delayed using the setTimeout function. We do this in order to let the user see the popup and see some progress which data is being fetched. Writing this in the onload event would make the user wait for the whole process to complete before seeing the popup.

/* Developer  : Ganesh Ranganathan
* Date       : 02/12/2010
* Description: This is a Google Chrome Extension created to display
            latest trending topics on twitter. */
 
//Global variables: the xmlHttpRequest and Feed URL
var _req;
var _feedURL = 'http://twitter.trends.free.fr/feed/';
 
function main() {
    //Call the loading method after a slight delay to allow
    //Chrome to load the extension body. Else it looks ugly with
    //the pressed button waiting for the feed to load.
    setTimeout("delayedCall()", 20);
 
}
 
function delayedCall() {
    //No browser compatibility code Yay!!!
    _req = new XMLHttpRequest();
    //Hooking up the readystate changed event handler.
    _req.onreadystatechange = function() {
        if (_req.readyState == 4 &amp;&amp; _req.status == 200) {
            //Come here only if all data is loaded.
            var doc = _req.responseText;
            var parser = new DOMParser();
            //Parsing the Data into the XML dom for manipulation
            var xmlDoc = parser.parseFromString(doc, "text/xml");
            var mainDiv = document.getElementById('mainDiv');
            mainDiv.innerHTML = "";
            //Getting all the items
            var items = xmlDoc.getElementsByTagName('item');
 
            for (var i = 0; i &lt; items.length; i++) {
                //Iterating through all the items and getting the title
                //and link. The description isnt a big deal in this feed.
                var title = items[i].getElementsByTagName('title');
                var link = items[i].getElementsByTagName('link');
                //Create an anchor and a span
                var spanNode = document.createElement('span');
                var linkNode = document.createElement('a');
                //Set Link to span
                linkNode.setAttribute('href', link);
                linkNode.innerText = title[0].childNodes[0].nodeValue;
                linkNode.className = 'linkClass';
                spanNode.appendChild(linkNode);
                spanNode.className = 'topicDisplayClass';
                var str = link[0].childNodes[0].nodeValue
                //Hooking the click event handler to the span
                //It calls the chrome tabs API to open the clikced
                //URL in a new tab
                spanNode.addEventListener("click",new Function("clickHandler('"+str+"')"),false);
                mainDiv.appendChild(spanNode);
            }
        }
    }
 
    //Without these two the AJAX request would never get sent.
    _req.open("GET", _feedURL, true);
    _req.send(null);
}
function clickHandler(str) {
    chrome.tabs.create({ url: str });
}

The markup is largely simple. Just the main content holder which contains a progress bar which is displayed to user while we load the data. Once that is done, the progress bar is replaced with the span nodes containing the trending topics.

<span id="title">Trending Topics</span>
<div id="mainDiv">
<!-- A Progress Bar to show the user something is being done -->
<img src="ajax-loader.gif" alt="" width="175px" /></div>

Now the manifest, html and image files need to be in a single folder. After that go to Chrome and click the customize dropdown button, and select Extensions. In the extensions page, select Load Unpacked Extension and select the folder where the extension files reside.

Once the extension is loaded, it shows as unpacked and you can do all the testing here by reloading it everytime any changes are done.

After testing, we can choose the pack option to package the extension for download. A .crx file would be generated which can be distributed for download. Below are some screenshots of the extension we just created.

Data is loading