5.2
Jednoduché úlohy
Při sestavování webové stránky si můžeme pomoci tím, že budeme mít jednotlivé prvky připravené jako objekty. V této kapitole si ukážeme velmi často používané prvky na webových stránkách, kterými je drobečková navigace a tabulka. Vytvořit si však můžeme i další prvky webu, jako jsou nadpis, obrázek, seznamy, formuláře a mnohé další.
5.2.1
Drobečková navigace
Drobečková navigace znázorňuje cestu strukturou webu k cíli. Díky ní tak jednak víme, kde se na webu nacházíme, a také se můžeme vrátit na libovolnou stránku zpět v cestě navigace. Cílem je tedy na webové stránce mít něco takového:
+
13. Snímek obrazovky s drobečkovou navigací
Obr. 13. Snímek obrazovky s drobečkovou navigací
V programu bychom ji chtěli vytvářet, upravovat a vypisovat velmi jednoduše:
$bc = new Breadcrumb(); $bc->addItem(new Item('Produkty', 'index.php')); $bc->addItem(new Item('Fotoaparáty', 'index.php')); $bc->addItem(new Item('Digitální zrcadlovky', 'index.php')); $bc->addItem(new Item('Fotoaparát XYZ')); echo $bc;
Nejprve si tedy vytvoříme objekt z třídy Breadcrumb a poté jen voláme metodu addItem() s parametry zobrazovaného textu drobku a jeho cíle odkazu vygenerované jako instanci třídy Item. Postupně si tak sestavujeme onu cestu odkazů, které říkáme drobečková navigace. Nakonec referenci na objekt bc vypíšeme příkazem echo. Abychom zobrazili i onen druhý řádek z výše uvedeného obrázku, zavoláme užitečnou metodu goBack() a opět objekt vypíšeme.
$bc->goBack(); echo $bc;
Tohoto lze docílit jednoduše. Stačit nám k tomu budou dvě třídy. Jedna základní Breadcrumb a druhá Item. Instance z třídy Item je položkou drobečkového seznamu. Objekt drobečkové navigace je složen z jednotlivých položek. Využíváme zde skládání objektů. U každé jednotlivé položky vedeme její popisek a odkaz v podobě URL. Při vytváření objektu bude možné zadávat popisek i URL. Dále očekáváme setter URL, který řádně dovolený tvar adresy ošetří. Nakonec použijeme magickou metodu __toString(), která objekt převede korektně na řetězec, tedy HTML odkaz, je-li vyplněna URL, nebo jen text, není-li vyplněna URL. Třída Breadcrumb poté bude mít za úkol spravovat jednotlivé položky seznamu a nabízet vyčištění seznamu, přidání prvku do seznamu, vrátit se o krok zpět a samozřejmě také vykreslení celého seznamu v HTML jazyce. Jednotlivé třídy si pak můžeme dále rozšiřovat o další funkce. Základní výše popsanou logiku znázorňuje následující digram tříd.
+
14. Diagram tříd drobečkové navigace
Obr. 14. Diagram tříd drobečkové navigace
Protože se jedná o poměrně jednoduchý a krátký příklad, můžeme si zde uvést celý zdrojový kód tříd ItemBreadrumb:
class Item { private $label; private $url; public function __construct($label, $url = null) { $this->label = $label; $this->url = $this->setUrl($url); } public function setUrl($url) { if ($url == null) { $this->url = null; } else { $this->url = preg_replace('~^-+|-+$~', '', strtolower(preg_replace('~[^a-zA-Z0-9_/:#?=\.]+~', '-', $url))); } } public function __toString() { if (!empty($this->url)) { return '<a href="' . $this->url . '">' . $this->label . '</a>'; } else { return $this->label; } } } class Breadcrumb { private $items; public function __construct() { $this->clear(); } public function clear() { $this->items = array(); } public function addItem($label, $url = null) { array_push($this->items, new Item($label, $url)); } public function goBack() { array_pop($this->items); $lastItem = end($this->items); $lastItem->setUrl(null); } public function __toString() { return '<div>' . implode(' / ', $this->items) . '</div>'; } }
V kompletním příkladu máte uveden i jmenný prostor a načítání tříd.
5.2.2
Tabulka
Vytváření tabulek je již komplikovanější než předchozí úloha, protože samotná tabulka je komplexnější prvek webové stránky. Chceme-li obdržet takovýto výsledek:
+
15. Snímek obrazovky HTML tabulky
Obr. 15. Snímek obrazovky HTML tabulky
V programu bychom chtěli takovouto tabulku vytvářet, upravovat a vypisovat jednoduše takto:
$table = new table\Table('Produkty'); $table->addColumn(new table\Column('Název')); $table->addColumn(new table\Column('Barva', 'Color')); $table->addColumn(new table\Column('Cena', 'Price')); $table->addColumn(new table\Column('Email', 'Mail')); $table->addRowData(array('A', '#0F0', 123.50, 'a@b.cz')); $table->addRowData(array('B', '#00F', 45456, 'c@d.cz')); echo $table;
Nejprve si vytvoříme objekt z třídy Table. Přidáme si všechny čtyři sloupce, u kterých si určíme jejich titulek a typ. Vložíme dva řádky s demonstrativními daty. Nakonec připravenou tabulku vypíšeme. Navenek potřebujeme jen třídu Table. Zbytek je od klienta odstíněn. Tabulka má sloupce, řádky a jednotlivé datové položky, které mohou být dle sloupce různého typu (text, barva, mail…). Struktura takovéhoto příkladu pak může vypadat například takto:
+
16. Diagram tříd HTML tabulky
Obr. 16. Diagram tříd HTML tabulky
Základní princip je podobný příkladu s drobečkovou navigací. Hlavní třída Table se stará o vytvoření tabulky, přidávání sloupců a dat řádků a samotné generování kompletní tabulky, které se skládá ze tří částí (titulek, hlavička, tělo). Metoda __toString() poté zajistí vygenerování kompletního HTML kódu tabulky do řetězce.
class Table { private $name; private $columns; private $rows; public function __construct($name) { $this->name = $name; $this->columns = array(); $this->rows = array(); } public function addColumn(Column $column) { $this->columns[] = $column; return $this; } public function addRowData(Array $rowData) { $this->rows[] = new Row($rowData, $this->columns); return $this; } private function renderCaption() { return '<caption>Tabulka: ' . $this->name . '</caption>'; } private function renderHead() { return '<thead>' . implode('', $this->columns) . '<thead>'; } private function renderBody() { return '<tbody>' . implode('', $this->rows) . '<tbody>'; } public function __toString() { return '<table>' . $this->renderCaption() . $this->renderHead() . $this->renderBody() . '</table>'; } }
Třída Column se stará o sloupec tabulky. Každý sloupec má svůj titulek a typ. Kromě standardní práce s atributy je zde potřeba ověřit, zdali se do konstruktoru při vytváření sloupce zadává povolený existující typ. Zde je to vyřešeno velmi jednoduše statickým polem povolených typů, kterým se ověřuje zadaná hodnota.
class Column { private static $allowedTypes = array('Text', 'Numeric', 'Price', 'Mail', 'Color'); private $title; private $type; public function __construct($title, $type = null) { $this->title = $title; $this->setType($type); } public function getType() { return $this->type; } public function setType($type) { if (in_array($type, self::$allowedTypes)) { $this->type = $type; } else { $this->type = self::$allowedTypes[0]; } } public function __toString() { return '<th>' . $this->title . '</th>'; } }
Třída Row obstarává celý řádek tabulky. Konstruktoru jsou předány všechny hodnoty řádku a definice sloupců, které mají podobu pole objektů z třídy Column. V konstruktoru se tak projede pole sloupců columns a pro každý sloupec se hledá jeho typ, dle kterého se vytvoří konkrétní datová položka řádku tabulky.
class Row { private $items; public function __construct(Array $row, $columns) { foreach ($columns as $key => $column) { $data = isset($row[$key]) ? $row[$key] : null; $className = 'BUB\K5PR02\table\Item' . $column->getType(); $this->items[] = new $className($data); } } public function __toString() { return '<tr>' . implode('', $this->items) . '</tr>'; } }
Ve výše uvedeném příkladu je zajímavé, jak lze generovat název třídy do proměnné className a objekt z této třídy dynamicky vytvořit a uložit na konec pole items. Protože tak pole items může obsahovat instance z různých tříd, potřebujeme zajistit správný převod prvků na HTML kód. Toto jsme v návrhu vyřešili třídou Item a jejími potomky, kteří vždy na řetězci „Item“ začínají. Třída Item je abstraktní, protože nepotřebujeme a ani nechceme, aby se z této třídy vytvářely instance. Slouží nám jako základní rodičovský předpis, ze kterého by měli již konkrétní potomci (ItemMail, ItemText, ItemColor…) vycházet. Také jsou zde definované všechny společné atributy a metody, které se již v potomcích implementovat nemusí. Každá instance z potomka třídy Item musí implementovat metodu render(). Pokud se tato instance převádí na řetězec, zavolá se metoda __toString() u rodičovské třídy Item, protože v potomcích není implementována. V této metodě __toString() se zavolá metoda render() již z korektního potomka třídy Item, která vygeneruje HTML kód již specializované položky. Tomuto volání metody render() v metodě __toString(), kdy se dle kontextu zavolá ta správná metoda render() v potomcích třídy, se říká polymorfismus.
abstract class Item{ protected $value; protected $type; public function __construct($value) { $this->setValue($value); } public function setValue($value) { $this->value = $value; } public function setType($type) { $this->type = $type; } abstract public function render(); public function __toString() { return $this->render(); } } class ItemText extends Item { public function render() { return '<td>' . $this->value . '</td>'; } }
Ve výše uvedeném zdrojovém kódu je uvedena abstraktní třída Item a specializovaná třída ItemText, která ze třídy Item dědí. Další potomci třídy Item se liší jen v těle metody render(), tedy způsobem generování HTML kódu položky tabulky.
Souhrn
Po nastudování uvedených příkladů by čtenář měl pochopit implementaci základních prvků webové prezentace pomocí objektů a měl by si sám zvládnout navrhnout a implementovat vlastní prvek.
Kompletní řešení obou našich ukázkových příkladů najdete v archívu K5PR02.zip.
Jaká je vazba mezi instancemi tříd Row a Item?
Jaká je vazba mezi třídami Item a ItemColor?