About this article:
Posted in: Blog
By: Blockcoder ( Admin ) / 17.01.2012
Stats: 4 responses / Views: 6,868
Tags: gadget, javascript, sidebar, windows 7, xml
There isn’t very much info about “how to make a windows gadget” in the Internet. You need to search and search and search but you only find little. That is why I made this blog post about it!
This article is about making a Windows 7 Sidebar Gadget that uses XML-formed RSS feed data to display the information. It works of course with Windows Vista too, but I developed with W7.
The gadget uses data from RSS-feeds from Iltalehti.fi, and more specifically the digi-news. During the developement, I realized that all the rss-feed tags are always written in the same way and hierarchy, so it’s actually possible to get any rss feeds working in the gadget just by changing the direct address to the xml-file. This makes the gadget very versatile for many users and purposes.
This blog post is big, so I divided it to chapters:
The gadget displays the selected news, in this case the “digi news” in time order from the newest to the oldest one still available on the service provider’s site.
Our goal is to achieve this kind of gadget. In the start view of the gadget are shown the headlines and their timestamps in chronological order. The headlines work as links to open the additional information displayed in sidebar called “Flyout”. The layout of the main gadget is very simplified, and it works logically with a vertical scrollbar.
The main view of the news gadget.
The Flyout displays the news description provided in the rss-formed xml file from the service provider. In this view the whole text works as a hyperlink to the site of the actual whole news story. The link address is also provided in the xml-file ( from the RSS-feed ).
It looks like this:
And it’s gonna pop up when you click any of the links on the main gadget.
Alright, now you know what we are going to do, so there won’t be much surprises. Let’s focus into how to make it!
Tips before you start making it:
The first tip is important: please develope your gadget in that folder because it will be very frustrating if you don’t. You will see what I mean if you try another folder where to develop gadgets, which is: C:\Program Files\Windows Sidebar\Gadgets. It is said that no third party gadget should not be installed to that directory.
If the javascript codes didn’t work as they should, I always moved the js-files to my domain and checked the errors with Firebug and then copied them back to gadget’s folder.
You have another option for debugging your javascript codes:
This registry key has been added to Windows 7, which enables the display of script errors at run time.
[HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Sidebar] “ShowScriptErrors”=dword:00000001
What files you need to create:
Let’s see what those 5 files are keeping inside.
Gadget.xml is a manifest file. It must be done so let’s do that first.
The gadget manifest is an XML file that contains general information for a gadget such as name and copyrights etc. This information is displayed in the Gadgets Gallery as gadget and developer details, along with functional or informational icons. Each gadget package must include a manifest.
Information what manifest-file offers:
You see the red circles in the picture, those are the information that the manifest file requires to be set.
The gadget.xml ( manifest file ):
<?xml version="1.0" encoding="utf-8" ?> <gadget> <name>Digi news</name> <namespace>windows.sdk</namespace> <version>1.0</version> <author name="Microsoft"> <info url="msdn.microsoft.com" /> <logo src="logo.png" /> </author> <copyright>© Microsoft Corporation.</copyright> <description>News gadget.</description> <icons> <icon height="66" width="66" src="icon.png" /> </icons> <hosts> <host name="sidebar"> <base type="HTML" apiVersion="1.0.0" src="newsgadget.html" /> <permissions>Full</permissions> <platform minPlatformVersion="1.0" /> <defaultImage src="icon.png" /> </host> </hosts> </gadget>
After this is done, you can move to next step.
Next we will look the main html-file which represents the main layout of our gadget. It is a skeleton like file, you just write empty divs which we will fill later on with all the news we want to download from rss sources.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" > <head> <title>Digi news</title> <link rel="stylesheet" href="styles.css" type="text/css" media="screen" /> <script type="text/javascript" src="scripts.js"></script> </head> <body onload="RSSRequest();"> <div id="gadgetContent"> <h3 id="main_head"></h3> <div id="status" style="display:none" ></div> <div id="content"> <ul id="ourList"></ul> </div> <div id="errorView"></div> </div> </body> </html>
Noticed the function call “RSSRequest();“? Next when we look at the scripts.js file I’ll tell you what it does, but make sure you call it in the “body onload” of this file.
We need some styles for our gadget. I’m now going to make it look like as in the pictures above.
The styles.css:
html { overflow: hidden; } body{ font-family: verdana; font-size: 12px; color: #fff; margin: 0px; width: 202px; height: 400px; background: #444; overflow: auto !important; line-height: 15px; } h3 { font-size: 14px; padding: 20px 10px 5px; text-align: center; } a:link { text-decoration: none; } a:active { } a:hover { cursor: pointer; text-decoration: underline; } /*GADGET*/ #main_head { border-bottom: 1px solid #000; background: #222; padding-bottom: 20px; } #gadgetContent { width: 183px; color: #fff; text-align: left; border: 1px solid #000; } #ourList li { list-style: none; margin-left: -30px; margin-bottom: 10px; font-size: 11px; padding-right: 3px; } #ourList li:first-child { padding-top: 0px; } #ourList li a:link { color: #fff; } #ourList li a:hover { text-decoration: underline; } #ourList li p { margin-bottom: 3px; } #ourList li p:hover { text-decoration: none !important; } /*FLYOUT*/ #flyout { background: #444; overflow: hidden; } #flyout .heading { color: #fff; width: 299px; height: 71px; } #flyout .heading td { vertical-align: middle; } #flyout .heading h3 { padding: 20px 17px 0px !important; text-align: left !important; font-size: 15px; } #flyout .heading h3 span { font-size: 12px; color: #aaa; } #details { color: #fff; padding: 0px 20px 20px; width: 250px; background: #444; } /*italics for the times*/ #flyout .heading h3 span, #ourList li p { font-style: italic; } /*same colours*/ #ourList li p, a:link { color: #a0d3e9; } #main_head, #flyout .heading h3 { color: #7be1fe; }
And it is done. If you copy it, you don’t need to worry about styles in this tutorial, or should I say blog post.
The scripts.js file contains all of the functions which will make the gadget dynamic and get information to it. A moment ago I talked about the body onload function “RSSRequest();” and now I’m going to tell you what it does:
// make the XMLHttpRequest Object var RSSRequestObject = false; // refresth the gadget 2 minutes interval window.setInterval("RSSRequest()", 120000); // try to create XMLHttpRequest - not used in stardard gadget if ( window.XMLHttpRequest ) { RSSRequestObject = new XMLHttpRequest(); } // if ActiveXObject use the Microsoft.Msxml2.XMLHTTP if ( window.ActiveXObject ) { RSSRequestObject = new ActiveXObject("Msxml2.XMLHTTP"); } function RSSRequest() { // gArticles must be initialized because otherwise it will put the new articles and old articles in row ( won't just refresh them ) gArticles.splice( 0, gArticles.lenght ); // Here the wanted rss-address var Backend = 'http://www.iltalehti.fi/rss/digi.xml'; // change the status to requesting data HideShow('status'); document.getElementById("status").innerHTML = ""; // lets open the request with wanted address RSSRequestObject.open("GET", Backend , true); // set the onreadystatechange RSSRequestObject.onreadystatechange = ReqChange; // lähetetään RSSRequestObject.send( null ); } // hide & show function ( shows or hides the status messages ) function HideShow(id){ var el = GetObject(id); if( el.style.display == "none" ) el.style.display=''; else el.style.display='none'; } function GetObject(id){ var el = document.getElementById(id); return(el); }
First we set the address ( var Backend ) from where we want to request rss-feed. Then if you want, you can change the “status” div content to tell whats going on ( if it’s still downloading or if the requested data is ready to use ). Then we send the request to server and set the onreadystatechange function.
Before moving forward, I’ll show you a simplified version of the “ReqChange();” function so there isn’t too much information at one time:
function ReqChange() { // If requested data has finished downloading and is ready to use if ( RSSRequestObject.readyState == 4 ) { // do whatever you want with the data here if its state is "fully complete" } else document.getElementById("status").innerHTML = RSSRequestObject.readyState; }
The above function is very self explaining, I assume you got the idea.
There is four different states that the readyState function can offer:
How do we fetch the requested data? You can use the “RSSRequestObject.responseXML” function that returns you the xml dom version of the rss-feed. Then it is easy parse the xml-data.
The above function “responseXML” gives us the rss feed in this form:
<?xml version="1.0" encoding="iso-8859-1" ?> <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"> <channel> <title>Iltalehti.fi tuoreimmat digiuutiset</title> <link>http://www.iltalehti.fi/etusivu/</link> <description>Iltalehden 20 tuoreinta digiuutista</description> <language>fi</language> <docs>http://blogs.law.harvard.edu/tech/rss</docs> <lastBuildDate>Tue, 17 Jan 2012 12:54:00 +0200</lastBuildDate> <image> <url>http://static.iltalehti.fi/kuvat/navi/logo.gif</url> <title>Iltalehti.fi tuoreimmat digiuutiset</title> <link>http://www.iltalehti.fi/etusivu/</link> <width>143</width> <height>29</height> </image> <item> <title>Applen perustaja kehuu Androidia</title> <link>http://www.iltalehti.fi/digi/2012011715087385_du.shtml</link> <guid>http://www.iltalehti.fi/digi/2012011715087385_du.shtml</guid> <description>Steve Wozniakin mukaan Android-käyttöjärjestelmässä moni asia on paremmin toteutettu. Applen yhdessä edesmenneen Steve Jobsin kanssa perustanut Steve Wozniak on edelleen uskollinen myös Applelle, mutta suitsuttaa avoimesti joitakin kilpailevan Android-käyttöjärjestelmän etuja. Wozniak nähtiin jonottamassa ensimmäisten joukossa uutta iPhone 4S -puhelinta, mutta hän on myös aktiivinen Android-käyttäjä.</description> <pubDate>Tue, 17 Jan 2012 12:54:00 +0200</pubDate> </item> <item> <title>Amerikkalaismedialla rajuja paljastuksia kännykkätehtaasta - iPhonen tuotantolinjalla 13-vuotiaita lapsia</title> <link>http://www.iltalehti.fi/digi/2012011615083696_du.shtml</link> <guid>http://www.iltalehti.fi/digi/2012011615083696_du.shtml</guid> <description>Amerikkalaismedia paljasti, ettei Applen alihankkija juuri tarkista työntekijöidensä ikää. Kiinalainen Shenzhenin kaupunki on paikka, josta merkittävä osa päivittäin käytössämme olevasta tekniikasta on peräisin. Kaupungissa toimii myös jättimäinen alihankkijayritys Foxconn, joka rakentaa tuotteita maailman suurimmille mobiilialan yhtiöille. Yksi asiakkaista on Apple. </description> <pubDate>Mon, 16 Jan 2012 15:13:00 +0200</pubDate> </item> ............... // it goes on like this..
Let’s do the parsing:
function ReqChange() { // If requested data has finished downloading and is ready to use if ( RSSRequestObject.readyState == 4 ) { // if data is valid if (RSSRequestObject.responseText.indexOf('invalid') == -1) { // get the XML DOM var node = RSSRequestObject.responseXML.documentElement; // browse the items //var content = ""; var channel = node.getElementsByTagName('channel').item(0); var title = channel.getElementsByTagName('title').item(0).firstChild.data; var link = channel.getElementsByTagName('link').item(0).firstChild.data; var description = channel.getElementsByTagName('description').item(0).firstChild.data; // here you can set the main title of the gadget document.getElementById("main_head").innerHTML = title; // intitialize important arrays var content2 = ''; var items = channel.getElementsByTagName('item'); var titles = new Array(); var links = new Array(); var description = new Array(); var pubDate = new Array(); // items parse loop for (var n=0; n < items.length; n++){ var itemTitle = items[n].getElementsByTagName('title').item(0).firstChild.data; var itemLink = items[n].getElementsByTagName('link').item(0).firstChild.data; var itemDescription = items[n].getElementsByTagName('description').item(0).firstChild.data; var itemDescription = ''; try { itemDescription = items[n].getElementsByTagName('description').item(0).firstChild.data; } catch (e) {} try { var year = items[n].getElementsByTagName('pubDate').item(0).firstChild.data.substring(12,16); var day = items[n].getElementsByTagName('pubDate').item(0).firstChild.data.substring(5,7); var month = items[n].getElementsByTagName('pubDate').item(0).firstChild.data.substring(8,11); var time = items[n].getElementsByTagName('pubDate').item(0).firstChild.data.substring(17,22); // dd/mm/klo var itemPubDate = day + "/" + month + " " + time; } catch (e){ var itemPubDate = ''; } // store parsed items to corresponding arrays titles[n] = itemTitle; links[n] = itemLink; description[n] = itemDescription; pubDate[n] = itemPubDate; var article = new Article( titles[n], links[n], description[n], pubDate[n] ,n ); gArticles[n] = article; } // when parsed, let's render the parsed data to the gadget renderDocument(); // Tell the reader the everything is done document.getElementById("status").innerHTML = ""; } else { // if there were errors document.getElementById("status").innerHTML = "Error"; } HideShow('status'); } else document.getElementById("status").innerHTML = RSSRequestObject.readyState; }
Okay, it may look like some big mess, but it is not. It’s actually really simple:
I think this needs to be explained more:
//store parsed values to corresponding arrays titles[n] = itemTitle; links[n] = itemLink; description[n] = itemDescription; pubDate[n] = itemPubDate; // create Article object and store parsed items ( news ) into it // gArticles stores all the Article objects so it is easier to modify var article = new Article( titles[n], links[n], description[n], pubDate[n] ,n ); gArticles[n] = article;
So we need to create a function that allows us to use Article as an object. I hope you understand some object oriented programming ( OOP ), because the next part is going to include a little of that. Don’t be afraid, it’s not so complicated.
It’s like making a class ( if you have done before ) and use it’s attributes via object.
//luodaan olio yhdestä artikkelista, helpompi käsitellä function Article( title, link, description, pubdate, index ) { this.Title = title; this.Link = link; this.Description = description; this.PubDate = pubdate; this.Index = index; }
Now it has been done. We made Article “class” and we can use its attributes like this:
// create an object from Article var articleObject = new Article(); // and use it var linkAddress = articleObject.Link;
Got it? Good, we can move forward.
Wondering when we are moving all this information into our gadget? It is not too far, couple of lines more.
Next, we are going to create a function which will fill our gadget ( not yet flyout ):
// fills the main gadget ( not flyout ) function renderDocument() { // this clears "ourlist" document.getElementById( 'ourList' ).innerHTML = ""; // loop for all the articles thats has been stored for( i=0; i < gArticles.length; i++ ) { var article = gArticles[i]; // creates date and rowspace var p = document.createElement('p'); p.setAttribute( 'class', 'pvm' ); var pvm = document.createTextNode( article.PubDate ); p.appendChild(pvm); // create a textnode from article's heading var text = document.createTextNode( article.Title ); // let's create <li> and <a> elements var li = document.createElement("li"); var a = document.createElement("a"); a.dataIndex = article.Index; // article's index // adds onclick handler in the link a.attachEvent('onclick', handleClick); // fills the new elements a.appendChild(text); li.appendChild(p); li.appendChild(a); // fills finally the gadget ourList.appendChild(li); } }
Again, the function is self explaining itself. Only thing that must be maybe explained more is this "a.dataIndex = article.Index;". You can add an "index" to your elements, now we are adding it for the link ( <a> element ). We need that for that we can easily figure out which link heading is clicked and which link's ( article's ) description ( excerpt ) is shown in the flyout.
Now that we has filled our main gadged view, we need to create the flyout anymore. Let's make it.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" > <head> <link rel="stylesheet" href="styles.css" type="text/css" media="screen" /> <!--<script type="text/javascript" src="scripts.js"></script>--> </head> <script type="text/javascript"> function initialize(){ document.body.style.width = 300; document.body.style.height = 400; document.body.style.margin = 0; document.body.style.border = "1px solid #000"; document.body.style.backgroundColor = "#444"; } </script> <body onload="initialize();" > <div id="flyout"> <table class="heading"> <tr> <td><h3 id="news_heading"></h3></td> </tr> </table> <div id="details"></div> </div> </body> </html>
I'll show you a different version of how to initialize gadget flyout. You can of course write your javascript code here within the html code. It is not recommended but you can do it. You can put that code in the scripts.js too. However what the initialize function does, is simple. It sets the flyout's height and width and background color etc.
What do we have anymore..? Almost forgot, when clicking a heading link in main gadged view, how to open the flyout and send content there?
Let's do that. Open the scripts.js again write down this:
// we need to initialize and tell to the computer that which file is the flyout file that we are referring var flyout = System.Gadget.Flyout.file = "flyout.html"; // gArticles holds all the articles var gArticles = new Array(); // when clicking of the news heading link, open the flyout // when clicked another news heading link, close the old flyout and open new flyout with new content function handleClick( event ) { var idx = event.srcElement.dataIndex ; if( idx == gSelectedIndex ) { gSelectedIndex = -1 ; System.Gadget.Flyout.show = false ; }else{ gSelectedIndex = idx ; if(System.Gadget.Flyout.show) { addContentToFlyout(); } else { System.Gadget.Flyout.show = true; System.Gadget.Flyout.onShow = function() { addContentToFlyout(); } System.Gadget.Flyout.onHide = function() { gSelectedIndex = -1 ; } } } }
As you can see, we can refer to the flyout.html by writing this: "var flyout = System.Gadget.Flyout.file = "flyout.html";". It's easy.
Next we create a function that sends the correct content to our flyout window!
// this function sends the correct news's data to the flyout function addContentToFlyout() { try { if( System.Gadget.Flyout.show ) { var flyout = System.Gadget.Flyout.document; var article = gArticles[gSelectedIndex]; flyout.getElementById("news_heading").innerHTML = article.Title + "
"; flyout.getElementById("details").innerHTML = article.Description; flyout.getElementById("details").innerHTML += "
Read more"; } } catch ( ex ) { //displayError( ex.message ); } }
Finally you can test the gadget, it should be working!
Now you only need to do icons and pictures for your gadget, if you want. Remember to name your gadget folder like "NewsGadget.Gadget" or "WhateverGadget.Gadget" and move it to the location offered in "Let's get down to the business" chapter.
To remind: C:\Users\Johe\AppData\Local\Microsoft\Windows Sidebar\Gadgets\.
When the gadget folder is in right place, we can check if our gadget shows in desktop.
It shows there, working when tested, I hope it works with you guys too!
I end this tutorial here hope you got something out from it. Thanks for reading!