Gadgets für Vista

Wie man für die Vista Sidebar ein Gadget erstellt, kann man an anderer Stelle nachlesen. Wie man ein Gadget per AJAX mit Seiten versorgt, kann man im Folgenden lesen.

Das Grundgerüst für die Gadget-File ist ein HTML-Gerüst:

<html lang="de" xml:lang="de" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http_equiv="content-type" content="text/html; charset=utf-8">
<style>body {margin:0px;width:375px;height:290px;}</style>
</head>
<body onload="init()"></body>
</html>

Ganz rudimentär - muss nicht so sein, ist aber in dem Beispiel so. Der init()-Aufruf haucht dem Ganzen das Leben ein. Was soll eigentlich passieren? In den body wird ein iframe integriert, welches dann per AJAX von einer anderen HTML-Page gefüttert wird. Warum? Weil man leider nicht direkt in einem Gadget andere Pages aufrufen kann; es wird halt immer ein neues Browser-Fenster geöffnet. Und deshalb muss man die Seiten per AJAX hinein laden sowie die Navigation's-Events abfangen, um weitere Pages auch wieder per AJAX laden zu können. Das Laden wird über eine Funktion namens get(url,type,msg) erledigt. Die drei Argumente sind url, type (GET|POST) und msg für das Senden von Inhalten an den Server. Also in der Funktion init() steht dann erstmal:

function init() { get('http://irgendwas','GET'); }

In der Funktion get wird das XMLHTTPRequest-Objekt erzeugt und an die URL gesendet, wenn die Antwort von Content-Type 'text/html' ist, dann wird das iframe angelegt und die empfangene Page hineingeschrieben:

var z,hostpath;
function get(url,type,msg){
hostpath=url.substr(0,url.lastIndexOf("/"));
var myXMLHTTPRequest = new ActiveXObject("Microsoft.XMLHTTP");
myXMLHTTPRequest.open(type, url, false);
myXMLHTTPRequest.setRequestHeader("Content-Type",
"application/x-www-form-urlencoded");
myXMLHTTPRequest.send(msg);
if (myXMLHTTPRequest.getResponseHeader('Content-Type').indexOf("html")!=-1) {
if (z) document.body.removeChild(z);
var item=document.createElement("iframe"); item.setAttribute("src","");
z = document.body.appendChild(item);
z.style.top='0px'; z.style.width='800px';z.style.height='600px';
var r = myXMLHTTPRequest.responseText;
z.contentWindow.document.write(r);
z.contentWindow.document.close();
if(z.contentWindow.document.forms[0])
z.contentWindow.document.forms[0].onsubmit=myEventHandler1;
z.contentWindow.document.body.onclick=myEventHandler1;
}
else { window.open(url,''); }
}

Ach ja, z ist das iframe-Element und das wird jedesmal gelöscht, damit die neuen Inhalte per write nicht hinten dran gehangen werden, sondern frisch an erster Stelle stehen. Aus unerfindlichen Gründen muss man für das iframe das Attribut src auf einen leeren String setzen :). Die style-Angaben sind auch notwendig, weil das iframe sonst sehr klein ist. Falls das per XMLHTTPRequest-Aufruf Erhalte nicht 'html' ist, wird ein neues Fenster für diese Page aufgemacht (z. B. für XML oder PDF).

Pages, die Bilder und andere verlinkten Dinge enthalten, brauchen noch eine base-Eintragung, damit sie auch gefunden werden. Das muss noch vor dem write passieren; also wird die Zeile mit dem responseText erweitert:

var r = myXMLHTTPRequest.responseText.replace(/<head>/,'<head><base href='+url+' />');

Damit kann man schon Pages laden und anzeigen. Aber die Interaktion fehlt noch. Dazu müssen nun für das iframe Event abgefangen werden. Im Beispiel sind das click und submit. Damit hätte man das Abschicken von Formularen und das Anklicken von Links im Griff. Nach dem close noch die Event-Behandlung (bei den Forms wird geprüft, ob es überhaupt welche gibt!).

Und natürlich brauchen wir nun die EventHandler-Funktion. Da wir es mit Windows und dem Internet Explorer bei einem Vista Gadget zu tun haben, wird nur die IE-Form notiert:

function myEventHandler1() {
var e=z.contentWindow.event;
if (e.type=='click') {
var s=z.contentWindow.event.srcElement;
if (s.href || s.parentNode.href) {
href=s.href?s.href:s.parentNode.href;
get(href,'GET');
return false;
}
}
else {
var f=z.contentWindow.document.forms[0];
if (f.action.indexOf('http://')==-1) f.action=hostpath+f.action;
var q=""; for (var i=0; i<f.length; i++) q+=f[i].name+'='+escape(f[i].value)+'&';
if (f.method && (f.method=="post" || f.method=="POST")) {get(f.action,'POST',q);}
else {get(f.action+'?'+q,'GET');}
return false;
}
}

Der Reihe nach: Der Event aus dem iframe wird der Variablen e zugeordnet; dann wird der Event-Typ inspiziert ('click') und nach der Herkunft des Click-Events geschaut (scrElement). Falls das Element ein Attribut namens href hat (oder sein Eltern-Element, denn oft wird im A-Tag nochmal hervorgehoben), dann wird die get-Funktion mit dieser neuen URL aufgerufen. Alle anderen click-Events werden nicht behandelt und bleiben beim iframe, was auch immer dort der Code auch macht :). Liegt ein submit-Event vor, wird der Alternativ-Zweig durchlaufen. Hier wird zunächst geschaut, ob im action-Attribut der form ein vollständiger Pfad vorliegt, falls nicht, wird ergänzt. Hierfür mussten wir in der get-Funktion noch zu Beginn vorsorgen mittels eines

hostpath=url.substr(0,url.lastIndexOf("/"));

Und natürlich muss die Variable hostpath global zu den Funktionen deklariert sein.

Als nächstes werden die Formular-Felder zu einem URI-Komponenten-String zusammengefasst. Ob man das mit escape() oder besser mit encodeURI() bzw. encodeURIComponent() macht, muss man für sich entscheiden. Als nächstes wird das method-Attribut der form untersucht, weil ja je nach Methode die Versandart für den XMLHTTPRequest gewählt werden muss. Der zu sendende Inhalt wird also entweder als Parameter oder als Anhängsel verschickt. Wichtig ist noch die Zeile return false, damit der iframe nicht noch den Event für sich auswertet.