2011/11/21
21 Nov, 2011

Writing Google Gadgets – A Tutorial – Part 04

  • Samisa Abeysinghe
  • VP, Training - WSO2

Writing Google Gadgets – A Tutorial – Part 04

So far, we have looked into various key concepts, of Google gadget development, using a Web content retrieval and data visualization example.
In this final part of the tutorial, we will look into the code in detail, and also explore a bit about the versatility of the gadget and discuss some tips and tricks.

Writing Google Gadgets - A tutorial - Part 01

        Google Gadgets Basics

                Anatomy of a Gadget

                Basic Gadget Example - Hello Gadget

                Hello Gadget in Action

        Pulling Information from the Web into a Gadget

                Content Type URL Gadgets

                Content Type HTML Gadgets

                Pulling Information into a Gadget with makeRequest

        Dynamic Height with Google Gadgets

Writing Google Gadgets – A tutorial – Part 02

        Processing Fetched HTML with JavaScript

        Using TEXT vs DOM Content Type when Working with HTML

        Using External JavaScript Libraries

Writing Google Gadgets - A tutorial - Part 03

        Setting User Preferences

                Using enum Data Type as an Options List

                Using Boolean Data Type

                Integer Settings with String Data Type

                Setting Preferences from Within the Gadget

        Dealing with Views

Writing Google Gadgets – A tutorial – Part 04

        Versatility of Google Gadgets as a Presentation Instrument

        The Complete Google Gadget Code

        Tips and Tricks

                Error Handling

                Incremental Development

                When Things go wrong: F12

                Always Test with IE

                Use an IDE


Versatility of Google Gadgets as a Presentation Instrument

In the past three parts of this tutorial, we developed a gadget incrementally, to fetch HTML from a URL, extract data and present those in an appealing manner.
Let’s have a look at a dashboard, built using this single gadget. The following is a screen shot of such a dashboard, created using WSO2 Gadget Server.

WSO2 Gadget Server Full

As it can be seen, the single gadget is duplicated six times and present data on six different mailing lists.
Moreover we have six themes used for the six graphs, so that mapping of the visual elements on the dashboard is naturally fed into the user’s mind.

Furthermore, if you look at the kind of data being presented, we have architecture list, developer lists, code commit message list and issue
reporting lists all presented side by side on the same dashboard using a single gadget. So we can see how design activities, developer discussions,
code development, issue reporting and fixing reflected on this dashboard. For example, you can easily spot the release efforts in sync with the peaks
in svn commits and issue related activities.

The moral of the story is that, a carefully designed gadget can help you present and visualize wealth of knowledge.

A gadget is worth a thousand pages.


The Complete Google Gadget Code

Following the final complete code of the gadget we developed so far. On left hand side, you see the code and on right hand side, you see the description. If you want more details on a line of code on the left, see the corresponding line number on the right pane for the description.

