Chapter 5: ビュー
ビュー
web2pyはモデル、コントローラ、ビューのためにPythonを使用しています。ただし、ビューにおいては若干修正したPython構文を用いています。これにより、適切なpythonの利用に制約をかけることなく、より可読性のあるコードが書けるようになります。
ビューの目的は、HTMLドキュメント内にPythonコードを埋め込むことです。これは一般的に、幾つかの問題を引き起こします:
- 埋め込まれたコードは、エスケープすべきか?
- インデントはPythonかHTMLのどちらのルールに基づいてされるべきか?
web2py は {{...}}
をHTMLに埋め込んだPythonコードをエスケープするために使用しています。中括弧を山括弧の代わりに用いる利点は、全ての一般的なHTMLエディタにおいて分かりやすいからです。これによりそれらのエディタでweb2pyのビューを作成することが可能になります。これらの区切り記号は、例えば次のように変更が可能です。
response.delimiters = ('<?','?>')
もしこの行がモデルにある場合、どこにでも適用されます。コントローラにある場合は、コントローラのアクションのビューにのみ適用されます。もしアクション内部にある場合は、そのアクションのビューのみに適用されます。
開発者はHTMLにPythonコードを埋め込むので、ドキュメントはPythonのルールでなく、HTMLのルールに従ってインデントされる必要があります。そのため、web2pyでは、{{ ... }}
のタグの中でインデント不要のPythonを書けるようにしています。Pythonは通常、コードのブロックを区切るためインデントを使用していますが、ここでは別の方法が要求されます。このような理由で、web2pyのテンプレート言語はPythonキーワードの pass
を活用しています。
コードブロックは、コロンで終わる行から始まり、
pass
で始まる行で終わります。キーワードpass
は、ブロックの終わりがコンテキストで明白な場合は必要ありません。
これはその例です:
{{
if i == 0:
response.write('i is 0')
else:
response.write('i is not 0')
pass
}}
pass
はPythonのキーワードで、web2pyのキーワードではないことに注意してください。Emacsなどの幾つかのPythonエディタは、pass
キーワードを用いてブロックの区切りを示し、自動的でコードを再インデントします。
web2pyのテンプレート言語は、Pythonと全く同じ動きをします。次のような記述は:
<html><body>
{{for x in range(10):}}{{=x}}hello<br />{{pass}}
</body></html>
テンプレート言語により、次のようなプログラムに変換されます:
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)
response.write
は、response.body
に書き込みます。
web2pyのビューにエラーがある場合、エラーレポートには、開発者が書いた実際のビューではなく、生成されたコードを表示します。これにより、実行された実際のコードをハイライトするため、コードをデバッグすることが容易になります(そのコードはHTMLエディタやブラウザのDOMインスペクタを使用して、デバッグすることができるものです)。
また注意する点として:
{{=x}}
これは以下のものを生成します
response.write(x)
このようにHTMLに注入された変数は、デフォルトでエスケープされます。 もし x
が XML
オブジェクトなら、escapeを True
にしても、エスケープは無視されます。
ここで示すのは、H1
ヘルパーの利用例です:
{{=H1(i)}}
これは、次に変換されます:
response.write(H1(i))
上記の解釈は、H1
オブジェクトとその要素は再帰的にシリアライズされ、さらにエスケープし、response.bodyに書かれます。H1
と内部HTMLによって生成されたタグはエスケープされません。この仕組みによって、Webページ上に表示される全てのテキスト--そしてテキストのみ--が、常にエスケープされ、それによりXSS脆弱性を防ぐことを保証します。同時に、コードはシンプルでデバッグし易いものとなります。
response.write(obj, escape=True)
メソッドは、出力するオブジェクトとエスケープするかどうか(デフォルトは True
)という2つの引数を取ります。obj
が .xml()
メソッドを持つ場合、メソッドが呼び出され、結果がresponseのbodyに書かれます(escape
引数は無視されます)。それ以外の場合は、オブジェクトの __str__
メソッドがシリアライズするのに用いられ、エスケープ引数が True
ならばエスケープされます。全ての組み込みのヘルパーオブジェクト(H1
など)は、.xml()
メソッドを介して自分自身をシリアライズする方法を知っています。
これは、全て透過的に行われます。response.write
メソッドを明示的に呼び出す必要は決してありません(呼び出すべきではないです)。
基本構文
web2pyのテンプレート言語は、全てのPythonの制御構造をサポートしています。ここでは、それぞれの例を示します。それらは通常のプログラミングのプラクティスに準じてネストさせることができます
for...in
テンプレートでは、どの反復可能オブジェクトに対してループを回すことができます:
{{items = ['a', 'b', 'c']}}
<ul>
{{for item in items:}}<li>{{=item}}</li>{{pass}}
</ul>
これは次のようになります:
<ul>
<li>a</li>
<li>b</li>
<li>c</li>
</ul>
ここで item
は、任意の反復可能オブジェクトです。これは、Pythonのリスト、Pythonのタプル、Rowsオブジェクト、イテレータとして実装された任意のオブジェクトなどです。表示されるオブジェクトは、最初にシリアライズとエスケープされます。
while
whileキーワードを用いてループを作成できます:
{{k = 3}}
<ul>
{{while k > 0:}}<li>{{=k}}{{k = k - 1}}</li>{{pass}}
</ul>
これは次のようになります:
<ul>
<li>3</li>
<li>2</li>
<li>1</li>
</ul>
if...elif...else
条件句を使用できます:
{{
import random
k = random.randint(0, 100)
}}
<h2>
{{=k}}
{{if k % 2:}}is odd{{else:}}is even{{pass}}
</h2>
これは次のようになります:
<h2>
45 is odd
</h2>
else
が最初の if
ブロックを閉じることは明らかなため、ここでは pass
文は必要なく、使用は不適切です。しかし else
ブロックは、pass
文とともに明示的に閉じなければなりません。
Pythonでは "else if" を elif
と書くことに留意して、次の例を見てください:
{{
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>
これは次のようになります:
<h2>
64 is divisible by 4
</h2>
try...except...else...finally
try...except
文をビューの中で使用することもできます。ただし一つ注意があります。次の例を考えてください:
{{try:}}
Hello {{= 1 / 0}}
{{except:}}
division by zero
{{else:}}
no division by zero
{{finally}}
<br />
{{pass}}
これは次のような出力を生成します:
Hello
division by zero
<br />
この例は、例外が発生する前の全ての出力が、tryブロック内でレンダリングされること示しています(例外の前の出力もレンダリングされます)。"Hello" は例外の前にあるので出力されます。
def...return
web2pyのテンプレート言語では、任意のPythonオブジェクトやtext/html文字列を返す関数を定義し実装することができます。ここでは、2つの例を考えます:
{{def itemize1(link): return LI(A(link, _href="http://" + link))}}
<ul>
{{=itemize1('www.google.com')}}
</ul>
これは次のような出力を生成します:
<ul>
<li><a href="http:/www.google.com">www.google.com</a></li>
</ul>
関数 itemize1
は、関数が呼び出された箇所に挿入するヘルパーオブジェクトを返します。
次のコードを考えてみてください:
{{def itemize2(link):}}
<li><a href="http://{{=link}}">{{=link}}</a></li>
{{return}}
<ul>
{{itemize2('www.google.com')}}
</ul>
これは上記と全く同じ出力を生成します。このケースの関数 itemize2
は、関数が呼ばれた場所でweb2pyのタグが置換されることになるHTMLの一部を表現します。itemize2
の呼び出しの手前に '=' がないことに注意してください。これは関数が文字列を返すのではなく、直接レスポンスに書き込んでいるからです。
1つ注意点として、ビュー内で定義された関数はreturn文で終了しなければなりません。そうでないと、自動インデントが失敗します。
HTMLヘルパー
次のようなビューのコードを考えます:
{{=DIV('this', 'is', 'a', 'test', _id='123', _class='myclass')}}
これは次のようにレンダリングされます:
<div id="123" class="myclass">thisisatest</div>
DIV
はヘルパークラスです。つまり、プログラムでHTMLを構築するために使用することができるものです。これは、HTMLの <div>
タグに対応します。
固定引数は、開始タグと終了タグの間に含まれるオブジェクトとして解釈されます。アンダースコアで始まる名前付き引数は、HTMLタグの(アンダースコアなしの)属性として解釈されます。幾つかのヘルパーは、アンダースコアで始まらない名前付き数を持ち、それらの引数はタグ固有のものです。
名前なし引数のセットの代わりに、ヘルパーは *
表記を用いた、単一のリストまたはタプルをコンポーネントのセットとして受け取ることもできます。さらに **
を用いて、単一の辞書を属性のセットとして受け取ることも可能です。以下はその例です:
{{
contents = ['this','is','a','test']
attributes = {'_id':'123', '_class':'myclass'}
=DIV(*contents,**attributes)
}}
(これは前述したものと同じ出力を生成します)
以下のヘルパーのセットは:
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
XML[xml-w] [xml-o] にシリアライズできる、複雑な表現を構築するのに使用することができます。例えば:
{{=DIV(B(I("hello ", "<world>"))), _class="myclass")}}
これは次のようにレンダリングされます:
<div class="myclass"><b><i>hello <world></i></b></div>
ヘルパーは __str__
もしくは xml
メソッドを用いても、同じように文字列にシリアライズすることができます。
>>> print str(DIV("hello world"))
<div>hello world</div>
>>> print DIV("hello world").xml()
<div>hello world</div>
web2pyのヘルパーの仕組みは、文字列を連結せずにHTMLを生成するシステム以上のものです。これは、サーバーサイドのドキュメントオブジェクトモデル(DOM)表現を提供します。
ヘルパーのコンポーネントは、その位置により参照可能です。またヘルパーは、それらコンポーネントに対してリストとして機能します:
>>> 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>
ヘルパーの属性は名前で参照することができ、ヘルパーはそれら属性に対して辞書として機能します:
>>> 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>
全てのコンポーネントのセットは、a.components
というリストを介してアクセスすることができます。また、全ての属性のセットは a.attributes
という辞書を介してアクセスすることができます。そうすると、a[i]
は i
が整数の時 a.components[i]
と等価になります。また、a[s]
は s
が文字列の時 a.attributes[s]
と等価になります。
なおヘルパーの属性は、キーワード引数としてヘルパーに渡すことができます。しかし場合によっては、属性の名前がPythonの識別子で許可されていない特殊文字を含むことがあり(例えば、ハイフンなど)、これらはキーワード引数の名前として利用することができません。例えば:
DIV('text', _data-role='collapsible')
これは "_data-role" がハイフンを含むため動作せず、Pythonシンタックスエラーが発生します。
このような場合、辞書とPythonの **
関数引数の表記を代わりに利用できます。この表記は、(キー:バリュー)ペアの辞書をキーワード引数のセットにマッピングします:
>>> print DIV('text', **{'_data-role': 'collapsible'})
<div data-role="collapsible">text</div>
また、特殊な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
はエスケープされるべきでないテキストを、カプセル化するために使用するオブジェクトです。テキストは有効なXMLが含まれる場合もあれば、そうでない場合もあります。例えば、JavaScriptを含むことができます。
次の例のテキストはエスケープされますが:
>>> print DIV("<b>hello</b>")
<b>hello</b>
次のように XML
を使用してエスケープを防ぐことができます:
>>> print DIV(XML("<b>hello</b>"))
<b>hello</b>
時々、変数に格納されたHTMLをレンダリングしたい場合があります。しかしHTMLはスクリプトなどの、安全でないタグが含まれている可能性があります:
>>> print XML('<script>alert("unsafe!")</script>')
<script>alert("unsafe!")</script>
このようなエスケープされてない実行可能な入力(例えば、ブログのコメント本文への入力)は安全ではありません。これを利用しページの他の訪問者に対して、クロスサイトスクリプティング(XSS)攻撃を生成し、利用することができるからです。
web2pyのXML
ヘルパーは、インジェクションを防ぐためにテキストを無害化することができ、明示的に許可しているものを除き全てのタグをエスケープします。これはその例です:
>>> print XML('<script>alert("unsafe!")</script>', sanitize=True)
<script>alert("unsafe!")</script>
XML
のコンストラクタはデフォルトでは、幾つかのタグの中身とそれらの幾つかの属性が安全であると想定します。このデフォルトは、permitted_tags
とallowd_attributes
というオプション引数で上書きすることができます。以下に示すのは、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']})
組み込みヘルパー
A
このヘルパーは、リンクを生成するために使用されます。
>>> print A('<click>', XML('<b>me</b>'),
_href='http://www.web2py.com')
<a href='http://www.web2py.com'><click><b>me/b></a>
_href
の代わりに、callback
引数を用いてURLを渡すことができます。例えばビューの中で:
{{=A('click me', callback=URL('myaction'))}}
とすると、リンクを押したときの挙動はリダイレクトではなく "myaction" へのajax呼び出しになります。 この場合、target
と delete
という2つの引数を任意に指定することができます:
{{=A('click me', callback=URL('myaction'), target="t")}}
<div id="t"><div>
こうすると、ajaxコールバックのレスポンスは、"t" と一致するidのDIVに格納されます。
<div id="b">{{=A('click me', callback=URL('myaction'), delete='div#b")}}</div>
また上記のレスポンスでは、"div#b" にマッチする最も近いタグが削除されます。この例では、ボタンが消されます。 典型的なアプリケーションは次のようなものです:
{{=A('click me', callback=URL('myaction'), delete='tr")}}
これはテーブル内です。ボタンを押すと、コールバックが実行され、テーブルの行が消されます。
callback
と delete
は組み合わせることができます。
Aヘルパーはcid
という特殊な引数を取ります。これは次のように動作します:
{{=A('linked page', _href='http://example.com', cid='myid')}}
<div id="myid"></div>
リンクをクリックすると、そのdivに中身がロードされるようになります。これは同様なものですが、ページのコンポーネントをリフレッシュするように設計されているため、上の構文よりも強力です。cid
の応用については第12章のコンポーネントで、詳しく説明します。
上記のajaxの機能を利用するにはjQueryと "static/js/web2py_ajax.js" が必要です。これらは {{include 'web2py_ajax.html'}}
をレイアウトのheadに置くことで自動的にインクルードされます。"views/web2py_ajax.html" は、request
に基づいて幾つかの変数を定義し、必要な全てのjsとcssファイルをインクルードします。
B
このヘルパーは、その中身を太字にします。
>>> print B('<hello>', XML('<i>world</i>'), _class='test', _id=0)
<b id="0" class="test"><hello><i>world</i></b>
BODY
このヘルパーは、ページのbodyを作成します。
>>> print BODY('<hello>', XML('<b>world</b>'), _bgcolor='red')
<body bgcolor="red"><hello><b>world</b></body>
BR
このヘルパーは改行を作成します。
>>> print BR()
<br />
ヘルパーは乗算演算子を使用して、次のように繰り返すことができることに注意してください:
>>> print BR()*5
<br /><br /><br /><br /><br />
CAT
このヘルパーは他のヘルパーを連結します。これは、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
このヘルパーは、その内容を中央に配置します。
>>> print CENTER('<hello>', XML('<b>world</b>'),
>>> _class='test', _id=0)
<center id="0" class="test"><hello><b>world</b></center>
CODE
このヘルパーは、Python、C、C++、HTML、そして、web2pyのコードでの構文のハイライトを実現します。これはコードリストに対して、PRE
を使うよりも望ましいです。CODE
はまた、web2pyのAPIドキュメントへのリンクを作成する機能があります。
ここでは、Pythonコードのセクションをハイライトする例を示します:
>>> 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>
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>
以下に示すのは、CODE
ヘルパーのデフォルトの引数です:
CODE("print 'hello world'", language='python', link=None, counter=1, styles={})
language
引数でサポートされている値は、"python"、"html_plain"、"c"、"cpp"、"web2py"、"html" です。"html" 言語では、{{及び}}のタグ は "web2py" のコードとして解釈されます。一方、"html_plain" ではそのように解釈されません。
link
の値が指定される場合、例えば "/examples/global/vars/" とされている場合、コード内のweb2pyのAPIレファレンスがそのリンクURLのドキュメントへ関係付けられます。例えば "request" は、"/examples/global/vars/request" へ関係付けられます。上記の例ではリンクURLは、"global.py" コントローラの "vars" アクションによって処理されます。"global.py" は、web2pyの "examples" アプリケーションの一部として配布されています。
counter
引数は行番号のために使用されます。これは3種類の値のいずれかを設定できます。None
にすると行番号は表示されません。数値で開始番号を指定できます。counterに文字列を設定した場合、プロンプトとして解釈され行番号は表示されません。
styles
引数は少し変わっています。上記のように生成されたHTMLを見ると、2つの列からなるテーブルがあり、各列はCSSを用いてインラインで宣言された独自のスタイルを持つことが分かります。styles
属性は、これら2つのCSSスタイルを上書きできるようにします。例えば次のようにします:
{{=CODE(...,styles={'CODE':'margin: 0;padding: 5px;border: none;'})}}
styles
属性は辞書である必要があり、2つのキーを取りえます: CODE
は実際のコードのスタイル、LINENUMBERS
は行番号を格納する左の列のスタイルです。これらのスタイルは、単純に足すのではなく、デフォルトのスタイルを完全に置き換えることに留意してください。
COL
>>> print COL('a','b')
<col>ab</col>
COLGROUP
>>> print COLGROUP('a','b')
<colgroup>ab</colgroup>
DIV
XML
以外の全てのヘルパーは DIV
から派生し、その基本メソッドを継承しています。
>>> print DIV('<hello>', XML('<b>world</b>'), _class='test', _id=0)
<div id="0" class="test"><hello><b>world</b></div>
EM
その内容を強調します。
>>> print EM('<hello>', XML('<b>world</b>'), _class='test', _id=0)
<em id="0" class="test"><hello><b>world</b></em>
FIELDSET
入力フィールドをラベルと共に作成するために使用されます。
>>> print FIELDSET('Height:', INPUT(_name='height'), _class='test')
<fieldset class="test">Height:<input name="height" /></fieldset>
FORM
これは最も重要なヘルパーの1つです。単純なフォームでは <form>...</form>
タグを作り出すだけですが、ヘルパーはオブジェクトであり、そして含まれているものの情報を持っているので、フォームの投稿処理を行うことができます(例えば、フィールドの検証など)。詳細は第7章で説明します。
>>> print FORM(INPUT(_type='submit'), _action='', _method='post')
<form enctype="multipart/form-data" action="" method="post">
<input type="submit" /></form>
"enctype" はデフォルトでは "multipart/form-data" です。
FORM
と SQLFORM
のコンストラクタは、hidden
という特別な引数を取ることができます。辞書が hidden
として渡される場合、その項目は "隠し" INPUTフィールドへと変換されます。例えば次のようになります:
>>> 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
これらのヘルパーは、段落の見出しと小見出しに利用します:
>>> print H1('<hello>', XML('<b>world</b>'), _class='test', _id=0)
<h1 id="0" class="test"><hello><b>world</b></h1>
HEAD
HTMLページのHEADをタグ付けに利用します。
>>> print HEAD(TITLE('<hello>', XML('<b>world</b>')))
<head><title><hello><b>world</b></title></head>
HTML
このヘルパーは少し異なります。<html>
タグの作成に加えて、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>
HTMLヘルパーは幾つかの追加のオプション引数を取り、次のようなデフォルト値を持ちます:
HTML(..., lang='en', doctype='transitional')
doctypeは'strict'、'transitional'、'frameset'、'html5'、または、完全なdoctype文字列にすることができます。
XHTML
XHTMLはHTMLに似ていますが、代わりにXHTMLのdoctypeを作成します。
XHTML(..., lang='en', doctype='transitional', xmlns='http://www.w3.org/1999/xhtml')
doctypeは、'strict'、'transitional'、'frameset'、または、完全なdoctype文字列にすることができます。 where doctype can be 'strict', 'transitional', 'frameset', or a full doctype string.
HR
このヘルパーは水平ラインをHTMLページに作成します。 This helper creates a horizontal line in an HTML page
>>> print HR()
<hr />
I
このヘルパーはその内容をイタリックにします。 This helper makes its contents italic.
>>> print I('<hello>', XML('<b>world</b>'), _class='test', _id=0)
<i id="0" class="test"><hello><b>world</b></i>
INPUT
<input.../>
タグを作成します。inputタグは、他のタグを含まないので、>
ではなく、/>
で閉じられます。inputタグは、オプション属性 _type
を持ち、"text" (デフォルト)や、"submit"、"checkbox",、"radio" といった値を設定できます。
>>> print INPUT(_name='test', _value='a')
<input value="a" name="test" />
これはまた、"value" という "_value" とは異なる特殊なオプション引数を取ります。後者は、inputフィールドに対してデフォルト値を設定します。一方、前者は現在の値を設定します。"text" タイプの入力の場合、前者は後者を上書きします:
>>> print INPUT(_name='test', _value='a', value='b')
<input value="b" name="test" />
ラジオボタンでは INPUT
は、選択の "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
チェックボックスでも同様です:
>>> 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" />
IFRAME
このヘルパーは、現在のページに別のWebページを取り込みます。他のページのURLは、"_src" 属性で指定します。
>>> print IFRAME(_src='http://www.web2py.com')
<iframe src="http://www.web2py.com"></iframe>
IMG
HTMLに画像を埋め込むために使用できます:
>>> IMG(_src='http://example.com/image.png',_alt='test')
<img src="http://example.com/image.ong" alt="rest" />
次に示すのは、AとIMGとURLのヘルパーを組み合わせて、リンク付きの静止画像を取り込んだものです:
>>> 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>
LABEL
INPUTフィールド用のLABELタグを作成するために使用されます。
>>> print LABEL('<hello>', XML('<b>world</b>'), _class='test', _id=0)
<label id="0" class="test"><hello><b>world</b></label>
LEGEND
フォームの中のフィールドに対する、LEGENDタグを作成するために使用されます。
>>> print LEGEND('Name', _for='myfield')
<legend for="myfield">Name</legend>
LI
リスト項目を作成します。UL
まはた OL
タグの中に含めるべきです。
>>> print LI('<hello>', XML('<b>world</b>'), _class='test', _id=0)
<li id="0" class="test"><hello><b>world</b></li>
META
HTML
のheadで、META
タグを作成するために使用します。例えば:
>>> print META(_name='security', _content='high')
<meta name="security" content="high" />
MARKMIN
markminのwiki構文を実装します。下の例ではmarkminのルールに従って、記述されたテキスト入力をhtml出力に変換します:
>>> 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>
markminの構文は、web2pyに付属している次のファイルで説明しています:
http://127.0.0.1:8000/examples/static/markmin.html
markminを用いて、HTML、LateX、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
(MARKMIN
ヘルパーは markmin2html
のショートカットです)
以下に基本構文の初歩を示します:
SOURCE | OUTPUT |
# title | title |
## section | section |
### subsection | subsection |
**bold** | bold |
''italic'' | italic |
``verbatim`` | verbatim |
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]] | click me |
$ $\int_a^b sin(x)dx$ $ |
画像やビデオ、音声ファイルへのマークアップなしのリンクを単純に含めると、対応する画像、ビデオ、音声ファイルが自動的に組み込まれます(音声とビデオにはHTMLの<audio>と<video>タグを使用します)。
リンクに qr:
プレフィックスを次のように追加すると、
qr:http://web2py.com
対応するQRコードが埋め込まれ、指定したURLへリンクするようになります。
リンクに embed:
プレフィックスを次のように追加すると、
embed:http://www.youtube.com/embed/x1w8hKTJ2Co
埋め込まれたページが表示されます。この場合は、youtubeのビデオが埋め込まれます。
画像はまた、次のような構文で埋め込むことができます。
[[image-description http://.../image.png right 200px]]
順序がないリストは次のように記述します:
- one
- two
- three
+ one
+ two
+ three
テーブルは次のように記述します:
----------
X | 0 | 0
0 | X | 0
0 | 0 | 1
----------
MARKMIN構文はまた、blockquotesや、HTML5のaudio、videoタグ、画像の位置調整、カスタムCSSをサポートし、拡張することができます:
MARKMIN("``abab``:custom", extra=dict(custom=lambda text: text.replace('a','c'))
これは次のものを生成します。
'cbcb'
カスタムブロックは ``...``:<key>
によって区切られ、そしてMARKMINの特別な辞書引数の対応するキーの値として、渡される関数によってレンダリングされます。関数は、XSSを防ぐために出力をエスケープする必要があるかもしれないことに注意してください。
OBJECT
HTMLにオブジェクト(例えば、flashプレーヤーなど)を埋め込むために使用します。
>>> print OBJECT('<hello>', XML('<b>world</b>'),
>>> _src='http://www.web2py.com')
<object src="http://www.web2py.com"><hello><b>world</b></object>
OL
順序付きリストを表します。リストはLIタグを含める必要があります。LI
オブジェクトでない OL
の引数は、自動的に <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
これは後方互換のためにあり、単に True
の別名となります。チェックボックスで利用されますが、True
の方がよりPython的ですので、非推奨になっています。
>>> print INPUT(_type='checkbox', _name='test', _checked=ON)
<input checked="checked" type="checkbox" name="test" />
OPTGROUP
SELECT内の複数のオプションをグループ化することができます。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
これは、SELECT/OPTIONの組み合わせの一部として使用されるべきです。
>>> print OPTION('<hello>', XML('<b>world</b>'), _value='a')
<option value="a"><hello><b>world</b></option>
INPUT
の場合と同じようにweb2pyは、"_value" (OPTIONの値)と "value" (SELECTボックスの現在の値)を区別することができます。もしそれらが等しい場合、このオプションは "selected" になります。
>>> print SELECT('a', 'b', value='b'):
<select>
<option value="a">a</option>
<option value="b" selected="selected">b</option>
</select>
P
段落のタグ付けに利用します。
>>> print P('<hello>', XML('<b>world</b>'), _class='test', _id=0)
<p id="0" class="test"><hello><b>world</b></p>
PRE
整形済みテキストを表示するための <pre>...</pre>
タグを生成します。コードリストには、CODE
ヘルパーの方が一般に望ましいです。
>>> print PRE('<hello>', XML('<b>world</b>'), _class='test', _id=0)
<pre id="0" class="test"><hello><b>world</b></pre>
SCRIPT
JavaScriptなどのスクリプトを組み込む、もしくはリンクします。タグに包まれる内容は、本当に古いブラウザのために、HTMLのコメントとしてレンダリングされます。
>>> print SCRIPT('alert("hello world");', _type='text/javascript')
<script type="text/javascript"><!--
alert("hello world");
//--></script>
SELECT
<select>...</select>
タグを作成します。これは、OPTION
ヘルパーと共に使用されます。OPTION
オブジェクト以外の SELECT
引数は、自動的にoptionに変換されます。
>>> 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
DIV
と似ていますが、(ブロックよりも)インラインのコンテンツをタグ付けするのに使用されます。
>>> print SPAN('<hello>', XML('<b>world</b>'), _class='test', _id=0)
<span id="0" class="test"><hello><b>world</b></span>
STYLE
scriptと似ていますが、CSSコードを組み込む、もしくはリンクするために使用されます。 次に示すのは、CSSを組み込んだ例です:
>>> print STYLE(XML('body {color: white}'))
<style><!--
body { color: white }
//--></style>
また、リンクする例です:
>>> print STYLE(_src='style.css')
<style src="style.css"><!--
//--></style>
TABLE
, TR
, TD
これらのタグは、(オプション的な THEAD
、TBODY
、TFOOTER
ヘルパーと共に)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
は TD
が来ることを想定しています。つまり TD
オブジェクトでない引数は自動的に変換されます:
>>> 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>
Python配列からHTMLテーブルへの変換は、Pythonの *
関数引数の表記を使用すると、リスト要素が関数引数の位置にマッピングされるため容易です。
次の例では、行単位で変換しています:
>>> 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>
次の例では、一度に全ての行を変換しています:
>>> 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
これはヘッダーやフッターとは反対でテーブルのボディを含む行集合をタグ付けするのに使用します。オプションです。
>>> print TBODY(TR('<hello>'), _class='test', _id=0)
<tbody id="0" class="test"><tr><td><hello></td></tr></tbody>
TEXTAREA
<textarea>...</textarea>
タグを作成するヘルパーです。
>>> print TEXTAREA('<hello>', XML('<b>world</b>'), _class='test')
<textarea class="test" cols="40" rows="10"><hello><b>world</b></textarea>
唯一の注意点は、オプションの "value" が、その内容(内部HTML)を上書きすることです。
>>> print TEXTAREA(value="<hello world>", _class="test")
<textarea class="test" cols="40" rows="10"><hello world></textarea>
TFOOT
テーブルのフッターの行集合をタグ付けするのに使用されます。
>>> print TFOOT(TR(TD('<hello>')), _class='test', _id=0)
<tfoot id="0" class="test"><tr><td><hello></td></tr></tfoot>
TH
テーブルのヘッダーで TD
の代わりに使用されます。
>>> print TH('<hello>', XML('<b>world</b>'), _class='test', _id=0)
<th id="0" class="test"><hello><b>world</b></th>
THEAD
テーブルのヘッダーの行集合をタグ付けするために使用されます。
>>> print THEAD(TR(TH('<hello>')), _class='test', _id=0)
<thead id="0" class="test"><tr><th><hello></th></tr></thead>
TITLE
HTMLヘッダーで、ページのタイトルをタグ付けするのに使用されます。
>>> print TITLE('<hello>', XML('<b>world</b>'))
<title><hello><b>world</b></title>
TR
テーブルの行をタグ付けします。テーブルの内部でレンダリングされ、かつ、<td>...</td>
タグを含む必要があります。TD
オブジェクトでない TR
の引数は、自動でに変換されます。
>>> 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
タイプライタ(固定幅)テキストとしてのテキストをタグ付けします。
>>> print TT('<hello>', XML('<b>world</b>'), _class='test', _id=0)
<tt id="0" class="test"><hello><b>world</b></tt>
UL
順序のないリストを示します。LIの項目を含める必要があります。中身がLIとしてタグ付けされていない場合は、ULが自動で変換します。
>>> 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>
embed64
embed64(filename=None, file=None, data=None, extension='image/gif')
は与えられた(バイナリ)データをbase64にエンコードします。
filename: 指定された場合、このファイルを 'rb' モードで開き、読み出します。 file: 指定された場合、このファイルを読み出します。 data: 指定された場合、指定されたデータを使用します。
xmlescape
xmlescape(data, quote=True)
は指定されたデータのエスケープされた文字列を返します。
>>> print xmlescape('<hello>')
<hello>
カスタム・ヘルパー
TAG
独自のXMLタグを生成したい場合があるかもしれません。web2pyは、普遍的なタグ生成機能として TAG
を提供しています。
{{=TAG.name('a', 'b', _c='d')}}
これは、次のようなXMLを生成します。
<name c="d">ab</name>
引数 "a"、"b"、"d" は自動的にエスケープされます。この挙動を抑えるには XML
ヘルパーを用いてください。TAG
を使用し、APIに提供されていないHTML/XMLタグを生成することができます。TAGはネストさせることができ、str()
によってシリアライズすることができます。
同等の構文は次の通りです:
{{=TAG['name']('a', 'b', c='d')}}
TAGオブジェクトが空の名前で作成された場合、囲むためのタグを挿入せずに、複数文字列及びHTMLヘルパーを連結するために使用することができます。しかしこのような用途は、非推奨になりました。代わりに CAT
を使用してください。
TAG
はオブジェクト、そしてTAG.name
もしくはTAG['name']
は一時的なヘルパークラスを返す関数、であることに注意してください。
MENU
MENUヘルパーは、(第4章で説明している) response.menu
の、リストのリストまたはリストのタプルを取り、メニューを表現する順序のないリストを用いてツリー型の構造を生成します。例えば次のようになります:
>>> 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>
各リスト/タプルの第3項目は、HTMLヘルパー(ネストしたヘルパーを含めることができます)にすることができます。そして
MENU
ヘルパーは、単純にレンダリングするヘルパーというよりも、独自の<a>
タグの作成を行います。
各メニュー項目は、ネストするサブメニューを第4引数に持つことができます(同じように再帰的に続きます):
>>> 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>
メニューアイテムは5番目のブール値のオプション要素を持つことができます。falseの場合、メニュー項目はMENUヘルパーによって無視されます。
MENUヘルパーは次のオプション引数を取ります:
_class
: 外側のUL要素のクラスを設定します。デフォルトは "web2py-menu web2py-menu-vertical" です。ul_class
: 内側のUL要素のクラスを設定します。デフォルトは "web2py-menu-vertical" です。li_class
: 内側のLI要素のクラスを設定します。デフォルトは "web2py-menu-expand" です。li_first
: 最初のリスト要素にクラスを追加することができます。li_last
: 最後のリスト要素にクラスを追加することができます。
MENU
は mobile
というオプション引数を取ります。True
が設定された場合、再帰的な UL
メニュー構造を生成するのに代わり、SELECT
ドロップダウンを返します。これは、全てのメニューオプションを持ち、選択オプションに対応したページにリダイレクトする onchange
属性を持ちます。これは電話のような小さなモバイルデバイスで、ユーザービリティを向上させる代替メニュー表示のために設計されています。
通常メニューは、次の構文でレイアウトに使用されます。
{{=MENU(response.menu, mobile=request.user_agent().is_mobile)}}
このようにモバイルデバイスは自動で検知され、それに応じてメニューはレンダリングされます。
BEAUTIFY
BEAUTIFY
は、リストやタプル、辞書を含む、複合的なオブジェクトのHTML表現を構築するために使用されます:
{{=BEAUTIFY({"a": ["hello", XML("world")], "b": (1, 2)})}}
BEAUTIFY
はXMLへとシリアライズされるXMLのようなオブジェクトを、コンストラクタの引数の見栄えの良い表現とともに返します。この場合、次のようなXML表現は:
{"a": ["hello", XML("world")], "b": (1, 2)}
次のようにレンダリングされます:
<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>
サーバーサイドの DOM'' と構文解析
elements
DIVヘルパーと全ての派生ヘルパーは、検索メソッドの element
と elements
を提供します。
element
は、指定した条件にマッチする最初の子供要素を返します(マッチしない場合、Noneを返します)。
elements
は、マッチした全ての子供のリストを返します。
element と elements はマッチ条件を指定するのにも同じ構文を用います。この構文は、次のように使用の許された3種のタイプがあり、混合してマッチングに使用可能です: jQueryのような表現と、正確に属性値と照合するもの、正規表現を使用して照合するものです。
ここでは簡単な例を示します:
>>> 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>
無名引数の elements
は、次のものを含む文字列です: タグの名前、ポンド記号が前に付くタグのID、ドットが前に付くクラス、角括弧内にある属性の明示的な値です。
ここでは、前述のタグをIDで検索するための4つの同等な方法を示します:
>>> d = a.elements('#target')
>>> d = a.elements('div#target')
>>> d = a.elements('div[id=target]')
>>> d = a.elements('div',_id='target')
ここでは、前述のタグをクラスで検索するための4つの同等な方法を示します:
>>> d = a.elements('.abc')
>>> d = a.elements('div.abc')
>>> d = a.elements('div[class=abc]')
>>> d = a.elements('div',_class='abc')
任意の属性は、要素の場所を特定するのに用いることができます(id
や class
だけではありません)。複数の属性(element関数は複数の名前付き引数を取ることができます)を用いることができますが、最初にマッチした要素だけを返します。
jQueryの構文 "div#target" を用いて、スペースで区切られた複数の検索条件を指定することも可能です:
>>> a = DIV(SPAN('a', _id='t1'), DIV('b', _class='c2'))
>>> d = a.elements('span#t1, div.c2')
または、次のように書けます
>>> a = DIV(SPAN('a', _id='t1'), DIV('b', _class='c2'))
>>> d = a.elements('span#t1', 'div.c2')
属性の値が名前付き引数で指定されている場合、文字列か正規表現を取ることができます:
>>> a = DIV(SPAN('a', _id='test123'), DIV('b', _class='c2'))
>>> d = a.elements('span', _id=re.compile('test\d{3}')
DIV(とその派生)ヘルパーの特別な名前付き引数は find
です。これは、タグのテキスト内容に対する、検索値、または、検索の正規表現を指定するために用いられます。例えば次のようになります:
>>> a = DIV(SPAN('abcde'), DIV('fghij'))
>>> d = a.elements(find='bcd')
>>> print d[0]
<span>abcde</span>
もしくは
>>> a = DIV(SPAN('abcde'), DIV('fghij'))
>>> d = a.elements(find=re.compile('fg\w{3}'))
>>> print d[0]
<div>fghij</div>
components
components
ここで、html文字列の全ての要素を列挙する例を示します:
html = TAG('<a>xxx</a><b>yyy</b>')
for item in html.components: print item
parent
と siblings
parent
は、現在の要素の親を返します。
>>> 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>
要素の置換
一致している要素は、replace
引数を指定することにより、置換もしくは削除することもできます。元のマッチング要素のリストは、まだ通常通り返されることに注意してください。
>>> 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>
replace
は呼び出しすることが可能です。この場合、元の要素が渡され、置換要素を返すことを要求されます:
>>> 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>
もし replace=None
の場合、マッチングした要素は完全に削除されます。
>>> a = DIV(SPAN('x'), DIV(SPAN('y'))
>>> b = a.elements('span', replace=None)
>>> print a
<div></div>
flatten
flatten
flattenメソッドは、再帰的に、与えられた要素の子要素の中身を(タグなし)標準テキストにシリアライズします:
>>> a = DIV(SPAN('this', DIV('is', B('a'))), SPAN('test'))
>>> print a.flatten()
thisisatest
flattenでは、オプション引数の render
を渡すことができます。すなわち、異なるプロトコルを使用し、内容をレンダリング/フラット化する関数です。次に示すのは、幾つかのタグをMarkminのwiki構文へとシリアライズする例です:
>>> 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]]
この記述時点では、markmin_serializer
と markdown_serializer
が用意されています。
構文解析
TAGオブジェクトはXML/HTMLの構文解析器でもあります。テキストを読み込んで、ヘルパーのツリー構造へと変換することができます。これにより、上記の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>
ページレイアウト
ビューはツリー状の構造に、拡張や他のビューを取り込むことができます。
例えば、"layout.html" を拡張し、"body.html" を取り込んだ "index.html" というビューを考えることができます。 同時に、"layout.html" は、"header.html" や "footer.html" を取り込むこともあります。
ツリーのルートは、レイアウトビューと呼ばれます。他のTHMLテンプレートファイルと同様に、web2pyの管理インタフェースを用いてそれを編集することができます。ファイル名 "layout.html" は単なる慣例です。
ここでは、"layout.html" ビューを拡張し、"page.html" ビューを取り込んだ最小のページを示します:
{{extend 'layout.html'}}
<h1>Hello World</h1>
{{include 'page.html'}}
拡張したレイアウトファイルは、次のように、{{include}}
ディレクティブを必ず含まなければなりません:
<html>
<head>
<title>Page Title</title>
</head>
<body>
{{include}}
</body>
</html>
ビューが呼び出されると、拡張した(レイアウト)ビューが呼び出され、呼び出したビューはレイアウト内部の {{include}}
ディレクティブを置換します。この処理は再帰的に、全ての extend
と include
ディレクティブの処理が終わるまで行われます。そして、結果のテンプレートは、Pythonコードに変換されます。ただし、アプリケーションがバイトコードにコンパイルされている場合、コンパイルされているのはPythonコードであり、オリジナルのビューファイル自身ではありません。したがって、提供されたビューのバイトコードにコンパイルされたされたバージョンは、オリジナルのビューファイルだけでなく、拡張とインクルードしたビューの全体ツリーの、Pythonコードを含む単一の.pycファイルになります。
extend
、include
、block
、super
、はテンプレート固有のディレクティブで、Pythonのコマンドではありません。
{{extend ...}}
ディレクティブの前にある、どのコンテンツまたはコードも、拡張したビューのコンテンツ/コードの始まりの前に挿入(したがって実行)されます。実際、HTMLコンテンツを、拡張ビューのコンテンツの前に挿入することは稀です。しかし拡張したビューで利用可能にする、変数や関数を定義するのに便利です。例えば、次のような "index.html" ビューを考えます:
{{sidebar_enabled=True}}
{{extend 'layout.html'}}
<h1>Home Page</h1>
そして、次のような "layout.html" の引用を考えます:
{{if sidebar_enabled:}}
<div id="sidebar">
Sidebar Content
</div>
{{pass}}
"index.html" における extend
の前の sidebar_enabled
の指定によって、"layout.html" が始まる前にその行が挿入され、"layout.html" のコード内のどの場所でも sidebar_enabled
が有効になります(この洗練されたバージョンが welcome アプリにおいて使用されています)。
なおコントローラの関数から返された変数は、その関数のメインのビューだけでなく、全ての拡張あるいはインクルードしたビュー中でも利用可能です。
extend
もしくは include
の引数(つまり拡張もしくはインクルードしたビューの名前)は、python変数(python式ではありません)にすることができます。しかし、これには制約があります。 -- extend
もしくは include
命令文に変数を使用するビューは、バイトコードにコンパイルすることはできません。前述したとおり、バイトコードにコンパイルされたビューは、拡張及びインクルードしたビューの全体ツリーを含んでいます。このため拡張及びインクルードされた指定のビューは、コンパイル時点で判明している必要があります。ビューの名前が変数の場合、これが不可能です(その値はランタイム時まで決定できないからです)。バイトコードにコンパイルされているビューは大幅な速度向上が期待できるため、extend
や include
に変数を使用するのは、一般に可能な限り避けたほうがよいでしょう。
場合によっては、include
の変数を用いる代わりに、通常の {{include ...}}
ディレクティブを if...else
ブロックの中に単に置くこともできます。
{{if some_condition:}}
{{include 'this_view.html'}}
{{else:}}
{{include 'that_view.html'}}
{{pass}}
上記のコードでは変数を含んでいないので、バイトコードのコンパイルに関する問題は生じません。ただしバイトコードにコンパイルしたビューは、"this_view.html" と "that_view.html" の両方のPythonコードを、some_condition
の値によって片方しか実行されない場合でも含んでいます。
注意として、これは include
に対してのみ機能します。 -- {{extend ...}}
指示文は if...else
ブロックの中に置くことはできません。
レイアウトは、ページの共通性(ヘッダー、フッター、メニュー)をカプセル化するために使用されます。それらは必須ではないですが、アプリケーションを書きやすく、保守しやすくします。とりわけコントローラで設定することのできる、次の変数を活用するレイアウトを書くことを推奨します。これらのよく知られた変数を用いることは、レイアウトを交換可能にするのに役立ちます:
response.title
response.subtitle
response.meta.author
response.meta.keywords
response.meta.description
response.flash
response.menu
response.files
menu
と files
を除いて、これらの全ての文字列と意味は明らかです。
response.menu
のメニューは、3-タプル、または、4-タプルからなるリストです。その3つの要素は、リンクの名前、リンクがアクティブ(現在のリンク)かどうかを示すブール値、リンク先のページのURLです。例えば次のようになります:
response.menu = [('Google', False, 'http://www.google.com',[]),
('Index', True, URL('index'), [])]
第4のタプル要素は、オプションのサブメニューです。
response.files
は、ページで必要とするCSSとJSファイルのリストです。
同様に、次のものをHTMLのheadで使用することを推奨しています:
{{include 'web2py_ajax.html'}}
これによって特殊効果やAjaxのための、jQueryライブラリと幾つかの後方互換があるJavaScript関数の定義が組み込まれます。"web2py_ajax.html" は、ビューの response.meta
タグや、jQeuryのベース、カレンダーの日付選択機能、そして必要な全ての response.files
のCSSとJSを含みます。
デフォルトのページのレイアウト
web2pyのひな形アプリケーション welcome (幾つかのオプションパーツは省かれています)に付属する "views/layout.html" はかなり複雑ですが、しかし次のような構造を持っています:
<!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>
このデフォルトのレイアウトにはちょっとした特徴があり、非常に使いやすく、カスタマイズしやすいようになっています:
- HTML5で書かれ、
modernizr
を用いてます。[modernizr] は後方互換性のためのライブラリです。実際のレイアウトではIEで必要とされる追加の条件文を含みますが、簡潔にするため無視します。 - モデルで設定することのできる
response.title
とresponse.subtitle
を表示します。設定されていない場合は、アプリケーションの名前がタイトルとなります。 web2py_ajax.html
ファイルをヘッダーに組み込みます。これは、全てのリンクおよびスクリプトのインポート命令文を生成します。- 柔軟なレイアウトのために、Twitter Bootstrap の修正したバージョンを使用しています。モバイルデバイス上で機能し、小さなスクリーンにフィットするようにコラム(列)を再配置します。
- Google Analytics への接続のため、"analytics.js" を使用しています。
{{=auth.navbar(...)}}
は、現在のユーザーへのあいさつ文を表示すると共に、login、logout、register、change passwordなどのauth関数へのリンクを、内容に応じて表示します。これはヘルパーのファクトリーで、その出力は他のヘルパーと同様に操作することができます。authが定義されていない時のために、{{try:}}...{{except:pass}}
の中に設置されています。{{=MENU(response.menu)}}
は、<ul>...</ul>
のようにメニュー構造を表示します。{{include}}
はページがレンダリングされる時に、拡張先のビューの内容に置き換えられます。- デフォルトでは、条件付きの3カラムです(左、右のサイドバーは拡張先のビューで消すことができます)。
- 次のようなクラスを用いています: header, main, footer
- 次のようなブロックを用いています: statusbar, left_sidebar, center, right_sidebar, footer
ビューでは、次のようにサイドバーの有効化とカスタマイズが可能です:
{{left_sidebar_enable=True}}
{{extend 'layout.html'}}
This text goes in center
{{block left_sidebar}}
This text goes in sidebar
{{end}}
デフォルトレイアウトのカスタマイズ
編集せずにデフォルトレイアウトをカスタマイズするのは簡単です。なぜなら、よく文書化されており、テーマもサポートする Twitter Bootstrapが、welcomeアプリケーションのベースだからです。web2pyのスタイルに関連する、4つの静的ファイルは次の通りです:
- "css/web2py.css" には、web2pyの特定のスタイルが含まれています。
- "css/bootstrap.min.css" には、Twitter Bootstrap CSS [bootstrap] Bootstrapが含まれています。
- "css/web2py_bootstrap.css" にはweb2pyのニーズに適合するよう、上書きした一部のBootstrapスタイルが含まれます。
- "js/bootstrap.min.js" には、メニューエフェクト、モーダル、パネル用のライブラリが含まれています。
色や背景画像を変更するには、次のようなコードをlayout.html headerに追加してみてください:
<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>
もちろん、"layout.html" や "web2py.css" ファイルを、独自のものに完全に置き換えることも可能です。
モバイル開発
デフォルトのlayout.htmlはモバイルデバイスに親和性があるように設計されていますが、十分とは言えません。ページがモバイルデバイスによって訪問されたときに、異なるビューを使用する必要があるかもしれません。
デスクトップとモバイルデバイスの開発を容易にするため、web2pyは @mobilize
デコレータを用意しています。このデコレータは、通常のビューとモバイルビューを持たなければいけないアクションに対して適用されます。ここでは、その利用例を示します:
from gluon.contrib.user_agent_parser import mobilize
@mobilize
def index():
return dict()
デコレータはコントローラで使用する前に、インポートされている必要があります。 "index" 関数が通常のブラウザ(デスクトップコンピュータ)から呼ばれる時は、web2pyは "[controller]/index.html" のビューを用いて、返される辞書をレンダリングします。しかしモバイルデバイスから呼ばれる時は、その辞書は "[controller]/index.mobile.html" によってレンダリングされます。すなわち、モバイルのビューは "mobile.html" 拡張子を持つことになります。
代わりに次のようなロジックを適用し、全てのビューをモバイルフレンドリーにすることができます:
if request.user_agent().is_mobile:
response.view.replace('.html','.mobile.html')
"*.mobile.html" ビューを作成するタスクは開発者に残されますが、そのタスクをとても簡単にする "jQuery Mobile" プラグインを使用することを強く推奨します。
ビュー内の関数
次の "layout.html" を考えます:
<html>
<body>
{{include}}
<div class="sidebar">
{{if 'mysidebar' in globals():}}{{mysidebar()}}{{else:}}
my default sidebar
{{pass}}
</div>
</body>
</html>
そして、次の拡張ビューも考えます。
{{def mysidebar():}}
my new sidebar!!!
{{return}}
{{extend 'layout.html'}}
Hello World!!!
ここで関数が {{extend...}}
文の前に定義されていることに注意してください。 -- これにより関数は "layout.html" コードが実行される前に作成され、その関数は "layout.html" 内の任意の場所、例えば {{include}}
の前でも呼び出すことができます。また関数が拡張元のビューで、=
プレフィックスが付かずに組み込まれていることに注意してください。
このコードは次のような出力を生成します:
<html>
<body>
Hello World!!!
<div class="sidebar">
my new sidebar!!!
</div>
</body>
</html>
この関数がHTML(これはまたPythonコードを含むことができます)の中で定義されることで、response.write
がその内容を書くために使用されていることに注意してください(関数は内容を返しません)。レイアウトで {{=mysidebar()}}
ではなく {{mysidebar()}}
を用いてビュー関数を呼び出している理由は、ここにあります。このような方法で定義された関数は、引数を取ることができます。
ビュー内のブロック
ビューをよりモジュール化するための主な方法は、{{block...}}
を使用することです。この仕組は、前のセクションで説明したメカニズムに代わるものです。
次の"layout.html"を考えます:
<html>
<body>
{{include}}
<div class="sidebar">
{{block mysidebar}}
my default sidebar
{{end}}
</div>
</body>
</html>
そして、次の拡張先ビューも考えます。
{{extend 'layout.html'}}
Hello World!!!
{{block mysidebar}}
my new sidebar!!!
{{end}}
これは次のような出力を生成します:
<html>
<body>
Hello World!!!
<div class="sidebar">
my new sidebar!!!
</div>
</body>
</html>
ブロックは複数定義することができます。拡張元のビューにブロックがあるが、拡張先のビューにはない場合、拡張元のビューの内容が使用されます。また関数と異なり、{{extend ...}}
の前にブロックを定義する必要はありません。 -- extend
の後で定義されても、拡張元ビュー中の任意の場所で置換できます。
ブロックの内部では、{{super}}
という式を使用して、親の内容を組み込むことができます。例えば、上記の拡張先ビューを次のように置換する場合:
{{extend 'layout.html'}}
Hello World!!!
{{block mysidebar}}
{{super}}
my new sidebar!!!
{{end}}
以下が得られます:
<html>
<body>
Hello World!!!
<div class="sidebar">
my default sidebar
my new sidebar!!!
</div>
</body>
</html>