Trocha historie
V minulých dílech jsme poznali, že ačkoliv byl Cocoon původně framework pro webovou publikaci, postupem času se vyvinul v obecnější framework pro tvorbu webových aplikací. Původní zaměření lze charakterizovat jako „mnoho webových stránek s minimální interakcí“, přičemž striktní oddělení obsahu a formy dovolovalo data snadno prezentovat v různých formátech a na různých platformách. Obecnou webovou aplikaci však charakterizuje velká interaktivita překračující prosté odkazy mezi stránkami (např. zadávaní dat do formulářů). Většina takových aplikací využívá pro ukládání a získávání dat nějaký databázový systém. Dá se říci, že uživatel webové aplikace bude její datový obsah nejen prohlížet, ale i měnit. Obecný framework by tedy měl obsahovat i dosti pokročilé prostředky pro zpracovávání formulářů (validace vstupních dat apod.).
Cocoon obsahuje i jednodušší prostředky pro práci s formuláři: FormValidatorAction, Precept, JXForms (podmnožina W3C XForms) a také je samozřejmě možné použít přímo HTML formuláře. Ani jeden z těchto přístupu však nebyl shledán dostatečně mocným pro požadavky zpracování formulářů dnešních aplikací, tak byl vyvinut blok jménem Woody, který byl nakonec přijat jako standard pro zpracování formulářů v Cocoonu. Nyní se „oficiálně“ nazývá Cocoon Forms (v kódu, konfiguraci i v současné dokumentaci jej ale najdete prozatím většinou pod starým názvem). Proč ale vlastně byly ty výše zmíněné jednodušší přístupy nedostatečné?
Woody – oddělení obsahu a prezentace
Možná jste si někdy uvědomili, že při použití tradičních HTML formulářů mícháme formu a obsah. Například takový prvek „listbox“ dovoluje uživateli vybrat jednu možnost z výčtu. Ale to samé může zařídit i skupina prvků typu „radiobutton“. Zaměříme-li se na obsah či, lépe řečeno, datový model formuláře, měl by existovat prvek „výběr z výčtu“, jehož konkrétní prezentací může být buď listbox, nebo radiobutton. U jednojazyčných aplikací s jedním typem klienta to sice nemá zas až tak velký význam, ale co když budeme potřebovat více typů klientů (PC, mobilní telefon apod.)? Zde si lze již velmi dobře představit, že např. na PC s GUI může lépe vypadat ovládání pomocí radiobuttonů, kdežto na mobilním telefonu by mohl být pro tentýž výběr užitečnější listbox (nebo naopak). A co když k tomu všemu budete chtít mít formuláře vícejazyčné? Woody striktně odděluje definici formuláře, která je zcela nezávislá na prezentaci a je to jakési schéma či datový model, jejž použijeme v šabloně (template), což je již popis konkrétního formuláře. V šabloně případně může už být i návrh na styl prezentace dané widget (například požadavek na zobrazení výběru z výčtu jako „listbox“). Třetí část popisu daného formuláře je instance, která kromě návrhu na prezentaci může obsahovat například aktuální hodnoty polí formuláře apod. Instance vzniká jako výstup transformátoru WoodyTemplate
(vstupem je definice a šablona) a pak se transformuje na konečnou reprezentaci (např. HTML forms) pomocí transformace XSLT. Každá výše uvedená část popisu formuláře má svůj jmenný prostor.
Woody – datové typy, validace, parsery
Jedním z hlavních požadavků při vývoji Woody byla možnost poměrně dokonalé validace zadaných dat. S tím je spojena nutnost použít silnějších datových typů než jen string
(např. typ datum, e-mailová adresa) a také konverze zadaných dat z textové formy na tyto typy (a také naopak).
Woody implicitně „zná“ tyto základní datové typy: string
, long
, decimal
, integer
, date
, boolean
a enum
. Protože v Cocoonu je všechno naprogramováno a sestaveno pomocí komponent, není problém v případě potřeby založit další datové typy například šité na míru aplikaci. Datový typ může mít přiřazeny i tzv. konvertory a validátory. Jak názvy napovídají, slouží ke konverzím mezi vlastním datovým typem a textovou reprezentací a k validaci vstupních dat.
Definice formuláře
Definice formuláře určuje (bez ohledu na jeho pozdější prezentaci) model formuláře pomocí tzv. widgets:
<wd:form>
: hlavní „widget“ formuláře,<wd:field>
: základní prvek pro vstup hodnoty pomocí textového pole nebo výběru hodnoty z výčtu,<wd:booleanfield>
: booleovský vstup,<wd:multivaluefield>
: vícenásobný výběr z výčtu,<wd:repeater>
: kolekce „widgets“ používaná pro vytváření tabulek, podřízených formulářů apod.,<wd:output>
: podobné jakofield
, obsah ale nelze editovat,<wd:action>
: „akční“ widget (obvykle zobrazen jako tlačítko) spouští událost na serveru, neukončuje však zpracování formuláře ani neprovádí validaci dat,<wd:submit>
: „submit“ formuláře: ukončuje zpracování formuláře při splnění validačních pravidel,<wd:upload>
: upload souboru na server.
Nejčastěji používaný widget <wd:field>
se konfiguruje takto:
<wd:field id="..." required="true|false"> <wd:label>...</wd:label> <wd:hint>...</wd:hint> <wd:help>...</wd:help> <wd:datatype base="..."> [...] </wd:datatype> <wd:selection-list .../> <wd:on-value-changed> ... </wd:on-value-changed> </wd:field>
Elementy <wd:label>
, <wd:hint>
a <wd:help>
mohou být v případě vícejazyčné aplikace internacionalizovány pomocí transformátoru i18n. Element <wd:on-value-changed>
umožnuje specifikovat kód pro obsluhu události, která vznikne změnou obsahu vstupního pole. Událost se zpracovává na serverové straně. Kód obsluhy události může být v JavaScriptu nebo v Javě. To umožňuje například zapsat pomocí pár řádek kódu změnu obsahu nějakého listboxu na základě změny výběru v jiném listboxu. Můžete si to představit například na dvou listboxech, kdy v prvním vybereme kraj a ve druhém okres. Obsah druhého listboxu musí záviset na vybrané hodnotě prvního listboxu.
Použití elementu <wd:field>
je nejlépe vidět na konkrétním příkladu. Řekněme, že je to definice jednoduchého formuláře pro registraci se vstupními poli pro jméno, heslo, e-mailovou adresu a nepovinný věk (soubor registrace.xml
):
<wd:form
xmlns:wd="http://apache.org/cocoon/woody/definition/1.0">
<wd:widgets>
<wd:field id="name" required="true">
<wd:label>Jméno:</wd:label>
<wd:datatype base="string">
<wd:validation>
<wd:length min="2"/>
</wd:validation>
</wd:datatype>
</wd:field>
<wd:field id="password" required="true">
<wd:label>Heslo:</wd:label>
<wd:datatype base="string">
<wd:validation>
<wd:length min="5" max="20"/>
</wd:validation>
</wd:datatype>
</wd:field>
<wd:field id="email" required="true">
<wd:label>E-mailová adresa:</wd:label>
<wd:datatype base="string">
<wd:validation>
<wd:email/>
</wd:validation>
</wd:datatype>
</wd:field>
<wd:field id="age">
<wd:label>Váš věk:</wd:label>
<wd:datatype base="long">
<wd:validation>
<wd:range min="0" max="150"/>
</wd:validation>
</wd:datatype>
</wd:field>
</wd:widgets>
</wd:form>
Jméno, heslo a e-mailová adresa jsou typu string, věk je typu long. Jméno a heslo budou validovány na délku řetězce, věk na povolené hodnoty. Všimněte si, že Woody má vestavěný validátor formátu e-mailové adresy. Vedle něj můžeme najít i validátor pro formát čísla platební karty (mod10) nebo pro validaci řetězce pomocí regulárního výrazu. Pomocí validátoru assert
lze ověřovat pravidla typu „obsah pole password
je shodný s obsahem pole confirmPassword
“.
Typy „widgets“, datové typy, konvertory a validátory jsou deklarovány v konfiguračním souboru cocoon.xconf
, což umožňuje přidání uživatelského datového typu, jeho validátoru a/nebo konvertoru. Pro místní aplikace by se například hodil konvertor a validátor rodného čísla (nejlépe pro již definovaný datový typ long
). Je však potřeba podotknout, že konfigurace v souboru cocoon.xconf
je (na rozdíl od mapy) globální pro všechny aplikace dané instance Cocoonu.
Definice formuláře má přiřazen jmenný prostor http://apache.org/cocoon/woody/definition/1.0
.
Šablona formuláře
šablona pro výše uvedený formulář by mohla vypadat takto (soubor registrace-templ.xml
):
<html
xmlns:wt="http://apache.org/cocoon/woody/template/1.0"
xmlns:wi="http://apache.org/cocoon/woody/instance/1.0">
<head>
<title>Registrační formulář</title>
</head>
<body>
<h1>Registration</h1>
<wt:form-template
action="#{$continuation/id}.continue"
method="POST">
<wt:widget-label id="name"/>
<wt:widget id="name"/>
<br/>
<wt:widget-label id="email"/>
<wt:widget id="email"/>
<br/>
<wt:widget-label id="age"/>
<wt:widget id="age"/>
<br/>
<wt:widget-label id="password"/>
<wt:widget id="password">
<wi:styling type="password"/>
</wt:widget>
<br/>
<input type="submit"/>
</wt:form-template>
</body>
</html>
Prvek <wt:widget-label>
zajistí vložení popisky pole na dané místo, prvek <wt:widget>
pak vložení samotného vstupního pole. Obojí se vkládá v XML reprezentaci instance formuláře. Šablona formuláře má přiřazen jmenný prostor http://apache.org/cocoon/woody/template/1.0
. Všimněte si ale, že ve výše uvedeném souboru používáme i jmenný prostor instance formuláře ( http://apache.org/cocoon/woody/instance/1.0
) pro navržení stylu prezentace vstupního pole na zadání hesla tak, aby nebylo při zadávání na obrazovce čitelné.
Instance formuláře se pak převádí na konečnou reprezentaci (v našem případě HTML) pomocí transformace XSLT. Pokud jde o vícejazyčnou aplikaci, zařazuje se před ni ještě transformace I18N.
Flowscript
Framework Woody je těsně spjat s flowscriptem (i když na něm není závislý). Má zde vlastní velmi jednoduché API, z čehož nejdůležitější je (vedle konstruktoru samotného formuláře) metoda showForm
, která (opakovaně) zobrazuje formulář, dokud nejsou splněna všechna validační pravidla. Metodou getModel
pak lze získat zadané hodnoty z formuláře pro další zpracování.
V našem případě by flowscript pro registraci mohl vypadat takto (soubor registrace.js
):
(Pozn. editora: mezera v URL samozřejmě být nemá, byla přidána v lámacích důvodů.)
cocoon.load("resource://org/apache/cocoon/ woody/flow/javascript/woody2.js");
function registrace() {
var myForm = new Form("forms/registrace.xml");
myForm.showForm("registracni-formular");
var model = myForm.getModel();
var myData = { "username" : model.name }
cocoon.sendPage("registrace-ok", myData);
}
Mapa
Na mapě aplikace není nic, co by nebylo popsáno v předchozích dílech seriálu. Je potřeba zaregistrovat flowscript:
<map:flow language="javascript"> <map:script src="flow/registrace.js"/> </map:flow>
Dále je nutné definovat zpracování požadavků podle použitých vzorů v rouře:
<map:match pattern="registrace">
<map:call function="registrace"/>
</map:match>
<map:match pattern="*.continue">
<map:call continuation="{1}"/>
</map:match>
<map:match pattern="registracni-formular">
<map:generate src="forms/registrace_templ.xml"/>
<map:transform type="woody"/>
<map:transform type="i18n">
<map:parameter name="locale" value="en-US"/>
</map:transform>
<map:transform src="resources/woody-samples-styling.xsl"/>
<map:serialize/>
</map:match>
<map:match pattern="registrace-ok">
<map:generate type="jx" src="forms/registrace-ok.jx"/>
<map:serialize/>
</map:match>
Funkce hlavní roury (a tedy princip zpracování formuláře ve frameworku Woody) by se zjednodušeně dala znázornit takto:
Souborový generátor čte šablonu formuláře a na jejím základě generuje události SAX, transformátor WoodyTemplate
dostává data z definice formuláře a transformuje s jejich pomocí šablonu na instanci formuláře. Po nahrazení jazykově závislých řetězců transformátorem i18n se pomocí XSL stylu formátují „widgets“ instance na HTML, které je nakonec serializováno a odesláno prohlížeči. Upozorňuji, že obrázek je poněkud zjednodušen (zanedbává funkci kontroléru). Úplné schéma činnosti lze najít v kapitole Introduction dokumentace k frameworku Woody .
Soubor registrace-ok.jx
by mohl vypadat například takto:
<html>
<head>
<title>Registrace OK</title>
</head>
<body>
Registrace uživatele ${username}
byla úspěšná!
</body>
</html>
Závěr
Tento krátký článek nemůže podrobně popsat všechny možnosti frameworku Woody. Na závěr bych chtěl rekapitulovat nejzajímavější vlastnosti. Woody:
- odděluje obsah a prezentaci při tvorbě formulářů,
- používá silné datové typy pro formulářová data,
- neobsahuje kontrolér a tudíž zde opět vhodně odděluje odlišné oblasti zájmů,
- je zde možnost složitější validace formuláře uživatelskou funkcí v JavaScriptu,
- pro tvorbu formulářů není potřeba programovat v Javě,
- formulářová data lze provázat s objekty Java Beans nebo s dokumentem XML,
- lze jej lehce rozšiřovat (nové datové typy, konvertory, validátory, …) a nakonec
- výborně zapadá do Cocoonu a jeho koncepce.
Jedinou, dle mého názoru zanedbatelnou, nevýhodou může být, že Woody je o něco komplikovanější než jednoduché HTML formuláře, a také to, že pro popis formuláře musíme vytvořit minimálně dva soubory: definici a šablonu. Jsem však přesvědčen, že u každé jen trochu komplikovanější formulářové aplikace se tento přístup rychle obrací ve výhodu.