<?xml version="1.0" encoding="UTF-8" ?>
<Module>
    <ModulePrefs title="WSO2 Open Source Development Data from MarkMail">
        <Require feature="dynamic-height" />
        <Require feature="setprefs"/>
        <Require feature="views" />
    </ModulePrefs>
    
    <UserPref name="ShowGraph" display_name="Show Graph"
        datatype="bool" default_value="true">
    </UserPref>
    <UserPref name="MailingList" display_name="Mailing List"
        datatype="enum" default_value="3">
        <EnumValue value="0"
            display_value="WSO2 Architecture List - https://markmail.org/browse/org.wso2.architecture" />
        <EnumValue value="1"
            display_value="WSO2 Carbon Dev List - https://markmail.org/browse/org.wso2.carbon-dev" />
        <EnumValue value="2"
            display_value="WSO2 Stratos Dev List - https://markmail.org/browse/org.wso2.stratos-dev" />
        <EnumValue value="3"
            display_value="WSO2 SVN Commits - https://markmail.org/browse/org.wso2.carbon-commits" />
        <EnumValue value="4"
            display_value="WSO2 Carbon Jira Activity - https://markmail.org/browse/org.wso2.carbon-jira" />
        <EnumValue value="5"
            display_value="WSO2 Stratos Jira Activity - https://markmail.org/browse/org.wso2.stratos-jira" />
    </UserPref>
    <UserPref name="GraphWidth" display_name="Graph Width"
        default_value="550">
    </UserPref>
    <UserPref name="GraphHeight" display_name="Graph Height"
        default_value="625">
    </UserPref>
    <UserPref name="GraphTheme" display_name="Graph Theme"
        datatype="enum" default_value="2">
        <EnumValue value="0" display_value="Keynote" />
        <EnumValue value="1" display_value="37signals" />
        <EnumValue value="2" display_value="Rails Keynote" />
        <EnumValue value="3" display_value="Odeo" />
        <EnumValue value="4" display_value="Pastel" />
        <EnumValue value="5" display_value="Greyscale" />
    </UserPref>

    <Content type="html">
        <![CDATA[
        
            <style>
                #wrapperDiv {
                 position:relative;
                }
                #contentDiv {
                 float: left;
                 margin: 20px; 
                }
                #graphDiv {
                 float: left;
                 margin: 20px;
                 }
            </style>
            
            <a href="https://markmail.org/"><img src="https://markmail.org/images/logo_white.gif" alt="MarkMail"/></a>  
            <div id="wrapperDiv">
                <div id="contentDiv"></div>
                <span id="hiddenSpan" style="display:none">Hidden</span>
                <div id="graphDiv">
                  <canvas id="graph" width="600" height="625">Graph</canvas>
                  Grapth Theme: <select id="graphThemeSelect" onchange="if (this.selectedIndex >=0 ) updateTheme(this);">
                      <option value="0">Keynote</option>
                      <option value="1">37signals</option>
                      <option value="2">Rails Keynote</option>
                      <option value="3">Odeo</option>
                      <option value="4">Pastel</option>
                      <option value="5">Greyscale</option>
                  </select>
                </div>
            </div>
            
            <script type="text/javascript" src="js/js-class.js"></script>
            <script type="text/javascript" src="js/excanvas.js"></script>
            <script type="text/javascript" src="js/bluff-min.js"></script>
            
            <script type="text/javascript">
            
              var prefs = new gadgets.Prefs();
              
              var urls = new Array("https://markmail.org/browse/org.wso2.architecture",
                                   "https://markmail.org/browse/org.wso2.carbon-dev",
                                   "https://markmail.org/browse/org.wso2.stratos-dev",
                                   "https://markmail.org/browse/org.wso2.carbon-commits",
                                   "https://markmail.org/browse/org.wso2.carbon-jira",
                                   "https://markmail.org/browse/org.wso2.stratos-jira");
                                   
              var titles = new Array("WSO2 Architecture List Discussions", 
                                      "WSO2 Carbon Dev List Discussions",
                                     "WSO2 Stratos Dev List Duscussions",  
                                     "WSO2 Carbon and Stratos SVN Commits",
                                     "WSO2 Carbon Jira Activity", 
                                     "WSO2 Stratos Jira Activity");
              
              var columns = new Array("Discussions", "Discussions", "Discussions",
                                      "Commits", "Activity", "Activity");
            
              function getHtml() {    
                  var parameters = {};  
                  parameters[gadgets.io.RequestParameters.CONTENT_TYPE] = gadgets.io.ContentType.TEXT;  
                  var listID = prefs.getString("MailingList");  
                  gadgets.io.makeRequest(urls[listID], processResponse, parameters);
              };
            
              function processResponse(response) {  
                    var textData = response.text;
                    textData = textData.replace(/,/g, '');
                
                    textData = textData.replace(/td><a/gi, 'th span="row"><a');
                    textData = textData.replace(/\/a><\/td/gi, '/a></th');
                   
                    document.getElementById('hiddenSpan').innerHTML = textData;
                   
                    var listID = prefs.getString("MailingList");
                    document.getElementById('contentDiv').innerHTML = "<table id='data'><caption>" + 
                                                                      titles[listID] + 
                                                                      "</caption><thead><tr><th></th><th scope='col'>" +
                                                                      columns[listID] + 
                                                                      "</th></tr></thead>" + 
                                                                      document.getElementById('browse').getElementsByTagName('table')[0].innerHTML + 
                                                                      "</table>";
                   
                    if (gadgets.views.getCurrentView().getName() == "canvas" ) {
                        drawGraph();
                        document.getElementById('contentDiv').style.display = "block";
                        document.getElementById('graphDiv').style.display = "block";
                    } else if (prefs.getBool("ShowGraph") == true) {
                        drawGraph();
                        document.getElementById('contentDiv').style.display = "none";
                        document.getElementById('graphDiv').style.display = "block";
                    } else {
                        document.getElementById('graphDiv').style.display = "none";
                        document.getElementById('contentDiv').style.display = "block";
                    }
                    gadgets.window.adjustHeight();
              };
            
              function drawGraph() {
                    var g = new Bluff.SideBar('graph', prefs.getInt("GraphWidth") + 'x' + prefs.getInt("GraphHeight"));
                    switch (prefs.getInt("GraphTheme")) {
                        case 0: 
                            g.theme_keynote();
                            break;
                        case 1:
                            g.theme_37signals();
                            break;
                        case 2:  
                            g.theme_rails_keynote();
                            break;
                        case 3:
                            g.theme_odeo();
                            break;
                        case 4:
                            g.theme_pastel();
                            break;
                        case 5:
                            g.theme_greyscale();
                    }
                    g.tooltips = true;
                    g.data_from_table('data');
                    g.draw();
              };
              
              function updateTheme(theme) {
                  prefs.set("GraphTheme", theme.value);
                  drawGraph();
              }
            
              gadgets.util.registerOnLoadHandler(getHtml);
            
            </script>
        ]]>
    </Content>
