Cocoon v příkladech: Skinovatelné fotoalbum

17. 12. 2003
Doba čtení: 9 minut

Sdílet

V tomto dílu se podíváme, co jsou to XSP, logicsheety, jak používat interní roury a komponenty typu akce. Také si povíme, co jsou to pseudoprotokoly. Naši aplikaci zkusíme upravit, aby byla skinovatelná, či, přesněji řečeno, abychom mohli dynamicky měnit styl.

Refaktoring aplikace

Internacionalizace aplikace webového fotoalba v minulém dílu byla založena na parametrech HTTP požadavku. Konkrétní jazyk určoval parametr lang, tedy například URL http://localhost:8888/album2/seznam.html?lang=de vracelo seznam fotek s německými popisy. Jenže nyní bychom potřebovali ještě určovat „skin“ aplikace. Abychom neměli mapu příliš komplikovanou a nečitelnou, budeme určovat požadovaný jazyk adresářem v URL a skin parametrem. URL pro anglickou verzi a černý skin bude vypadat takto: http://localhost:8888/album3/en/album.html?skin=black. To si vyžádá změny v mapě i přejmenování adresářů.

Skinovatelná aplikace

Skinovatelnost aplikace můžeme zajistit v různém stupni. V našem případě ji založíme na kaskádových stylech CSS a pro jednoduchost budeme měnit jen barvy pozadí a textu. Styl v souboru „album.css“ v adresáři „styl“ bude základní a použije se vždy. Kromě toho tam přibudou další styly, které přepíší barvu textu a pozadí podle nastaveného skinu. Jejich názvy budou shodné s označením skinů. Například skinu „yellow“ bude odpovídat tento soubor yellow.css

