Single Page Apps a řešení problémů s historií

Know how

S nástupem AJAXu a moderních prohlížečů se schopnostmi offline práce a s pokročilými JS API se stávají jednostránkové webové aplikace (single-page apps), tedy takové, kde je veškerá funkcionalita umístěna v jedné stránce a se serverem se pracuje jen prostřednictvím datového rozhraní, stále populárnější. Jejich použití ale přináší některé problémy.

Seriál: Webdesignérův průvodce po HTML5 (21 dílů)

  1. Webdesignérův průvodce po HTML5 – díl nultý 25.5.2010
  2. Webdesignérův průvodce po HTML5 – nová sémantika 1.6.2010
  3. Webdesignérův průvodce po HTML5 – nová sémantika II 8.6.2010
  4. Webdesignérův průvodce po HTML5 – pohyblivé obrázky 15.6.2010
  5. Webdesignérův průvodce po HTML5 – používáme pohyblivé obrázky 22.6.2010
  6. Webdesignérův průvodce po HTML5 – taháme data od návštěvníka 29.6.2010
  7. HTML5 Audio: rádio ve vašich stránkách 13.7.2010
  8. Webdesignérův průvodce po HTML5: Microdata 20.7.2010
  9. AppCache: webové aplikace i bez připojení 27.7.2010
  10. Webdesignérův průvodce po HTML5: WebStorage 3.8.2010
  11. Webdesignérův průvodce po HTML5: Multithreading s WebWorkers 10.8.2010
  12. Webdesignérův průvodce po HTML5: Databáze v prohlížečích 17.8.2010
  13. Webdesignérův průvodce po HTML5: Shrnutí a rozhrnutí 24.8.2010
  14. HTML5: ukládáme si data k elementům 6.12.2010
  15. Webdesignérův průvodce po HTML5: Táhni a srůstej 5.1.2011
  16. HTML5: První krůčky s FileSystem API 15.2.2011
  17. Mobilizujeme web v HTML5 4.4.2011
  18. Single Page Apps a řešení problémů s historií 1.6.2011
  19. Page Visibility API: Kouká na mě vůbec někdo? 10.8.2011
  20. Práce se soubory v prohlížeči, díl 1 15.8.2011
  21. Práce se soubory v prohlížeči, díl 2 5.9.2011

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/to­olsuites, tedy sadám samostatných spustitelných programů – utilit, které se navzájem volají a ukončují.

Analogie mezi desktopovými a webovými aplikacemi
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:

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@ca­niuse). 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í.

Začal programovat v roce 1984 s programovatelnou kalkulačkou. Pokračoval k BASICu, assembleru Z80, Forthu, Pascalu, Céčku, dalším assemblerům, před časem v PHP a teď je rád, že neprogramuje…

Komentáře: 14

Přehled komentářů

karf Tlačítko zpět
pas Re: Tlačítko zpět
Martin Malý Re: Tlačítko zpět
karf Re: Tlačítko zpět
Martin Malý Re: Tlačítko zpět
karf Re: Tlačítko zpět
Opravdový odborník :-) Re: Tlačítko zpět
Martin Malý Re: Tlačítko zpět
František Kučera Re: Tlačítko zpět
LV Re: Single Page Apps a řešení problémů s historií
Pepa o čem je ten článek?
trulant Re: o čem je ten článek?
Jakub Vrána history.pushState
Martin Malý Re: history.pushState
Zdroj: http://www.zdrojak.cz/?p=3499