Když AJAXem stáhnete nějaké HTML, můžete ho zobrazit na stránce několika způsoby. Ty se liší tím, co se staženým kódem můžete dělat a jaký bude výsledek.
jQuery.load()
Nejjednodušší způsob je nechat celý proces na nějakém frameworku. Například jQuery má metody load()
, ale většina frameworků má nějakou podobnou metodu nebo komponentu (např. ExtJS/Sencha má komponentu Panel
, do kterého můžete načíst obsah pomocí zadané URL).
//GET pro HTML soubor $('#container') .load('/static/license.html'); //GET $('#container') .load('/api/html?profile=12345'); //POST $('#container').load('/api/html', { profile: 12345 });
Do metody load()
jednoduše předáte URL, kterou chcete stáhnout a případně ještě parametry, které jsou potřeba, aby server správně HTML vygeneroval (v případě, že nestahujete statické HTML).
Metoda má ještě dvě alternativy. V první můžete omezit, jaký prvek se má do kontejneru zobrazit v případě, že server vrátí více než kolik potřebujete:
$('#user-picture').load( '/api/html?profile=12345 img.profile');
V tomto případě server vrátí celý profil, ale jQuery z něj vybere pouze obrázek se třídou profile
a ten zobrazí v kontejneru. Nevýhodou tohoto přístupu je, že nemůžete dynamicky vybrat prvek podle toho, co server vrátil (tzn. že identifikátor požadovaného prvku musíte znát před odesláním requestu).
Do funkce ještě můžete předat callback, pomocí kterého můžete ošetřit např. chybu (pokud server nevrátí platné HTML, metoda load()
jen vymaže obsah kontejneru) nebo upravit přijatý HTML kód. Nemůžete ale ovlivnit, co funkce nastaví do prvku, protože callback se zavolá až poté, co jQuery zpracuje odpověď a vloží ji do kontejneru. Sice ho můžete znovu přepsat uvnitř callbacku, ale v tom případě už můžete použít některou z ostatních metod.
$('#user-picture').load( '/api/html?profile=12345 img.profile', function(response, status) { if ('success' !== status) { alert('Chyba při stahování!'); return; } if (!$(response) .find('img.profile').length) { $('#user-picture').html( '<img src="none.jpg" />'); } } );
Při uvádění callbacku dejte pozor na to, že v jQuery existují dvě metody load()
– pokud má jako první parametr řetězec a druhý funkci, načítá obsah z URL; pokud má funkci jako první parametr, nastavuje naopak handler události onload
(zkrácený zápis $().on('load', function() {};
).
Výhody:
- jednoduchost stažení
- možnost přepínat mezi
GET
aPOST
jen podle parametrů
Nevýhody:
- musíte mít dopředu připravený kontejner
- musíte dopředu znát selektor prvku, pokud nechcete celé HTML
Metody ajax(), get(), post()
Pokud se vám nehodí metoda load()
, můžete použít funkci ajax()
(nebo její modifikace get()
a post()
) a následně ručně zpracovat odpověď a nastavit ji tam, kam potřebujete.
$.ajax({ url: '/api/html', data: { profile: 12345 }, dataType: 'html' }).always(function(response, status) { if ('success' !== status) { alert('Chyba při stahování!'); return; } var html = $(response), el = html.find('img.profile'); if (!el.length) { el = html.find('.error'); } $('container').empty().append(el); });
Výhody:
- můžete přesně určit, co z odpovědi vás zajímá
- můžete stažené HTML vložit kam chcete
Nevýhody:
- pro každý request musíte psát callback a ošetřovat chyb
- musíte si pamatovat jména parametrů metody
$.ajax()
Vložení do iFramu
Stažený HTML samozřejmě nemusíte vkládat do stránky samotné, ale můžete pro staženou stránku vytvořit iFrame:
$.ajax({ url: '/api/html', data: { profile: 12345 }, dataType: 'html' }).always(function(response, status) { if ('success' !== status) { alert('Chyba při stahování!'); return; } var html = $(response), ifrm = $('<iframe />') .attr('src', 'about:blank') .hide() .appendTo($('body')); iDoc = $(ifrm[0] .contentWindow.document); iDoc.find('body').append(html); ifrm .width('100%') .height(ifrm[0] .contentWindow.innerHeight) ; $('container').append(ifrm.show()); });
Při vytváření iFrame musíte pamatovat na několik věcí. iFramu musíte nastavit nějaký src
, protože jinak by byl prázdný a nedalo by se s ním pracovat. Nastavením na about:blank
vytvoříte v iFrame základní strukturu HTML-HEAD-BODY
, do které následně můžete vkládat váš kód.
Aby se ale src
zpracoval, musíte iFrame vložit do stránky, před čímž je vhodné ho skrýt, aby nerozbil stránku před tím, než ho budete schopni upravit a zobrazit na správné pozici.
Poté, co iFrame zpracuje načtenou (prázdnou) stránku, můžete pomocí jeho vlastnosti contentWindow
získat přístup k document
elementu, pomocí kterého pak můžete pracovat v jeho DOMem.
Poznámka 1: contentWindow
je vlastnost původního HTMLElementu IFRAME
, takže pokud máte iFrame jako jQuery wrapper, musíte nejprve získat HTMLElement
pomocí metody get(0)
nebo jako pole [0]
.
Poznámka 2: některé prohlížeče podporují místo contentWindow.document
přímo vlastnost contentDocument
, ale vzhledem k tomu, že není univerzální a delší zápis není zase o tolik delší, je lepší používat univerzální contentWindow.document
.
Když získáte přístup do vnitřního dokumentu, můžete pak najít tagy HEAD
nebo BODY
a vložit do nich, co potřebujete. Následně již můžete iFrame zobrazit do stránky tam, kam potřebujete.
Pokud potřebujete zjistit rozměry vnitřního okna, můžete používat contentWindow
stejně jako používáte window
u hlavní stránky.
Výhody:
- styly a skripty v iFramu neovlivní aktuální stránku
Nevýhody:
- složitější vytváření iFramu
- nutnost nastavení CSS souborů do hlavičky iFramu pro správné naformátování obsahu
Přepsání dokumentu
Poslední možností, jak zpracovat stažený HTML kód, je přepsat celý aktuálně zobrazený obsah. To se hodí v případě, že stáhnete stránku a zjistíte, že nic z ní nejste schopni zobrazit v současné stránce a musíte danou stránku zobrazit jako novou. Pokud byste ale jednoduše přepsali document.location
(nebo znovu zavolali form.submit();
), došlo by k nové stažení stránky, což by trvalo déle a navíc by nemuselo vrátit stejný výsledek (např. u formuláře by došlo k dvojitému uložení dat).
Základem přepsání dokumentu je použití metody document.write()
, která umožňuje zapisovat HTML kód přímo do dokumentu (bez nutnosti pracovat s DOMeme). V případě, že již došlo k zavolání document.onload()
, smaže jakýkoliv zápis do dokumentu současný obsah a začne vytvářet nový:
url = '/api/html?profile=12345'; $.ajax({ url: url, dataType: 'html' }).always(function(response, status) { if ('success' !== status) { alert('Chyba při stahování!'); return; } var html = $(response), required = ('img.profile, .error'); if (!html.find(required).length) { //stránka neobsahuje žádný //z požadovaných elementů //může jít třeba o login stránku if (window.history) { history.replaceState({ reloadUrl: location.href }, '', location.href); history.pushState({ reloadHtml: response }, '', url); } //if(window.history) document.open('text/html','replace'); document.write(response); document.close(); return; } //... pokračuje zpracování HTML });
Před tím, než zapíšete nový obsah pomocí metody document.write()
, musíte nejprve zavolat metodu document.open()
, které předáte jako parametry typ obsahu a zda jím má přepsat stávající obsah.
Typ obsahu by mohl teoreticky zahrnovat jakýkoliv MIME typ, ale v současnosti prohlížeče podporují pouze html, takže nic jiného než „text/html
“ nemá smysl. Parametr se uvádí hlavně proto, že potřebujete uvést i druhý parametr.
Podle W3C specifikace by neuvedení druhého parametru mělo automaticky vytvořit nový záznam v History a zachovat se stejně, jako kdyby uživatel klikl na odkaz nebo tlačítko. Nicméně ve WebKitu (kvůli chybě) tohle nefunguje a obsah se vždy přepíše. Proto je nutné i pro ostatní prohlížeče uvést parametr jako „replace
“ (což je stejné jako True
), aby se všechny chovali stejně.
Poté, co zapíšete do dokumentu nové HTML, musíte ještě zavolat metodu document.close()
, čím způsobíte nové vyvolání document.onload()
(které bude nyní to z nové stránky).
Oprava History objektu
Jelikož výše uvedeným zavoláním document.open()
nahradíte současnou stránku, přestanou fungovat správně tlačítka Zpět a Vpřed v prohlížeči. Aby k tomu nedošlo, musíte, před zápisem nového obsahu, pomocí metody history.pushState()
vložit do historie novou položku, která bude reprezentovat nový obsah – jako třetí parametr uvedeme URL tohoto obsahu, aby uživatel věděl, která stránka je zobrazena.
Použití history.pushState()
ale vytvoří pouze fiktivní záznam, který po kliknutí na Zpět nebo Vpřed nezpůsobí změnu obsahu stránky, je potřeba ještě dopsat metodu, která tohle zajistí. Proto jsme ve výše uvedeném kódu ukládali do statutu stránky hodnoty reloadUrl
a reloadHtml
:
var allowPopState = false; $(function() { setTimeout('allowPopState = true;', 1); }); $(window).on('popstate', function(event) { if (!history.state) { return; } if (!allowPopState && document.readyState === 'complete') { event.preventDefault(); event.stopImmediatePropagation(); return; } if (history.state.reloadHtml) { //stránka byla vytvořena // z načteného HTML kódu document.open('text/html','replace'); document.write( history.state.reloadHtml); document.close(); } else if (history.state.reloadUrl) { //normální stránka z URL if (history.state.reloadUrl === location.href) { location.reload(); } else { location.replace( history.state.reloadUrl); } } });
Metoda hlídá událost popstate
, která se vyvolá při kliku na tlačítka Zpět nebo Vpřed. Pokud v statutu stránky najde HTML kód (který byl dříve stažen pomocí AJAX), zapíše ho do obsahu stránky (tak jako poprvé). Pokud naopak najde ve statutu URL, musí stránku znovu stáhnout stejně, jako kdyby uživatel klikl na tlačítko Aktualizovat (Refresh). Je potřeba správně použít buď metodu location.reload()
nebo location.replace()
! V opačném případě by se buď zobrazil špatný obsah stránky nebo by došlo k nekonečnému vyvolávání popstate
události a prohlížeč by se zasekl.
Pozor na to, že prohlížeč v okamžiku zavolání document.write()
smaže DOM a všechny skripty současného dokumentu. Jediná metoda, která je schopna pokračovat je ta, která document.write()
zavolala. Po jejím skončení již nezůstane z původní stránky nic!
Safari a starší Webkit verze obsahují chybu, kdy se popstate
událost vyvolávají i při prvním načtení. Z toho důvodu je potřeba ošetření popstate
povolit teprve poté, co se celý dokument načte. Pokud k popstate
události dojde během provádění onload
události, je potřeba takový popstate
ignorovat (a nejlépe zastavit metodami preventDefault()
a stopImmediatePropagation()
). K tomu slouží globální proměnná allowPopState
. Podmínka testující readyState
zajišťuje, aby mohl uživatel kliknout na tlačítka Zpět a Vpřed ještě během načítání stránky.
Write is evil… ale ne tady
Některé editory a validátory mohou hlásit chybu na řádku s document.write()
. To je proto, že obecně je write
považováno za nebezpečnou funkci, protože může vyvolávat útočné skripty. V tomto případě ale naopak požadujeme, aby se všechny skripty ze staženého HTML vyvolaly a tudíž je toto použití metody správné (a chybu můžete ignorovat). Pokud chcete chybu odstranit, použijte následující trik:
function replaceDocument(html) { //sestav jméno metody skriptem, aby //validátor nevěděl, o co se jedná var method = 'wr'+'ite'; document.open('text/html','replace'); //zavolání write jménem document[method](html); document.close(); }
Výhody:
- HTML se zobrazí jako samostatná stránka
- Není potřeba stránku stahovat dvakrát (AJAX a normálně)
Nevýhody:
- dojde ke smazání původní stránky
- musíte sami ošetřit správu historie a obnovení stránky
- nová stránka musí stáhnout všechny skripty a styly a vykreslit se, což z pohledu uživatele vypadá stejně jako normální stažení stránky
Přepsání iFrame
Můžete samozřejmě zkombinovat dvě předchozí metody a stažený HTML kód zapsat do iFrame. Stačí místo document.write()
použít iframe.contentWindow.document.write()
:
function(html, iframe) { var iDoc = $(iframe).get(0) .contentWindow.document; iDoc.write(html); iDoc.close(); }
Zde již není potřeba volat metodu document.open()
, protože iFrame nemá vlastní historii a nezáleží tak, zda obsah přepíšete nebo přidáte nový. Metoda document.open()
se zavolá automaticky s výchozími parametry před zavoláním document.write()
.
Můžete ale požadovat, aby hlavní stránka obsahovala nový záznam v historii, který iFrame zobrazí nebo skryje.
Pozor na XmlHttpRequest
Některé serverové frameworky jsou schopny rozlišit normální a AJAXové requesty a pak se k nim chovají různě – např. negenerují hlavičku stránky nebo neprovádí některé funkce.
V případě, že potřebujete přes AJAX stáhnout celou stránku, může být potřeba buď prohlížeči zakázat odeslání hlavičky X-Requested-With
nebo naopak odeslat ještě další hlavičku a na serveru upravit podmínky, které rozlišují AJAX requesty.
$.ajax({ //... //změní XHR hlavičku, // aby server nepoznal AJAX headers: { 'X-Requested-With': 'HtmlLoader' }, //NEBO - XHR hlavička se neposílá //u requestů na jiný server crossDomain: true, //NEBO - přepíše hlavičku // před odesláním requestu beforeSend: function(xhr){ xhr.setRequestHeader( 'X-Requested-With', 'HtmlLoader'); } });
nebo:
$.ajax({ //... //hlavička s požadavkem na plné HTML headers: { 'X-Requested-Content': 'FullHtml' }, //NEBO - změna před odesláním beforeSend: function(xhr){ xhr.setRequestHeader( 'X-Requested-Content', 'FullHtml' ); } });
Pro oba případy by serverová část mělo kontrolovat request takto:
<?php $request = /* získání requestu */; $headers = $request->getHeaders(); $isAjax = ('XMLHttpRequest' === $headers['X-Requested-With']); $isFullContent = ('FullHtml' === $headers['X-Requested-Content']); function generateHeader() { if ($isAjax && !$isFullContent) { return; //no header for AJAX } //... }
1 komentář u „Zobrazení HTML staženého pomocí AJAX“