Přejít k navigační liště

Zdroják » Různé » Single Page Apps a řešení problémů s historií

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

Články Různé, Webdesign

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.

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í.

Komentáře

Subscribe
Upozornit na
guest
14 Komentářů
Nejstarší
Nejnovější Most Voted
Inline Feedbacks
View all comments
karf

Podotknul bych, že tlačítko zpět není analogické funkci undo (jak je naznačeno v tabulce) a neplatí, že by nebylo používané v desktopových aplikacích. Používá se a plní stejnou roli jako u webu – změna pohledu (namátkou Explorer/Sala­mander, Eclipse/Netbeans, Thunderbird a další). Zásadní rozdíl mezi zpět a undo je ten, že undo vrací změnu dat provedenou uživatelem, což by tlačítko zpět dělat nemělo.

Další otázkou je, které změny stavu se mají ukládat do historie a které nikoliv. Příklad s lightboxem považuji za diskutabilní, pak bychom mohli ukládat i různé popovery a skončili bychom u stavování vysouvacích menu.

pas

Tak, tak. Dobrým zdrojem inspirace je i třeba Android, kde tlačítko Back funguje občas jako historie a občas jako up/exit (jít ve struktuře aplikace o úroveň výš) a je to podle mě většinou naprosto intuitivní.

karf

Není mým záměrem někoho o něčem urputně přesvědčovat, žádný agrumentační trik jsem použít nechtěl, hledáte v tom zas něco, co v tom není. Moje poznámka o lightboxu rozhodně nebyla myšlena jako jednoznačný argument pro nebo proti, psal jsem, že to je diskutabilní. Přechod od lightboxu k jiným podobným prvkům není ostrý, často se použije podobná komponenta na různé věci, a to, jestli ukládat nebo neukládat do historie, není vždy jednoznačné. Zatím neexistuje nic jako průmyslový standard, očekávání uživatelů se může v čase měnit atd. Samozřejmě je to otázka použitelnosti, čili v závěru se shodneme.

Ještě k tomu prvnímu odstavci – změna stavu/změna pohledu – přiznávám, že mi rozdíl asi není úplně jasný, použil jsem slovo pohled spíš ve smyslu „stav UI“. Nevím, jak ten rozdíl intuitivně vnímají normální uživatelé, možná mám jen já nějakou špatnou intuici.

karf

Hm, no já v tom pořád nevidím nějaký zásadní rozdíl, alespoň v aplikacích, které mám na mysli, se to chová taky jako cesta nebo „návrat tam, kde jsem už byl“. Ale to je jedno, asi jde jen o jiné úhly pohledu na stejnou věc.

Opravdový odborník :-)

Ad „ovšem jsou i na webu situace, kdy uživatel věří, že tlačítko Zpět vrátí změny (a je frustrován, že se tak nestalo…)“

Je dobré se podívat jak se tahle tlačítka jmenují v anglicky lokalizovaných prohlížečích: forward a backward. A skutečně znamenají změnu pohledu, skok na předchozí umístění. Zatímco vracení operací jen undo a redo. V češtině to trochu splývá protože v obou případech je to zpět — ale tím bychom se neměli nechat zmást, je to jen věc lokalizace, význam se nemění.

Ano, očekávání uživatele někdy neodpovídají realitě, ale to neznamená, že by se realita měla měnit.

František Kučera

jj, ta mapovací tabulka se mi taky moc nezdá. Tlačítko „undo“ bývá i na webu (běžné u WYSIWYG editorů, ale i jinde) a tlačítko „zpět“ bývá i v těch desktopových aplikacích. „URL v rámci aplikace“ u desktopových přeci taky je — viz parametry na příkazovém řádku — např. prohlížeči obrázků zadám jako parametr, do které složky má skočit, nebo editoru přímo cestu k souboru, někdy jde zadat i stránku, nebo jiné upřesnění místa.

LV

Nevim, jestli je tento clanek spravne zarazovat do serialu o HTML5, jelikoz popisovane techniky se tykaji i predchozich verzi HTML. Kazdopadne je to pekne cteni, dekuji.

Pepa

nějak postrádám nějakou strukturu nebo myšlenku

trulant

clanek je o webovych aplikacich, o posunu od vicestrankovych k jednostrankovym, o problemech co to prinasi a o tom, jak resit ten nejvetsi problem co maji, s historii v prohlizeci. myslenka je ta, ze pokud neco takoveho pises, tak se ti tyhle informace muzou hodit.

Jakub Vrána

Metoda history.pushState funguje s kompletní cestou (tedy na stejné doméně). Nevím, odkud jsi čerpal, když tvrdíš, že může měnit jen část za otazníkem (a tedy i mřižkou). Jak Firefox tak Chrome (které tuto metodu jako jediné pokud vím podporují) to zvládají bez problémů.

Enum a statická analýza kódu

Mám jednu univerzální radu pro začínající programátorty. V učení sice neexistují rychlé zkratky, ovšem tuhle radu můžete snadno začít používat a zrychlit tak tempo učení. Tou tajemnou ingrediencí je statická analýza kódu. Ukážeme si to na příkladu enum.