Skapa en widget för Windows Mobile 6.5
Skapa Widgets för Windows Mobile 6.5
I Windows Vista kom möjligheten att skapa Gadgets, vilket är små applikationer som gör det enkelt att komma åt data från olika hall. Dessa är skrivna i HTML, CSS och JavaScript, vilket gör att det är otroligt enkelt att skapa dem. De packas sedan ned i en zip-fil som får filändelsen ”.gadget”, vilket gör att man kan installera dem lokalt och sedan köra direkt på skrivbordet.
I Windows Mobile 6.5 så introduceras något kallat ”Widgets”. Dessa är uppbyggda på samma sätt som gadgets, det vill säga av just HTML, CSS och JavaScript. Skillnaden här är att de istället för att köras i en liten ruta körs som en fullfjädrad applikation. De får även filändelsen ”.widget” istället för ”.gadget”.
Dessa små applikationer körs genom Internet Explorer, och har ingen möjlighet att komma åt filsystemet eller en databas som SQL Server direkt, utan kör istället med egen lokal lagring genom JavaScript-API:er. Det gör dem till säkra program som kan köras utan att behöva några speciella rättigheter.
Tidigare så har man behövt skriva applikationer för Windows Mobile i t.ex. .NET, vilket har krävt att man sitter i Windows, men då dessa Widgets är uppbyggda på standardiserade webbtekniker så går det att utveckla och testa dem på vilken plattform som helst!
Kom igång!
Så, vad behövs för att skapa en Widget? Då det är uppbyggt med HTML, CSS och JavaScript så behöver vi inget annat än just en text-editor, som t.ex. anteckningar eller Visual Studio. Jag själv använder Visual Studio 2010 då det är mitt förstaval vad gäller all utveckling.
För att kunna testa dessa så behövs Windows Mobile 6.5. Då det inte är alla som har det på sina mobiltelefoner än så finns det emulatorer att ladda ned, vilket gör det mycket enklare.
Emulatorer för Windows Mobile 6.5 finns att ladda ned här:
När emulatorn är installerad så finns det olika varianter i startmenyn, där WVGA är den som har högst upplösning (480x800, vilket används för t.ex. HTC Touch HD). Vilken ni tar spelar dock ingen större roll.
Ni bör dock tänka på att Windows Mobile Professional har stöd för Touch, medan Standard bara har knappar!
Om vi startar upp en emulator så finns det faktiskt redan några Widgets förinstallerade. Ett exempel på det är MSN Money som visar aktuella aktiekurser. Dessa är helt byggda med de tekniker vi kommer att använda oss utav här.
Skapandet av en enkel widget
En Widget är som jag nämnde tidigare helt enkelt en zip-fil som innehåller en HTML-sida med dess resurser. Ett krav för varje Widget är att den skall innehålla minst en ikon, en HTML-sida samt en XML-fil som beskriver Widgeten.
Till att börja med så behöver vi en enkel HTML-sida, som vi kallar för index.html. Denna kan se ut så här:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1 Strict" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<title>Min widget</title>
</head>
<body>
<h1>Min widget</h1>
<p>
Det här är min nya widget!
</p>
</body>
</html>
Sen behöver vi en ikon, och då använder jag Aspsidans favicon för enkelhetens skull.
Det sista som behövs är XML-filen. Den har olika element som kan användas, där ”content” är obligatorisk.
Namn på element |
Beskrivning |
access |
Har ett attribut vid namn ”network” och som kan ha värden true eller false. För att Ajax-anrop skall kunna göras mot sidor på Internet så måste detta vara true, samtidigt som användaren måste godkänna detta. |
author |
Author innehåller information om personen som har skapat widgeten. Värdet i denna skall vara namnet på personen. Det finns även attributen ”href” som är URL:en till personens hemsida, ”email” som är personens e-postadress och ”img” som är en bild på personen. |
content |
Det här är det enda obligatoriska elementet. Det har ett attribut vid namn ”src” och som pekar mot den html-fil som skall vara startsidan i vår widget. |
description |
När widgeten skall installeras första gången så kommer en text fram som beskriver vad widgeten har för syfte. Texten skall vara elementets värde. |
icon |
När widgeten är installerad så visas den i startmenyn med en ikon och namnet. Här kan vi med attributet ”src” ange sökvägen till vår ikon. |
name |
För att vara säkra på att widgeten har rätt namn i startmenyn så kan vi ange det här. |
Min XML-fil ser ut så här, men ni kan självklart anpassa dessa efter er egen Widget:
<?xml version="1.0" encoding="utf-8" ?>
<widget xmlns="http://www.w3.org/ns/widgets" version="1.0">
<content src="index.html" type="text/html" />
<icon src="favicon.png" />
<name>Min widget</name>
<description>Det här är min nya widget!</description>
<author href="http://weblogs.asp.net/mikaelsoderstrom" email="vimpyboy@msn.com">Mikael Söderström</author>
</widget>
Det här är allt vi behöver för att nu kunna installera vår widget. För att kunna göra det så packar vi ned allt i en vanlig zip-fil och byter filändelsen till ”.widget”. Vi skall sedan föra över denna till emulatorn för att sedan kunna installera den där.
För att få tillgång till filer från vår dator till emulatorn så skall vi emulera ett minneskort. Det gör vi enklast genom att skapa upp en mapp där vi har vår widget-fil, och sedan i emulatorn välja File -> Configure… och där bläddra fram till mappen under ”Shared folder”.
Går vi sedan in i File Explorer i Windows Mobile så får vi fram widgeten under ”Storage Card”. Vi väljer här att köra den och får då upp en fråga om vi vill installera, vilket vi gör.
Det bör se ut ungefär så här när vi startar widget-filen:
Efter att installationen är klar så startas applikationen och vi får upp det här:
Vi kan även starta vår Widget från startmenyn i fortsättningen, genom att klicka på ikonen för den.
Nu har vi skapat en Widget som vi enkelt kan sprida vidare till andra med Windows Mobile 6.5 och låta dem installera den. Om vi gör uppdateringar i widget-filen och installerar om så kommer den att ersätta den gamla versionen automatiskt.
Vi kan även hitta vår, samt alla andra installerade widget under /Program Files/widgets/User i Windows Mobile, vilket gör att vi kan ändra direkt där om vi vill göra ändringar snabbt.
Det här är dock en väldigt enkel variant, och ger inte särskilt mycket nytta, samtidigt som det inte är den snyggaste applikationen. Vi ska därför använda CSS för att snygga till den, samt JavaScript för att få den mer dynamisk.
Dags att snygga till widgeten
För att få ett bättre utseende på widgeten så använder vi vanlig CSS. Vi kommer även att ändra lite i HTML-koden för att kunna få ett syfte med widgeten. I fortsättningen så kommer det här att byggas ut till att bli en RSS-läsare, helt baserad på vanliga webbtekniker.
Till att börja med så skapar vi en css-fil direkt i roten som heter widget.css. Vi ska även ha en referens till denna i HTML-koden, samtidigt som vi skall ändra texterna och utseendet för att passa en RSS-läsare.
Ett exempel på hur HTML-filen kan se ut är som följande:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1 Strict" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<title>Min RSS-läsare</title>
<link href="widget.css" rel="stylesheet" type="text/css" />
</head>
<body>
<h1>Min RSS-läsare</h1>
<p>Det här är min RSS-läsare.</p>
<ul id="feeds">
<li><a href="http://feeds.feedburner.com/MikaelSoderstrom?format=xml">Mikael Söderström</a></li>
</ul>
<ul id="feeditems">
<li>
<h2>Rubrik #1</h2>
<span class="rss-date">2009-01-01 00:00</span>
<span class="rss-entry">En rss-feed...</span>
<a href="#">Läs mer...</a>
</li>
<li>
<h2>Rubrik #2</h2>
<span class="rss-date">2009-01-01 00:00</span>
<span class="rss-entry">En rss-feed...</span>
<a href="#">Läs mer...</a>
</li>
</ul>
</body>
</html>
Här har vi först och främst en lista med olika RSS-feeds (nu finns dock bara en). Sen har vi en lista med posterna i RSS-feeden som visar upp rubriken, datumet, själva texten samt en länk till inlägget.
För att få ett lite roligare utseende på detta så ska vi använda CSS. Jag använder CSS:en som följer nedan, men vill ni få den att se annorlunda ut så kommer det inte att påverka något i resten av artikeln.
body
{
background-color: #ccc;
margin: 0px;
padding: 0px;
font-family: Arial;
}
h1
{
background-color: #fff;
color: #000;
}
a
{
text-decoration: none;
}
ul
{
margin: 0px;
padding: 0px;
}
li
{
background-color: #fff;
border: 1px solid #777;
list-style-type: none;
margin: 10px;
padding: 5px;
}
ul#feeds li
{
background-color: #444;
border-color: #aaa;
margin: 0px;
}
ul#feeds li a
{
display: block;
color: #fff;
}
ul#feeditems h2
{
margin: 0px;
}
ul#feeditems span
{
display: block;
}
ul#feeditems span.rss-date
{
font-style: italic;
}
Om vi uppdaterar filerna I widget-filen och kör den i Windows Mobile igen så ser det nu ut så här:
Den ser helt klart bättre ut nu!
Hämta RSS-feeds
En RSS-läsare som inte hämtar RSS-feeds är inte mycket att ha. Vi kommer därför nu att använda Ajax för att hämta en RSS-feed och visa upp den när man klickar på rubriken (Mikael Söderström).
För att vi skall kunna komma åt Internet från vår widget så behöver vi lägga till följande rad i config.xml:
<access network="true" />
Först och främst så skapar vi en js-fil. Här skall vi sedan ha metoder för att skapa Ajax-anrop mot en vald RSS-feed och till sist skapa upp elementen och lista.
Flödet kommer att se ut ungefär så här:
- Användaren klickar på en länk i listan över RSS-feeds.
- Med JavaScript har vi sett till så att alla klick på länkar i listan triggar ”RenderFeed(url)”, där parametern ”url” är den aktuella länken.
- Vi gör ett Ajax-anrop mot den URL:en som har angivits och anropar PrintResult där vi även skickar med resultatet (RSS-feeden).
- Vi renderar HTML för alla items i listan. För själva innehållet så plockar vi enbart ut de 350 första tecknen, då vi vill klicka på länken för att få se mer.
- Vi skriver ut vår nya HTML på sidan.
Jag kommer inte att gå igenom JavaScriptet mer grundläggande, då det inte är specifikt för Widgets. Det som visas här är bara helt vanlig JavaScript som kan köras direkt i webbläsaren på datorn.
JavaScriptet som jag kommer att använda ser ut som följande:
window.onload = function() {
SetupRssFeedList();
};
function SetupRssFeedList() {
var feeds = document.getElementById('feeds').getElementsByTagName('a');
for (var i = 0; i < feeds.length; i++) {
feeds[i].onclick = function() {
RenderFeed(event.srcElement.href);
return false;
}
}
}
function RenderFeed(url) {
var xmlhttp = null;
if (window.XMLHttpRequest)
xmlhttp = new XMLHttpRequest();
else
return;
xmlhttp.open('GET', url);
xmlhttp.onreadystatechange = function() {
if (xmlhttp.readyState == 4) {
if (xmlhttp.status == 200) {
var xmldoc = xmlhttp.responseXML;
if (!xmldoc) return;
PrintResult(xmldoc);
}
delete xmlhttp;
}
};
xmlhttp.send();
}
function PrintResult(xmldoc)
{
var threads = xmldoc.getElementsByTagName('item');
var itemContainer = document.getElementById('feeditems');
if (itemContainer.childNodes)
itemContainer.innerHTML = '';
for (var i = 0; i < threads.length; i++) {
var newLi = document.createElement('li');
var currentItem = threads[i];
var header = document.createElement('h2');
header.innerHTML = currentItem.getElementsByTagName('title')[0].text;
newLi.appendChild(header);
var datespan = document.createElement('span');
datespan.className = 'rss-date';
datespan.innerHTML = currentItem.getElementsByTagName('pubDate')[0].text;
newLi.appendChild(datespan);
var description = document.createElement('span');
description.className = 'rss-entry';
description.innerHTML = currentItem.getElementsByTagName('description')[0].text.substring(0, 350) + '...';
newLi.appendChild(description);
var readmore = document.createElement('a');
readmore.href = currentItem.getElementsByTagName('link')[0].text;
readmore.innerHTML = 'Läs mer....';
newLi.appendChild(readmore);
itemContainer.appendChild(newLi);
}
}
Om vi nu sparar JavaScriptet som rss.js och lägger till raden nedan I vårt HTML-dokument så kommer vi, efter att ha klickat på rubriken för RSS-feeden, att få resultatet på bilden.
OBS! Om du kör vi emulatorn så kräver det att du har ställt in det korrekt så att du har en internetuppkoppling i den.
<script src="rss.js" type="text/javascript"></script>
Nu har vi en enkel RSS-läsare, som genom att man klickar på en lank i menyn kan hämta och presentera en RSS-feed.
Utöka funktionaliteten med lagring av feeds
För att få widgeten mer personaliserad så ska vi även ge möjligheten att lägga till egna feeds. Detta kan vi göra genom att använda de interna funktionerna för att dels påverka menyn, men även göra det möjligt att lagra information i cachen. Värt att tänka på är att det som lagras i cachen inte krypteras, och kan därmed genom en ”riktig” applikation läsas av. Vi kan dock inte komma åt en annan widgets cache genom en annan widget.
Det första vi ska göra är att anpassa menyn som syns längst ned. Vi skall i den vänstra menyn byta ut ”Exit” mot ”Lägg till”, och i den högra lägga till alternativet ”Om…” som skall hämta information från XML-filen och presentera.
För att påverka det som finns i menyn så kommer vi att bygga ut vårt JavaScript så att vi har metoder för det.
I window.onload där vi för närvarande enbart anropar ”SetupRssFeedList()” ska vi även lägga till ett anrop mot ”SetupMenu()”. Sedan skapar vi en ny funktion som heter just ”SetupMenu()” i js-filen. För att kunna arbeta mot menyn så kommer vi att använda objektet ”widget.menu” som är specifikt för just widgets. Vi kommer att använda detta för att dels ersätta menyn till vänster, men även lägga till ett alternativ till menyn till höger.
Funktionen ”SetupMenu()” kommer att se ut som följande:
function SetupMenu() {
var menu = widget.menu;
var leftMenu = menu.createMenuItem(0);
leftMenu.text = "Lägg till";
leftMenu.onSelect = function() { AddRssFeed(); };
menu.setSoftKey(leftMenu, menu.leftSoftKeyIndex);
var rightMenu = menu.createMenuItem(1);
rightMenu.text = "Om...";
rightMenu.onSelect = function() { alert('Skapad av ' + widget.authorName + '.'); };
menu.append(rightMenu);
}
Först skapar vi upp en variabel som tillhandahåller referensen till widget.menu. Sedan skapar vi upp två nya variabler, en för vänstra menyn, och en för den högra. Dessa har en property vid namn ”text”, vilket är texten som skall synas i menyn. De har även ett event, ”onSelect”, vilket sker när användaren klickar på alternativet i menyn.
I den vänstra menyn så anropar vi en metod kallad ”AddRssFeed()”, vilken vi skall använda för att lägga till nya feeds. I den högra menyn så visar vi upp en alert-ruta med namnet på personen från XML-filen.
Så här ser det ut när vi kör applikationen nu:
Högra menyn:
Om…:
Nästa steg är att göra det möjligt att lägga till nya feeds.
I den vänstra menyn så har vi ett alternativ kallat ”Lägg till”, vilket anropar en JavaScript-funktion kallad ”AddRssFeed()”. Nästa steg är att skapa upp den. Det den skall göra är att visa två prompt-rutor, en för URL:en och en för namnet på feeden. Sedan skall vi skapa upp ett nytt li-element med en länk och lägga till i listan med RSS-feeds som vi har. Vi måste även binda om onclick på denna, då den som vi satte när sidan laddas inte fungerar med element som skapas upp i efterhand.
JavaScript-funktionen i sin helhet ser ut på detta vis:
function AddRssFeed() {
var url = prompt('URL till RSS-feed', 'http://');
var name = prompt('Namn på RSS-feed', '');
var feedlist = document.getElementById('feeds');
var newFeed = document.createElement('li');
var feedurl = document.createElement('a');
feedurl.href = url;
feedurl.innerHTML = name;
feedurl.onclick = function() {
RenderFeed(event.srcElement.href);
return false;
}
newFeed.appendChild(feedurl);
feedlist.appendChild(newFeed);
}
Om vi t.ex. lägger till en lank till RSS-feeden som används för de senaste trådarna på Aspsidan, och sätter namnet till just “Aspsidan” för att sedan klicka på länken så blir resultatet det här:
Vi kan nu alltså lägga till RSS-feeds dynamiskt i vår widget, och sedan välja att hämta dem.
Problemet nu är att de nya feedsen försvinner när vi stänger ned applikationen, så det vi ska göra är att spara undan en lista på alla feeds och hämta när applikationen startas upp igen. Det kan vi göra tack vare den inbyggda cache-funktionen som finns.
Först och främst så kommer vi nu att refaktorisera funktionerna för att slippa skriva samma kod flera gånger.
Tidigare så genererade vi HTML direkt i AddRssFeed(), men nu ska vi plocka bort den biten som skapar element och lägga i en egen metod (AddFeedHtml(name, url)). Detta gör det möjligt att generera samma HTML från flera metoder.
AddFeedHtml(name, url) bör nu se ut så här:
function AddFeedHtml(name, url) {
if (!(name && url)) return;
var feedlist = document.getElementById('feeds');
var newFeed = document.createElement('li');
var feedurl = document.createElement('a');
feedurl.href = url;
feedurl.innerHTML = name;
feedurl.onclick = function() {
RenderFeed(event.srcElement.href);
return false;
}
newFeed.appendChild(feedurl);
feedlist.appendChild(newFeed);
}
Nästa steg är att se till så att alla egna feeds sparas ned I en lista som finns kvar efter att widgeten har stängts ned.
För att göra det så kommer vi att använda oss utav en widget-metod som heter setPreferenceForKey().
Med denna metod tillagd, samt efter att ändringen med AddFeedHtml() har gjorts så ser AddRssFeed() ut som följande:
Vår nya AddRssFeed ser ut på detta vis:
function AddRssFeed() {
var url = prompt('URL till RSS-feed', 'http://');
var name = prompt('Namn på RSS-feed', '');
var oldList = null;
if (widget.preferenceForKey('feedlist'))
oldList = widget.preferenceForKey('feedlist') + '///url:' + url + ',name:' + name;
else
oldList = '///url:' + url + ',name:' + name;
widget.setPreferenceForKey(oldList, 'feedlist');
AddFeedHtml(name, url);
}
Det vi gör här är att vi hämtar url och namn. Sedan kollar vi om ett värde finns lagrat med nyckeln “feedlist”. Om värdet finns så hämtar vi det och lägger till vår nya feed efteråt. Om det inte finns så hämtar vi inget, utan sätter bara värdet direkt.
I vårt exempel så kommer vi att lagra alla feeds i formen ///url:UrlTillFeed,name:NamnPåFeed.
När vi har den nya strängen så använder vi setPreferenceForKey för att lagra listan med nyckeln ”feedlist”.
Det som sker nu är att vi sparar ned alla feeds som läggs till, så att vi kan komma åt dem senare. Om widgeten stängs ned så finns dem lagrade, men än så länge så syns inte det när den öppnas igen, så det vi behöver göra nu är att se till att de hämtas vid nästa start.
För att lösa det på enklaste vis så kan vi skapa en ny metod vid namn ”AddFeedList()”, och se till att denna körs i window.onload. Denna metod kommer att läsa av cachen och se om något finns lagrat. Om så är fallet så kommer vi att hämta det som finns lagrat och skapa upp nya länkar i vår lista.
AddFeedList():
function AddFeedList()
{
if (widget.preferenceForKey('feedlist'))
{
var list = widget.preferenceForKey('feedlist').split('///url:');
for (var i = 0; i < list.length; i++) {
var name = list[i].split(',name:')[1];
var url = list[i].split(',name:')[0];
AddFeedHtml(name, url);
}
}
}
Det som sker här är att vi först laser av värdet för “feedlist” och ser om det har något värde. Sedan delar vi upp strängen vid ”///url:” då detta skiljer url:erna åt. Vi loopar igenom alla dessa och splittar återigen på ”,name:” då det bara finns ett sådant värde per sträng.
Till sist så anropar vi AddFeedHtml(name, url) för att rendera länkarna till listan.
Det sista som behövs är möjligheten att ta bort gamla feeds. Det mest logiska sättet för detta är att lägga till ett alternativ i högra menyn, som låter oss göra detta.
På samma ställe som vi lägger till de andra menyvalen så lägger vi även till detta:
var rightMenu = menu.createMenuItem(3);
rightMenu.text = 'Ta bort feeds';
rightMenu.onSelect = function() { widget.setPreferenceForKey(null, 'feedlist'); };
menu.append(rightMenu);
Genom att sätta värdet för “feedlist” till null så tas alla tidigare värden bort.
Nu har vi en fullt fungerande RSS-läsare för Windows Mobile 6.5 skriven helt med webbstandarder!
Här är ett exempel på hur det kan se ut när vi nu har lagt till egna feeds:
Språkstöd för widgets
När man skriver applikationer så händer det ofta att man vill ha dem på flera språk. Med widgets så är detta väldigt enkelt att lösa, det räcker med att helt enkelt skapa upp en mapp med landet och språket (sv-SE för svenska och Sverige), eller bara språket om man så vill (sv). I denna mapp så lägger vi sedan till kopior av originalfilerna, men översatta på det aktuella språket. Widget-motorn läser sedan automatiskt av rätt filer så att vi får dessa lokaliserade.
Jag kommer nu att skapa en mapp vid namn ”en”, då jag vill ha en engelsk variant av widgeten, för de som inte talar svenska.
Det första steget är att skapa en mapp som heter en i vår katalog. För enkelhetens skulle så kommer jag bara att ta en kopia av js-filen vi redan har och placera den i den nya mappen. Vi kan även ta en kopia på t.ex. XML-filen, eller vilken annan fil som helst som vi vill ha översatt.
För att byta text på rubriken och texten i paragrafen nedan så kommer vi att skapa en metod vid namn ”Localize()”. Vi anropar denna i window.onload, och skapar den i stil med det här:
Nästa steg är att gå igenom alla hårdkodade värden i JavaScript-filen och ersätta dem med de engelska värdena.
Om vi nu ställer in på mobiltelefonen att vi ska ha något engelskspråkigt land under regionsinställningarna så kommer programmet att se ut så här:
Nu har vi en fullt fungerande RSS-läsare som dessutom har stöd för flera språk!
Fullständig kod finns att laddas ned här:
http://www.aspsidan.se/default.asp?page=scriptsShow&scriptId=554&scriptCat=5&scriptSubCat=54