Thursday, October 16, 2008

Creating a Scrolling List Gadget

One of the most important distractions for me as I develop is my music, it helps to keep me focused. In an effort to share that, I added a widget to the right of my blog that shows a list of the music that I am "currently" listening to. Not finding a good alternative, I simply used a link list widget and inserted a couple of the songs from my current playlist. Well.... not being satisfied with the geek factor, what I really wanted was something more dynamic, a kind of scrolling list, a music reel or marquee, if you will. So I set out to find out how to make my own Blogger Widget....

I started with the Blogger help files:

However, I found these a little limiting. I expanded my search and found that, as many others have done, the easiest way to do this to create a new Google Gadget. And better yet, once our Gadget is ready, Google will even host it for us on their GGE (or for those of us looking for more features at Google Code).

A Gadget consists of a xml file with three basic elements:

  • ModulePrefs
  • UserPref
  • Content

And looks something like this...

<?xml version="1.0" encoding="UTF-8" ?>
<Module>
     <ModulePrefs title="" description="" height="" width="" thumbnail="" screenshot="" author_email="" author="" title_url="">
          <!-- all of the following ModulePref Elements are optional -->
          <Locale lang="" language_direction="" messages="" country="" />
          <Link rel="" href="" method="" />
          <Require feature="">
               <Param name="">
          </Require>
          <Optional feature="">
               <Param name="">
          </Optional>
          <Preload href="" authz="none(default),signed,oauth" />
          <Icon mode="" type="">
               <!--base64 encoded data needed when using mode attribute-->
               <!--can also use url to icon file if mode attribute is not used-->
          </Icon>
     </ModulePrefs>
     <UserPref name="userPref1" display_name="Example" datatype="string(default),bool,enum,hidden,list,location" urlparam=""
        required="true,false(default)" default_Value="" />
     <UserPref name="UserPref2" display_name="stringExample" />
     <UserPref name="userPref3" display_name="enumExample" datatype="enum" default_value="A">
          <EnumValue value="A" display_value="Alpha">
          <EnumValue value="B" display_value="Beta">
          <EnumValue value="G" display_value="Gamma">
     </UserPref >
     <Content type="html,url" href="" />
          <![CDATA[
               <!--All the html and javascript needed to get the job done -->
               <!--CDATA section only needed if the content type is html-->
          ]]>
     </Content>
     </Module>
A couple of things to keep in mind before you begin. Blogger will provide a user setting for Height and Title by default. List Items can only be Added and Removed, in other words no Edit functionality. To change an entry, it will have to be removed and recreated. Further, it will host your Gadget inside of an iframe, so you will lose any styles applied to the blog. I have tried to compensate for this in my design below. Also, a <div> tag will be added with a class of widget-content and a black border. It will also add scroll bars, if necessary.

I wanted consumer's of my Gadget to be able to set the following options:
  • Number of Items to Show - integer from 1 to 99,999
  • Time to wait before Scrolling (in seconds) - integer from 1 to 99,999
  • Content Font Style Definition
  • List of Items

Arriving at a simple UserPrefs section that looks like...

<UserPref name="myNumItems" display_name="Items to Show" />
<UserPref name="myTimer" display_name="Time to Scroll" />
<UserPref name="myStyle" display_name="Content Font Style" default_value="8pt Georgia" />
<UserPref name="myList" display_name="List of Items" datatype="list" />

You could add additional style items to give user's more display flexibility, things like color, line size, padding, etc. Finally, I took a stab at the CDATA portion of the Content element. After several rounds of modifications, saves, publishing and testing on my iGoogle page, I arrived at the following code:

<div id="content_div"> </div>

<script type="text/javascript">

// Get UserPrefs Options
var prefs = new _IG_Prefs(); //new gadgets.Prefs();

// Get Details of UserPrefs
var items = prefs.getArray("myList");
var style = prefs.getString("myStyle");
var numToShow = 99999;
var timer = 99999;

//validate entries
if (!isNaN(prefs.getString("myNumItems")))
{
     numToShow = new Number(prefs.getString("myNumItems"));
}

if (numToShow > 99999)
{
     numToShow = 99999
}

if (!isNaN(prefs.getString("myTimer")))
{
     timer = new Number(prefs.getString("myTimer"));
}

if (timer > 99999)
{
     timer = 99999;
}

// keep track of where we are in our loop
var curIndex = 0;

// load list once for initial display
displayList();

// reload list after delay
setInterval("displayList()", timer * 1000);

function displayList()
{

     // Build HTML to show the user
     var html = "<table id='content-table' cellPadding='1px' border='0'>";

     // Loop through and display items
     if (items.length > 0)
     {
          var loopUntil = numToShow;
          var itemToShow = new Number(curIndex);

          // if user has set the number to show greater than the number of items we
          // have in our list, only loop through the number in the list

          if (items.length < loopUntil)
          {
               loopUntil = items.length;
          }

          //loop through our items, keeping track of where we are and what needs to be shown
          for (var i = 0; i < loopUntil; i++)
          {
               //if we have moved past the end of the list start back at the beginning
               if (itemToShow >= items.length)
               {
                    itemToShow = 0;
               }

               html += "<tr><td>" + items[itemToShow] + "</td></tr>";
               itemToShow++;
          }

          // Start loop at the next one in the list
          curIndex++;

          if (curIndex >= items.length)
          {
               curIndex = 0;
          }
     }

     html += "</table>"

     document.getElementById("content_div").innerHTML = html;

     //load in our style element
     var cells=document.getElementsByTagName("td");
     for (var i=0; i < cells.length; i++)
     {
          cells[i].style.font = style;
     }
}

</script>

Don't be surprised if Google warns you about missing elements, like thumbnail and width, when you publish your Gadet. I was able to accept the warning and still publish successfully.

I then went to import this Gadget to my blog using the url that Google gave me during publishing, http://hosting.gmodules.com/ig/gadgets/file/102621166658384583871/scrolling-list.xml. Next, using the Edit HTML tab in the Layout section of my blog, I was able to remove the border around my Gadget and change the padding to the left of the Gadget by checking the Expand Widget Definitions and locating the <div> tag just before my Gadget. I also set the scrolling attribute on the iFrame to no. While changing the Font Style in user settings, I will sometimes get JavaScript errors, however, in my mind, the benefits here outweigh the issue.

You can see the fruits of my labor over to the right, titled "Currently Developing To...".....Wait for it.....

Let me know what you think and feel free to add the Gadget to your own site! Happy blogging!

2 comments:

Rebecca Croft said...

That is a great question David. Let me look around and see what I can find out for you.

Rebecca Croft said...

Hey David, take a look at this conversation thread.
http://groups.google.com/group/Google-Custom-Buttons/browse_thread/thread/d9b6bb30e29964a0/8c4c4f687f896f7f?hl=en&lnk=raot
I hope it helps!