Hlavní navigace

Seaside (3)

14. 3. 2005
Doba čtení: 6 minut

Sdílet

Dnes se budeme věnovat generování HTML kódu pro jednotlivé komponenty, což je oblast, ve které se Seaside hodně liší od nejnasazovanějších webových frameworků, protože k vytváření stránek nepoužívá žádné šablony, ale pouze čistý Smalltalk.

Komponenty tvoří základní prvky aplikací napsaných pomocí Seaside. Slouží k intuitivní prezentaci modelu pomocí renderování do HTML kódu a ke kontrole její aktuálnosti.

Jsou to instance podtříd třídy WAComponent (prefix WA u tříd Seaside je zkratka pro Web Aubergine). Samotná WAComponent není abstraktní třída (Smalltalk tento pojem de facto ani nezná), což znamená, že ji lze přímo využívat v aplikacích jako komponentu s prázdným obsahem. Její instance se vytvářejí běžným konstruktorem new

WAComponent new

nicméně nelze je vytvářet samostatně bez toho, aby vznikly v kontextu skutečné aplikace, která jim umožní získat session. Samotný Workspace vám v tomto případě nepomůže.

Pro naše pokusy si zcela běžným způsobem vytvoříme vlastní třídu komponenty. Zatím u ní nebudeme potřebovat žádné instanční ani třídní proměnné.

WAComponent subclass: #MyComponent
    instanceVariableNames: ''
    classVariableNames: ''
    poolDictionaries: ''
    category: 'MyWebApplication'

Z běžných komponent se vyčleňují tzv. kořenové komponenty. Ty se od ostatních liší tím, že přetěžují třídní metodu canBeRoot vracející booleovskou hodnotu. Tyto speciální komponenty pak mohou sloužit jako základní kameny jednotlivých aplikací, přičemž všechny další komponenty jsou potomky této komponenty (ve smyslu hierarchie uspořádání komponent na stránce, nikoliv ve smyslu dědičnosti tříd).

canBeRoot
    ^ true

Pak už stačí jen zaregistrovat naši novou aplikaci. To lze udělat dvěma způsoby.

V první řadě můžeme využít konfigurační aplikaci, která se nachází na adrese /seaside/config. Zde do kolonky add entry point – Path zadáme jméno nové aplikace (např. test) a stiskneme tlačítko Add. Tím ji vytvoříme a přepneme se do její konfigurace, kde v položce Root Component vybereme třídu MyComponent. Do seznamu přípustných tříd obdržíme na výběr právě jen ty komponenty, které na zprávu canBeRoot odpovídají kladně. Po uložení konfigurace se můžeme na naši novou aplikaci podívat na adrese/seaside/tes­t.

Druhou možností je zaregistrovat ji programově, což zajistí velice jednoduché zaslání zprávy.

MyComponent registerAsApplication: 'test'

Tato konstrukce se velmi často vkládá do inicializačního kódu třídy (třídní metoda initialize) kořenové komponenty, aby aplikace byla po nahrání zdrojových kódů okamžitě přístupná.

Nyní již můžeme přistoupit k samotnému zobrazování obsahu komponenty. K tomu slouží metoda renderContentOn:, která jako svůj jediný parametr dodává objekt třídy WAHtmlRenderer. Každá komponenta se samozřejmě stará pouze o vykreslování sebe samotné. Vykreslování skeletonu stránky zařizuje Seaside.

Generování HTML prvků se provádí pomocí zasílání zpráv rendereru. Tento způsob zcela nahrazuje systém šablon. Typický pozdrav světu by pak vypadal například takto:

renderContentOn: html
    html text: 'Hello World'.

Renderer nabízí celou řadu metod pro generování HTML. Pár příkladů:

metoda element
heading: h1
heading:level: h1-hN
paragraph: p
table: table
tableData: td
div: div
space  

Jako parametry se přijímají buď přímo řetězce, nebo smalltalkovské bloky, které mohou obsahovat další generování HTML.

html heading: 'Tabulka'.
html table: [
    html tableRow: [
        html tableData: 'label 1'.
        html tableData: [ html bold: 'data 1' ].
    html tableRowWithLabel: 'label 2' column: 'data 2' ] ].

Renderer pak vygeneruje následující HTML kód:

<h1>Tabulka</h1>
<table>
  <tbody>
    <tr>
      <td>label 1</td> <td><b>data 1</b></td>

    </tr>
    <tr>
      <td class="label">label 2</td> <td>data 2</td>
    </tr>

  </tbody>
</table>

Pokud nám renderer nenabízí pro generování nějakého elementu metodu (např. pro elementy iframe), můžeme jednoduše použít zprávu tag: přijímající jako parametr řetězec nebo symbol se jménem elementu. Např. tyto řádky generují stejný výstup:

html horizontalRule.
html tag: 'hr'.
html tag: #hr.

Atributy

Často je potřeba HTML elementy doplňovat o různé atributy. K tomuto účelu zapouzdřuje renderer kolekci atributů, která se během psaní kódu postupně naplňuje, aby pak tyto atributy byly aplikovány na příslušný element. Definice atributů tedy předchází definici samotné komponenty. Kupříkladu tělo existující metody rendereru jménem image:altText: pro vložení obrázku do stránky vypadá takto:

