Pro potřeby libisemi, "prvního asociálního sociálního serveru" jsem potřeboval vytvořit bookmarklet. Protože to však nešlo udělat jednoduše, jako odeslání adresy pomocí GETu, protože na straně serveru používám framework CakePHP a v něm user friendly URLs ve formátu http://domain/controller/action/param. V tomto případě bych jako parametr nemohl předat http adresu. Z toho důvodu jsem byl nucen poslat tuto informaci metodou POST. A když už takto, tak rovnou asynchronně a počkat si na výsledek. V tomto postu bych se chtěl podělit o získané zkušenosti vzniklé na základě vyřešení vzniklých problémů.
Začnu nejprve rozhraním na straně serveru. Dovolím si vypustit zbytečnosti a defakto celý zápis zobecnit. Vzhůru tedy do Controlleru. Následující funkce (vlastně spíš metoda, protože je umístěna ve třídě rozšiřující AppController, jen mi tohle vznostné oslovení u PHP nesedí ;-)) přijmě data zaslaná (asynchronně) pomocí metody post a pustí se do zpracování, které předá zobrazovací vrstvě.
function addajax(){
// prijeti dat
$formular = $this->params['form'];
$url = $formular['url'];
// vystupni layout chci xml a nechci zobrazovat debug informace
$this->layout = 'xml';
Configure::write('debug',0);
/*
* Dalsi akce, jako je napr. parsovani ziskaneho dokumentu
*/
// zapsani informaci a ulozeni do session
$output[0]['url'] = $url;
$this->Session->write('toSave',$innerHTML);
$sessId = $this->Session->id();
// zapisu jeste cislo session
$output[0]['sessionId'] = $sessId;
// a cele pole predam vrstve View
$this->set('output', $output);
}
Uvnitř předcházející metody vyžaduju zobrazení v layoutu xml, ten je definován následujícím způsobem, který mi vygeneruje dokument typu text/xml, zapíše do něj záhlaví a vloží vlastní strukturu, která je generovaná uvnitř vrstvy View. Celkový výstup je uveden na výpisu níže.
<?php
header('Content-type: text/xml');
echo $xml->header();
?>
<addInfo>
<?php echo $content_for_layout; ?>
</addInfo>
Další činnost prováděná uvnitř metody v Controlleru je vytvoření pole "output", které je předáno vrstvě View. Do prvního prvku tohoto pole ještě přidám požadované url (index "url"), celou tuto konstrukci uložím do session a následovně do toho stejného pole přidám i číslo této session (index "sessionId"). Samotný výstup v podobě XML je generován ve view, kterému předávám data pomocí posledního řádku ve výpisu kontrolleru. View je zhruba následující:
echo $xml->elem('sessId',null,$output[0]['sessionId']);
echo $xml->elem('url',null,$output[0]['url']);
. . .
Tato konstrukce za pomocí helperu Xml vygeneruje tagy sessId a url a vloží do nich dané hodnoty. Výstupem je potom následující XML, které je odesláno zpět skriptu na straně prohlížeče (klienta), který provedl volání.
<?xml version="1.0" encoding="UTF-8" ?>
<addInfo>
<sessId>4f40061d6ffa5e6a4a8f4b64b79eb4d3</sessId>
<url>http://domain/page.htm</url>
</addInfo>
Bookmarklet jsem definoval konstrukcí tzv. anonymní funkce – void((function(){ . . . }()), který provede kód umístěný vevnitř. Tímto kódem vytvářím nový element "script", který připojím ke stávající stránce.
javascript:
void((
function(){
var element=document.createElement('script');
element.setAttribute('src','http://www.libisemi.cz/send.js');
document.body.appendChild(element);
var http=getHttp();
sendPost(http,encodeURIComponent(location.href));
}()
)
Tento skript sestává z metody, která na základě typu prohlížeče vygeneruje tzv. HttpRequest objekt, který je použit pro připojení k serveru a odeslání dat. V prohlížečích Netscape Navigator, Apple Safari a Firefoxu se tento objekt vytváří pomocí konstruktoru objektu XMLHttpRequest(), který je součástí objektu window, takžestačí otestovat jeho existenci. Microsoft samozřejmě musí mít něco extra, takže tento objekt vytváří pomocí ActiveX komponenty, takže pokud nevyjde první testování, otestuju existenci ActiveXObject komponenty. Pokud ani tohle testování nevyjde, ohlásím chybu a uživatel má smůlu.
function getHttp(){
var http = false;
if(window.XMLHttpRequest){
http = new XMLHttpRequest();
}else if(window.ActiveXObject){
http = new ActiveXObject("Microsoft.XMLHTTP");
}else alert("nejde vytvorit request!");
return http;
}
Vytvořený HttpRequest objekt předám funkci, která pomocí něj metodou POST odešle data na server. Pomocí metody onReadyStateChange je ještě nastavena další anonymní funkce, která testuje přijatou odpověď serveru – stav požadavku (readyState), kdy čeká na požadavek, až je kompletní a současně na stavový kód protokolu HTTP 200 – OK. Pokud tato skutečnost nastane, pokračuje ve zpracování, rozparsuje přijaté xml a přesměruje stávající dokument na novou adresu, ke které přiloží získané informace (v tomto případě číslo session, které vygeneroval server na základě přijatých dat POST metodou). Jedná se o jednoduchý AJAX, spousta dalších informací se válí volně na síti, nebo někde v archívu mého blogu.
function sendPost(http,url){
http.open("POST","http://www.libisemi.cz/addajax");
http.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
http.setRequestHeader("Connection","close");
http.onreadystatechange = function(){
if(http.readyState == 4 && http.status == 200){
var response = http.responseXML;
// … parsovani odpovedi a ziskani promenne 'id' – cislo session
window.location = "http://www.libisemi.cz/addform/"+id;
}
}
http.send("url="+url);
}
Tímto způsobem je vytvořen (polo-)funkční bookmarklet, který funguje po druhém kliknutí a pouze na stejné doméně. Nemám ale rád polovičatá řešení, tak už mám rozdělané řešení. Tohle bych si ale dovolil popsat až příště, tenhle post mi připadá už dost dlouhý ;-).