AJAX

Školení, která pořádám

Dopsal jsem knihu

Článek vyšel na serveru Root.cz.

Č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 jeden 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:

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 onreadystatechange 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 funkce zajišťující obsluhu při změně stavu požadavku, dostane parametr s XMLHttp objektem
* @param string GET|POST|...
* @param string URL požadavku
* @param string tělo zprávy
* @param object předané hlavičky ve tvaru { 'hlavička': 'obsah' }
* @return boolean true v případě úspěchu, false jinak
* @copyright Jakub Vrána, http://php.vrana.cz/
*/
function send_xmlhttprequest(state_change, 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 () {
		state_change(xmlhttp);
	};
	headers = headers || {};
	headers['X-Requested-With'] = headers['X-Requested-With'] || 'XMLHttpRequest';
	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.

<?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í encodeURIComponent). 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í.

Související odkazy

Přijďte si o tomto tématu popovídat na školení JavaScript a AJAX (4.10.2010, Praha).

Jakub Vrána, Seznámení s oblastí, 3.10.2005, diskuse: 46 (nové: 0)

Diskuse

takasija:

Chtel bych se zeptat kde vznika problem, kdyz se pri pokusu o odeslani objevi hlaska "Odepření přístupu při volání metody XMLHttpRequest.open" ?
# 7.11.2005 14:11:05 reagovat

Vlkcy:

Podařilo se získat nějaké informace?
# 9.6.2007 14:09:16 reagovat

ikona Jakub Vrána:

Jedná se nejspíš o cross-domain request. Požadavek je možno pokládat jen na adresu ze stejné domény, na které je stránka.
# 11.6.2007 15:40:01 reagovat

Jirka K:

Možná budu působit jako diletant, ale já měl podobný problém na localhostu. IE prudil, Firefox nereagoval. Stačilo přidat http:// do volané adresy.
# 18.10.2008 13:18:18 reagovat

opera:

ako je to soperou?
# 20.3.2006 14:52:58 reagovat

Vilém Málek:

2 opera: Opera se přizpůsobila diktátu doby, viz http://interval.cz/clanky/google-suggest-…-xmlhttprequest/ ;-)
# 31.3.2006 14:25:53 reagovat

Malcik:

Když se provede načtení toho kódu, tak se mi ztratí diakritika (místo písmen s háčkem nebo čárkou se ukážou je otazníky). Jak to vyřeším?

Díky
# 14.4.2006 10:36:35 reagovat

FantomX1:

V dokumente vratenej spravy zmenis header("Content-Type: text/xml") na header("Content-Type: text/xml; charset=windows-1250") alebo na take ake mas na stranke, mozno iso-8859-2.
# 17.4.2006 19:38:56 reagovat

peta:

Se mi velice libi opakovani konstrukce
document.getElementById
Proc si nezalozis fukci treba
function xObject(x) {return document.getElementById(x)}

Proc? Se ti z nejakeho duvodu stane, ze budes mit potrebu resit objekty pres document.all nebo pres layers, a pak to budes cely pracne prepisovat.

Prikladam funkci pracujici s FRAME nebo aktualnim framem, cili WINDOW, kde vlastne presny zapis by mel spravne byt:
window.document.get...()
zapis pres frame
nazevframu.document.get...()
<FRAME name="nazevframu">

function objGet2(d,x)    {d=d==0?document:d; return (d.getElementById?d.getElementById(x):d.all?d.all[x]:d.layers?d.layers[x]:null);}
# 29.5.2006 19:40:40 reagovat

ikona Jakub Vrána:

document.getElementById() je v současných prohlížečích, které podporují AJAX, zcela standardní funkce, přístup přes all nebo layers je zbytečný. A vytvářet funkci jenom jako kratší zápis jiné funkce považuji za samoúčelnou obfuskaci.
# 30.5.2006 12:15:44 reagovat

Ferda Mravenec:

toto nemusi nutne platit, pri ajaxovani sveho webu jsem se ted dostal do situaci, kdy to v IE 6.0.x funguje, v FF 1.5.x ne, a jako reseni se nabizi kratoucky javascript

