Tuesday, November 25, 2008

How to add user preferences to your Google Gadget

In the previous post, we saw how to create a simple Google Gadget that reads the CNN.com Top Stories feeds and display them. We make it by default to display only 10 stories and to display the summaries, but what about if I want to see some more stories or less? or if I don't want to the see the summary, only the story headline? Well, there is a way for the user to customize the gadget , we just need to use User Preferences.

With user preferences we can set information in the gadget that is specific to the user. To have user preferences you just need to specify them inside the Module tag of the gadget. See the following example:

<UserPref name="show_summ" display_name="Show Summaries?" datatype="bool" default_value="false" required="true"/>


As you can see what you need to specify is the name of the preference, the display name, the data type, the default value and if it is required or not. The data type, the default value and required attributes are optional. The default value for data type is string and the default value for required is false.

Let's see how our CNN.com Top Stories gadget would look with preferences that will allow the user to set how many stories wants to see and if he/she wants to see the summary or not.
<?xml version="1.0" encoding="UTF-8"?>
<Module>
<ModulePrefs
title="CNN.com Top Stories"
title_url="http://www.cnn.com/"
author="Your name"
author_email="youremail@domain.com"
description="Gadget to see the current CNN.com top stories."
width="450"
height="350"
scrolling="true">
<Require feature="dynamic-height"/>
</ModulePrefs>
<UserPref name="num_entries" display_name="Number of Entries:" default_value="5"/>
<UserPref name="show_summ" display_name="Show Summaries?" datatype="bool" default_value="false"/>

<Content type="html">
<![CDATA[
<style>
div#main_container {
font-size:12px;
font-family:Arial;
margin: 5px;
text-align: left;
text-decoration: none;
}

div#main_container h2 {
background: url(http://i2.cdn.turner.com/cnn/.element/img/1.0/logo/cnn.logo.rss.gif) no-repeat left top;
display: block;
height: 33px;
text-indent: -100000;
width: 144px;
}

</style>
<script type="text/javascript">

// Get userprefs
var prefs = new _IG_Prefs();
var summary = prefs.getBool("show_summ");
var entries = prefs.getInt("num_entries");

// Function to dipslay content retrieved from XML feed
function displayContent() {

// If user wants to display more than 50 entries, display an error
// and set the value to 50, the max allowed.
if (entries > 50)
{
alert("You cannot display more than 50 entries.");
entries = 50;
}

var url = "http://rss.cnn.com/rss/edition.rss";

// Use the _IG_FetchFeedAsJSON() function to retrieve core feed data from
// the specified URL. Then combine the data with HTML markup for display in
// the gadget.
_IG_FetchFeedAsJSON(url, processFeed, entries, summary);

}

// Function to process the feed
function processFeed(feed) {
var contentDiv = _gel("main_container");

if (feed == null) {
contentDiv.innerHTML = "Invalid data.";
return;
}

// Start building HTML string that will be displayed in gadget.
var html = "";

// Access the fields in the feed
html += "<h2>" + feed.Title + "</h2>";
html += "<div>" + feed.Description + "</div><br>";

// Access the data for a given entry
if (feed.Entry) {
for (var i = 0; i < feed.Entry.length; i++) {
html += "<div>" + "<a target='_blank' href='" + feed.Entry[i].Link + "'>" + feed.Entry[i].Title + "</a> ";
// The feed entry Date field contains the timestamp in seconds
// since Jan. 1, 1970. To convert it to the milliseconds needed
// to initialize the JavaScript Date object with the correct date,
// multiply by 1000.
var milliseconds = (feed.Entry[i].Date) * 1000;
var date = new Date(milliseconds);
html += date.toLocaleDateString();
html += " ";
html += date.toLocaleTimeString();
if (summary == true) {
html += "<br><i>" + feed.Entry[i].Summary + "</i>";
}

html += "</div>";
}
}

contentDiv.innerHTML = html;

// Adjust gadget height according to contents
_IG_AdjustIFrameHeight();

}

_IG_RegisterOnloadHandler(displayContent);


</script>

<div id="main_container"></div>

]]>
</Content>
</Module>

As you saw in the code we need the following code to retrieve the user preferences:

var prefs = new _IG_Prefs();
var summary = prefs.getBool("show_summ");
var entries = prefs.getInt("num_entries");


There is a "get" method for each data type available. To get more information about user preferences click here.

Allowing the user to set up its preferences is really simple and it adds a lot value to the gadget functionality!

Friday, November 21, 2008

How to create a Google Gadget

Google Gadgets are a great tool, they allow you to have specific information in, what you could call, a small HTML page. You can export your gadgets to iGoogle, Google Desktop, Google Toolbar, to any web page and even you can create a Windows Vista Gadget based on them, isn't that great?

In this post, I am going to walk you through the creation of a simple gadget, but first let's talk about what Google Gadgets are.

Google gadgets are small pieces of code created using XML, HTML, CSS and Javascript that allow you to display data. These gadgets can be embedded in certain applications allowing you to export your information more easily.

They are really easy to create, you just need basic knowledge of HTML and JavaScript and Google provides you with JavaScript APIs to you make your life easier, you can check them in their Legacy Gadgets API Developer's Guide