image: urlString altText: altString
    self attributeAt: 'alt' put: altString.
    self attributeAt: 'src' put: urlString.
    self tag: #img.

a generuje například kód

<img alt="delete" src="/del.gif">

O reprezentaci kolekce atributů se stará třída WAHtmlAttributes. Ta má jednu pro používání příjemnou vlastnost, že pomocí DNU protokolu (doesNotUnder­stand) simuluje existenci metod pro zápis atributů, a to pomocí metod s nulovým počtem argumentů pro bezhodnotové atributy nebo s jedním argumentem pro atributy s hodnotou. Ve výsledku je pak možno výše uvedenou metodu implementovat i následovně:

image: urlString altText: altString
    self attributes alt: altString; src: urlString.
    self tag: #img.

Ostatně podobně přistupuje i samotný renderer k chybějícím tagům, takže

html iframe.

vygeneruje kód HTML kód

<iframe></iframe>

i když žádná metoda iframe neexistuje. Tento přístup má jednu záludnost spočívající v tom, že smalltalkovský kompilátor kontroluje, zda v systému existuje symbol, který odpovídá použitému selektoru metody. Proto je vhodné jej před samotnou kompilací nejdříve vytvořit.

Pro všechny případy nám ještě zbyla možnost vzkládat do textu přímo HTML kód, což by se ovšem mělo využívat jen v případě, kdy ostatní metody selžou. K tomu nám renderer nabízí jednoduchou metodu html:

html html: '<script language="javascript" type="text/javascript">
initRTE("/rte.css");

</script>'.

I takovýto kód můžeme nahradit z pohledu Seaside čistším voláním

html attributes language: 'javascript'; type: 'text/javascript'.
html script: 'initRTE("/rte.css");'

Je na místě ještě vyzvednout rozdíl mezi metodami text: ahtml:. Metoda text: překódovává výsledný HTML kód.

html text: '> Powered by Smalltalk <'

vygeneruje

&gt; Powered by Smalltalk &lt;

Vlastní renderer

U složitých komponent se může celkem lehce stát, že využívání pouze stávajících možností standardního rendereru není příliš obratné a výsledné konstrukce by bylo dobré zabalit do samostatných metod. V takovém případě máme tři možnosti, jak toho docílit.

První je napsat takové metody přímo jako součást komponenty s tím, že renderer jim bude předán jako parametr. Toto řešení je velmi jednoduché, nicméně porušuje objektovou čistotu výsledného kódu se vším, co to s sebou přináší (menší obecnost, horší orientace v kódu apod.).

Druhou možností je doplnit rozšiřující metody přímo do stávajícímu rendereru. Protože nic, co by mělo obdobu v knihovnách, které by byly sdíleny mezi více samostatnými imagemi, ve Squeaku neexistuje, je toto řešení poměrně neproblematické. U větších Seaside aplikací mívá každá aplikace vlastní image, takže není žádný vážný důvod, aby nepovažovala celý Smalltalk za svou nedílnou součást, se kterou si může dělat, co se jí zamane. Je to její image. S problémy se však můžeme setkat například u přetěžování metod, kdy máme v jedné aplikaci více komponent, které vyžadují u některých zpráv odlišné chování.

Třetí a nejčistší řešení je odvodit si ze stávající třídy rendereru WAHtmlRenderer vlastní třídu, např.

WAHtmlRenderer subclass: #MyRenderer
    instanceVariableNames: ''
    classVariableNames: ''
    poolDictionaries: ''
    category: 'MyWebApplication'

Do ní si pak můžeme směle doplňovat či přetěžovat metody dle libosti. Do třídy komponent, které tento uživatelský renderer budou používat, pak už jen stačí přetížit metodu pro přiřazení třídy rendereru.

rendererClass
^ MyRenderer

Metoda renderContentOn: pak už bude získávat jako parametr instance této třídy.

Fantazii při psaní vlastních rendererů se meze nekladou, takže je lze použít ke všemu možnému od zjednodušení generování kódu až po jeho překódovávání. Metoda v tomto příkladu například slouží pro vykreslení obdélníků na specifikovaných souřadnicích s textem uvnitř.

CS24 tip temata

rectangle: rect with: aBlock

    self attributeAt: #style put:
        (' width: {1}; height: {2}; left: {3}; top: {4};
            background-color: lightgray;
            position: absolute;
            border: 1px solid black;
        ' format: {
            rect width asString, 'px'.
            rect height asString, 'px'.
            rect left asString, 'px'.
            rect top asString, 'px'. }).

    self div: [ self render: aBlock ].

využití v komponentě pak může vypadat například takto:

renderContentOn:  html

    html rectangle: (100@100 extent: 200@50) with: 'Text 1'.
    html rectangle: (200@120 extent: 200@50) with: [ html bold: 'Text 2'].

Tím samozřejmě s generováním statického HTML kódu nejsme zdaleka u konce. V příštích pokračováních si ukážeme, jakým způsobem pracuje Seaside s kaskádovými styly a s Javascriptem. Poté se konečně dostaneme k práci s formuláři a našim aplikacím vdechneme život.

Seriál: Seaside