</Module>
 
<- Start Module
    <- Start ModulePrefs
        <- Require dynamic height feature
        <- Require set user preferences feature
        <- Require views feature
    <- End ModulePrefs
    
    <- User preference Show Graph
              Type boolean and defaults to true
              So we will show the graph by default, if false the table
    <- User preference Mailing List
              Type enum and defaults to WSO2 SVN Commits mailing list
              Values go from 0 to 5
            
        
        
        
        
        
        
        
        
        
        
    
    <- User preference Graph Width
              Type string and defaults to 550
    
    <- User preference Graph Height
              Type string and defaults to 625
    
    < User preference Graph Theme
         Type enum and defaults to Keynote theme 
         
        
        
        
        
        
    

    <- Start HTML Content 
        < - Start CDATA section containing main gadget logic 
        
            <- Start style section for content and graph divs
                Wrapper div  containing content and graph divs
                    Will allow relative positioning 
                
                Content div will float to the left
                 
                  
                
                Graph div too will float to the left
                    but after content div, if it is visible  
                So we will see both the table and graph in 
                    canvas view, side by side  
            <- End style 
            
            <- Link to MarkMail home page   
            <- Wrapper div that contains the content div and graph div
                <- Content div to hold HTML table with raw data
                <- Hidden span to hold data before processing. Used to convert text response into document DOM. 
                <- Graph div to hold the graph canvas and theme options input 
                  <- Graph canvas to draw the graph using Bluff library
                  <- Graph themes select control. 
                            When a theme is selected, updateTheme function is called 
                      
                      
                      
                      
                      
                      
                  
                
            
            <- Bluff JavaScript library files used to draw the graph. 
                      Since we have these loaded to the same location as the gadget location 
                      in registry, we can use relative links from within the gadget. 
            
            <- Start of main JavaScript logic of the gadget
            
              Gadget Prefs object to deal with user preferences 
              
              Array of mailing list URLs. 
                  Array indexes map to the MailingList user preference enum values. 
                                   
                                   
                                   
                                   
                                   
              Array of graph titles.  
                  Array indexes map to the MailingList user preference enum values.
                                       
                                     
                                      
                                     
              
              Array of graph column names. 
                  Array indexes map to the MailingList user preference enum values.
            
              JavaScript function getHtml    
                  
                  Set content type to TEXT  
                  Get the user selected mailing list ID, based on user preferences   
                  Make request with the selected mailing list's URL and set processResponse as the callback
              
            
              JavaScript function processResponse. Called when response from the fetched URL is received.   
                    Retrieve text data from response
                    Remove all ',' characters from numbers. e.g. convert 1,256 into 1256. This is required to let the graphs pick numbers right from HTML table.   
                
                    Convert td elements into th in the months column to treat them as Y axis on the graph
                    
                   
                    Set hidden span's inner HTML to processed text data
                   
                    Pick the user selected mailing list ID to select the corresponding table title and column names 
                    Set content div elemen's inner HTML to processed table data
                         along with matching table title and 
                         matching column name 
                                               
                                               
                                               
                                              
                   
                    If we are in the canvas view
                        Draw the graph
                        and make both content div and graph view visible 
                        
                    If it is not the canvas view and show graph user option is set to true 
                        Draw the graph
                        and make content div hidden and and graph view visible 
                        
                    Else show graph user option is set to false, hence
                        make content div and graph view hidden
                        
                    
                    Adjust the window height to make sure that what is marked as visible is properly seen on the gadget
              
            
              JavaScript function drawGraph
                    Create a Bluff SideBar graph with user set width and height 
                    Switch based on user set Graph Theme option and set the theme
                        
                        
                        
                        
                        
                        
                          
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
                    
                    Set tooltips option to true
                    Set the table to pick data from to be the table with ID 'data'
                    Draw the graph
              
              
              JavaScript function updateTheme
                  Set the user selected theme from select control as a user preference 
                  Draw the graphs to update the theme
              
            
              Register getHtml function as the on load handler. getHtml will be triggered when the gadget is loaded. 
            
            <-End of main JavaScript logic
        >- End CDATA section
    <- End HTML Content