One of the greatest advantages about a gadget is that they can be used in so many places, but you manage only one source code, so if you need to apply a change to your gadget, you just do it and then all the places were the gadget is used would be updated with no new installation or update required.

Before we start coding, let's talk about a little bit about the gadget components. A Google Gadget is a XML file that contains one big tag called Module that encloses the gadget, then we have two main different components, the module preferences and the gadget content.

The module preferences are tags that you use to set the gadget information like title, author, width, height, etc. In our example, you will see this tag <Require feature="dynamic-height"/>, this tells the gadget parser that the height should be dynamically changed depending on the size of the content. This feature only works with iGoogle though.

The gadget content is where you do all the work, here you need to specify a type, mainly "html" if you want to have your HTML and JavaScript in your gadget or "url" if you want the gadget content to live on a remote web page referenced by a URL in the gadget spec. Inside this tag you can code JavaScript and HTML to layout your data, and CSS if you want to give some style to your content. You need to enclose your code into the tags ![CDATA[ and ]].

For this post, we are going to create a simple gadget that reads the CNN.com top stories feed and display them using the API called _IG_FetchFeedAsJSON to read the feed as JSON. You will see that this code is really simple and easy to follow.

To work with Google gadgets you need a proper editor, you could use notepad if you want at first, but then you would need a way to publish it. Google already provides a tool called Google Gadget Editor (GGE) which is a gadget itself, so you can embedded it in your iGoogle, which allows you to edit your gadget and preview it in the same editor if want to. You can add it to your iGoogle by clicking here.

Now that you have the GGE in your iGoogle page, copy the following code into your GGE.

<?xml version="1.0" encoding="UTF-8"?>
<Module>
<ModulePrefs
title="CNN.com Top Stories"
title_url="http://www.cnn.com/"
author="Your name"
author_email="youremail@domain.com"
description="Gadget to see the current CNN.com top stories."
width="450"
height="350"
scrolling="true">
<Require feature="dynamic-height"/>
</ModulePrefs>

<Content type="html">
<![CDATA[
<style>
div#main_container {
font-size:12px;
font-family:Arial;
margin: 5px;
text-align: left;
text-decoration: none;
}

div#main_container h2 {
background: url(http://i2.cdn.turner.com/cnn/.element/img/1.0/logo/cnn.logo.rss.gif) no-repeat left top;
display: block;
height: 33px;
text-indent: -100000;
width: 144px;
}
</style>

<script type="text/javascript">

// Function to dipslay content retrieved from XML feed
function displayContent() {

var url = "http://rss.cnn.com/rss/edition.rss";
// Use the _IG_FetchFeedAsJSON() function to retrieve core feed data from
// the specified URL. Then combine the data with HTML markup for display in
// the gadget.
// Let's say we want to display only 10 entries and that we want to display the summary
var entries = 10;
var summary = true;
_IG_FetchFeedAsJSON(url, processFeed, entries, summary);
}

// Function to process the feed
function processFeed(feed) {
var contentDiv = _gel("main_container");

if (feed == null) {
contentDiv.innerHTML = "Invalid data.";
return;
}

// Start building HTML string that will be displayed in gadget.
var html = "";

// Access the fields in the feed
html += "<h2>" + feed.Title + "</h2>";
html += "<div>" + feed.Description + "</div><br>";

// Access the data for a given entry
if (feed.Entry) {
for (var i = 0; i < feed.Entry.length; i++) {
html += "<div>" + "<a target='_blank' href='" + feed.Entry[i].Link + "'>" + feed.Entry[i].Title + "</a> ";
// The feed entry Date field contains the timestamp in seconds
// since Jan. 1, 1970. To convert it to the milliseconds needed
// to initialize the JavaScript Date object with the correct date,
// multiply by 1000.
var milliseconds = (feed.Entry[i].Date) * 1000;
var date = new Date(milliseconds);
html += date.toLocaleDateString();
html += " ";
html += date.toLocaleTimeString();
html += "<br><i>" + feed.Entry[i].Summary + "</i>";
html += "</div>";
}
}
// Set the html into the main container
contentDiv.innerHTML = html;

// Adjust gadget height according to contents
_IG_AdjustIFrameHeight();
}
// Register the function displayContent in the onLoad handler so it is called when the gadget is first loaded
_IG_RegisterOnloadHandler(displayContent);

</script>

<div id="main_container"></div>

]]>
</Content>
</Module>

As you can see in the code, the function _IG_FetchFeedAsJSON receives as parameters the feed URL and the name of the callback function in charge of processing and displaying the data.

In the callback function what we do first is to check if the JSON response we received is valid, if it isn't then we just display a message saying that the data is invalid. If the response is valid then we go through the JSON properties to retrieve the data and display it using HTML and CSS.

As you can see this particular API is really easy to use and understand, you just need some basic knowledge of Javascript and that is it!

Now that you have the code in your GGE, you can publish it to your iGoogle so you can see how the dynamic height works depending on the content. Also you can publish it to a web page. Just click on the File menu and select the Publish option. You will get a warning saying that we did not set the Thumbnail preference, but that is not going to affect the code itself, since the Thumbnail is used just to display the gadget Thumbnail in the Google Gadget Directory, so people know how the gadget looks like.

I hope this helps! Post your comments and/or questions!