Jednostránkové webové aplikace
Pro stále se zvětšující rodinu webových aplikací je obvyklá představa webu coby sady provázaných hypertextových dokumentů nedostatečná.Ve chvíli, kdy se web začal měnit z „knihovny dokumentů“ na aplikační platformu, začala být evidentní potřeba změny tohoto modelu.
První webové aplikace dodržovaly model více stránek, kdy každá stránka odpovídala nějakému úkolu. Tento přístup má některé výhody – např. lze jednotlivé stránky („stavy aplikace“) uložit do záložek, procházet vyhledávačem, intuitivně funguje tlačítko Back atd. Vícestránkové aplikace mají ale i nedostatky – velká část přenášených dat jsou „servisní data“, posílají se záhlaví, zápatí, menu a další části stále dokola. K tomu se přidávají problémy s nechtěným cachováním, problém s opakovaným odesláním formulářů. Single Page Interface Manifesto dokonce tvrdí, že vícestránkový model webových aplikací nutí kodéry psát „divné“ věci, psát repetitivní kód (se spoustou includes) a mrhat prostředky (výkonem i přenosovou kapacitou) způsobem, jaký je v tvorbě desktopových aplikací nevídaný.
Puristé mohou přijít s tvrzením, že každá webová stránka je de facto aplikace. Ano, při určitém úhlu pohledu tomu tak může být; pro naše účely si ale rozdělme stránky podle míry interaktivity. Webová aplikace pro nás bude takový web, který počítá s intenzivnější interakcí uživatele, který neslouží pouze ke konzumaci informací. Definice to není přesná a existuje v ní široká oblast nejednoznačnosti, ale pro případ tohoto článku to nepředstavuje problém.
Nahradit vícestránkový model webových aplikací modelem jednostránkovým začalo být reálné s příchodem AJAXu a pokročilých možností moderních prohlížečů. Veškerá klientská logika se nahrává v jedné webové stránce; pokud je potřeba změnit zobrazená data či uživatelské rozhraní, načítal se ze serveru pomocí AJAXu pouze změněný obsah.
Single page model se ukázal hned v několika směrech nesnadný. V první řadě dokázal nešikovný programátor nešikovnými manipulacemi s obsahem snadno udělat z prohlížeče paměťového nenažrance. Ale i dobře navržená aplikace měla problémy – například tlačítko BACK nefungovalo úplně intuitivně, resp. „zavřelo aplikaci“, stejně tak stavy webové aplikace neměly vlastní URL, takže vyhledávače byly bezmocné.
Mezi webovými single-page aplikacemi a aplikacemi desktopovými je řada analogií. Některé z nich platí i pro multi-page aplikace. Vícestránkové aplikace by v tomto pohledu spíš odpovídaly toolchains/toolsuites, tedy sadám samostatných spustitelných programů – utilit, které se navzájem volají a ukončují.
| Webové | Desktopové |
|---|---|
| Události UI | Události UI |
| JS knihovny | Knihovny |
| JS knihovny z CDN | Systémové knihovny / DLL / SO |
| HTML + CSS | UI pomocí systémových prostředků / resources |
| On-page JavaScript | Vlastní programový kód |
| Tlačítko BACK | Není / „Undo“ |
| URL v rámci aplikace | Není („Stav aplikace“) |
| Přístup k datům pomocí AJAX | Přístup k souborům pomocí ODBC / File API |
Proč sinle-page aplikace?
Kromě důvodů zmíněných výše (nižší náročnost na přenosovou kapacitu atd.) jsou tu i další faktory, které je zapotřebí vzít v úvahu. Jedním z nich může být i důraz na oddělení klientské části od serverového API. Pokud napíšete jednostránkovou aplikaci, která bude intenzivně využívat API (a sama může být i prostý statický HTML soubor), přinese to možná zvýšené úvodní náklady (je potřeba vše, co se běžně řeší šablonami a přegenerováváním kódu, řešit pomocí AJAXu a manipulace s obsahem stránky), ale další práci to usnadní. Usnadní to změny na straně serveru (dokud zůstane konzistentní API, tak můžete server přepisovat, aniž by to uživatelé poznali) a zjednoduší to i údržbu klientské části. Jednodušší bude pak i vytvoření „klonů“ např. pro tablety či mobilní zařízení. (Zde můžeme použít další postupy a metody, např. z článku HTML5: píšeme aplikaci pro iPad.)
Taková webová aplikace v důsledku ani nepotřebuje serverovou technologii pro vytváření stránek (šablonovací jazyk) a vystačí si se statickým HTTP serverem, který posílá klientovi HTML, CSS, JS a další statické soubory. Pokročilejší technologie je potřeba pouze pro přístup k datům – a zde můžeme výhodně použít CouchDB nebo možností např. Google App Engine.
K tématu:
- http://blog.nodejitsu.com/single-page-apps-with-nodejs
- http://happyworm.com/blog/2010/08/23/the-future-of-web-apps-single-page-applications/
- http://itsnat.sourceforge.net/php/spim/spi_manifesto_en.php
- http://functionsource.com/post/hashbang-wallop
- http://tripleodeon.com/2011/06/whoops-and-hashbangs/
Historie stránek a URL
Zásadní problém single-page aplikací je jejich „zavření“ při kliknutí na tlačítko Back, a obecně práce s URL a historií prohlížení. Tento problém je třeba řešit na několika místech zároveň.
Indexovatelnost AJAXových aplikací řeší např. hashbang notace (blíže se jí věnujeme v článku Přepište historii webových stránek), tedy využití „fragmentu URL“ (ta část za znakem #) pro označení stavu aplikace. Využívá se zde toho, že při změně fragmentu se stránka nenačítá znovu.
window.onhashchange
Základní JavaScriptový nástroj pro práci s fragmentem URL je událost onhashchange (použitelná ve většině prohlížečů, viz onhashchange@caniuse). Použití je velmi snadné:
function locationHashChanged() {
alert (location.hash); // zde máme nový URL fragment
}
// Nastavíme obsluhu události
window.onhashchange = locationHashChanged;
Ke kódu není moc co dodat. Podle návrhu Mozilly by tělo zprávy (event) mělo obsahovat vlastnosti oldURL a newURL, ale při testech se ukázalo jako nefunkční (jde o bug 628069). Využívejte tedy vlastnost location.hash. Fungování si můžete vyzkoušet na našem příkladu onhashchange. Můžete si zkusit chování odkazů na kotvy ( <a href="#anchor">), můžete si zadat fragment URL ručním přepsáním v adrese, a můžete si zkusit chování tlačítka Back a Forward. Změny fragmentu se totiž zapisují do historie navštívených stránek.
Samozřejmé je, že lze fragment URL měnit i skriptem, pomocí nastavení vlastnosti window.location. I v takovém případě se původní URL uloží do historie navštívených stránek.
Práci s fragmentem URL vám může usnadnit knihovna Sammy.js – knihovna je inspirována nástrojem Sinatra z Ruby a slouží především k „routování“ (tj. převádění cest z URL, resp. z fragmentů, na volání patřičných ovládacích rutin). Na NetTuts+ naleznete stručný úvod do Sammy.js.
History
S historií navštívených stránek ve webovém prohlížeči lze pracovat pomocí History API, jehož jedna funkce je známá už od prvopočátků JavaScriptu – totiž history.back(); Mnoho stránek nabízí odkaz „na předchozí stránku“, a jeho hodnota je právě javascript:history.back() nebo history.go(-1). Označit to za matoucí je mírné vyjádření, obzvlášť např. ve vícestránkových dokumentech. Uživatel se může dostat například na stránku 3 přímo z vyhledávače, a kliknutí na odkaz „předchozí stránka“ jej nepřenese na stránku 2, jak by očekával, ale zpátky na výsledky vyhledávání.
History API ale nabízí dnes už víc než jen back() a go(). Pro jednostránkové aplikace je zajímavá možnost pushState(), která umožňuje ukládat do historie celý „stav aplikace“ – tedy nějaké informace (v podobě JS objektu) o tom, v jakém je aplikace stavu – otevřené formuláře, speciální efekty apod.
Hezký příklad, kde lze práci s historií použít, je procházení galerií s lightbox efektem. Na stránce example.com klikne uživatel na odkaz /galerie. Dostane se na URL example.com/galerie, kde je série náhledů obrázků. Po kliknutí na náhled se v lightboxu ukáže obrázek v plné velikosti, s možností kliknout na Další pro přesun k dalšímu obrázku. Uživatel pak klikne na tlačítko Zpět – a pokud není ošetřená historie prohlížení, ocitne se zpátky na homepage, což pravděpodobně není to, co čekal. Očekávané chování je buď přechod na předchozí obrázek, nebo nanejvýš zavření lightboxu a návrat do galerie.
pushState
Metoda pushState() slouží k zapsání informací o aktuálním stavu do historie a do adresního řádku. Má tři argumenty: history.pushState(state, title, URL). State je zmíněný stavový objekt – libovolná objektová data, která dají skriptu informaci o stavu aplikace, potřebnou ke zrekonstruování podoby před opuštěním aktuální stránky. Argument title by měl obsahovat titulek stránky, ale Firefox i Chrome jej svorně ignorují (v jiných prohlížečích netestováno). A konečně URL udává adresu, která bude zapsaná do adresního řádku a do historie stránek. Zde je omezení – nelze změnit celou adresu, dokonce ani název souboru (ačkoli dokumentace Mozilly tvrdí opak), pouze query string a fragment (tedy část za ? a za #). Zde jste omezeni pouze na změny URL v rámci jedné domény. (Při testování z lokálního filesystému (file:///) dostanete bezpečnostní chybu i při pokusu o změnu souboru. – díky za upozornění Jakubovi Vránovi, pozn.red.) Pokud změníte query (?par=123), stránka se nebude načítat znovu – chování je tu podobné jako při změně fragmentu.
Metoda pushState() se v něčem podobá výše popsanému způsobu měnění fragmentu, ale jsou zde i významné rozdíly: pomocí pushState lze do historie zapsat informace o stavu aplikace, které by bylo jinak potřeba kódovat do řetězce za znak #, nová položka se do historie zapíše vždy (i když je URL stejné) a v neposlední řadě – změna fragmentu URL pomocí pushState() nevyvolá událost onHashChange!
S pushState() souvisí událost onpopstate – tato událost je vyvolána vždy, když je z historie vyzvednuta položka (nemusí být pouze při stisku BACK, funguje i při FORWARD, a platí nejen pro položky vytvořené pomocí pushState, ale pro veškerou historii, tedy i pro změny fragmentu). Obsluha události onpopstate může zkontrolovat vlastnost event.state – pokud byl stav uložen pomocí pushState, jsou v této vlastnosti příslušná data.
Fungování pushState a onpopstate si můžete opět vyzkoušet v příkladu práce s historií prohlížeče.
Alternativní metoda replaceState() funguje podobně jako pushState, ale jak už název napovídá, nevytváří nový záznam v historii, ale přepisuje aktuální. Ke zjištění aktuálního stavu slouží vlastnost history.state.
Závěr
Manipulace s historií navštívených stránek či hlídání změny URL fragmentu funguje v moderních prohlížečích bez problémů. Moderní webové aplikace, a to nejen jednostránkové, se bez inteligentní obsluhy historie neobejdou. Vhodné použití těchto metod může rapidně zlepšit uživatelský prožitek a komfort ovládání aplikace, kdy uživateli tlačítko BACK funguje konzistentně s jeho zvyklostmi i s AJAXovou aplikací.


Přehled komentářů