<- End Module


Tips and Tricks

Here are some tips and tricks to keep in mind, when developing gadgets


Error Handling

When developing this example gadget, we did not pay much attention to error handling. For example, what would happen,
if we could not fetch the content from URL, due to network outage? As of now, we would display an empty gadget.
However it would be nice to display a meaningful error to the user.

This is relatively simple to deal with. All that you need to do is to use a try/catch block.

<?xml version="1.0" encoding="UTF-8" ?>
<Module>
    <ModulePrefs title="WSO2 Open Source Development Data from MarkMail">
        <Require feature="dynamic-height" />
    </ModulePrefs>
    <Content type="html">
  <![CDATA[
  <a href="https://markmail.org/"><img src="https://markmail.org/images/logo_white.gif" alt="MarkMail"/></a>
  <div id="contentDiv"></div>
  <span id="hiddenSpan" style="display:none;"/>
  <script type="text/javascript">
      var url = "https://markmail.org/browse/org.wso2.carbon-commits";
      function getHtml() {    
        try {  
          var parameters = {};  
          parameters[gadgets.io.RequestParameters.CONTENT_TYPE] = gadgets.io.ContentType.TEXT;  
          gadgets.io.makeRequest(url, processResponse, parameters);
        } catch (err) {
             document.getElementById('contentDiv').innerHTML = "<stong> error connecting to URL <a href='" + url +"'>" + url + "</a> </strong>";
        } 
      };
    
      function processResponse(response) {  
        try {
           document.getElementById('hiddenSpan').innerHTML = response.text;
           document.getElementById('contentDiv').innerHTML = "<table>" + document.getElementById('browse').getElementsByTagName('table')[0].innerHTML + "</table>";
        } catch (err) {
           document.getElementById('contentDiv').innerHTML = "<stong> error processing data from URL <a href='" + url +"'>" + url + "</a> </strong>";
        }
           gadgets.window.adjustHeight();
      };

      gadgets.util.registerOnLoadHandler(getHtml);

 </script>
  ]]>
    </Content>
</Module>


Incremental Development

Gadgets are simple to implement for simple cases, but are complex for complex cases. Like we did in this tutorial,
it is a good practice to start small and incrementally keep on building the gadget, step by step, testing along the way.

Incremental testing is key to success, as when things go wrong, we need to back track and recollect on what went wrong and fix those.
If we are to take big steps, it would be harder to pinpoint problem cases.


When Things Go Wrong: F12

When things are not working, the best way to debug is to use developer tools offered by the browsers. Both with Chrome and Internet Explorer,
the function key, F12, pops up the developer tools.

When you expect things to happen and cannot seem to figure out why things are not the way they supposed to be with the gadget,
the console of the developer tools can lead you to the errors and error locations.

In addition to developer tools, the "Inspect element" option you get on the right click pop-up on Chrome Web browser is quite handy,
when you know where in the gadget things supposed to appear and yet does not.


Always Test with IE

Internet Explorer (IE) is often notorious for "weird" issues with Web developers.
But on the other hand, if you can make it to work with IE, the chances are that, it will work with others most of the time, though not always.

Since you are developing for wider audience, it is important consider cross browser aspects. If you plan to open up the gadgets for wider audience,
it is important to use IE as part of the incremental test efforts. In other words, test with IE, while you are developing, rather than trying to
test the finished gadget with IE.


Use an IDE

I used Eclipse to develop the sample gadget. However, due to the use of CDATA element to wrap the main logic, syntax highlighting is not that great.

But even the simple things like indentation can help a great deal, when you are developing a fairly complex gadget.

The syntax highlighting problem can be worked around by developing the main logic using an html file, and then copying over to the gadget XML CDATA element, when using Eclipse.




This brings us to the end of this four part tutorial. Please feel free to provide us the feedback, and help us improve the content.

Please refer to the table of contents section to find out about the other parts of this tutorial.

 

About Author

  • Samisa Abeysinghe
  • VP, Training
  • WSO2 Inc.