if((!document.getElementById) && document.all){
document.getElementById = function(id){return document.all[id];};
}

ktery v pripade, ze stranka neumi getElementById ale umi document.all proste nadefinuje funkci getElementById pouzivajic prave document.all ... to nikomu neublizi a naopak programator se dal muze "odstinit" od ruznych anomalii ve volani gEBI v ruznych prohlizecich ...
# 3.8.2006 18:44:23 reagovat

longin:

Asi zijes v dobe kde neni mozny nahrazovani textu regulernim vyrazem, coz ;)
# 30.6.2007 22:30:47 reagovat

ikona venca:

regulárním proboha, regulÁrním!
# 6.8.2007 21:24:15 reagovat

Flamer:

myslím, že ve slovníku češtiny je i regulerní. :)
# 12.3.2008 17:26:35 reagovat

ikona Jakub Vrána:

Je to tak, ale „regulární výraz“ je ustálený pojem.
# 12.3.2008 17:32:22 reagovat

SmallM:

Regulerní používal soudruh Husák páč se nikdy nenaučil dobře česky a po něm všichni ti soudruzi co pospávali na těch stranických schůzích- Mají to v podvědomí.
# 20.3.2008 16:30:45 reagovat

Tomas:

Ahoj, zaprve diky za pekny clanek. Zadruhe bych se rad zeptal jak je to s 'licenci' uvedenych kousku kodu. Konkretne jsem funkci 'send_xmlhttprequest' pouzil v jedne firemni webove aplikaci, ale nez to zpristupnime tak bych rad vedel ze to je OK - aby mne pak nekdo nenarkl ze jsem loupeznik. Uvedena tam samozrejme bude vcetne copyrightu atd.
# 18.7.2006 01:22:28 reagovat

ikona Jakub Vrána:

Je to uvedeno v patičce každé stránky: "Ukázky kódu smíte používat s uvedením autora a URL tohoto webu bez dalších omezení."
# 18.7.2006 11:25:28 reagovat

Tomas:

OK, diky. Hledat to v patičce mne nenapadlo.
# 18.7.2006 22:38:36 reagovat

CyberLuke:

hezke...ale proc znovu vynalezat kolo? Ajax knihoven je spousta - jen na PHP je jich tusim pres 15 - ma nejoblibenejsi je TinyAjax...ale kdo ma cas a nervy na reseni bugu, tak asi vynaleza kolo porad dokola. No...ale aspon na root.cz vyjde i nejaky ten clanek o ajaxu.

Jeste by se hodilo dodat, ze ten kod je jen ukazkovy...v aplikacich se pouzit neda - resp. da, ale ne dlouho, protoze jak se v tom za chvili nebude dat vyznat, takze doufam, ze ty lamy jen tak nekopiruji do sveho editoru.

# 3.8.2006 23:43:12 reagovat

Honza Hučín:

Díky tomuto článku jsem úspěšně implementoval techologii AJAXu do jedné aplikace, a to na první pokus. Děkuju moc.
# 20.8.2006 20:55:04 reagovat

Milan:

