5. Zarządzanie atrybutami

Gdy umiemy już stworzyć ogólną strukturę szablonów, możemy zapoznać się z procesem dynamicznego definiowania wartości atrybutów oraz ich tworzenia w locie. Kto pisał szablony w PHP czy w Smartym, ten z pewnością pamięta, że próba napisania odpowiedniego algorytmu do wyświetlania jakiegoś atrybutu w zależności od spełnienia określonych warunków dawała nam wybitnie nieczytelny kod. Open Power Template nie uświadczymy takich problemów, lecz musimy zmienić nasz sposób myślenia o atrybutach. Przede wszystkim ponownie przypominamy, że szablon musi być poprawnym dokumentem XML, dlatego nie wolno nam napisać czegoś w stylu:

<div $class>
 
</div>

Co więcej, nie możemy też napisać <div class="$zmienna">, ani <div class="{$zmienna}"> (błąd często popełniany przez programistów, którzy przesiedli się ze Smarty'ego). Wszystko stanie się jasne po zrozumieniu kilku prostych reguł. Zacznijmy od uściślenia pojęć. Wszelkie zapisy w stylu $zmienna, funkcja($zmienna) itd. nazywane są po prostu wyrażeniami. Nawiasy klamrowe, które widzimy w statycznym tekście między znacznikami:

<p>{$zmienna}</p>

nie są częścią wyrażenia, lecz znakiem dla kompilatora: "o, w tym miejscu tekstu ma się pojawić wartość tego wyrażenia". Dlatego gdy umieszczamy je w jakimkolwiek atrybucie, nie wolno nam takich klamer dodać. W przypadku zwyczajnych znaczników HTML-a, OPT domyślnie drukuje taką wartość atrybutu, jaką tam spotkał. Zapis <div class="$zmienna"> da nam w rezultacie kod <div class="$zmienna">, gdyż nie poinformowaliśmy parsera, że nasz atrybut zawiera wyrażenie OPT, a nie statyczną wartość. Musimy użyć specjalnej przestrzeni nazw parse:

<div parse:class="$zmienna">
  ...
</div>

Dopiero tak zapisany atrybut sprawi, że OPT zrobi to, co chcemy: stworzy atrybut class i wczyta jego wartość ze zmiennej $zmienna. Przestrzeni nazw parse można także używać w przypadku części atrybutów instrukcji. Szczegóły dotyczące konkretnych przypadków znajdują się w dokumentacji.

Taki sposób zapisywania rodzi pewne problemy, gdy nasz atrybut już znajduje się w przestrzeni nazw. Zostanie to rozwiązane w OPT 2.1, gdzie pojawi się zapis <div class="parse:$zmienna">. O szablony napisane dla OPT 2.0 nie musimy się martwić, gdyż wersja ta wyposażona będzie w tryb kompatybilności oraz automatyczny konwerter szablonów do nowej składni.

Dynamicznie obliczane wartości mogą zawierać złośliwy kod, który potencjalnie może uszkodzić strukturę naszego HTML-a. Jednak w OPT nie musimy się tym martwić. Wartości atrybutów są automatycznie oczyszczane, a domyślnie identyczna procedura aplikowana jest również wyrażeniom osadzanym w tekście, przy czym tam mamy nad tym kontrolę.

Można teraz zadać sobie pytanie: dobra, a co w przypadku, gdy cały atrybut ma się wyświetlać tylko w pewnej, konkretnej sytuacji? Do bardziej zaawansowanych zabaw atrybutami przyda nam się instrukcja opt:attribute, która tworzy atrybut o podanej nazwie i wartości. Jedno i drugie wczytujemy oczywiście z wyrażenia. Poniżej widoczny jest przykład, w którym możemy dynamicznie wyróżnić konkretny temat na forum, dodając do niego odpowiednią klasę:

<opt:section name="topics">
<tr>
  <opt:attribute str:name="class" str:value="marked" opt:if="$topics.marked" />
 
  <td> ... </td>
  <td> ... </td>
  ...
</tr>
</opt:section>

Do opt:attribute możemy doklejać inne instrukcje OPT w postaci atrybutowej takie, jak opt:if (warunkowe wyświetlenie atrybutu) czy opt:section (załadowanie listy atrybutów z sekcji). Przy okazji widzimy też to, o czym wspominaliśmy dwa akapity wyżej. Dodatkowe przestrzenie nazw mogą być używane także przy atrybutach instrukcji OPT. Tutaj, instrukcja spodziewa się domyślnie dostać wyrażenia OPT, które zdefiniuja nam nazwę oraz wartość nowego atrybutu. Jednak aby nie pisać potworka name="'class'", kompilator udostępnia czytelniejszą i bardziej elegancką konstrukcję str:, która mówi: "spodziewasz się w tym miejscu dynamicznego wyrażenia, ale ja chcę tu wstawić statyczny tekst". Czasami bywa na odwrót - instrukcja domyślnie spodziewa się statycznego tekstu, a my chcemy załadować go ze zmiennej i dlatego używamy parse:.

Możliwe jest zastosowanie jeszcze ciekawszej sztuczki, mianowicie warunkowy wybór wartości atrybutu:

<opt:section name="topics">
<tr>
  <opt:attribute str:name="class">
    <opt:value test="$topics.sticky">sticky</opt:value>
    <opt:value test="$topics.announcement">announcement</opt:value>
    <opt:value test="$topics.important">important</opt:value>
    <opt:value test="$topics.hot">hot</opt:value>
  </opt:attribute>
 
  <td> ... </td>
  <td> ... </td>
  ...
</tr>
</opt:section>

Aby zdefiniować domyślną wartość, do opt:attribute dodajemy atrybut value lub też wstawiamy dodatkowy znacznik opt:value, lecz bez warunku test.

Jeśli chcemy dynamicznie zbudować listę atrybutów, nie musimy opakowywać opt:attribute w skomplikowane klauzule z sekcjami itd. Wystarczy, że skorzystamy z pomocy dwóch atrybutów specjalnych: opt:attributes-build oraz opt:attributes-ignore, które wczytują listę atrybutów z pojemnika. Drugi z nich pozwala zdefiniować nazwy atrybutów, które z jakiegoś powodu chcemy zignorować (może to być tablica lub lista nazw oddzielonych przecinkami):

<textarea opt:attributes-build="$attributeList" opt:attributes-ignore="class, id">
  Tekst
</textarea>

Powyższy kod pozwala nam dynamicznie zbudować listę atrybutów dla znacznika <textarea> z gwarancją, że złośliwy programista nie doda nam do kodu żadnego class oraz id. Poniżej widzimy przykładową listę:

$view->attributeList = array(
  'rows' => 50,
  'cols' => 30,
  'class' => 'foo'  // ten atrybut sie nie wyswietli, gdyz jest ignorowany
);

Warto zapoznać się z jeszcze jedną sztuczką. Czasami chcemy dokleić dynamicznie atrybut do znacznika pojedynczego, jednak opt:attribute wymaga, aby on miał treść. Musimy jakoś zmusić OPT, by zignorował białe znaki i wydrukował nam znacznik pojedynczy pomimo tego, co widzi w kodzie szablonu. Możemy to zrobić w następujący sposób:

<hr opt:single="yes">
  <opt:attribute str:name="class" str:value="foo" opt:if="$jakisWarunek" />
</hr>

Dzięki opt:single nasz znacznik zostanie zawsze wydrukowany jako pojedynczy <hr />, ewentualnie z dodanym opcjonalnym atrybutem class.

Jak widać, pomimo zaawansowanej funkcjonalności, OPT cały czas stara się, aby kod szablonu był przejrzysty i czytelny poprzez wiele uproszczeń składni i dodatkowych wspomagaczy.