Chapter 5: Widoki
Widoki
W web2py do tworzenia modeli, kontrolerów i widoków wykorzystuje się Python, chociaż w widokach używa się nieco zmodyfikowaną składnię Pythona, aby umożliwić większą czytelność kodu bez nakładania jakichkolwiek ograniczeń na możliwości tego języka.
Przeznaczeniem widoku jest osadzenie kodu Pythona w dokumencie HTML. Zasadniczo sprawia to pewne problemy:
- Jak opakowywać osadzany kod Pythona?
- Czy dało by się uniknąć konieczności stosowania w kodzie wcięć, tak jak przewidują to zasady składniowe Pythona lub HTML?
W web2py używa się znacznik {{ ... }}
do opakowanie kodu Pythona osadzanego w HTML. Zaletą używania nawiasów klamrowych zamiast nawiasów ostrych jest to, że są przejrzyste dla większości edytorów HTML. Umożliwia to wykorzystywanie edytorów HTML do tworzenia widoków web2py. Ograniczniki te można zmienić, na przykład tak
response.delimiters = ('<?','?>')
Jeśli ta linia zostanie umieszczona w modelu, to kod ten będzie dostępny wszędzie, jeśli w kontrolerze, to tylko w widokach dla akcji tego kontrolera, jeśli w akcji, to tylko w widoku dla tej akcji.
Ponieważ programista osadza kod Pythona w HTML, dokument powinien dostosowywać się do zasad HTML, a nie Pythona. Dlatego pozwalamy na niestosowanie wcięć wewnątrz znaczników {{ ... }}
. Ponieważ Python zwykle używa wcięć do rozdzielenia bloków kodu, potrzebujemy na to inny sposób – to dlatego w szablonach web2py stosuje się słowo kluczowe Pythona pass
.
Blok kodu rozpoczyna się od linii kończącej się znakiem dwukropka a kończy linią będącą słowem
pass
. Słowo kluczowepass
nie jest konieczne, gdy koniec bloku jest oczywisty (wynika z kontekstu).
Oto przykład:
{{
if i == 0:
response.write('i is 0')
else:
response.write('i is not 0')
pass
}}
Trzeba mieć na uwadze, że słowo pass
jest słowem kluczowym Pythona, a nie słowem kluczowym web2py. Niektóre edytory Pythona, takie jak Emacs, używają słowa kluczowego pass
do oznaczania podziału bloków i wykorzystują go do automatycznego odtworzenia wcięć bloków.
Język szablonowania web2py robi dokładnie to samo. Gdy znajdzie coś takiego:
<html><body>
{{for x in range(10):}}{{=x}}hello<br />{{pass}}
</body></html>
przekształca to do programu:
response.write("""<html><body>""", escape=False)
for x in range(10):
response.write(x)
response.write("""hello<br />""", escape=False)
response.write("""</body></html>""", escape=False)
Metoda response.write
dokonuje zapisu do response.body
.
Gdy w widoku web2py pojawi się błąd, widok generuje raport błędu, a nie rzeczywisty widok zakodowany przez programistę. Pomaga to programiście debugować kod przez podświetlanie rzeczywistego kodu, który jest wykonywany (kodu, który może być debugowany w edytorze HTML lub inspektorze DOM przeglądarki).
Należy również pamiętać, że:
{{=x}}
generuje
response.write(x)
Zmienne wstrzykiwane do HTML w ten sposób są domyślnie zabezpieczane znakami ucieczki. Zabezpieczanie jest ignorowane, jeśli x
jest obiektem XML
, nawet jeśli parametr escape jest ustawiony na True
.
Oto przykład wprowadzający helper H1
:
{{=H1(i)}}
który jest łumaczony na:
response.write(H1(i))
Po sprawdzeniu, obiekt H1
i jego elementy zostają rekursywnie serializowane, zabezpieczane znakami ucieczki i zapisywane do ciała odpowiedzi. Znaczniki generowane przezH1
i wewnętrzny kod HTML nie są zabezpieczane znakami ucieczki. Ten mechanizm gwarantuje, że cały tekst (i tylko tekst) wyświetlany na stronie jest zawsze zabezpieczany znakami ucieczki, chroniąc w ten sposób przed atakami XSS. Jednocześnie kod jest prosty i łatwy do debugowania.
Metoda response.write(obj, escape=True)
pobiera dwa argumenty, obiekt do zapisu i argument wskazujący, czy tekst w obiekcie ma być zabezpieczony znakami ucieczki (domyślnie ustawiony na True
). Jeśli obj
ma metodę .xml()
, to jest ona wywoływana a wynik zapisywany do ciała odpowiedzi (argument escape
jest ignorowany). W przeciwnym razie wykorzystywana jest metoda __str__
tego obiektu w celu jego serializacji i jeśli argument escape jest ustawiony na True
, następuje zabezpieczenie tekstu znakami ucieczki. Wszystkie wbudowane helpery HTML (H1
w tym przykładzie) są obiektami, które wiedzą jak serializować same siebie poprzez metodę .xml()
.
Wszystko to odbywa się w sposób transparenty. Nigdy nie trzeba (i nie powinno się) wywoływać jawnie metody response.write
.
Podstawy składni
Język szablonowania web2py obsługuje wszystkie instrukcje sterujące języka Python. Podano tutaj kilka przykładów każdej z nich. Mogą być one zagnieżdżane według zwykłej praktyki programowania.
Instrukcja for...in
W szablonie można zapętlić każdy iterowalny obiekt:
{{items = ['a', 'b', 'c']}}
<ul>
{{for item in items:}}<li>{{=item}}</li>{{pass}}
</ul>
co wytworzy:
<ul>
<li>a</li>
<li>b</li>
<li>c</li>
</ul>
Tutaj item
jest dowolnym iterowalnym obiektem, takim jak lista Pythona, krotka Pythona lub obiekt Rows albo każdy inny obiekt, który jest implementowany jako iterator. Wyświetlane elementy są najpierw serializowane a następnie zabezpieczane znakami ucieczki.
Instrukcja while
Można utworzyć pętlę wykorzystując słowo kluczowe while:
{{k = 3}}
<ul>
{{while k > 0:}}<li>{{=k}}{{k = k - 1}}</li>{{pass}}
</ul>
co wyprodukuje:
<ul>
<li>3</li>
<li>2</li>
<li>1</li>
</ul>
Instrukcja if...elif...else
Można użyć klauzul warunkowych:
{{
import random
k = random.randint(0, 100)
}}
<h2>
{{=k}}
{{if k % 2:}}is odd{{else:}}is even{{pass}}
</h2>
co wyprodukuje:
<h2>
45 is odd
</h2>
Ponieważ oczywistym jest, że else
zamyka blok if
, to nie ma potrzeby stosować wyrażenie pass
i użycie tego byłoby nieprawidłowe. Jednakże trzeba jawnie zamknąć blok else
słowem pass
.
Przypominamy, że w Pythonie wyrażenie "else if" jest pisane jako elif
, tak jak w następującym przykładzie:
{{
import random
k = random.randint(0, 100)
}}
<h2>
{{=k}}
{{if k % 4 == 0:}}is divisible by 4
{{elif k % 2 == 0:}}is even
{{else:}}is odd
{{pass}}
</h2>
Wyprodukuje to:
<h2>
64 is divisible by 4
</h2>
Instrukcja try...except...else...finally
W widoku można również użyć wyrażenia try...except
z jednym zastrzeżeniem. Rozważmy następujący przykład:
{{try:}}
Hello {{= 1 / 0}}
{{except:}}
division by zero
{{else:}}
no division by zero
{{finally}}
<br />
{{pass}}
Wyprodukuje do następujące wyjście:
Hello
division by zero
<br />
Przykład ten ilustruje, że dane wyjściowe generowane przed zgłoszeniem wyjątku są renderowane (łącznie z danymi przetworzonymi przed zgłoszeniem wyjątku) wewnątrz bloku try. "Hello" zostaje napisane ponieważ poprzedza wyjątek.
Instrukcja def...return
Język szablonowania web2py umożliwia programistom definiowanie i implementowanie funkcji, które mogą zwracać jakikolwiek obiekt Pythona lub łańcuch tekstowy (w tym kod HTML). Przyjrzyjmy się dwóm przykładom:
{{def itemize1(link): return LI(A(link, _href="http://" + link))}}
<ul>
{{=itemize1('www.google.com')}}
</ul>
wytworzy następujące wyjście:
<ul>
<li><a href="http:/www.google.com">www.google.com</a></li>
</ul>
Funkcja itemize1
zwraca obiekt helpera, który jest wkładany w kod w miejscu gdzie funkcja została wywołana.
Rozwazmy poniższy kod:
{{def itemize2(link):}}
<li><a href="http://{{=link}}">{{=link}}</a></li>
{{return}}
<ul>
{{itemize2('www.google.com')}}
</ul>
Wytworzy to dokładnie takie samo wyjście jak w powyższym przykładzie. W tym przypadku, funkcja itemize2
reprezentuje porcję kodu HTML, który ma zastąpić znacznik web2py w którym funkcja została wywołana. Proszę zwrócić uwagę, że nie ma znaku '=' przed wywołaniem itemize2
, ponieważ funkcja ta nie zwraca tekstu, ale zapisuje dane wyjściowe bezpośrednio do odpowiedzi.
Jest jedno zastrzeżenie: funkcje definiowane wewnątrz widoku muszą kończyć się wyrażeniem return albo automatyczne wcięcie zawiedzie.
Helpery HTML
Rozważmy następujący kod widoku:
{{=DIV('this', 'is', 'a', 'test', _id='123', _class='myclass')}}
jest on zwracany jako:
<div id="123" class="myclass">thisisatest</div>
DIV
jest klasą helpera HTML,czyli czymś co może zostać zastosowane do programowego zbudowania kodu HTML. Odpowiada on znacznikowi <div>
HTML.
Argumenty pozycyjne są interpretowane jak obiekty zawarte pomiędzy znacznikiem otwierającym a zamykającym HTML. Argumenty nazwane rozpoczynające się znakiem podkreślenia są interpretowane jako atrybuty znacznika HTML (bez znaków podkreślenia). Niektóre helpery maja również nazwane argumenty nie rozpoczynające się znakiem podkreślenia – argumenty te są specyficzne dla znacznika.
Zamiast ustawienia nienazwanych argumentów, można zrobić tak, że helper pobierze pojedynczą listę lub krotkę jako swój zestaw komponentów, gdy użyje się notacji ze znakiem gwiazdki *
oraz tak, że pobierze pojedynczy słownik jako zbiór atrybutów, gdy użyje się dwóch gwiazdek **
, na przykład:
{{
contents = ['this','is','a','test']
attributes = {'_id':'123', '_class':'myclass'}
=DIV(*contents,**attributes)
}}
produkuje to samo, co poprzedni kod.
Helpery:
A
, B
, BEAUTIFY
, BODY
, BR
, CAT
, CENTER
, CODE
, COL
, COLGROUP
, DIV
, EM
, EMBED
, FIELDSET
, FORM
, H1
, H2
, H3
, H4
, H5
, H6
, HEAD
, HR
, HTML
, I
, IFRAME
, IMG
, INPUT
, LABEL
, LEGEND
, LI
, LINK
, MARKMIN
, MENU
, META
, OBJECT
, ON
, OL
, OPTGROUP
, OPTION
, P
, PRE
, SCRIPT
, SELECT
, SPAN
, STYLE
, TABLE
, TAG
, TBODY
, TD
, TEXTAREA
, TFOOT
, TH
, THEAD
, TITLE
, TR
, TT
, UL
, URL
, XHTML
, XML
, embed64
, xmlescape
mogą być używane do budowy złożonych wyrażeń, które mogą być serializowane do XML[xml-w] [xml-o]. Na przykład:
{{=DIV(B(I("hello ", "<world>"))), _class="myclass")}}
zostanie renderowane tak:
<div class="myclass"><b><i>hello <world></i></b></div>
Helpery można również serialiować do łańcuchów tekstowych, równoważnie z metodami __str__
i xml
:
>>> print str(DIV("hello world"))
<div>hello world</div>
>>> print DIV("hello world").xml()
<div>hello world</div>
Komponenty helperów mogą być odnoszone poprzez ich pozycję a helpery działają jak listy w stosunku do ich komponentów:
>>> a = DIV(SPAN('a', 'b'), 'c')
>>> print a
<div><span>ab</span>c</div>
>>> del a[1]
>>> a.append(B('x'))
>>> a[0][0] = 'y'
>>> print a
<div><span>yb</span><b>x</b></div>
Atrybuty helperów mogą być odnoszone przez nazwę a helpery działają jak słownik w stosunku do ich atrybutów:
>>> a = DIV(SPAN('a', 'b'), 'c')
>>> a['_class'] = 's'
>>> a[0]['_class'] = 't'
>>> print a
<div class="s"><span class="t">ab</span>c</div>
Uwaga, kompletny zestaw komponentów może być dostępny poprzez listę o nazwie a.components
a kompletny zestaw atrybutów poprzez słownik o nazwie a.attributes
. Tak więc a[i]
jest równoważnikiem a.components[i]
gdzie i
to liczba całkowita, natomiast a[s]
jest równoważnikiem a.attributes[s]
gdzie s
jest ciągiem znakowym.
Atrybuty helpera są przekazywane do helpera jako argumenty kluczowe. Jednak w niektórych przypadkach nazwy atrybutów zawierają specjalne znaki, które nie są dozwolone w identyfikatorach Pythona (np. myślniki) i dlatego nie mogą być użyte jako nazwy argumentów kluczowych. Na przykład:
DIV('text', _data-role='collapsible')
nie będzie działać, ponieważ "_data-role" zawiera myślnik, który będzie powodował błąd składni Pythona.
W takich przypadkach ma się kilka możliwości. Można zastosować argument data
(tym razem bez wiodącego znaku podkreślenia) do przekazania słownika związanych atrybutów bez ich wiodącego myślnika a wyjście będzie miało pożądaną kombinacje danych np.
>>> print DIV('text', data={'role': 'collapsible'})
<div data-role="collapsible">text</div>
lub można zamiast tego przekazać atrybuty jako słownik i wykorzystać notację dwóch gwiazdek (**
) argumentów funkcji Pythona, co odwzoruje słownik par (klucz:wartość) na zestaw argumentów kluczowych:
>>> print DIV('text', **{'_data-role': 'collapsible'})
<div data-role="collapsible">text</div>
Bardziej skomplikowane zapisy mogą zawierać znaki, które powinny być na wyjściu zamienione na znaki encji HTML, np.
>>> print DIV('text', data={'options':'{"mode":"calbox", "useNewStyle":true}'})
<div data-options="{"mode":"calbox", "useNewStyle":true}">text</div>
Można również dynamicznie utworzyć specjalny TAG:
>>> print TAG['soap:Body']('whatever',**{'_xmlns:m':'http://www.example.org'})
<soap:Body xmlns:m="http://www.example.org">whatever</soap:Body>
XML
XML
to obiekt stosowany do hermetyzowania tekstu, który nie powinien być zabezpieczany sekwencjami ucieczki. Tekst ten może, ale nie musi, zawierać prawidłowy kod XML - na przykład, może zawierać kod JavaScript.
Tekst w tym przykładzie jest zabezpieczany encjami znakowymi XML:
>>> print DIV("<b>hello</b>")
<b>hello</b>
stosując XML
można się zabezpieczyć przed wstawianiem w tekstu sekwencji ucieczki:
>>> print DIV(XML("<b>hello</b>"))
<b>hello</b>
Czasem zachodzi potrzeba renderowania kodu HTML zapisanego w zmiennej, ale kod taki może zawierać niebezpieczne znaczniki, takie jak script:
>>> print XML('<script>alert("unsafe!")</script>')
<script>alert("unsafe!")</script>
Nie zabezpieczenie wykonywalnego kodu, takiego jak ten (na przykład, wprowadzonego w treści komentarza na blogu) jest niebezpieczne, ponieważ może zostać użyty do wygenerowania ataku Cross Site Scripting (XSS) na strony innych odwiedzających.
Helper XML
web2py może zabezpieczyć tekst przed iniekcją poprzez wstawienie encji znakowych przy wszystkich znacznikach, z wyjątkiem tych, które wskazało się jawnie. Oto przykład:
>>> print XML('<script>alert("unsafe!")</script>', sanitize=True)
<script>alert("unsafe!")</script>
Konstruktory XML
traktują domyślnie zawartość niektórych znaczników i niektóre z ich atrybutów za bezpieczne. Można zastąpić domyślne ustawienie opcjonalnych argumentów permitted_tags
i allowed_attributes
. Oto domyślne wartości opcjonalnych argumentów helpera XML
.
XML(text, sanitize=False,
permitted_tags=['a', 'b', 'blockquote', 'br/', 'i', 'li',
'ol', 'ul', 'p', 'cite', 'code', 'pre', 'img/'],
allowed_attributes={'a':['href', 'title'],
'img':['src', 'alt'], 'blockquote':['type']})
Helpery wbudowane
A
Helper ten jest używany do tworzenia odnośników.
>>> print A('<click>', XML('<b>me</b>'),
_href='http://www.web2py.com')
<a href='http://www.web2py.com'><click><b>me/b></a>
Zamiast _href
można przekazać adres URL wykorzystując argument callback
. Oto przykład fragmentu widoku:
{{=A('click me', callback=URL('myaction'))}}
skutkujący tym, że po kliknięciu odnośnika wywołany zostanie kod ajax "myaction" zamiast kod przekierowania. W tym przypadku, opcjonalnie można podać dwa argumenty: target
i delete
:
{{=A('click me', callback=URL('myaction'), target="t")}}
<div id="t"><div>
Odpowiedź wywołania zwrotnego ajax zostanie zapisana w obiekcie DIV z atrybutem id równym "t".
<div id="b">{{=A('click me', callback=URL('myaction'), delete='div#b")}}</div>
W odpowiedzi, najbliższy znacznik dopasowany do "div#b" zostanie usunięty. W tym przypadku usunięty zostanie przycisk. Typowym zastosowanie to użycie w tablicy:
{{=A('click me', callback=URL('myaction'), delete='tr")}}
Naciśnięcie przycisku wykona wywołanie zwrotne i usunie wiersz tablicy.
callback
i delete
można łączyć.
Helper A pobiera specjalny argument o nazwie cid
. Działa on następująco:
{{=A('linked page', _href='http://example.com', cid='myid')}}
<div id="myid"></div>
Kliknięcie na na link spowoduje załadowanie treści w znaczniku div. Jest to podobne, ale silniejsze niż powyższa składnia, ponieważ jest zaprojektowane do odświeżania treści strony. Omówimy zastosowanie cid
bardziej szczegółowo w rozdziale 12, w kontekście komponentów.
Te funkcjonalności ajax wymagają dostępu do jQuery i skryptu "static/js/web2py_ajax.js", które są automatycznie dołączane poprzez umiejscowienie w układzie head wyrażenia {{include 'web2py_ajax.html'}}
. Skrypt "views/web2py_ajax.html" definuje kilka zmiennych opartych na request
i zawiera wszystkie niezbędne pliki js i css.
B
Helper ten dokonuje pogrubienia tekstu.
>>> print B('<hello>', XML('<i>world</i>'), _class='test', _id=0)
<b id="0" class="test"><hello><i>world</i></b>
BODY
Helper ten umieszcza ciało strony.
>>> print BODY('<hello>', XML('<b>world</b>'), _bgcolor='red')
<body bgcolor="red"><hello><b>world</b></body>
BR
Helper ten tworzy podział wiersza.
>>> print BR()
<br />
Warto pamiętać, że w helperach istnieje możliwość powtarzania wyjścia przy wykorzystaniu operatora mnożenie:
>>> print BR()*5
<br /><br /><br /><br /><br />
CAT
Helper ten łączy inne helpery, takie jak TAG[''].
>>> print CAT('Here is a ', A('link',_href=URL()), ', and here is some ', B('bold text'), '.')
Here is a <a href="/app/default/index">link</a>, and here is some <b>bold text</b>.
CENTER
Helper ten powoduje wyśrodkowanie tekstu.
>>> print CENTER('<hello>', XML('<b>world</b>'),
>>> _class='test', _id=0)
<center id="0" class="test"><hello><b>world</b></center>
CODE
Powoduje podświetlanie składni kodu Pythona, C, C++, HTML i web2py, co ulepsza kod wykazywany w PRE
. Helper CODE
ma również zdolność tworzenia odnośników do dokumentacji API web2py.
Oto przykład z podświetlaniem kodu Pythona:
>>> print CODE('print "hello"', language='python').xml()
<table><tr valign="top"><td style="width:40px; text-align: right;"><pre style="
font-size: 11px;
font-family: Bitstream Vera Sans Mono,monospace;
background-color: transparent;
margin: 0;
padding: 5px;
border: none;
background-color: #E0E0E0;
color: #A0A0A0;
">1.</pre></td><td><pre style="
font-size: 11px;
font-family: Bitstream Vera Sans Mono,monospace;
background-color: transparent;
margin: 0;
padding: 5px;
border: none;
overflow: auto;
"><span style="color:#185369; font-weight: bold">print </span>
<span style="color: #FF9966">"hello"</span></pre></td></tr>
</table>
Oto podobny przykład z HTML:
>>> print CODE(
>>> '<html><body>{{=request.env.remote_add}}</body></html>',
>>> language='html')
<table>...<code>...
<html><body>{{=request.env.remote_add}}</body></html>
...</code>...</table>
Helper CODE
ma domyślne argumenty:
CODE("print 'hello world'", language='python', link=None, counter=1, styles={})
Obsługiwanymi wartościami dla argumentu language
są "python", "html_plain", "c", "cpp", "web2py" i "html". Język "html" interpretuje znaczniki {{ i }} jako kod "web2py", natomiast "html_plain" nie.
Jeśli określona jest wartość link
, na przykład "/examples/global/vars/", API web2py dokona odniesienia w kodzie umieszczając odnośnik URL do dokumentacji. Na przykład "request" zostanie zlinkowany z "/examples/global/vars/request". W powyższym przykładzie, odnośnik URL jest obsługiwany przez przez akcję "vars" w kontrolerze "global.py", który jest rozpowszechniany jako część aplikacji "examples" web2py.
Argument counter
jest używany dla numerowania linii kodu. Może zostać ustawiony na jedną z trzech możliwych wartości. Może to być None
wskazująca na brak numeracji, wartość numeryczna wskazująca na numer początkowy oraz ciąg znakowy. Jeśli argument ten jest ustawiony na jakiś ciąg znakowy, to jest on traktowany jako znak zachęty w kodzie i linie kodu nie są numerowane.
Argument styles
jest nieco skomplikowany. Jeśli spojrzy się na powyżej wygenerowany kod HTML, to widać, żezawiera on tabelę z dwoma kolumnami, przy czym każda kolumna ma określony wierszowo swój własny styl CSS. Atrybuty styles
umożliwiają nadpisać te dwa domyślne style CSS. Na przykład:
{{=CODE(...,styles={'CODE':'margin: 0;padding: 5px;border: none;'})}}
Atrybut styles
musi być słownikiem umożliwiającym określenie dwóch kluczy: CODE
dla stylu rzeczywistego kodu i LINENUMBERS
dla stylu kolumny z numeracją wierszy. Warto pamiętać, że style te całkowicie wymieniają domyślne style a nie są dodawane do stylów domyślnych.
COL
>>> print COL('a','b')
<col>ab</col>
COLGROUP
>>> print COLGROUP('a','b')
<colgroup>ab</colgroup>
DIV
Wszystkie helpery, z wyjątkiem XML
, pochodzą od DIV
i dziedziczą jego podstawowe metody.
>>> print DIV('<hello>', XML('<b>world</b>'), _class='test', _id=0)
<div id="0" class="test"><hello><b>world</b></div>
EM
Uwydatnia treść, zmieniając czcionkę najczęściej na italik (interpretacja znacznika em zależy od przeglądarki).
>>> print EM('<hello>', XML('<b>world</b>'), _class='test', _id=0)
<em id="0" class="test"><hello><b>world</b></em>
FIELDSET
Jest używany do tworzenia pola wejściowego wraz z etykietą.
>>> print FIELDSET('Height:', INPUT(_name='height'), _class='test')
<fieldset class="test">Height:<input name="height" /></fieldset>
FORM
Jest to jeden z najważniejszych helperów. W bardzo prosty sposób tworzy znacznik <form>...</form>
, ale ponieważ helper ten jest obiektami i posiada wiedzę o tym co zawiera, to może przetwarzać zgłoszone formularze (na przykład, wykonywać walidację pól). Jest to szczegółowo omówione w rozdziale 7.
>>> print FORM(INPUT(_type='submit'), _action='', _method='post')
<form enctype="multipart/form-data" action="" method="post">
<input type="submit" /></form>
Domyślnie "enctype" to "multipart/form-data".
Konstruktor FORM
i SQLFORM
mogą pobierać argument o nazwie hidden
. Gdy słownik jest przekazywany jako hidden
, to jego elementy są przekształcane do pola INPUT "hidden". Na przykład:
>>> print FORM(hidden=dict(a='b'))
<form enctype="multipart/form-data" action="" method="post">
<input value="b" type="hidden" name="a" /></form>
H1
, H2
, H3
, H4
, H5
, H6
Helpery te są nagłówkami paragrafów i podparagrafów:
>>> print H1('<hello>', XML('<b>world</b>'), _class='test', _id=0)
<h1 id="0" class="test"><hello><b>world</b></h1>
HEAD
Do wygenerowania znaczników sekcji HEAD strony HTML.
>>> print HEAD(TITLE('<hello>', XML('<b>world</b>')))
<head><title><hello><b>world</b></title></head>
HTML
Helper ten jest trochę inny. Oprócz generowania znaczników <html>
, poprzedza znacznik ciągiem doctype[xhtml-w] [xhtml-o] [xhtml-school] .
>>> print HTML(BODY('<hello>', XML('<b>world</b>')))
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html><body><hello><b>world</b></body></html>
Helper HTML pobiera kilka opcjonalnych argumentów, która mają następujące wartości domyślne:
HTML(..., lang='en', doctype='transitional')
gdzie argument doctype może mieć wartość 'strict', 'transitional', 'frameset', 'html5' lub jakiś pełny ciąg doctype.
XHTML
XHTML jest podobny do HTML ale tworzy tworzy doctype XHTML a nie HTML.
XHTML(..., lang='en', doctype='transitional', xmlns='http://www.w3.org/1999/xhtml')
gdzie argument doctype może mieć wartość 'strict', 'transitional', 'frameset' lub jakiegoś pełnego ciągu doctype.
HR
Helper ten tworzy na stronie HTML poziomą linię
>>> print HR()
<hr />
I
Tworzy treść pisaną italikiem.
>>> print I('<hello>', XML('<b>world</b>'), _class='test', _id=0)
<i id="0" class="test"><hello><b>world</b></i>
IFRAME
Helper ten zawiera inną stronę internetową w bieżącej stronie. Adres URL innej strony jest określany poprzez atrybut "_src".
>>> print IFRAME(_src='http://www.web2py.com')
<iframe src="http://www.web2py.com"></iframe>
IMG
Używany jest do osadzania obrazów w HTML:
>>> IMG(_src='http://example.com/image.png',_alt='test')
<img src="http://example.com/image.ong" alt="rest" />
Oto połączenie helperów A, IMG i URL użyte w celu zawarcia statycznego obrazu z linkiem:
>>> A(IMG(_src=URL('static','logo.png'), _alt="My Logo"),
_href=URL('default','index'))
<a href="/myapp/default/index">
<img src="/myapp/static/logo.png" alt="My Logo" />
</a>
INPUT
Tworzy znacznik <input.../>
. Znacznik input może nie zawierać innych znaczników i jest zamykany znakami />
zamiast >
. Znacznik input ma opcjonalny atrybut _type
, który można ustawić na "text" (domyślnie), "submit", "checkbox" lub "radio".
>>> print INPUT(_name='test', _value='a')
<input value="a" name="test" />
Helper ten pobiera opcjonalny specjalny argument o nazwie "value", odrębny od argumentu "_value". Ten ostatni ustawia domyślną wartość dla pola input. Pierwszy ustawia bieżącą wartość. Dla pola input typ "text", pierwszy argument zastępuje drugi:
>>> print INPUT(_name='test', _value='a', value='b')
<input value="b" name="test" />
Dla przycisków radio INPUT
selektywnie ustawia atrybut "checked":
>>> for v in ['a', 'b', 'c']:
>>> print INPUT(_type='radio', _name='test', _value=v, value='b'), v
<input value="a" type="radio" name="test" /> a
<input value="b" type="radio" checked="checked" name="test" /> b
<input value="c" type="radio" name="test" /> c
i podobnie dla pól wyboru:
>>> print INPUT(_type='checkbox', _name='test', _value='a', value=True)
<input value="a" type="checkbox" checked="checked" name="test" />
>>> print INPUT(_type='checkbox', _name='test', _value='a', value=False)
<input value="a" type="checkbox" name="test" />
LABEL
Jest używany do tworzenia znacznika <label> dla pól formularza.
>>> print LABEL('<hello>', XML('<b>world</b>'), _class='test', _id=0)
<label id="0" class="test"><hello><b>world</b></label>
LEGEND
Jest używany do tworzenia znacznika <legend> dla pól formularza.
>>> print LEGEND('Name', _for='myfield')
<legend for="myfield">Name</legend>
LI
Tworzy element listy (znacznik <li>), który powinien być zawarty w znacznikach <ul> lub <ol>.
>>> print LI('<hello>', XML('<b>world</b>'), _class='test', _id=0)
<li id="0" class="test"><hello><b>world</b></li>
META
Używany do budowania znaczników <meta> w sekcji HEAD. Na przykład:
>>> print META(_name='security', _content='high')
<meta name="security" content="high" />
MARKMIN
Implementuje składnię wiki markmin. Konwertuje tekst wejściowy do wyjścia HTML zgodnie z poniższym przykładem:
>>> print MARKMIN("this is **bold** or ''italic'' and this [[a link http://web2py.com]]")
<p>this is <b>bold</b> or <i>italic</i> and
this <a href="http://web2py.com">a link</a></p>
Składnia markmin jest opisana w pliku:
http://127.0.0.1:8000/examples/static/markmin.html
dostarczanym wraz z web2py.
Markmin można używać do generowania dokumentów HTML, LaTeX i PDF:
m = "Hello **world** [[link http://web2py.com]]"
from gluon.contrib.markmin.markmin2html import markmin2html
print markmin2html(m)
from gluon.contrib.markmin.markmin2latex import markmin2latex
print markmin2latex(m)
from gluon.contrib.markmin.markmin2pdf import markmin2pdf
print markmin2pdf(m) # requires pdflatex
(helper MARKMIN
jest skrótem dla markmin2html
)
Oto podstawowa składnia:
ŹRÓDŁO | WYJŚCIE |
# title | tytuł |
## section | rozdział |
### subsection | podrozdział |
**bold** | gruby |
''italic'' | italik |
``verbatim`` | dosłowny |
http://google.com | http://google.com |
http://... | <a href="http://...">http:...</a> |
http://...png | <img src="http://...png" /> |
http://...mp3 | <audio src="http://...mp3"></audio> |
http://...mp4 | <video src="http://...mp4"></video> |
qr:http://... | <a href="http://..."><img src="qr code"/></a> |
embed:http://... | <iframe src="http://..."></iframe> |
[[click me #myanchor]] | kliknij mnie |
[[myanchor]] | Utworzenie kotwicy dla odnośnika |
$ $\int_a^b sin(x)dx$ $ |
Odnośniki MARKMIN
Odnośniki mają postać: [[wyświetlany tekst odnośnika <link>]]
. <link> może być kotwicą np. #myanchor
lub adresem URI np. http://www.web2py.com
lub względną referencją np. [[patrz rodział 8 ../08]]
lub [[patrz rozdział 8 ../08#myanchor]]
Wystarczy dołączyć odnośnik do pliku obrazu, wideo lub audio bez wynikowego znacznika a zostanie automatycznie dołączony odpowiedni plik obrazu, wideo lub audio (dla pliku audio i video będzie użyty odpowiednio znacznik <audio> i <video>).
Dodanie odnośnika z przedrostkiem qr:
, tak jak tu
qr:http://web2py.com
spowoduje, że osadzony zostanie odpowiedni kod QR i zlinkowany z określonym adresem URL.
Dodanie odnośnika z przedrostkiem embed:
, tak jak tu
embed:http://www.youtube.com/embed/x1w8hKTJ2Co
spowoduje, osadzenie określonej strony, w tym przypadku wideo youtube.
Obrazy mogą być również osadzane na następującą składnią:
[[image-description http://.../image.png right 200px]]
Listy i tabele MARKMIN
Listy nieuporządkowane tworzy się używajac znak myślnika przed każdym elementem listy:
- one
- two
- three
Listy uporządkowane tworzy się stosując znak plus przed każdym elementem listy:
+ one
+ two
+ three
Natomiast tablice w ten sposób:
----------
X | 0 | 0
0 | X | 0
0 | 0 | 1
----------
Rozszerzanie MARKMIN
Składnia MARKMIN obsługuje również znaczniki bloków cytatów (ang. blockquotes), znaczniki audio i wideo HTML, wyrównywanie obrazów, własne style CSS i to wszystko można rozszerzać:
MARKMIN("``abab``:custom", extra=dict(custom=lambda text: text.replace('a','c'))
generuje
'cbcb'
Niestandardowe bloki są ograniczane przez ``...``:<key>
i są renderowane przez funkcję przekazywaną jako odpowiedni klucz w słowniku argumentu extra
MARKMIN. Trzeba pamiętać, aby zabezpieczyć funkcję znakami ucieczki w celu zapobieżenia atakom XSS.
OBJECT
Używany do osadzania w HTML obiektów (na przykład flash player).
>>> print OBJECT('<hello>', XML('<b>world</b>'),
>>> _src='http://www.web2py.com')
<object src="http://www.web2py.com"><hello><b>world</b></object>
OL
Wskazuje na listę uporządkowaną. Lista powinna zawierać znaczniki LI. Argumenty OL
, które nie są obiektami LI
są automatycznie otaczane znacznikami <li>...</li>
.
>>> print OL('<hello>', XML('<b>world</b>'), _class='test', _id=0)
<ol id="0" class="test"><li><hello></li><li><b>world</b></li></ol>
ON
Znajduje się tutaj dla zachowania wstecznej kompatybilności i jest po prostu aliasem True
. Jest stosowany wyłącznie dla pól wyboru i jest przestarzały, ponieważ True
jest bardziej poprawne dla języka Python.
>>> print INPUT(_type='checkbox', _name='test', _checked=ON)
<input checked="checked" type="checkbox" name="test" />
OPTGROUP
Pozwala na grupowanie wielu opcji w bloku SELECT i jest użyteczne dla dostosowywania pól z użyciem CSS.
>>> print SELECT('a', OPTGROUP('b', 'c'))
<select>
<option value="a">a</option>
<optgroup>
<option value="b">b</option>
<option value="c">c</option>
</optgroup>
</select>
OPTION
Używać się to powinno tylko jako część kombinacji SELECT/OPTION.
>>> print OPTION('<hello>', XML('<b>world</b>'), _value='a')
<option value="a"><hello><b>world</b></option>
Tak jak w przypadku INPUT
, web2py rozróżnia między atrybutami "_value" (wartość OPTION) i "value" (aktualna wartość "select"). Jeśli są równe, opcja ma wartość "selected".
>>> print SELECT('a', 'b', value='b'):
<select>
<option value="a">a</option>
<option value="b" selected="selected">b</option>
</select>
P
Helper ten generuje znacznik początkowy i końcowy akapitu, <p>...</p>.
>>> print P('<hello>', XML('<b>world</b>'), _class='test', _id=0)
<p id="0" class="test"><hello><b>world</b></p>
PRE
Generuje znacznik <pre>...</pre>
dla wyświetlania tekstu preformatowanego. Helper CODE
jest ogólnie korzystniejszy dla wyświetlania kodu.
>>> print PRE('<hello>', XML('<b>world</b>'), _class='test', _id=0)
<pre id="0" class="test"><hello><b>world</b></pre>
SCRIPT
Dołącza albo linkuje skrypt, taki jak JavaScript. Dla bardzo starych przeglądarek zawartość pomiędzy znacznikami jest renderowana jako komentarz HTML.
>>> print SCRIPT('alert("hello world");', _type='text/javascript')
<script type="text/javascript"><!--
alert("hello world");
//--></script>
SELECT
Generuje znacznik <select>...</select>
. Jest używany razem z helperemOPTION
. Argumenty SELECT
, które nie są obiektami OPTION
są automatycznie konwertowane do opcji.
>>> print SELECT('<hello>', XML('<b>world</b>'), _class='test', _id=0)
<select id="0" class="test">
<option value="<hello>"><hello></option>
<option value="<b>world</b>"><b>world</b></option>
</select>
SPAN
Podobny do DIV
, ale używany do wygenerowania wierszowego znacznika treści <span> ...</span>.
>>> print SPAN('<hello>', XML('<b>world</b>'), _class='test', _id=0)
<span id="0" class="test"><hello><b>world</b></span>
STYLE
Podobny do SCRIPT, ale używany do dołączania albo linkowania kodu CSS. Oto jak dołącza się kod CSS:
>>> print STYLE(XML('body {color: white}'))
<style><!--
body { color: white }
//--></style>
a oto jak jest linkowany plik CSS:
>>> print STYLE(_src='style.css')
<style src="style.css"><!--
//--></style>
TABLE
, TR
, TD
Helpery te (wraz z opcjonalnymi THEAD
, TBODY
i TFOOTER
) są używane do tworzenia tabel HTML.
>>> print TABLE(TR(TD('a'), TD('b')), TR(TD('c'), TD('d')))
<table><tr><td>a</td><td>b</td></tr><tr><td>c</td><td>d</td></tr></table>
TR
oczekuje zawartości TD
. Argumenty, które nie są obiektami TD
są konwertowane automatycznie.
>>> print TABLE(TR('a', 'b'), TR('c', 'd'))
<table><tr><td>a</td><td>b</td></tr><tr><td>c</td><td>d</td></tr></table>
Konwersję tablicy Pythona do tabeli HTML można dokonać łatwo przy wykorzystaniu notacji z gwiazdka (*
) argumentów funkcji, która odwzorowuje elementy list na argumenty pozycyjne funkcji.
Tutaj zrobimy to linia za linią:
>>> table = [['a', 'b'], ['c', 'd']]
>>> print TABLE(TR(*table[0]), TR(*table[1]))
<table><tr><td>a</td><td>b</td></tr><tr><td>c</td><td>d</td></tr></table>
Tutaj zrobimy wszystkie linie naraz:
>>> table = [['a', 'b'], ['c', 'd']]
>>> print TABLE(*[TR(*rows) for rows in table])
<table><tr><td>a</td><td>b</td></tr><tr><td>c</td><td>d</td></tr></table>
TBODY
Jest wykorzystywany do wygenerowania znacznika określającego obszar wierszy zawartych w ciele tabeli, w odróżnieniu do wierszy nagłówka i stopki. Nie jest to element obowiązkowy tabel.
>>> print TBODY(TR('<hello>'), _class='test', _id=0)
<tbody id="0" class="test"><tr><td><hello></td></tr></tbody>
TEXTAREA
Helper ten generuje znacznik <textarea>...</textarea>
.
>>> print TEXTAREA('<hello>', XML('<b>world</b>'), _class='test')
<textarea class="test" cols="40" rows="10"><hello><b>world</b></textarea>
Jedynym ograniczeniem jest to, że jego opcjonalny argument "value" zastępuje treść znacznika <textarea> ... </textarea> (wewnątrz HTML)
>>> print TEXTAREA(value="<hello world>", _class="test")
<textarea class="test" cols="40" rows="10"><hello world></textarea>
TFOOT
Jest stosowany do zaznaczenia obszaru wierszy stopki tabeli.
>>> print TFOOT(TR(TD('<hello>')), _class='test', _id=0)
<tfoot id="0" class="test"><tr><td><hello></td></tr></tfoot>
TH
Jest używany zamiast TD
w nagłówku tabeli.
>>> print TH('<hello>', XML('<b>world</b>'), _class='test', _id=0)
<th id="0" class="test"><hello><b>world</b></th>
THEAD
Jest używany do określenia obszaru wierszy nagłówka tabeli.
>>> print THEAD(TR(TH('<hello>')), _class='test', _id=0)
<thead id="0" class="test"><tr><th><hello></th></tr></thead>
TITLE
Jest używany do wygenerowania znacznika <title> w sekcji header strony HTML.
>>> print TITLE('<hello>', XML('<b>world</b>'))
<title><hello><b>world</b></title>
TR
Generuje znaczniki wierszy tabeli. Należy umieszczać wewnątrz tabeli. Powinny zawierać znaczniki <td>...</td>
. Argumenty TR
, które nie są obiektami TD
będą automatycznie przekonwertowane.
>>> print TR('<hello>', XML('<b>world</b>'), _class='test', _id=0)
<tr id="0" class="test"><td><hello></td><td><b>world</b></td></tr>
TT
Generuje znaczniki do wyświetlania tekstu stałej szerokości.
>>> print TT('<hello>', XML('<b>world</b>'), _class='test', _id=0)
<tt id="0" class="test"><hello><b>world</b></tt>
UL
Generuje znacznik nieuporządkowanej listy i wymaga umieszczenie wewnątrz elementów LI. Jeśli jego zawartość nie posiada obiektów LI, UL doda je automatycznie.
>>> print UL('<hello>', XML('<b>world</b>'), _class='test', _id=0)
<ul id="0" class="test"><li><hello></li><li><b>world</b></li></ul>
URL
Helper URL jest udokumentowany w rozdziale 4
embed64
embed64(filename=None, file=None, data=None, extension='image/gif')
szyfruje dostarczone dane (binarne) do base64.
filename
: otwiera i odczytuje plik o podanej nazwie w trybie 'rb', jeśli podano;file
: odczytuje wskazany plik, jeśli podano;data
: wykorzystuje podane dane do zaszyfrowania, jeśli podano.
xmlescape
xmlescape(data, quote=True)
zwraca ciąg znakowy dostarczonych danych, zabezpieczony znakami ucieczki.
>>> print xmlescape('<hello>')
<hello>
Helpery indywidualne
TAG
Czasem zachodzi potrzeba wygenerowania własnych znaczników XML. W web2py dostarczany jest helper TAG
- uniwersalny generator znaczników.
{{=TAG.name('a', 'b', _c='d')}}
generuje następujący kod XML
<name c="d">ab</name>
Argumenty "a", "b" i "d" są automatycznie zabezpieczane znakami ucieczki. Do stłumienia tego zachowania użyty zostaje helper XML
. Stosując TAG
można wygenerować znaczniki HTML/XML nie dostarczane przez API. Helpery TAG być mogą być zagnieżdżane i serializowane poprzez str().
Równoważna składnia to:
{{=TAG['name']('a', 'b', c='d')}}
Jeśli obiekt TAG zostanie utworzony z pustą nazwą, to może być wykorzystany do łączenia wielu ciągów znakowych i helperów HTML, razem z zawarciem ich otaczającym je znaczniku, lecz używanie tego jest zdeprecjonowane na rzecz helpera CAT
.
Za pomocą helpera TAG można generować znaczniki samozamykające. Ich nazwa musi się kończyć znakiem ukośnika ("/").
{{=TAG['link/'](_href='http://web2py.com')}}
generuje następujący kod XML:
<link ref="http://web2py.com"/>
Proszę zwrócić uwagę, że TAG
jest obiektem a TAG.name
lub TAG['name']
jest funkcja zwracająca tymczasową klasę helpera.
MENU
Helper MENU pobiera listę list lub krotkę formularzy response.menu
(tak jak opisano to w rozdziale 4) i generuje drzewiastą strukturę używającą nieuporządkowanych list reprezentujących menu. Na przykład:
>>> print MENU([['One', False, 'link1'], ['Two', False, 'link2']])
<ul class="web2py-menu web2py-menu-vertical">
<li><a href="link1">One</a></li>
<li><a href="link2">Two</a></li>
</ul>
Trzeci element w każdej liście (krotce) może być helperem HTML (który może zawierać zagnieżdżone helpery) a wtedy renderowany będzie helper
MENU
, zamiast tworzenia własnego znacznika<a>
.
Każdy element menu może mieć czwarty argument, który jest zagnieżdżonym podmenu (i rekursywnie tak dalej):
>>> print MENU([['One', False, 'link1', [['Two', False, 'link2']]]])
<ul class="web2py-menu web2py-menu-vertical">
<li class="web2py-menu-expand">
<a href="link1">One</a>
<ul class="web2py-menu-vertical">
<li><a href="link2">Two</a></li>
</ul>
</li>
</ul>
Element menu może mieć również opcjonalny piąty element, który ma wartość logiczną. Gdy ta wartość wynosi false, to ten element menu jest ignorowany przez helper MENU.
Helper MENU pobiera następujące opcjonalne argumenty:
_class
: ustawia klasę zewnętrznych elementów UL, domyślnie "web2py-menu web2py-menu-vertical";ul_class
: ustawia klasę wewnętrznych elementów UL, domyślnie "web2py-menu-vertical";li_class
: ustawia klasę wewnętrznych elementów LI, domyślnie "web2py-menu-expand";li_first
: pozwala dodać klasę do pierwszego elementu listy;li_last
: pozwala dodać klasę do ostatniego elementu listy.
MENU
pobiera opcjonalny argument mobile
. Gdy zostanie ustawiony na True
, to zamiast budowania menu o rekursywnej strukturze UL
, zwracane jest rozwijane menu SELECT
ze wszystkimi opcjami menu i atrybutem onchange
, który przekierowuje do strony odpowiadającej wybranej opcji. Jest to zaprojektowane jako alternatywne reprezentacja menu, która zwiększa użyteczność dla małych urządzeń mobilnych, takich jak telefony.
Zwykle menu jest używane w układzie z następującą składnią:
{{=MENU(response.menu, mobile=request.user_agent().is_mobile)}}
W ten sposób urządzenie mobilne jest automatycznie wykrywane i odpowiednio renderowane jest menu.
BEAUTIFY
Helper BEAUTIFY
wykorzystywany jest do budowania reprezentacji HTML złożonych obiektów, w tym list, krotek i słowników:
{{=BEAUTIFY({"a": ["hello", XML("world")], "b": (1, 2)})}}
BEAUTIFY
zwraca obiekt podobny do XML serializowalny do XML, z ładnie wyglądającą reprezentacją argumentu swojego konstruktora. W tym przypadku reprezentacja XML:
{"a": ["hello", XML("world")], "b": (1, 2)}
zostanie zrenderowana tak:
<table>
<tr><td>a</td><td>:</td><td>hello<br />world</td></tr>
<tr><td>b</td><td>:</td><td>1<br />2</td></tr>
</table>
Model DOM po stronie serwera a parsowanie
elements
Helper DIV oraz wszystkie helpery pochodne udostępniają metody wyszukujące element
i elements
.
Metoda element
zwraca pierwszy element podrzędny dopasowany do określonego warunku (lub None
jeśli nic nie zostanie dopasowane).
Metoda elements
zwraca wszystkie dopasowane elementy podrzędne.
Metody element i elements używają tej samej składni do określenia warunków dopasowania, dopuszczającej trzy możliwości, które mogą być mieszane i dopasowywane: wyrażenia takie jak jQuery, dopasowanie przez dokładną wartość atrybutu i dopasowanie przy użyciu wyrażeń regularnych.
oto prosty przykład:
>>> a = DIV(DIV(DIV('a', _id='target',_class='abc')))
>>> d = a.elements('div#target')
>>> d[0][0] = 'changed'
>>> print a
<div><div><div id="target" class="abc">changed</div></div></div>
Nienazwany argument metody elements
jest ciągiem znakowym, który może zawierać: nazwę znacznika, id znacznika poprzedzonego znakiem funta, nazwą klasy poprzedzoną kropka, jawną wartością atrybutu w nawiasach kwadratowych.
Oto 4 równoważne sposoby wyszukiwania poprzedniego znacznika poprzez id:
>>> d = a.elements('#target')
>>> d = a.elements('div#target')
>>> d = a.elements('div[id=target]')
>>> d = a.elements('div',_id='target')
Oto 4 równoważne sposoby wyszukiwania poprzedniego znacznika poprzez klasę:
>>> d = a.elements('.abc')
>>> d = a.elements('div.abc')
>>> d = a.elements('div[class=abc]')
>>> d = a.elements('div',_class='abc')
Do lokalizacji elementu może zostać użyty każdy atrybut (nie tylko id
czy class
), włączając w to wszystkie nazwane atrybuty (element funkcji może pobierać wiele argumentów nazwanych), ale tylko pierwszy element zostanie zwrócony.
Korzystając ze składni jQuery "div#target" możliwe jest określenie wielu kryteriów wyszukiwania rozdzielanych przecinkiem:
>>> a = DIV(SPAN('a', _id='t1'), DIV('b', _class='c2'))
>>> d = a.elements('span#t1, div.c2')
lub równoważnie:
>>> a = DIV(SPAN('a', _id='t1'), DIV('b', _class='c2'))
>>> d = a.elements('span#t1', 'div.c2')
Jeśli wartość atrybutu została określona przy użyciu nazwanego argumentu, to może być ciągiem znakowym lub wyrażeniem regularnym:
>>> a = DIV(SPAN('a', _id='test123'), DIV('b', _class='c2'))
>>> d = a.elements('span', _id=re.compile('test\d{3}')
Specjalnym nazwanym argumentem helpera DIV (i helperów pochodnych) jest find
. Można go stosować do określenia wyszukiwanej wartości lub wyrażenia regularnego w treści tekstu znacznika. Na przykład:
>>> a = DIV(SPAN('abcde'), DIV('fghij'))
>>> d = a.elements(find='bcd')
>>> print d[0]
<span>abcde</span>
lub
>>> a = DIV(SPAN('abcde'), DIV('fghij'))
>>> d = a.elements(find=re.compile('fg\w{3}'))
>>> print d[0]
<div>fghij</div>
components
Oto przykład uzyskania wykazu wszystkich elementów w łańcuchu html:
html = TAG('<a>xxx</a><b>yyy</b>')
for item in html.components: print item
parent
i siblings
parent
zwraca element nadrzędny bieżącego elementu.
>>> a = DIV(SPAN('a'),DIV('b'))
>>> s = a.element('span')
>>> d = s.parent
>>> d['_class']='abc'
>>> print a
<div class="abc"><span>a</span><div>b</div></div>
>>> for e in s.siblings(): print e
<div>b</div>
Zastępowanie elementów
Dopasowane elementy można również wymieniać lub usuwać podając argument replace
. Lista oryginalnie dopasowanych elementów jest zwracana nadal w zwykły sposób.
>>> a = DIV(SPAN('x'), DIV(SPAN('y'))
>>> b = a.elements('span', replace=P('z')
>>> print a
<div><p>z</p><div><p>z</p></div>
Argument replace
może być wywoływalny. W takim przypadku przekazuje on oryginalny element i oczekiwany jest zwrot zamienionego elementu:
>>> a = DIV(SPAN('x'), DIV(SPAN('y'))
>>> b = a.elements('span', replace=lambda t: P(t[0])
>>> print a
<div><p>x</p><div><p>y</p></div>
Jeśli replace=None
, to dopasowane elementy zostaną całkowicie usunięte.
>>> a = DIV(SPAN('x'), DIV(SPAN('y'))
>>> b = a.elements('span', replace=None)
>>> print a
<div></div>
flatten
Metoda flatten rekursywnie serializuje zawartość elementów podrzędnych danego elementu do zwykłego tekstu (bez znaczników):
>>> a = DIV(SPAN('this', DIV('is', B('a'))), SPAN('test'))
>>> print a.flatten()
thisisatest
Metoda flatten może przekazywać opcjonalny argument render
, czyli funkcję, która renderuje (spłaszcza) zawartość przy użyciu innego protokołu. Oto przykład serializacji kilku znaczników do składni wiki Markmin:
>>> a = DIV(H1('title'), P('example of a ', A('link', _href='#test')))
>>> from gluon.html import markmin_serializer
>>> print a.flatten(render=markmin_serializer)
# titles
example of [[a link #test]]
W chwili pisania tego rozdziału udostępniamy markmin_serializer
i markdown_serializer
.
Parsowanie
Obiekt TAG jest również parserem XML/HTML. Może odczytywać tekst i konwertować do struktury drzewiastej helperów. Pozwala to na manipulowanie przy wykorzystaniu powyższego API:
>>> html = '<h1>Title</h1><p>this is a <span>test</span></p>'
>>> parsed_html = TAG(html)
>>> parsed_html.element('span')[0]='TEST'
>>> print parsed_html
<h1>Title</h1><p>this is a <span>TEST</span></p>
Układ strony
Widoki można rozszerzać i wstawiać jeden widok w drugi tworząc drzewiastą strukturę.
Na przykład widok "index.html" rozszerza "layout.html" i wstawiany jest w nim "body.html". Równocześnie w "layout.html" wstawiany jest "header.html" i "footer.html".
Korzeniem tego drzewa jest to co nazywamy układem strony (ang. page layout). Można go edytować, jak każdy inny plik szablonowy HTML, wykorzystując interfejs administracyjny web2py. Nazwa pliku "layout.html", to tylko konwencja.
Oto minimalna strona rozszerzająca widok "layout.html" i zawierająca widok "page.html":
{{extend 'layout.html'}}
<h1>Hello World</h1>
{{include 'page.html'}}
Plik układu możliwego do rozszerzania musi zawierać dyrektywę {{include}}
, podobnie jak tu:
<html>
<head>
<title>Page Title</title>
</head>
<body>
{{include}}
</body>
</html>
Podczas wywoływania widoku ładowany jest widok (układ) będący bazą rozszerzenia oraz wywoływany jest wewnątrz tego układu widok zamieniający dyrektywę {{include}}
. Przetwarzanie jest kontynuowane dopóki wszystkie dyrektywy extend
i include
nie zostaną przetworzone. Wynikowy szablon jest następnie tłumaczony na kod Pythona. Proszę mieć na uwadze, że gdy aplikacja jest skompilowana do kodu bajtowego, jest to kod Pythona, który został skompilowany, a nie oryginalne pliki widoku. Tak więc skompilowana do kodu bajtowego wersja określonego widoku jest pojedynczym plikiem .pyc, obejmującym całe drzewo rozszerzonych i wstawianych widoków.
Dyrektywy
extend
,include
,block
isuper
są specjalnymi dyrektywami szablonowymi a nie poleceniami Pythona.
Każda treść poprzedzający dyrektywę {{extend ...}}
zostanie wstawiona (i przetworzona) przed rozpoczęciem przetwarzania rozszerzonej (bazowej) treści widoku. Chociaż nie jest to zwykle używane do wstawiania rzeczywistej zawartości HTML, może być użyteczne do określenia zmiennych lub funkcji, które chce się uczynić dostępnymi dla rozszerzanego widoku. Na przykład, rozważmy widok "index.html":
{{sidebar_enabled=True}}
{{extend 'layout.html'}}
<h1>Home Page</h1>
i fragment z "layout.html":
{{if sidebar_enabled:}}
<div id="sidebar">
Sidebar Content
</div>
{{pass}}
Ponieważ przypisanie sidebar_enabled
w "index.html" następuje przed extend
, to linia pobierająca wstawiona będzie przed rozpoczęciem przetwarzania "layout.html" czyniąc, że sidebar_enabled
będzie dostępny w całym kodzie "layout.html" (nieco bardziej zaawansowaną wersję tego stosuje się w aplikacji welcome).
Warto również mieć na uwadze, że zmienne zwracane przez funkcje kontrolera są dostępne nie tylko w głównym widoku funkcji ale również we wszystkich jego widokach rozszerzających i wstawianych.
Argumentem dyrektyw extend
lub include
(tj. nazwą rozszerzonego lub wstawionego widoku) może być zmienna Pythona (lecz nie wyrażenie Pythona). Jednak nakłada to ograniczenie – widoki używające zmiennych w wyrażeniach extend
lub include
nie mogą być kompilowane do kodu bajtowego. Jak wspomniano powyżej, widoki skompilowane bajtowo zawierają całe drzewo widoków rozszerzających i wstawianych, tak więc określone widoki rozszerzające i wstawiane muszą być znane w czasie kompilacji, co nie jest możliwe, jeśli nazwy tych widoków są zmiennymi (ich wartości nie są określane w w czasie wykonania). Ponieważ widoki skompilowane bajtowo znacząco przyspieszają przetwarzanie, to należy unikać używania zmiennych w argumentach dyrektyw extend
i include
, jak to tylko możliwe.
W niektórych przypadkach, alternatywą dla używania zmiennej w dyrektywie include
jest umieszczenie zwyklego wyrażenia {{include ...}}
w bloku if...else
.
{{if some_condition:}}
{{include 'this_view.html'}}
{{else:}}
{{include 'that_view.html'}}
{{pass}}
Powyższy kod nie stanowi żadnego problemu dla kompilacji bajtowej, ponieważ żadne zmienne nie są zaangażowane. Należy jednak pamiętać, że skompilowany bajtowo widok w rzeczywistości zawiera kod Pythona zarówno dla "this_view.html" jak i "that_view.html", choć tylko kod jednego z nich zostanie wykonany, w zależności od wartości some_condition
.
Trzeba pamiętać, że działa to tylko dla dyrektywy include
-- nie można umieścić dyrektywy {{extend ...}}
w bloku if...else
.
Układy są wykorzystywane do hermetyzacji ujednoliconej strony (nagłówków, stopek, menu) i choć nie są one obowiązkowe, to ułatwiają pisanie i utrzymanie aplikacji. W szczególności sugerujemy pisanie układów korzystających z następujących zmiennych, które mogą być ustawione w kontrolerze. Korzystanie z tych powszechnie znanych zmiennych pomoże uczynić układy wymienialnymi:
response.title
response.subtitle
response.meta.author
response.meta.keywords
response.meta.description
response.flash
response.menu
response.files
Z wyjątkiem menu
i files
, wszystkie te ciągi znakowe i ich znaczenie powinny być oczywiste.
Zmienna response.menu
jest listą trzech lub czterech krotek. Te trzy elementy to: nazwa odnośnika, logiczna reprezentacja tego, czy odnośnik jest aktywny (jest bieżącą pozycją menu) i adres URL linkowanej strony. Na przykład:
response.menu = [('Google', False, 'http://www.google.com',[]),
('Index', True, URL('index'), [])]
Czwarty element jest opcjonalnym podmenu.
Zmienna response.files
jest listą plików CSS i JS, które są wymagane na stronie.
Zalecamy również użycie w sekcji head dokumentu HTML wyrażenie:
{{include 'web2py_ajax.html'}}
ponieważ wstawia to biblioteki jQuery i definiuje kilka funkcji JavaScript wstecznej kompatybilności dla efektów specjalnych i Ajax. Plik "web2py_ajax.html" wstawia w widoku znaczniki response.meta
, podstawowy kod jQuery, okienko kalendarza (ang. datepicker) i wszystkie wymagane w response.files
pliki CSS i JS.
Domyślny układ strony
Układ "views/layout.html" dostarczany wraz ze szkieletową aplikacją welcome ma następującą strukturę (okrojoną z niektórych opcjonalnych części):
<!DOCTYPE html>
<head>
<meta charset="utf-8" />
<title>{{=response.title or request.application}}</title>
...
<script src="{{=URL('static','js/modernizr.custom.js')}}"></script>
{{
response.files.append(URL('static','css/web2py.css'))
response.files.append(URL('static','css/bootstrap.min.css'))
response.files.append(URL('static','css/bootstrap-responsive.min.css'))
response.files.append(URL('static','css/web2py_bootstrap.css'))
}}
{{include 'web2py_ajax.html'}}
{{
# using sidebars need to know what sidebar you want to use
left_sidebar_enabled = globals().get('left_sidebar_enabled',False)
right_sidebar_enabled = globals().get('right_sidebar_enabled',False)
middle_columns = {0:'span12',1:'span9',2:'span6'}[
(left_sidebar_enabled and 1 or 0)+(right_sidebar_enabled and 1 or 0)]
}}
{{block head}}{{end}}
</head>
<body>
<!-- Navbar ================================================== -->
<div class="navbar navbar-inverse navbar-fixed-top">
<div class="flash">{{=response.flash or ''}}</div>
<div class="navbar-inner">
<div class="container">
{{=response.logo or ''}}
<ul id="navbar" class="nav pull-right">
{{='auth' in globals() and auth.navbar(mode="dropdown") or ''}}
</ul>
<div class="nav-collapse">
{{if response.menu:}}
{{=MENU(response.menu)}}
{{pass}}
</div><!--/.nav-collapse -->
</div>
</div>
</div><!--/top navbar -->
<div class="container">
<!-- Masthead ================================================== -->
<header class="mastheader row" id="header">
<div class="span12">
<div class="page-header">
<h1>
{{=response.title or request.application}}
<small>{{=response.subtitle or ''}}</small>
</h1>
</div>
</div>
</header>
<section id="main" class="main row">
{{if left_sidebar_enabled:}}
<div class="span3 left-sidebar">
{{block left_sidebar}}
<h3>Left Sidebar</h3>
<p></p>
{{end}}
</div>
{{pass}}
<div class="{{=middle_columns}}">
{{block center}}
{{include}}
{{end}}
</div>
{{if right_sidebar_enabled:}}
<div class="span3">
{{block right_sidebar}}
<h3>Right Sidebar</h3>
<p></p>
{{end}}
</div>
{{pass}}
</section><!--/main-->
<!-- Footer ================================================== -->
<div class="row">
<footer class="footer span12" id="footer">
<div class="footer-content">
{{block footer}} <!-- this is default footer -->
...
{{end}}
</div>
</footer>
</div>
</div> <!-- /container -->
<!-- The javascript =============================================
(Placed at the end of the document so the pages load faster) -->
<script src="{{=URL('static','js/bootstrap.min.js')}}"></script>
<script src="{{=URL('static','js/web2py_bootstrap.js')}}"></script>
{{if response.google_analytics_id:}}
<script src="{{=URL('static','js/analytics.js')}}"></script>
<script type="text/javascript">
analytics.initialize({
'Google Analytics':{trackingId:'{{=response.google_analytics_id}}'}
});</script>
{{pass}}
</body>
</html>
Istnieje kilka cech tego domyślnego układu, które sprawiają, że jest on bardzo łatwy w użyciu i dostosowaniu:
- Jest napisany w HTML5 i stosuje bibliotekę "modernizr" [modernizr] do zapewnienia kompatybilności wstecznej. Rzeczywisty układ zawiera kilka dodatkowych wyrażeń warunkowych wymaganych przez IE i zostały one tutaj pominięte dla uproszczenia.
- Wyświetla zarówno
response.title
jak iresponse.subtitle
, które mogą zostać ustawione w modelu lub kontrolerze. Jeśli nie zostaną ustawione, przyjmie jako tytuł nazwę aplikacji. - Wstawia w sekcji header plik
web2py_ajax.html
, który generuje wszystkie wyrażenia importulink
iscript
. - Wykorzystuje zmodyfikowaną wersję Twitter Bootstrap dla elastycznych układów strony, które działają na urządzeniach mobilnych i reorganizują kolumny w celu dopasowania strony do małych ekranów.
- Używa "analytics.js" do łączenia się z Google Analytics.
- Wyrażenie
{{=auth.navbar(...)}}
wyświetla powitanie dla bieżącego użytkownika i odnośniki do funkcji uwierzytelniających, takich jak login, logout, register, change password itd., w zależności od kontekstu. Jest to helper fabryka i jego wyjściem można manipulować jak w każdym innym helperze. Jest on umieszczany w bloku{{try:}}...{{except:pass}}
na wypadek gdy uwierzytelniania nie zostanie zdefiniowane. - Wyrażenie
{{=MENU(response.menu)}}
wyświetla strukturę menu jako<ul>...</ul>
. - Wyrażenie
{{include}}
jest zamieniane przez treść rozszerzającego widoku podczas renderowania strony. - Domyślnie wykorzystuje warunkowy układ trzykolumnowy (lewy i prawy pasek boczny może zostać wyłączony przez widoki rozszerzające).
- Używa klas: header, main, footer.
- Zawiera bloki: statusbar, left_sidebar, center, right_sidebar, footer.
W widokach można włączyć i dostosować paski boczne w sposób następujący:
{{left_sidebar_enable=True}}
{{extend 'layout.html'}}
This text goes in center
{{block left_sidebar}}
This text goes in sidebar
{{end}}
Dostosowywanie domyślnego układu
Dostosowywanie domyślnego układu bez jego edytowania jest łatwe, ponieważ aplikacja welcome jest oparta na Twitter Bootstrap, który jest dobrze udokumentowany i obsługuje motywy. W web2py są cztery statyczne pliki istotne dla stylizacji:
- "css/web2py.css" zawiera style specyficzne dla web2py;
- "css/bootstrap.min.css" zawiera style CSS Twitter Bootstrap [bootstrap]; Bootstrap
- "css/web2py_bootstrap.css" zawiera style nadpisujące kilka stylów Bootstrap w celu dostosowania ich do potrzeb web2py;
- "js/bootstrap.min.js" który zawiera biblioteki dla efektów menu, okien modalnych i paneli.
Jeśli chce się zmienić kolory i obraz tła, można spróbować dodać następujący kod do sekcji header pliku layout.html:
<style>
body { background: url('images/background.png') repeat-x #3A3A3A; }
a { color: #349C01; }
.header h1 { color: #349C01; }
.header h2 { color: white; font-style: italic; font-size: 14px;}
.statusbar { background: #333333; border-bottom: 5px #349C01 solid; }
.statusbar a { color: white; }
.footer { border-top: 5px #349C01 solid; }
</style>
Oczywiście można też całkowicie wymienić pliki "layout.html" i "web2py.css" według własnych potrzeb.
Układy na urządzenia mobilne
Domyślnie layout.html jest stworzony tak, aby był przyjazny dla urządzeń mobilnych, ale to może nie wystarczyć. Może zachodzić potrzeba użycia specjalnych widoków, gdy strona jest odwiedzana z urządzenia mobilnego.
W celu wspomożenia pracy nad graficznymi interfejsami dla różnych urządzeń stacjonarnych i przenośnych, web2py oferuje dekorator @mobilize
. Dekorator ten stosuje się do akcji, które mają widok dla urządzeń stacjonarnych i mobilnych. Oto przykład:
from gluon.contrib.user_agent_parser import mobilize
@mobilize
def index():
return dict()
Dekorator musi być importowany przed wykorzystaniem go w kontrolerze. Gdy funkcja "index" jest wywoływana w zwykłej przeglądarce (na komputerze stacjonarnym), web2py będzie renderował zwracany słownik przy użyciu widoku "[controller]/index.html". Lecz gdy funkcja ta będzie wywoływana w urządzeniu mobilnym, słownik będzie renderowany przez widok "[controller]/index.mobile.html". Proszę zauważyć, że mobilny widok ma tutaj rozszerzenie "mobile.html".
Alternatywnie można zastosować następującą logikę do wykonania widoków przyjaznych dla urządzeń mobilnych:
if request.user_agent().is_mobile:
response.view.replace('.html','.mobile.html')
Wprawdzie zadanie utworzenia widoków "*.mobile.html" leży w gestii programisty, ale zalecamy używanie wtyczki "jQuery Mobile", która czyni takie zadanie bardzo prostym.
Funkcje w widokach
Rozważmy widok "layout.html":
<html>
<body>
{{include}}
<div class="sidebar">
{{if 'mysidebar' in globals():}}{{mysidebar()}}{{else:}}
my default sidebar
{{pass}}
</div>
</body>
</html>
i ten widok rozszerzający:
{{def mysidebar():}}
my new sidebar!!!
{{return}}
{{extend 'layout.html'}}
Hello World!!!
Powyższa funkcja jest zdefiniowana przed wyrażeniem {{extend...}}
-- w efekcie funkcja zostaje utworzona zanim wykonany będzie kod "layout.html", tak więc funkcja może być wywołana w każdym miejscu "layout.html", nawet przed wyrażeniem {{include}}
. Trzeba pamiętać, że funkcja jest wstawiana w rozszerzonym widoku bez przedrostka =
.
Kod ten generuje następujące wyjście:
<html>
<body>
Hello World!!!
<div class="sidebar">
my new sidebar!!!
</div>
</body>
</html>
Powyższa funkcja jest zdefiniowana w HTML (choć może też zawierać kod Pythona), tak że response.write
jest użyty do zapisu jej treści (funkcja nie zwraca treści). Dlatego właśnie układ wywołuje funkcję widoku używając {{mysidebar()}}
zamiast {{=mysidebar()}}
. Funkcje zdefiniowane w ten sposób mogą pobierać argumenty.
Bloki w widokach
Podstawowym sposobem ma uczynienie widoków bardziej modularnymi jest użycie wyrażeń {{block...}}
. Mechanizm ten jest alternatywą dla mechanizmu omówionego w poprzednim rozdziale.
Aby zrozumieć jak to działa, rozważmy aplikacje oparte na szkielecie Welcome, który ma widok layout.html. Widok ten jest zawarty w widoku default/index.html
w wyniku wyrażenia {{extend 'layout.html'}}
. Treść layout.html definiuje wstępnie pewne bloki z jakąś zawartością i dlatego są one zawarte w default/index.html.
Można zastąpić domyślne treści bloków załączając nowa treść wewnątrz takiego bloku o tej samej nazwie. Położenie bloku w layout.html nie ulega zmianie, ale treść bloku tak.
Oto wersja uproszczona. Załóżmy, że mamy taki "layout.html":
<html>
<body>
{{include}}
<div class="sidebar">
{{block mysidebar}}
my default sidebar (this content to be replaced)
{{end}}
</div>
</body>
</html>
oraz takie prosty widok rozszerzający default/index.html
:
{{extend 'layout.html'}}
Hello World!!!
{{block mysidebar}}
my new sidebar!!!
{{end}}
Generuje on następujące wyjście, gdzie treść jest dostarczana przez przesłaniający blok w widoku rozszerzającym, ale zakrywany DIV i klasa znajdują się w layout.html. Pozwala to na osiągnięcie spójności widoków:
<html>
<body>
Hello World!!!
<div class="sidebar">
my new sidebar!!!
</div>
</body>
</html>
Rzeczywisty layout.html definiuje kilka pożytecznych bloków i można łatwo dopasować układ do swojej wizji.
Można mieć wiele bloków i jeśli blok jest obecny w widoku rozszerzonym ale nie w widoku rozszerzającym, użyta zostanie treść z widoku rozszerzanego. W przeciwieństwie do funkcji, jest zbyteczne definiowanie bloku przed wyrażeniem {{extend ...}}
-- nawet jeśli zdefiniowano bloki po wyrażeniu extend
, mogą być one użyte do zastąpienia gdziekolwiek w rozszerzanym widoku.
Wewnątrz bloku można używać wyrażenia {{super}}
w celu wstawienia treści widoku nadrzędnego. Na przykład, jeśli wymienimy wyżej omówiony widok rozszerzający na:
{{extend 'layout.html'}}
Hello World!!!
{{block mysidebar}}
{{super}}
my new sidebar!!!
{{end}}
otrzymamy:
<html>
<body>
Hello World!!!
<div class="sidebar">
my default sidebar
my new sidebar!!!
</div>
</body>
</html>