Domnívám se, že by ve skriptu pro výpis ankety mělo být <?php if (!isset($_COOKIE["anketa"])) { ... ?> místo <?php if (isset($_COOKIE["anketa"])) { ... ?>. Jinak by se nikdy nedalo hlasovat. Promiňte, jestli se pletu.
# 17.9.2006 11:17:18 reagovat

ikona Jakub Vrána:

Fakt že jo. Zvláštní, že si toho dosud nikdo nevšiml. Opravil jsem to.
# 18.9.2006 12:13:54 reagovat

Adolf Fritz:

Chcem len podotknúť, že ak vám prehliadač pri vypisovaní pomocou ezophp nbrazuje znaky, ako: ľščťžýáíé... tak stačí do daného skriptu pridať takúto hlavičku... potom to pôjde...

<?header('Content-Type: text/html; charset=Windows-1250');?>
# 28.1.2007 22:40:09 reagovat

Orly?:

diky moc, toto som dlho hladal, toto mi osobne pomohlo
# 17.12.2009 23:47:01 reagovat

Rasto:

zdravim. skusal som tento kod a nejako mi to nefunguje. zobrazi sa sice o 1 hlas viac, aj sa odstrani odkaz, ale nezapise sa novy stav do DB.
# 4.2.2007 11:56:08 reagovat

Yučikala:

Super článek, všechno se mi podařilo rozchodit. Až na diakritiku. Ať sem zkoušel, co sem zkoušel, na místo háčků a čárek mám paznaky "novÄ›  čřžýáíé"

zkoušel sem přidat charset do hlavišky funkce send_xmlhttprequest , ale nepomohlo to...

používám metodu POST

Nevíte, co s tím? Děkuju moc
# 4.2.2007 18:46:11 reagovat

hugoman:

Skus zmenit kodovanie suboru ;)
# 24.3.2007 12:42:51 reagovat

Katamaru:

Zdravím, AJAX mi v FF funguje spravne, ale v IE se nedeje nic. Jak ho upravit pro IE?
# 16.4.2007 11:51:53 reagovat

ikona Marty:

Použil jsem jednoduchou ajaxovou techniku do svého redakčního systému a narazil jsem na problem se zabezpecenim:

makePOSTRequest('../admin/admin_articles_save_now.php?PHPSESSID='+session_id, poststr);

Tato funkce posle data metodou POST souboru admin_articles_save_now.php, jako zabezpeceni jsem pouzil SESSIONS, ve kterych mam ulozene promenne s pravy. Zajimalo by me, jestli je toto reseni dostatecne nebo byste doporucovali nejake jine.
# 6.7.2007 00:38:37 reagovat

choswe:

Tak sem si vyzkousel tu anketu a bohuzel mi to nefunguje:(
prohlizec mi hlasi u radku:
  var xmlhttp = (window.XMLHttpRequest ? new XMLHttpRequest : (window.ActiveXObject ? ...  syntax error a pritom sem to zkopiroval cele odsud
# 4.9.2007 00:45:13 reagovat

firgas:

ja používam ajaxovú triedu z tejto adresy

http://xkr.us/code/javascript/XHConn/

je to jednoduché, rýchle, účinné, funguje to a setri to miesto
# 23.9.2007 15:55:34 reagovat

Pepa:

Dávám přednost použití Hidden Iframe před Ajax řešením
všude, kde je to únosné. Nicméně jsem už narazil na situace,
kdy Ajax byl jediným možným řešením. Ajax je dle mého názoru
rovněž elegantnější řešeni pro dynamické změny ve formuláří
i jinde.
# 28.12.2007 20:28:55 reagovat

ikona Jakub Vrána:

Mohl bys popsat situaci, kdy by Ajax byl jediným možným řešením?
# 29.12.2007 12:43:16 reagovat

kozotoč:

Mě napadá případ, kdy má uživatel ve formuláři vložit firmu / skupinu / družstvo / ... které ještě neexistuje a které je záhodno, aby ji zadal. Klikne na odkaz "vytvořit novou", který mu v novém okně zobrazí druhý formulář pro vytvoření této firmy/skupiny/... , a po odeslání napíše, že byla vytvořena. No, avšak, pak je třeba, aby se v původním formuláři seznam firem/skupin/... aktualizoval. Obdobně v případech, kdy se něco často mění (rezervace knih v knihovně, např. aneb - mezi otevřením formuláře a jeho odesláním mu knihu, co si chce půjčit, může "vyfouknout" jiný čtenář). Atd.
# 31.8.2008 13:15:51 reagovat

Pepa:

Pokusil jsem se vytvořit Web Online textový editor MVS a Unix (Aix) souborů pomoci Textarea. Samozřejmě v PHP pro
Unix, ale v Gwapirexx pro MVS. Ale to není podstatné. Do kompletně přijatého Formuláře editoru na straně prohlížeče
jsem v onLoad() sekci ajax scriptem dodatečně musel vložit vlastní tělo souboru do pole textarea. Soubor múže mít i tisíce záznamů a všechny možné speciální znaky. Pokud byl soubor součástí html dokument posláného na prohlížeč, tento nedokázal tyto data zpracovat (zblbnul). Nenašel jsem jiné řešení, než poslat obsah souboru asynchronně Ajax metodou do kompletně přijatého dokumentu dodatečně. Na pohled to vypadá docela elegantně a Ajax si získal mé velké sympatie.
# 30.12.2007 00:34:12 reagovat

ikona Jakub Vrána:

Se správně zakódovanými daty by si měl prohlížeč poradit. Kdyby ne (třeba proto, že by to byly bajty s kódem menším než 32), tak by to jistě šlo JavaScriptem. Pro AJAX zde nevidím uplatnění.
# 30.12.2007 18:22:59 reagovat

Alojz:

Dalo by sa nejakou funkciou kontrolovat trebars po 3 minutach, kolko je prihlasenych uzivatelov na stranke?
Ako by sa dal ajax vyuzit v tomto?
Ak dobre rozumiem odoslal by sa ajaxom dotaz na vykonanie skriptu a ako by sa zaistilo vypisanie?
Je vobec nutne posielat dotaz na vykonanie skriptu, ked mam zaujem iba o vypis?
# 25.3.2008 12:43:53 reagovat

Gifi:

mam dotaz.
mam hlavni dokument a v nem reaguje na urcite udalosti ajax v jinem dynamickem php dokumentu. a potrebuji z toho druheho vytahnout data do do prvniho abych mohl data odeslat pres formular. zatim me napadlo reseni pres cookie? existuje neco jineho?
# 24.4.2008 12:05:38 reagovat

ikona Lukáš Zdechovan:

Ja som mal tiez problemy s diakritikou a chcel som sem len doplnit, ze castokrat ked zapisujete/citate udaje do/z databaze (napriklad cez ajax) a dostavate divne znaky skuste pred vyknonanim databazoveho prikazu SELECT, INSERT, atd zadat  DB prikaz SET NAMES 'UTF8'
# 5.5.2008 14:52:36 reagovat

štíhloprd:

k řádku "if (xmlhttp.readyState == 4) {" v anketa_obsluha().
Co se tak koukám na ostatní ajaxové příklady, v této podmínce pro zpracování bývá přidáván i xmlhttp.status == 200 (HTTP kód 200 - OK). Jinak se bude zpracovávat, i když server bude hlásit chybu.
# 20.8.2008 17:32:34 reagovat

papajuk:

jemný úvod je též zde (anglicky)
http://developer.mozilla.org/en/AJAX/Getting_Started
# 31.8.2008 13:59:02 reagovat

Tonda:

Ahoj,bylo by možné dát celý příklad ke stažení ?
# 15.1.2009 01:12:01 reagovat

ikona Jakub Vrána:

Stačí zkopírovat ukázky zdrojového kódu ze článku.
# 15.1.2009 02:05:31 reagovat

ikona deny:

Může mi někdo prosím vysvětlit, jak je možné, že mi někdo hlasuje v mé anketě bez toho, aniž by se mi uložila jeho ip, i když v jiných případech to funguje? Scriptem by to být nemělo a ochranu před roboty mám snad vyřešenou... Nějaké nápady? Děkuji...
# 23.12.2009 12:17:35 reagovat

ikona deny:

Již vyřešeno...
# 25.12.2009 13:12:59 reagovat

Vložit příspěvek

Používejte diakritiku. Nelze používat HTML značky, ale URL budou převedeny na odkazy a PHP kód uzavřený do <?php ?> bude zvýrazněn. Pokud máte dotaz, který nesouvisí s článkem, zkuste raději diskusi o PHP, zde se odpovědi pravděpodobně nedočkáte.

Jméno: URL:

© 2005-2010 Jakub Vrána. Publikované texty můžete přetiskovat pouze se svolením autora. Ukázky kódu smíte používat s uvedením autora a URL tohoto webu bez dalších omezení Creative Commons. Můžeme si tykat. Skripty předpokládají nastavení: magic_quotes_gpc=Off, magic_quotes_runtime=Off, error_reporting=E_ALL & ~E_NOTICE a očekávají předchozí zavolání mysql_set_charset. Skripty by měly být funkční v PHP >= 4.3 a PHP >= 5.0.