body {color: #000; background-color: #ffc;}
table {border-color: #a66;}
td {border-color: #a66;}
h1, h2 {color: #a33;}

Například album (seznam) fotek může vypadat při zvolení černého skinu takto:

Album

Přidali jsme přepínání jazyků a skinů (v horní části). Kliknutím na náhled se dostaneme na zobrazení jednotlivé fotografie. Oproti minulým dílům je i tato funkcionalita zlepšena, takže nyní vypadá (např. ve žlutém skinu) takto:

Fotka

Kliknutím na fotografii se vracíme zpět do seznamu, kliknutím na náhled pokračujeme v prohlížení další fotografie v pořadí.

Název skinu z parametru HTTP požadavku musíme dostat nějakým způsobem do XML dat v rouře, aby transformátor XSLT při transformaci na XHTML podle něj mohl přidat odkaz na příslušný soubor CSS. Jak je v Cocoonu zvykem, lze to provést několika způsoby. Například transformátor XSLT umí použít parametry HTTP požadavku přímo. Je potřeba jej nadeklarovat s parametrem use-request-parameters nastaveným na true a pak v XSL stylesheetu deklarovat parametr se stejným jménem, jako má parametr HTTP požadavku. V našem případě by to tedy bylo <xsl:param name="skin"/>. Toto řešení ale degraduje výkon XSLT transformace, která je z tohoto hlediska v rouře nejslabším místem již nyní. Proto jsem nakonec zvolil řešení, kdy se hodnota parametru skin HTTP požadavku dostane do roury už v generátoru jako XML element. Použijeme pro to XML Server Pages (XSP).

XSP – XML Server Pages

XSP byly inspirovány JSP (Java Server Pages). Přinášejí možnost smíchat XML data s kódem v Javě, Javascriptu nebo Pythonu. Je ovšem pravdou, že mícháním kódu a XML dat kombinujeme logiku aplikace s jejím datovým obsahem, což rozhodně není v souladu s přístupem SoC (Separation of Concerns). JSP se potýkají se stejným problémem. Snaží se jej řešit pomocí knihoven značek (taglibs).

Obdobou knihoven značek jsou v Cocoonu logicsheets. Jsou to vlastně XSL dokumenty, které transformují značky na programový kód. Oproti XSL transformacím v rouře je však jejich výkonový dopad zřetelný jen při prvním voláním XSP, neboť XSP se překládají na třídu v Javě. Výsledek je podobný, jako bychom napsali uživatelský generátor v Javě a pak jej použili v rouře. Přeložené XSP se konec konců dají uložit i trvale a později lze soubor „class“ jako uživatelský generátor použít. Logicsheets a jejich jmenné prostory musejí být nadeklarovány v souboru „cocoon.xconf“.

Cocoon obsahuje několik standardních logicsheetů, například pro práci s uživatelskou relací (session), cookies, validace formulářů atd. Jeden z nich je také určen k předávaní parametrů HTTP požadavku do XML dat. Pro generování seznamu fotek použijeme soubor „album.xsp“, který vypadá např. pro anglickou verzi takto:

<?xml version="1.0" encoding="UTF-8"?>

<xsp:page
  xmlns:xsp="http://apache.org/xsp"
  xmlns:ci="http://apache.org/cocoon/include/1.0"
  xmlns:req="http://apache.org/xsp/request/2.0"
>
 <album>
  <skin><req:get-parameter name="skin"/></skin>
  <pict><req:get-parameter name="pict"/></pict>
  <nazev>The Hell Mountains</nazev>
  <datum>28-30 September 2001</datum>
  <fotky>
   <ci:include src="cocoon:/en/hg01.xml"/>
   <ci:include src="cocoon:/en/hg02.xml"/>
   <ci:include src="cocoon:/en/hg03.xml"/>
   <ci:include src="cocoon:/en/hg04.xml"/>
   <ci:include src="cocoon:/en/hg05.xml"/>
   <ci:include src="cocoon:/en/hg06.xml"/>
  </fotky>
 </album>
</xsp:page> 

Všimněte si značky req:get-parameter, která do XML dat vkládá hodnotu parametru skin. Soubor používáme jako generátor roury, což v mapě zapíšeme takto:

...
<map:match pattern="*/album.html">
 <map:select type="resource-exists">
  <map:when
     test="album3/texty/{1}/album.xsp">
   <map:generate type="serverpages"
      src="texty/{1}/album.xsp"/>
  </map:when>
  <map:otherwise>
   <map:redirect-to
      uri=".."/>
  </map:otherwise>
 </map:select>
 <map:transform type="cinclude"/>
 <map:transform src="xsl/album.xsl"/>
 <map:serialize type="xhtml"/>
</map:match>
... 

V mapě si ještě všimněte použití selektoru, kterým testujeme, zda soubor „album.xsp“ existuje pro daný jazyk – jinými slovy zda existuje soubor „album.xsp“ v adresáři s názvem stejným jako je zkratka jazyka. Pokud ne (tj. pokud požadujeme neznámý jazyk), je HTTP požadavek přesměrován na „prázdné“ URL aplikace, které je směrováno na anglickou verzi aplikace a implicitní černý skin (o tom se ještě zmíním dále v souvislosti s akcemi).

V souboru „album.xsp“ si všimněte, že byly změněny zdroje pro transformátor CInclude. Používá se zde pseudoprotokol cocoon:/..., který odkazuje do aktuální mapy. Pro soubory s popisy jednotlivých fotek byla v mapě zřízena interní roura (není přístupná klientovi):

...
<map:pipeline internal-only="true">

 <map:match pattern="*/hg*.xml">
  <map:generate type="file"
     src="texty/{1}/hg{2}.xml"/>
  <map:serialize type="xml"/>
 </map:match>

</map:pipeline>
... 

Soubor „album.xsp“ využijeme i při zobrazení jednotlivých fotografií. Název se předává pomocí parametru pict. V mapě je prohlížení jedné fotografie konfigurováno tak­to:

<map:match pattern="*/foto.html">
 <map:generate type="serverpages"
    src="texty/{1}/album.xsp"/>
 <map:transform type="cinclude"/>
 <map:transform src="xsl/fotka.xsl"/>
 <map:serialize type="xhtml"/>
</map:match> 

Oproti generování alba (seznamu) je použit jen jiný XSL dokument pro transformaci. Je to opět příklad znovupoužití datového zdroje pro různé výsledky.

Rozsah použití XSP je obrovský. Jednou z hlavních oblastí je přístup k SQL databázím, pro které existuje logicsheet ESQL. Slouží k dotazování SQL databází a serializaci výsledků do XML. Zvládá statické i dynamické dotazy, updaty i práci s uloženými procedurami.

Pokud se rozhodnete použít XSP, zvláště bez logicsheetů, vždy zvažte, zda jejich přínos vyváží nevýhody kombinace obsahu a logiky aplikace.

Pseudoprotokoly

Minule jsme již poznali, že pokud v pseudoprotokolu cocoon použijeme dvě lomítka ( cocoon://...), odkazujeme do jiné aplikace. Přesněji řečeno, odkazujeme do hlavní (root) mapy Cocoonu. Pseudoprotokoly se používají v Cocoonu i pro další aktivity. Jedním z nejdůležitějších je pseudoprotokol xmldb:, který se používá pro přístup ke XML databázím (např. Apache Xindice). Protokol file: umí přistupovat k lokálnímu souborovému systému (lze užít i absolutní cestu), protokoly jar: a zip: slouží k přístupům do archivů, protokol slide: k přístupu do Jakarta Slide Repository (WebDAV).

Akce

V minulém dílu jsme neměli dokonale vyřešený přenos parametrů, pokud uživatel zvolil „prázdné“ aplikační URL. Přesněji řečeno, nebyl definován implicitní parametr pro jazyk. V této variantě aplikace jsou jazyky řešeny jinak, nicméně by se hodil implicitní parametr pro skin. K tomu se hodí nejlépe komponenty typu akce (action). Jejich hlavním úkolem je manipulace s interními parametry mapy. Pokud mapu považujeme za kontrolér v modelu MVC, tyto parametry můžeme považovat za proměnné. Akce mohou být velmi jednoduché (např. propagace parametrů HTTP požadavku do interních parametrů mapy) i poměrně komplikované (dotazy do SQL databáze). Z hlediska programování komponent Cocoonu jsou však tyto komponenty jednoduché. Často mají jen pár řádků kódu v Javě. Potřebujeme-li funkcionality Cocoonu rozšířit, je vhodné považovat komponentu typu akce za prvního kandidáta. Napsat akci je opravdu mnohem jednodušší než efektivní transformátor.

Definice implicitního skinu pro „prázdné“ URL bude v naší mapě vypadat takto:

<map:match pattern="">
 <map:act type="request">
  <map:parameter name="parameters"
     value="true"/>
  <map:parameter name="default.skin"
     value="black"/>
  <map:redirect-to
     uri="en/album.html?skin={skin}"/>
 </map:act>
</map:match> 

Pokud bude klient žádat URL http://localhos­t:8888/album3/, přesměruje se na: http://localhost:8888/album3/album.html?skin=black. Pokud však klient parametr skin zadá ( http://local­host:8888/album3/?skin=y­ellow), použije se zadaná hodnota.

Z historických důvodů lze akce používat i místo selektorů. Pokud komponenta vrací objekt typu Map (tj. kontejner Javy), probíhá zpracování normálně, pokud vrací null, část roury definovaná akcí se přeskočí. Selektory ovšem tuto činnost zvládají lépe a mapa je i čitelnější.

Archiv celé aplikace „album3“ najdete zabalený v album3.zip. Ke své práci potřebuje i původní aplikaci „album“, neboť si z ní (stejně jako aplikace „album2“) bere obrázky.

Je ta aplikace skutečně skinovatelná?

Nevím, zda všichni budou souhlasit s tím, že prostá změna barvy textu a pozadí aplikace se dá označit jako skin. Nicméně jde jen o příklad a dokonalejšími kaskádovými styly toho lze udělat opravdu hodně. Nejuniverzálnější řešení by bylo vyhradit každému skinu adresář, kde by mohly být i kaskádové styly, ikony pro daný skin, ale mohly by zde být i XSL soubory náležející danému skinu. Takové řešení je i velmi flexibilní, neboť roura by se dala sestavit i tak, že pokud v adresáři daného skinu nebude žádný soubor XSL, vezme se implicitní. A konec konců, lze v adresáři skinu použít jen parametry XSL transformace nebo XSL fragmenty a výsledný XSL soubor lze generovat interní rourou. Možností je nepřeberně.

Vlastní aplikace je poměrně dobře lokalizovatelná do různých jazyků (až na jednu drobnou chybičku – najdete ji?). Část problematiky internacionalizace jsme ale obešli tím, že používáme pro odkazy obrázky. Úpravu aplikace, abychom měli více textové navigace (např. „Zpět“, „Na seznam“, „Další“ apod.), byste bezpochyby již zvládli sami.

bitcoin_skoleni

Je ta aplikace skutečně aplikace?

Někdo může namítnout, že to fotoalbum, kterým jsme se zatím zabývali, snad ani webová aplikace není. Opravdu by se (téměř) vše dalo zařídit statickými stránkami. Nemáme zde ani formuláře – odkazy jsou jedinými interaktivními prvky. Je to sice pravda, ale Cocoon býval framework pro publikování na webu, takže se jeho vlastnosti ukazují lépe na takových příkladech. Ale i tak je, myslím, fotoalbum zajímavé už jen proto, že se obsah (kromě obrázků) skládá z několikařádkových XML souborů, dvou asi padesátiřádkových XSL souborů a tří kratičkých CSS souborů. No a pak je tu naše mapa: konfigurace komponent, MVC kontrolér a největší (přes 100 řádků :-) soubor této aplikace.

Co příště?

Snad se vám může zdát, že i když je XML roura dobrá myšlenka, mapa jako MVC kontrolér může být pro složitější případy poněkud komplikovaná a nepřehledná. A protože s tím souhlasím nejen já, ale i tvůrci Cocoonu, podíváme se příště na flowscript. Je to možnost, jak napsat tok stránek v Javascriptu. Webový wizard? S flowscriptem je to hračka. Návrhový vzor MVC tak dostává další kvalitu, kterou je možné označit jako MVC+. Bez flowscriptu se neobejdou ani některé pokročilé možnosti vytváření webových formulářů.