Hlavní navigace

AJAX

3. 10. 2005
Doba čtení: 6 minut

Sdílet

Článek rozebírá různé možnosti umísťování interaktivních komponent na webové stránky a soustřeďuje se na jednu v současné době poměrně populární. Díky technologii Asynchronous JavaScript and XML je možné z webového prohlížeče elegantně komunikovat s webovým serverem a vyměňovat si s ním data, která se mohou ihned uložit na serveru nebo naopak zobrazit u klienta.

Pokud je na stránce nějaká dynamická komponenta, např. anketa, hlasování nebo konec konců i diskuse, dá se k jejímu zpracování přistoupit mnoha různými způsoby:

  • Tradiční řešení spočívá v tom, že akce je realizována běžným odkazem nebo formulářem a po jejím provedení se nahraje celá stránka znovu. Nevýhody jsou zřejmé – pomalost a pracnost vracení stránky na místo, které bylo zobrazeno před provedením akce.
  • Zpracování akce v nově otevřeném okně netrpí neduhy tradičního řešení, řada uživatelů je ale na otevírání nových oken alergická, obzvlášť když je to jenom kvůli oznámení, že jejich hlas byl započten. Toto řešení navíc původní stránku obvykle neaktualizuje aktuálními daty.
  • Umístění komponent do samostatného rámu je nejelegantnější u jednoduchých stránek, které využívají dynamické komponenty provozované na jiném serveru. U větších serverů má ale i toto řešení své nevýhody – ve striktním HTML jsou rámy zakázané a stažení každé komponenty znamená další požadavek na webový server.
  • Využít skriptování u klienta je pravděpodobně nejpracnější, ale zároveň přináší největší uživatelský prožitek. Realizovat se dá několika způsoby – přesměrováním akce do neviditelného rámu, který skriptem změní původní stránku, dynamickým vytvořením elementu script a jeho umístěním do HTML dokumentu nebo pomocí objektu XMLHttpRequest, který byl pro komunikaci skriptů se serverem přímo navržen.
  • Pro hodnocení komentářů na Rootu je použit poměrně neobvyklý přístup, kdy se hlasy ukládají u klienta do cookie a na server se odešlou teprve až s návštěvou další stránky. Vzhledem k množství hodnocených komentářů se mi jeví tento přístup jako velice rozumný, protože jinak by počet požadavků na server neúměrně vzrostl, použít ho však lze jen ojediněle.
  • Existují samozřejmě i další možnosti, např. umístění komponenty do Flashe, ty ale není možné použít vždy.

Pro použití objektu XMLHttpRequest se ustálil pojem AJAX, přestože korektní použití tohoto objektu nemusí odpovídat ani jednomu slovu ve zkratce – komunikace nemusí být asynchronní, jako skriptovací jazyk nemusí být použit JavaScript a data se nemusí přenášet pouze pomocí XML. Naopak AJAX se dá výše naznačenými způsoby zajistit i bez objektu XMLHttpRequest.

Příklad – realizace ankety v PHP

Na jednoduchém příkladu si ukážeme, jak se AJAX dá použít pro zpracování ankety. Důležitá část skriptů je univerzální a použitelná i v dalších komponentách.

Hlavní stránka se od ostatních řešení příliš lišit nebude. V případě vypnutého JavaScriptu nebo obecně nefunkčnosti technologie AJAX u klienta se navštíví běžný odkaz, který aktualizuje databázi a stránku celou překreslí. Pro kontrolu toho, jestli uživatel ještě nehlasoval, se používá cookie s platností jeden měsíc, podle potřeb je možné tuto kontrolu samozřejmě změnit. Vazbu na AJAX v tomto souboru zajišťuje volání funkce anketa_hlasovat.

<?php
// uložení odpovědi v případě vypnutého JavaScriptu
if (isset($_GET["anketa"]) && !isset($_COOKIE["anketa"])) { // primitivní kontrola toho, jestli uživatel ještě nehlasoval
    setcookie("anketa", $_GET["anketa"], strtotime("+1 month"));
    $_COOKIE["anketa"] = $_GET["anketa"];
    mysql_query("UPDATE anketa SET pocet = pocet + 1 WHERE id = " . intval($_GET["anketa"]));
}

// zobrazení ankety
echo "<h3>Anketa</h3>\n";
$result = mysql_query("SELECT * FROM anketa");
echo "<table border='1' id='anketa'>\n";
while ($row = mysql_fetch_assoc($result)) {
    $odpoved = htmlspecialchars($row["odpoved"]);
    if (!isset($_COOKIE["anketa"])) {
        $odpoved = "<a href='?anketa=$row[id]' onclick='return !anketa_hlasovat($row[id]);'>$odpoved</a>";
    }
    echo "<tr><td class='odpoved'>$odpoved</td><td id='pocet$row[id]'>$row[pocet]</td></tr>\n";
}
echo "</table>\n";
echo "<span id='stav-anketa'></span>\n";
mysql_free_result($result);
?> 

Největší část funkčnosti zajišťuje následující JavaScriptový kód. Funkce send_xmlhttprequest je obálka nad objektem XMLHttpRequest, která zajistí vytvoření správného objektu v závislosti na schopnostech prohlížeče, inicializuje požadavek a nastaví mu případné hlavičky a tělo, prováže ho s obsluhou události a odešle ho. Funkce anketa_hlasovat tuto funkci využívá a kromě toho znemožní opětovné hlasování vymazáním odkazů a překreslí anketu se započteným hlasem uživatele. V neposlední řadě informuje uživatele o tom, že se ukládají výsledky – vzhledem k tomu, že uživatelé na tento způsob modifikace stránek nemusí být zvyklí, je jejich informování poměrně důležité. Funkce anketa_obsluha přiřazená k obsluze události onreadystatechan­ge se volá při každé změně stavu, a pokud je požadavek dokončen (readyState má hodnotu 4), aktualizuje data na základě momentálního počtu odpovědí a informuje uživatele o dokončení požadavku.

