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

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ů)
- Webdesignérův průvodce po HTML5 – díl nultý 25. 5. 2010
- Webdesignérův průvodce po HTML5 – nová sémantika 1. 6. 2010
- Webdesignérův průvodce po HTML5 – nová sémantika II 8. 6. 2010
- Webdesignérův průvodce po HTML5 – pohyblivé obrázky 15. 6. 2010
- Webdesignérův průvodce po HTML5 – používáme pohyblivé obrázky 22. 6. 2010
- Webdesignérův průvodce po HTML5 – taháme data od návštěvníka 29. 6. 2010
- HTML5 Audio: rádio ve vašich stránkách 13. 7. 2010
- Webdesignérův průvodce po HTML5: Microdata 20. 7. 2010
- AppCache: webové aplikace i bez připojení 27. 7. 2010
- Webdesignérův průvodce po HTML5: WebStorage 3. 8. 2010
- Webdesignérův průvodce po HTML5: Multithreading s WebWorkers 10. 8. 2010
- Webdesignérův průvodce po HTML5: Databáze v prohlížečích 17. 8. 2010
- Webdesignérův průvodce po HTML5: Shrnutí a rozhrnutí 24. 8. 2010
- HTML5: ukládáme si data k elementům 6. 12. 2010
- Webdesignérův průvodce po HTML5: Táhni a srůstej 5. 1. 2011
- HTML5: První krůčky s FileSystem API 15. 2. 2011
- Mobilizujeme web v HTML5 4. 4. 2011
- Single Page Apps a řešení problémů s historií 1. 6. 2011
- Page Visibility API: Kouká na mě vůbec někdo? 10. 8. 2011
- Práce se soubory v prohlížeči, díl 1 15. 8. 2011
- Práce se soubory v prohlížeči, díl 2 5. 9. 2011
Nálepky:
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í.
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/Salamander, 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.
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í.
Tlačítko „Zpět“ v prohlížeči není změna pohledu, jeho význam je širší a spíš stavový než pohledový. To, co označujete za „Zpět“ u přepínače pohledů má s tlačítkem Zpět (a jeho intuitivním významem) společný většinou pouze tvar šipky doleva… ;)
Poznámka o vracení změn je dobrá, 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…)
Vaše argumentace „… skončili bychom u stavování vysouvacích menu“ je známý argumentační trik zvaný „šikmá plocha“. Ale pokud ji vezmu vážně, tak: Stran toho, které změny „stavovat“ a které ne, by měl mít hlavní slovo návrhář UI, odborník na použitelnost, protože to je právě otázka, která do použitelnosti spadá a extrémismus jakýmkoli směrem („stavovat vše“ vs „nestavovat nic“) je jen na škodu. Takže opět: Použít hlavu!
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.
Shodneme se téměř úplně. Tím upozorněním na argumentační trik jsem Vám nechtěl podsouvat nějaké zlé úmysly, jen upozornit na častou chybu v uvažování, kdy se naznačí řetězec důsledků a příčin, vedoucí ke katastrofě („dnes jim povolíme vlastnit majetek, zítra ho budou chtít bránit majetek, pozítří si koupí pistole a pak propukne anarchie…“) – a říká se tomu „argumentační triky“; terminus technicus. Vy jste naznačil takovou šikmou plochu, vedoucí k bezhlavému stavování čehokoli kdekoli, jenže k tomu nemusí nezbytně dojít. A zase jsme u otázky použitelnosti, takže shoda.
U toho webu to trošku rozšířím: intuitivní vnímání tlačítka Zpět souvisí s vnímáním webu coby cesty: „vrátím se na místo, kde jsem už byl“, „vrátím se tam, odkud jsem vyšel“, „jsem ve slepé uličce, musím couvnout“. Smysl je cosi mezi „kliknul jsem na špatný odkaz, chci vrátit akci“ a „vraťme se k předchozímu stavu“. Přepínání pohledů je něco jiného – tam jsou stále stejná data, jen jinak prezentovaná. Pro uživatele je často „Zpět“ ekvivalentem „zavřít tento web a vrátit se na výchozí místo“. Analogie se těžko hledá, ale „přepnutí pohledu“ to není, v něm chybí ten moment „návratu zpět“.
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.
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.
O tom není sporu. I proto jsem to dal do závorky – ano, jejich očekávání neodpovídá realitě, a v tomto případě ani není na místě realitu měnit. byla to jen poznámka k občasným povzdechům některých uživatelů: „Omylem jsem změnil… a když jsem dal zpět, tak to nefungovalo!“ – „Jak nefungovalo?“ – „No ty změny tam zůstaly!“
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.
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.
nějak postrádám nějakou strukturu nebo myšlenku
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.
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ů.Zkoušel jsem to včera ve FF4, hlásilo „Security error“, ale je možné, že tam byl jiný problém. Ještě ověřím.