An iGoogle Gadget for your Game
July 23rd, 2008
If you’ve ever personalized a Google home page, you’ve seen the various gadgets that can be added. These gadgets can also be placed into other web pages, blogs, and so forth pretty easily, and they share a very similar framework with “gadgets” in other sites, such as MySpace’s new MySpace Apps. In this article, I’ll show you how to make a simple gadget that pulls dynamic data from your game and displays it in a nice little box.
Here’s a basic gadget I created for Aethora that shows a player’s list of characters and each character’s skill levels. It also displays the Health of each character, which is nice – if you’ve ever played Aethora, you know that inactive characters heal up as time passes. This gadget is handy for seeing at a glance which characters are ready to get back into action. A little minor JavaScript makes it interactive, allowing you to click on the skills link and expand each character’s skill list.
Rather than go into detail about how I created this gadget specifically, I’m going to make up a generic game called Fake RPG and explain how to build a gadget for it.
0. What you need
Before we get started, let’s run down a short list of requirements.
- A game to pull data from
- Some knowledge of JavaScript
- Firebug is highly recommended
- A design in mind: a gadget is not like a full web page, so give it a little thought. I did my Aethora gadget at 320×300, but for this example, I started with a design that fits into 300×200 pixels.
- Reference: Legacy Gadgets API Developer’s Guide
1. Prepping the Data
First we’ll need to create a page that will serve up our player’s data. We’ve got a couple of options when it comes to displaying data. We can use plain old text, perhaps CSV-style, but it’s probably best if we use XML or JSON. XML is typically good for generating well-formatted blocks of data intended to be used for a very broad range of applications. Parsing XML data in JavaScript is not real fun; thankfully, we have JSON, which is much easier to work with. In this example, I’ll be using JSON for that reason.
Most languages provide simple methods for getting JSON-formatted data. In Ruby on Rails, there is a handy to_json method that can be called on any ActiveRecord object, and allows for options such as restricting which attributes get displayed, including method calls as attributes, and including associated objects. PHP (5.2.0 and up) provides a json-encode function that translates any variable (be it array, string, or any other type) into a JSON string.
Let’s do this example with a simple PHP file. Since this is Fake RPG, we’ll pretend each player in the game has four player characters (PCs) and each PC has some attributes. I’m going to fake the data as if I pulled it from a database, but really it’s just a bunch of arrays.
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 |
<?php function make_up_stats() { return array( 'Strength'=>rand(1,20), 'Wisdom'=>rand(1,20), 'Dexterity'=>rand(1,20), 'Luck'=>rand(1,20) ); } // pretend we loaded some data from a database $pc1 = array('name'=>'Jack', 'level'=>5, 'health'=>100, 'stats'=>make_up_stats() ); $pc2 = array('name'=>'Jannus', 'level'=>6, 'health'=>25, 'stats'=>make_up_stats() ); $pc3 = array('name'=>'Samathia', 'level'=>4, 'health'=>65, 'stats'=>make_up_stats() ); $pc4 = array('name'=>'Keleton', 'level'=>6, 'health'=>0, 'stats'=>make_up_stats() ); $pcs = array( $pc1, $pc2, $pc3, $pc4 ); $player = array ( 'name'=> $_GET['user_login'], 'pcs'=> $pcs ); // output as JSON echo json_encode($player); ?> |
Notice our flimsy little PHP script looks for a ‘user_login’ parameter in the URL to determine the player’s username. Normally, we’d use this parameter to actually look up the player’s data and whatnot, but this tutorial is about iGoogle gadgets, so we’re faking it. If we were doing this for real, we might consider using HTTP authentication or something like that. Aethora’s gadget does not require authentication, because the player and character stats that are being displayed are public anyway. The only real “danger”, so to speak, is that someone could display another player’s stats on their own web page using one of these gadgets (of course, they say imitation is the highest form of flattery, so I don’t consider that scenario much of a security breach).
Now we test our PHP script by pointing our browser to it. You can try this by going to my example at: http://examples.rangersheck.com/fake_game_json.php?user_login=Ranger+Sheck
Notice the string of data blocked out with {}’s and []’s; that’s JSON, baby, and it translates to pure JavaScript!
2. Creating a Gadget: the XML definition file
All iGoogle gadgets require a single XML file to start with. Google prefers that you host this XML file on your own server, but if you need to, you can use their Google Gadgets Editor to host your XML file. Since I want to break out my JavaScript and CSS for the sake of cleanliness, I’m going to host my XML file on my own server.
The entire XML block should be wrapped in a Module element. We start with a Module Prefs section. This is where we can set up the configuration for our little gadget as well as provide some miscellaneous data such as screenshots.
1 2 3 4 5 6 7 8 9 |
<?xml version="1.0" encoding="UTF-8" ?> <Module> <ModulePrefs title="Fake RPG Character Sheets" scrolling="true" height="300" width="320" description="This is an example of what you might do with a web-based game and igoogle gadgets." author="Ranger Sheck" author_email="rangersheck@skipintrodevelopment.com" title_url="http://rangersheck.com" screenshot="http://examples.rangersheck.com/gadget/screenshot.png" thumbnail="http://examples.rangersheck.com/gadget/screenshot-tn.png" > </ModulePrefs> |
Next, we add as many UserPref elements as we need. Each of these UserPrefs can be set by the user when they install the gadget. In this case, we only need to know the login name they use for our game, and we're going to make it a required attribute.
1 |
<UserPref name="player_login" display_name="Fake RPG Login name (username):" required="true"/> |
Here we can start displaying some HTML. When an iGoogle gadget is displayed on a page, it's actually being displayed inside an iframe. That makes it easy for us to set up whatever we want in terms of HTML, JavaScript, CSS. Notice here, I reference a .css file, a couple .js files. Then I stick in some real basic HTML that will make a nice little two-column layout. At the end, I use a couple of Google's _IG_... methods to grab the UserPref variables and to make a call to a function once the page is done loading.
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 |
<Content type="html"> <![CDATA[ <link href="http://examples.rangersheck.com/gadget/fake-rpg-stats.css" rel="stylesheet" type="text/css" /> <script src="http://examples.rangersheck.com/gadget/utils.js"></script> <script src="http://examples.rangersheck.com/gadget/json2-min.js"></script> <script src="http://examples.rangersheck.com/gadget/fake-rpg-stats.js"></script> <div id="content_div" style="width:300px; background-color: #EEE; margin:0 auto;"> <div id="char_wrap"> </div> <div id="details_block"> <h1>Fake RPG</h1> <h2 id="login_name"></h2> <p><em>mouse over each character for details</em></p> <div id="details" style="display:none;"></div> </div> <div style="clear:both"></div> </div> <script type="text/javascript"> // Get userprefs var prefs = new _IG_Prefs(); // make JSON request once page loads _IG_RegisterOnloadHandler(makeJSONRequest); </script> ]]> </Content> </Module> |
3. Parsing the JSON and generating HTML
At the very end of the XML file, we are triggering a call to a function called makeJSONRequest. This is just a function I created myself in the file fake-rpg-stats.js. Let's take a look at that file:
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 |
function makeJSONRequest() { // This URL returns a JSON-encoded string that represents a JavaScript object var url = "http://yourdomain.com/fake_game_json.php?user_login=" var url += escape(prefs.getString("player_login")); // here we call the iGoogle method that grabs the text from the url and passes // it off to our responseHandler function // let's set the refreshInterval to tell it to grab the content every 10 minutes _IG_FetchContent(url, responseHandler, { refreshInterval: (60 * 10) }); } function responseHandler(response) { // display error if we got bad response if (response == null || typeof(response) != "string") { msg = "<i>Invalid data. Response: " + response + " (" + typeof(response) + ")</i>"; $("content_div").innerHTML = msg; return; } // parse the response string as JSON - note that the parser resides in the json2-min.js file var player = JSON.parse(response); // we're creating two chunks of HTML - one for the left side and one for the right // on the left side, we stack up the character names in boxes // on the right side are blocks containin each of the PCs details - they all start out hidden var pc_html = '' var hidden_pc_html = '' // loop through player characters for (var p = 0; p < player.pcs.length ; p++) { pc = player.pcs[p] pc_html += '<div class="char">'; pc_html += '<strong>' + pc.name + '</strong><br/>'; pc_html += 'Level ' + pc.level + '</div>'; pc_html += "</div>"; hidden_pc_html += '<div style="display:none" id="pc_detail_' + p + '">'; hidden_pc_html += '<strong>' + pc.name + '</strong><br/>'; hidden_pc_html += 'Health: ' + colored_number(pc.health) + '<br/>'; hidden_pc_html += 'Strength: ' + pc.stats.Strength +'<br/>'; hidden_pc_html += 'Wisdom: ' + pc.stats.Wisdom +'<br/>'; hidden_pc_html += 'Dexterity: ' + pc.stats.Dexterity +'<br/>'; hidden_pc_html += 'Luck: ' + pc.stats.Luck +'<br/></div>'; } // end player characters for loop // set login name header to our player's name $('login_name').innerHTML = player.name; // fill in the left side $('char_wrap').innerHTML = pc_html; // fill in the hidden blocks on the right side $('details').innerHTML = hidden_pc_html; // now that the new HTML is generated, we can add some onmouseover functionality setupRollovers(); } |
A couple of things to note here. I've created some "utility" functions that you can find in the utils.js file. These are basically just some prototype-like shortcuts for grabbing HTML elements and showing or hiding them. Also, since we want to safely parse the JSON, I grabbed the parser from json.org and minified it.
If you've never worked with JSON before, go back and take a look at the code from 'fake-rpg-stats.js'. Notice how we assigned the whole block of data (after parsing it) to the player variable. This variable is a javascript object, or associative array. That means all of it's children can be accessed using an object-oriented notation or an array notation. I find OO-style more readable, so when I want the player's name, I use:
1 |
player.name |
but you could also use:
1 |
player["name"]
|
Notice how player.pcs is an array containing the PCs, and each of those elements contains a few attributes like 'name' and 'level' as well as another array of 'stats'. It's pretty basic JavaScript to loop through our array of PCs, grab the attributes from each, and plug that data into a block of simple HTML.
4. DHTML Magic
You'll notice the HTML in both the XML file and the stuff generated by our JavaScript is pretty bare. I've kept all the styling in fake-rpg-stats.css, and that's where you'll find the oh-so-fancy background images, text colorations, and block decoration. When you mouse-over each of the names in the left side of the widget, the "stats" for that character should appear on the right. The "setupRollovers()" function we call at the very end of our responseHandler can be found in that 'utils.js' file. This function unobtrusively grafts an "onmouseover" attribute onto each of the divs with the PC names in them on the left side. When the mouse goes over one of those divs, we want to hide all other player-detail divs and show only the one we moused over.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
function setupRollovers() { pc_name_divs = document.getElementById('char_wrap').getElementsByTagName('div'); for (var i = 0; i < pc_name_divs.length ; i++) { // need a way to reference the index of this PC, so set a pc_id attribute pc_name_divs[i].pc_id = i; pc_name_divs[i].onmouseover = function() { switch_to_pc(this.pc_id); }; } } // display this PC's stats and hide others function switch_to_pc(p) { // 'details' is hidden by default so we don't get the border with nothing inside show('details'); pc_divs = document.getElementById('details').getElementsByTagName('div') for (var i = 0; i < pc_divs.length ; i++) { if (i == p) { show(pc_divs[i].id); } else { hide(pc_divs[i].id); } } } |
5. Testing the code in iGoogle
Assuming you have an iGoogle account, and you're logged in, testing your gadget out is pretty easy. I like to have a separate "tab" for testing purposes. If you've never added a tab to iGoogle, notice that there is a Home tab just below the search form. Next to that, there should be an "Add tab" link - just click that to add another tab and name it Testing. Once you're in a Testing tab, you can add the "My Gadgets" developer gadget. Go to this page and click the "Add it now" button.
With this utility gadget, you can paste the URL of your base XML file into the "Add a gadget" form and your gadget will automatically show up under your Testing tab. Under normal circumstances, Google will cache your XML file, refreshing it only after an hour or so. During testing, this behavior can be quite the pain in the neck, but the "My Gadgets" gadget allows you to disable caching for your gadget just by unchecking a box. Note that this only bypasses the cached file for you. If this is a live gadget, any other users who are using will still only get an updated version of the XML file every hour or so. There is no way to tell Google to manually purge their cache of your gadget's XML file. If you make a change, your users will just have to wait until the cached version expires.
Notice that when you add your gadget to iGoogle, it prompts you for the "login name" for "Fake RPG". - That prompt comes from the UserPref requirement we set in the very beginning. Since I wrote the fake PHP script to take any username you give it, you can just type anything into this field and click Save. After that, your gadget should load. Note that a user can go back and change the value they entered for username by clicking the little down-arrow at the top right corner of the gadget, and then clicking "Edit settings".
6. End Notes
The iGoogle _IG_... functions come from an API that is currently being re-written by Google to conform more to the OpenSocial API. Changing over this example code to the new API is extremely easy. You'll only have to replace the three functions that start with _IG_. Most of them are just renamed using a gadgets.* namespace. The _IG_Fetch* functions are replaced by a more generic gadgets.io.makeRequest function that takes a parameter that specifies the type of content you're expecting to grab. In the new API, you can specify JSON as a content type, which means that you'll be able to drop the JSON parser library and skip the parsing step because Google's API will handle that stuff automatically.
At this time (July, 2008), the Google's implementation of the OpenSocial API is not public. You can register to try it out in some kind of iGoogle "Sandbox mode", but the gadgets.* stuff won't work for your general end-users at this time. It's worth checking out, however, because other sites that implement the OpenSocial API use the same gadgets.* format. I experimented a little with MySpace's implementation of "gadgets" (they call them Apps), but unfortunately, MySpace Apps don't use the UserPref setting (most likely as a security precaution - it's a minor miracle they are allowing any kind of javascript back into MySpace at all). So there's no way to prompt the user for their username on another, non-MySpace system. I haven't had time to experiment with the actual inter-linking of OpenSocial data, but I expect there's a way to use OpenSocial to get a MySpace App to act just like your iGoogle gadget. From what I can tell, MySpace uses a makeRequest function which can be used to fetch XML or JSON data, very similarly to the Google implementation.
That's all for now. Here's all the code I showed you in one archive: fake-rpg-gadget.zip. Now get started on some gadgets of your own!

Sorry, comments are closed for this article.