<script type="text/javascript">
/** odeslání XMLHttp požadavku
@param function obsluha funkce zajišťující obsluhu při změně stavu požadavku, dostane parametr s XMLHttp objektem
@param string method GET|POST|...
@param string url URL požadavku
@param string [content] tělo zprávy
@param array [headers] pole předaných hlaviček ve tvaru { 'hlavička': 'obsah' }
@return bool true v případě úspěchu, false jinak
*/
function send_xmlhttprequest(obsluha, method, url, content, headers)
{
    var xmlhttp = (window.XMLHttpRequest ? new XMLHttpRequest : (window.ActiveXObject ? new ActiveXObject("Microsoft.XMLHTTP") : false));
    if (!xmlhttp) {
        return false;
    }
    xmlhttp.open(method, url);
    xmlhttp.onreadystatechange = function() {
        obsluha(xmlhttp);
    };
    if (headers) {
        for (var key in headers) {
            xmlhttp.setRequestHeader(key, headers[key]);
        }
    }
    xmlhttp.send(content);
    return true;
}
</script> 

Za zmínku stojí, že vytvořený objekt xmlhttp se obslužné funkci předává jako parametr. V tomto jednoduchém případě by to mohla být i globální proměnná, ale vzhledem k tomu, že na stránce může být dynamických komponent více, je nutné mezi nimi rozlišovat. Pokud by se použila globální proměnná a nějaká komponenta by vytvořila nový požadavek ještě před dokončením předešlého, vedlo by to k jeho nedokončení. Je potřeba pamatovat na to, že zpracování je asynchronní.

<script type="text/javascript">
function anketa_hlasovat(hlas)
{
    // odeslání požadavku na aktualizaci dat
    if (!send_xmlhttprequest(anketa_obsluha, 'GET', 'anketa_rpc.php?anketa=' + hlas)) {
        return false;
    }
    document.getElementById('pocet' + hlas).innerHTML++; // zobrazení hlasu u klienta
    // znemožnění opětovného hlasování smazáním odkazů
    for (var key in document.getElementById('anketa').getElementsByTagName('td')) {
        var val = document.getElementById('anketa').getElementsByTagName('td')[key];
        if (val.className == 'odpoved') {
            val.innerHTML = val.firstChild.innerHTML;
        }
    }
    document.getElementById('stav-anketa').innerHTML = 'Ukládá se';
    return true;
}

function anketa_obsluha(xmlhttp)
{
    if (xmlhttp.readyState == 4) {
        // aktualizace odpovědí na základě aktuálního stavu
        var odpovedi = xmlhttp.responseXML.getElementsByTagName('odpoved');
        for (var i=0; i < odpovedi.length; i++) {
            document.getElementById(odpovedi[i].getAttribute('id')).innerHTML = odpovedi[i].firstChild.data;
        }
        document.getElementById('stav-anketa').innerHTML = 'Uloženo';
    }
}
</script> 

Skript anketa_rpc.php je velice jednoduchý – zkontroluje, jestli uživatel ještě nehlasoval, započte jeho hlas a zpět odešle jednoduché XML s aktuálním počtem odpovědí. Jak již bylo řečeno, nemusí spolu skripty komunikovat pomocí XML, u strukturovaných dat je to však poměrně praktické. Pokud by skript generoval nestrukturovaná data, lze k nim z JavaScriptu přistoupit přes vlastnost responseText.

bitcoin_skoleni

<?php
if (!isset($_COOKIE["anketa"])) {
    setcookie("anketa", $_GET["anketa"], strtotime("+1 month"));
    mysql_query("UPDATE anketa SET pocet = pocet + 1 WHERE id = " . intval($_GET["anketa"]));
}

header("Content-Type: text/xml");
echo "<anketa>\n";
$result = mysql_query("SELECT * FROM anketa");
while ($row = mysql_fetch_assoc($result)) {
    echo "<odpoved id='pocet$row[id]'>$row[pocet]</odpoved>\n";
}
mysql_free_result($result);
echo "</anketa>\n";
?> 

Objekt XMLHttpRequest umožňuje i odesílání dat metodou POST. Opět lze poslat XML dokument, je ale možné použít i hlavičku Content-Type: application/x-www-form-urlencoded a data poslat standardně URL-zakódovaná (jednotlivé části funkcí encodeURICompo­nent). Z takto poslaných dat PHP vytvoří standardní proměnné.

AJAX se samozřejmě dá použít i na spoustu dalších věcí. Zmíněny byly další dynamické komponenty stránky, dobře viditelné je použití u tzv. našeptávačů nebo u mapových aplikací, použít se dá např. i pro předběžnou kontrolu unikátnosti uživatelského jména u klienta. Fantazii se meze nekladou, důležité je ale vždy zajistit, aby aplikace byla použitelná i bez této funkce. Nevhodné je také AJAXem nahradit např. běžný přechod mezi stránkami, protože uživatelé potom nemohou používat historii a mohou být zmateni, že se stránky chovají jinak, než jsou zvyklí.

Autor článku

Autor se živí programováním v PHP, podílí se na jeho oficiální dokumentaci, vyučuje ho na MFF UK a vede odborná školení. Poznámky si zapisuje na weblog PHP triky.