Chapter 5: The views
The views
web2py uses Python for its models, controllers, and views, although it uses a slightly modified Python syntax in the views to allow more readable code without imposing any restrictions on proper Python usage.
The purpose of a view is to embed code (Python) in an HTML document. In general, this poses some problems:
- How should embedded code be escaped?
- Should indenting be based on Python or HTML rules?
web2py uses {{ ... }}
to escape Python code embedded in HTML. The advantage of using curly brackets instead of angle brackets is that it's transparent to all common HTML editors. This allows the developer to use those editors to create web2py views. These delimiters can be changed for example with
response.delimiters = ('<?', '?>')
If this line is in a model it will be applied everywhere, if in a controller only to views for the controller actions, if inside an action only to the view for that action.
Since the developer is embedding Python code into HTML, the document should be indented according to HTML rules, and not Python rules. Therefore, we allow unindented Python inside the {{ ... }}
tags. Since Python normally uses indentation to delimit blocks of code, we need a different way to delimit them; this is why the web2py template language makes use of the Python keyword pass
.
A code block starts with a line ending with a colon and ends with a line beginning with
pass
. The keywordpass
is not necessary when the end of the block is obvious from the context.
Here is an example:
{{
if i == 0:
response.write('i is 0')
else:
response.write('i is not 0')
pass
}}
Note that pass
is a Python keyword, not a web2py keyword. Some Python editors, such as Emacs, use the keyword pass
to signify the division of blocks and use it to re-indent code automatically.
The web2py template language does exactly the same. When it finds something like:
<html><body>
{{for x in range(10):}}{{=x}}hello<br />{{pass}}
</body></html>
it translates it into a program:
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
writes to the response.body
.
When there is an error in a web2py view, the error report shows the generated view code, not the actual view as written by the developer. This helps the developer debug the code by highlighting the actual code that is executed (which is something that can be debugged with an HTML editor or the DOM inspector of the browser).
Also note that:
{{=x}}
generates
response.write(x)
Variables injected into the HTML in this way are escaped by default. The escaping is ignored if x
is an XML
object, even if escape is set to True
.
Here is an example that introduces the H1
helper:
{{=H1(i)}}
which is translated to:
response.write(H1(i))
upon evaluation, the H1
object and its components are recursively serialized, escaped and written to the response body. The tags generated by H1
and inner HTML are not escaped. This mechanism guarantees that all text --- and only text --- displayed on the web page is always escaped, thus preventing XSS vulnerabilities. At the same time, the code is simple and easy to debug.
The method response.write(obj, escape=True)
takes two arguments, the object to be written and whether it has to be escaped (set to True
by default). If obj
has an .xml()
method, it is called and the result written to the response body (the escape
argument is ignored). Otherwise it uses the object's __str__
method to serialize it and, if the escape argument is True
, escapes it. All built-in helper objects (H1
in the example) are objects that know how to serialize themselves via the .xml()
method.
This is all done transparently. You never need to (and never should) call the response.write
method explicitly.
Basic syntax
The web2py template language supports all Python control structures. Here we provide some examples of each of them. They can be nested according to usual programming practice.
for...in
In templates you can loop over any iterable object:
{{items = ['a', 'b', 'c']}}
<ul>
{{for item in items:}}<li>{{=item}}</li>{{pass}}
</ul>
which produces:
<ul>
<li>a</li>
<li>b</li>
<li>c</li>
</ul>
Here items
is any iterable object such as a Python list, Python tuple, or Rows object, or any object that is implemented as an iterator. The elements displayed are first serialized and escaped.
while
You can create a loop using the while keyword:
{{k = 3}}
<ul>
{{while k > 0:}}<li>{{=k}}{{k = k - 1}}</li>{{pass}}
</ul>
which produces:
<ul>
<li>3</li>
<li>2</li>
<li>1</li>
</ul>
if...elif...else
You can use conditional clauses:
{{
import random
k = random.randint(0, 100)
}}
<h2>
{{=k}}
{{if k % 2:}}is odd{{else:}}is even{{pass}}
</h2>
which produces:
<h2>
45 is odd
</h2>
Since it is obvious that else
closes the first if
block, there is no need for a pass
statement, and using one would be incorrect. However, you must explicitly close the else
block with a pass
.
Recall that in Python "else if" is written elif
as in the following example:
{{
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>
It produces:
<h2>
64 is divisible by 4
</h2>
try...except...else...finally
It is also possible to use try...except
statements in views with one caveat. Consider the following example:
{{try:}}
Hello {{= 1 / 0}}
{{except:}}
division by zero
{{else:}}
no division by zero
{{finally:}}
<br />
{{pass}}
It will produce the following output:
Hello division by zero
<br />
This example illustrates that all output generated before an exception occurs is rendered (including output that preceded the exception) inside the try block. "Hello" is written because it precedes the exception.
def...return
The web2py template language allows the developer to define and implement functions that can return any Python object or a text/html string. Here we consider two examples:
{{def itemize1(link): return LI(A(link, _href="http://" + link))}}
<ul>
{{=itemize1('www.google.com')}}
</ul>
produces the following output:
<ul>
<li><a href="http:/www.google.com">www.google.com</a></li>
</ul>
The function itemize1
returns a helper object that is inserted at the location where the function is called.
Consider now the following code:
{{def itemize2(link):}}
<li><a href="http://{{=link}}">{{=link}}</a></li>
{{return}}
<ul>
{{itemize2('www.google.com')}}
</ul>
It produces exactly the same output as above. In this case, the function itemize2
represents a piece of HTML that is going to replace the web2py tag where the function is called. Notice that there is no '=' in front of the call to itemize2
, since the function does not return the text, but it writes it directly into the response.
There is one caveat: functions defined inside a view must terminate with a return
statement, or the automatic indentation will fail.
HTML helpers
Consider the following code in a view:
{{=DIV('this', 'is', 'a', 'test', _id='123', _class='myclass')}}
it is rendered as:
<div id="123" class="myclass">thisisatest</div>
DIV
is a helper class, i.e., something that can be used to build HTML programmatically. It corresponds to the HTML <div>
tag.
Positional arguments are interpreted as objects contained between the open and close tags. Named arguments that start with an underscore are interpreted as HTML tag attributes (without the underscore). Some helpers also have named arguments that do not start with underscore; these arguments are tag-specific.
Instead of a set of unnamed arguments, a helper can also take a single list or tuple as its set of components using the *
notation and it can take a single dictionary as its set of attributes using the **
, for example:
{{
contents = ['this', 'is', 'a', 'test']
attributes = {'_id':'123', '_class':'myclass'}
=DIV(*contents, **attributes)
}}
(produces the same output as before).
The following set of helpers:
A
, ASSIGNJS
, 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
can be used to build complex expressions that can then be serialized to XML[xml-w] [xml-o]. For example:
{{=DIV(B(I("hello ", "<world>")), _class="myclass")}}
is rendered:
<div class="myclass"><b><i>hello <world></i></b></div>
Helpers can also be serialized into strings, equivalently, with the __str__
and the xml
methods:
>>> print str(DIV("hello world"))
<div>hello world</div>
>>> print DIV("hello world").xml()
<div>hello world</div>
The helpers mechanism in web2py is more than a system to generate HTML without concatenating strings. It provides a server-side representation of the Document Object Model (DOM).
Components of helpers can be referenced via their position, and helpers act as lists with respect to their components:
>>> 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>
Attributes of helpers can be referenced by name, and helpers act as dictionaries with respect to their attributes:
>>> 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>
Note, the complete set of components can be accessed via a list called a.components
, and the complete set of attributes can be accessed via a dictionary called a.attributes
. So, a[i]
is equivalent to a.components[i]
when i
is an integer, and a[s]
is equivalent to a.attributes[s]
when s
is a string.
Notice that helper attributes are passed as keyword arguments to the helper. In some cases, however, attribute names include special characters that are not allowed in Python identifiers (e.g., hyphens) and therefore cannot be used as keyword argument names. For example:
DIV('text', _data-role='collapsible')
will not work because "_data-role" includes a hyphen, which will produce a Python syntax error.
In such cases you have a couple of options. You can use the data
argument (this time without a leading underscore) to pass a dictionary of related attributes without their leading hyphen, and the output will have the desired combinations e.g.
>>> print DIV('text', data={'role': 'collapsible'})
<div data-role="collapsible">text</div>
or you can instead pass the attributes as a dictionary and make use of Python's **
function arguments notation, which maps a dictionary of (key:value) pairs into a set of keyword arguments:
>>> print DIV('text', **{'_data-role': 'collapsible'})
<div data-role="collapsible">text</div>
Note that more elaborate entries will introduce HTML character entities, but they will work nonetheless e.g.
>>> print DIV('text', data={'options':'{"mode":"calbox", "useNewStyle":true}'})
<div data-options="{"mode":"calbox", "useNewStyle":true}">text</div>
You can also dynamically create special TAGs:
>>> print TAG['soap:Body']('whatever', **{'_xmlns:m':'http://www.example.org'})
<soap:Body xmlns:m="http://www.example.org">whatever</soap:Body>
XML
XML
is an object used to encapsulate text that should not be escaped. The text may or may not contain valid XML. For example, it could contain JavaScript.
The text in this example is escaped:
>>> print DIV("<b>hello</b>")
<div><b>hello</b></div>
by using XML
you can prevent escaping:
>>> print DIV(XML("<b>hello</b>"))
<div><b>hello</b></div>
Sometimes you want to render HTML stored in a variable, but the HTML may contain unsafe tags such as scripts:
>>> print XML('<script>alert("unsafe!")</script>')
<script>alert("unsafe!")</script>
Un-escaped executable input such as this (for example, entered in the body of a comment in a blog) is unsafe, because it can be used to generate Cross Site Scripting (XSS) attacks against other visitors to the page.
The web2py XML
helper can sanitize our text to prevent injections and escape all tags except those that you explicitly allow. Here is an example:
>>> print XML('<script>alert("unsafe!")</script>', sanitize=True)
<script>alert("unsafe!")</script>
The XML
constructors, by default, consider the content of some tags and some of their attributes safe. You can override the defaults using the optional permitted_tags
and allowed_attributes
arguments. Here are the default values of the optional arguments of the XML
helper.
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']})
Built-in helpers
A
This helper is used to build links.
>>> print A('<click>', XML('<b>me</b>'),
_href='http://www.web2py.com')
<a href='http://www.web2py.com'><click><b>me</b></a>
Instead of _href
you can pass the URL using the callback
argument. For example in a view:
{{=A('click me', callback=URL('myaction'))}}
and the effect of pressing the link will be an ajax call to "myaction" instead of a redirection. In this case, optionally you can specify two more arguments: target
and delete
:
{{=A('click me', callback=URL('myaction'), target='t')}}
<div id="t"><div>
and the response of the ajax callback will replace the content (inner HTML) of the DIV with id equal to "t".
<div id="b">{{=A('click me', callback=URL('myaction'), delete='div#b')}}</div>
and upon response, the closest tag matching "div#b" will be deleted. In this case, the whole DIV with the link will be deleted.
A typical application is:
{{=A('click me', callback=URL('myaction'), delete='tr')}}
in a table. Using the link will perform the callback and delete the table row.
target
and delete
can be combined.
The A helper takes a special argument called cid
. It works as follows:
{{=A('linked page', _href='http://example.com', cid='myid')}}
<div id="myid"></div>
and a click on the link causes the content to be loaded in the div. This is similar but more powerful than the above syntax since it is designed to refresh page components. We discuss applications of cid
in more detail in Chapter 12, in the context of components.
These ajax features require jQuery and "static/js/web2py_ajax.js", which are automatically included by placing
{{include 'web2py_ajax.html'}}
request
and includes all necessary js and css files.ASSIGNJS
ASSIGNJS allows a server-side value to be used as a client-side javascript value.
For instance, if in a controller you write
return dict(stra='abcd', obj=alist)
and in a view write
<script>
{{=ASSIGNJS(o=obj)}}
...
Then the javascript variable o will have the value passed in obj, which is alist.
A further example is shown below under Javascript In Views.
B
This helper makes its contents bold.
>>> print B('<hello>', XML('<i>world</i>'), _class='test', _id=0)
<b id="0" class="test"><hello><i>world</i></b>
BODY
This helper makes the body of a page.
>>> print BODY('<hello>', XML('<b>world</b>'), _bgcolor='red')
<body bgcolor="red"><hello><b>world</b></body>
BR
This helper creates a line break.
>>> print BR()
<br />
Notice that helpers can be repeated using the multiplication operator:
>>> print BR()*5
<br /><br /><br /><br /><br />
CAT
This helper concatenates other helpers, same as 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
This helper centers its content.
>>> print CENTER('<hello>', XML('<b>world</b>'), _class='test', _id=0)
<center id="0" class="test"><hello><b>world</b></center>
CODE
This helper performs syntax highlighting for Python, C, C++, HTML and web2py code, and is preferable to PRE
for code listings. CODE
also has the ability to create links to the web2py API documentation.
Here is an example of highlighting sections of Python code.
>>> print CODE('print "hello"', language='python').xml()
<table><tr style="vertical-align:top;">
<td style="min-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;
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;
white-space: pre !important;
"><span style="color:#185369; font-weight: bold">print </span>
<span style="color: #FF9966">"hello"</span></pre></td></tr></table>
Here is a similar example for 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>
These are the default arguments for the CODE
helper:
CODE("print 'hello world'", language='python', link=None, counter=1, styles={})
Supported values for the language
argument are "python", "html_plain", "c", "cpp", "web2py", and "html". The "html" language interprets {{ and }} tags as "web2py" code, while "html_plain" doesn't.
If a link
value is specified, for example "/examples/global/vars/", web2py API references in the code are linked to documentation at the link URL. For example "request" would be linked to "/examples/global/vars/request". In the above example, the link URL is handled by the "vars" action in the "global.py" controller that is distributed as part of the web2py "examples" application.
The counter
argument is used for line numbering. It can be set to any of three different values. It can be None
for no line numbers, a numerical value specifying the start number, or a string. If the counter is set to a string, it is interpreted as a prompt, and there are no line numbers.
The styles
argument is a bit tricky. If you look at the generated HTML above, it contains a table with two columns, and each column has its own style declared inline using CSS. The styles
attributes allows you to override those two CSS styles. For example:
CODE(..., styles={'CODE':'margin: 0;padding: 5px;border: none;'})
The styles
attribute must be a dictionary, and it allows two possible keys: CODE
for the style of the actual code, and LINENUMBERS
for the style of the left column, which contains the line numbers. Mind that these styles completely replace the default styles and are not simply added to them.
COL
>>> print COL('a', 'b')
<col>ab</col>
COLGROUP
>>> print COLGROUP('a', 'b')
<colgroup>ab</colgroup>
DIV
All helpers apart from XML
are derived from DIV
and inherit its basic methods.
>>> print DIV('<hello>', XML('<b>world</b>'), _class='test', _id=0)
<div id="0" class="test"><hello><b>world</b></div>
EM
Emphasizes its content.
>>> print EM('<hello>', XML('<b>world</b>'), _class='test', _id=0)
<em id="0" class="test"><hello><b>world</b></em>
FIELDSET
This is used to create an input field together with its label.
>>> print FIELDSET('Height:', INPUT(_name='height'), _class='test')
<fieldset class="test">Height:<input name="height" /></fieldset>
FORM
This is one of the most important helpers. In its simple form, it just makes a <form>...</form>
tag, but because helpers are objects and have knowledge of what they contain, they can process submitted forms (for example, perform validation of the fields). This will be discussed in detail in Chapter 7.
>>> print FORM(INPUT(_type='submit'), _action='', _method='post')
<form enctype="multipart/form-data" action="" method="post">
<input type="submit" /></form>
The "enctype" is "multipart/form-data" by default.
The constructor of a FORM
, and of SQLFORM
, can also take a special argument called hidden
. When a dictionary is passed as hidden
, its items are translated into "hidden" INPUT fields. For example:
>>> 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
These helpers are for paragraph headings and subheadings:
>>> print H1('<hello>', XML('<b>world</b>'), _class='test', _id=0)
<h1 id="0" class="test"><hello><b>world</b></h1>
HEAD
For tagging the HEAD of an HTML page.
>>> print HEAD(TITLE('<hello>', XML('<b>world</b>')))
<head><title><hello><b>world</b></title></head>
HTML
This helper is a little different. In addition to making the <html>
tags, it prepends the tag with a doctype string[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>
The HTML helper also takes some additional optional arguments that have the following default:
HTML(..., lang='en', doctype='transitional')
where doctype can be 'strict', 'transitional', 'frameset', 'html5', or a full doctype string.
XHTML
XHTML is similar to HTML but it creates an XHTML doctype instead.
XHTML(..., lang='en', doctype='transitional', xmlns='http://www.w3.org/1999/xhtml')
where doctype can be 'strict', 'transitional', 'frameset', or a full doctype string.
HR
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>
IFRAME
This helper includes another web page in the current page. The url of the other page is specified via the "_src" attribute.
>>> print IFRAME(_src='http://www.web2py.com')
<iframe src="http://www.web2py.com"></iframe>
IMG
It can be used to embed images into HTML:
>>> print IMG(_src='http://example.com/image.png', _alt='test')
<img src="http://example.com/image.ong" alt="rest" />
Here is a combination of A, IMG, and URL helpers for including a static image with a link:
>>> print 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
Creates an <input.../>
tag. An input tag may not contain other tags, and is closed by />
instead of >
. The input tag has an optional attribute _type
that can be set to "text" (the default), "submit", "checkbox", or "radio".
>>> print INPUT(_name='test', _value='a')
<input value="a" name="test" />
It also takes an optional special argument called "value", distinct from "_value". The latter sets the default value for the input field; the former sets its current value. For an input of type "text", the former overrides the latter:
>>> print INPUT(_name='test', _value='a', value='b')
<input value="b" name="test" />
For radio buttons, INPUT
selectively sets the "checked" attribute:
>>> 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
and similarly for checkboxes:
>>> 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
It is used to create a LABEL tag for an INPUT field.
>>> print LABEL('<hello>', XML('<b>world</b>'), _class='test', _id=0)
<label id="0" class="test"><hello><b>world</b></label>
LEGEND
It is used to create a legend tag for a field in a form.
>>> print LEGEND('Name', _for='myfield')
<legend for="myfield">Name</legend>
LI
It makes a list item and should be contained in a UL
or OL
tag.
>>> print LI('<hello>', XML('<b>world</b>'), _class='test', _id=0)
<li id="0" class="test"><hello><b>world</b></li>
META
To be used for building META
tags in the HTML
head. For example:
>>> print META(_name='security', _content='high')
<meta name="security" content="high" />
MARKMIN
Implements the markmin wiki syntax. It converts the input text into output html according to the markmin rules described in the example below:
>>> 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>
The markmin syntax is described in gluon/contrib/markmin/markmin.html that ships with web2py.
You can use markmin to generate HTML, LaTeX and PDF documents:
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
(the MARKMIN
helper is a shortcut for markmin2html
)
Here is a basic syntax primer:
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 |
[[myanchor]] | Creating an anchor for a link |
$ $\int_a^b sin(x)dx$ $ |
MARKMIN links
Links take this form:
[[link display text <link>]]
where <link> can be:
- an anchor (e.g.
#myanchor
), - an URI (e.g.
http://www.web2py.com
), or - a relative reference (like in
[[See Chapter 8 ../08]]
or[[See Chapter 8 ../08#myanchor]]
).
Simply including a link to an image, a videos or an audio files without markup result in the corresponding image, video or audio file being included automatically (for audio and video it uses html <audio> and <video> tags).
Adding a link with the qr:
prefix such as
qr:http://web2py.com
results in the corresponding QR code being embedded and linking the said URL.
Adding a link with the embed:
prefix such as
embed:http://www.youtube.com/embed/x1w8hKTJ2Co
results in the page being embedded, in this case a youtube video is embedded.
Images can also be embedded with the following syntax:
[[image-description http://.../image.png right 200px]]
MARKMIN lists and tables
Unordered lists with:
- one
- two
- three
Ordered lists with:
+ one
+ two
+ three
and tables with:
----------
X | 0 | 0
0 | X | 0
0 | 0 | 1
----------
extending MARKMIN
The MARKMIN syntax also supports blockquotes, HTML5 audio and video tags, image alignment, custom css, and it can be extended:
MARKMIN("``abab``:custom", extra=dict(custom=lambda text: text.replace('a', 'c'))
generates
'cbcb'
Custom blocks are delimited by ``...``:<key>
and they are rendered by the function passed as value for the corresponding key in the extra dictionary argument of MARKMIN. Mind that the function may need to escape the output to prevent XSS.
OBJECT
Used to embed objects (for example, a flash player) in the HTML.
>>> print OBJECT('<hello>', XML('<b>world</b>'), _src='http://www.web2py.com')
<object src="http://www.web2py.com"><hello><b>world</b></object>
OL
It stands for Ordered List. The list should contain LI tags. OL
arguments that are not LI
objects are automatically enclosed in <li>...</li>
tags.
>>> 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
This is here for backward compatibility and it is simply an alias for True
. It is used exclusively for checkboxes and deprecated since True
is more Pythonic.
>>> print INPUT(_type='checkbox', _name='test', _checked=ON)
<input checked="checked" type="checkbox" name="test" />
OPTGROUP
Allows you to group multiple options in a SELECT and it is useful to customize the fields using 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
This should only be used as part of a SELECT
/OPTION
combination.
>>> print OPTION('<hello>', XML('<b>world</b>'), _value='a')
<option value="a"><hello><b>world</b></option>
As in the case of INPUT
, web2py make a distinction between "_value" (the value of the OPTION), and "value" (the current value of the enclosing select). If they are equal, the option is "selected".
>>> print SELECT('a', 'b', value='b'):
<select>
<option value="a">a</option>
<option value="b" selected="selected">b</option>
</select>
P
This is for tagging a paragraph.
>>> print P('<hello>', XML('<b>world</b>'), _class='test', _id=0)
<p id="0" class="test"><hello><b>world</b></p>
PRE
Generates a <pre>...</pre>
tag for displaying pre-formatted text. The CODE
helper is generally preferable for code listings.
>>> print PRE('<hello>', XML('<b>world</b>'), _class='test', _id=0)
<pre id="0" class="test"><hello><b>world</b></pre>
SCRIPT
This is include or link a script, such as JavaScript. The content between the tags is rendered as an HTML comment, for the benefit of really old browsers.
>>> print SCRIPT('alert("hello world");', _type='text/javascript')
<script type="text/javascript"><!--
alert("hello world");
//--></script>
SELECT
Makes a <select>...</select>
tag. This is used with the OPTION
helper. Those SELECT
arguments that are not OPTION
objects are automatically converted to options.
>>> 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
Similar to DIV
but used to tag inline (rather than block) content.
>>> print SPAN('<hello>', XML('<b>world</b>'), _class='test', _id=0)
<span id="0" class="test"><hello><b>world</b></span>
STYLE
Similar to script, but used to either include or link CSS code. Here the CSS is included:
>>> print STYLE(XML('body {color: white}'))
<style><!--
body { color: white }
//--></style>
and here it is linked:
>>> print STYLE(_src='style.css')
<style src="style.css"><!--
//--></style>
TABLE
, TR
, TD
These tags (along with the optional THEAD
, TBODY
and TFOOTER
helpers) are used to build HTML tables.
>>> 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
expects TD
content; arguments that are not TD
objects are converted automatically.
>>> 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>
It is easy to convert a Python array into an HTML table using Python's *
function arguments notation, which maps list elements to positional function arguments.
Here, we will do it line by line:
>>> 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>
Here we do all lines at once:
>>> 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
This is used to tag rows contained in the table body, as opposed to header or footer rows. It is optional.
>>> print TBODY(TR('<hello>'), _class='test', _id=0)
<tbody id="0" class="test"><tr><td><hello></td></tr></tbody>
TEXTAREA
This helper makes a <textarea>...</textarea>
tag.
>>> print TEXTAREA('<hello>', XML('<b>world</b>'), _class='test')
<textarea class="test" cols="40" rows="10"><hello><b>world</b></textarea>
The only caveat is that its optional "value" overrides its content (inner HTML)
>>> print TEXTAREA(value="<hello world>", _class="test")
<textarea class="test" cols="40" rows="10"><hello world></textarea>
TFOOT
This is used to tag table footer rows.
>>> print TFOOT(TR(TD('<hello>')), _class='test', _id=0)
<tfoot id="0" class="test"><tr><td><hello></td></tr></tfoot>
TH
This is used instead of TD
in table headers.
>>> print TH('<hello>', XML('<b>world</b>'), _class='test', _id=0)
<th id="0" class="test"><hello><b>world</b></th>
THEAD
This is used to tag table header rows.
>>> print THEAD(TR(TH('<hello>')), _class='test', _id=0)
<thead id="0" class="test"><tr><th><hello></th></tr></thead>
TITLE
This is used to tag the title of a page in an HTML header.
>>> print TITLE('<hello>', XML('<b>world</b>'))
<title><hello><b>world</b></title>
TR
Tags a table row. It should be rendered inside a table and contain <td>...</td>
tags. TR
arguments that are not TD
objects will be automatically converted.
>>> 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
Tags text as typewriter (monospaced) text.
>>> print TT('<hello>', XML('<b>world</b>'), _class='test', _id=0)
<tt id="0" class="test"><hello><b>world</b></tt>
UL
Signifies an Unordered List and should contain LI
items. If its content is not tagged as LI
, UL
does it automatically.
>>> 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
The URL helper is documented in Chapter 4 URL
embed64
embed64(filename=None, file=None, data=None, extension='image/gif')
encodes the provided (binary) data into base64, it takes the following optional arguments:
filename
: if provided, opens and reads this file in 'rb' mode.file
: if provided, reads this file.data
: if provided, uses the provided data.
xmlescape
xmlescape(data, quote=True)
returns an escaped string of the provided data.
>>> print xmlescape('<hello>')
<hello>
Custom helpers
TAG
Sometimes you need to generate custom XML tags. web2py provides TAG
, a universal tag generator.
{{=TAG.name('a', 'b', _c='d')}}
generates the following XML
<name c="d">ab</name>
Arguments "a", "b", and "d" are automatically escaped; use the XML
helper to suppress this behavior. Using TAG
you can generate HTML/XML tags not already provided by the API. TAGs can be nested, and are serialized with str().
An equivalent syntax is:
{{=TAG['name']('a', 'b', c='d')}}
If the TAG object is created with an empty name, it can be used to concatenate multiple strings and HTML helpers together without inserting them into a surrounding tag, but this use is deprecated. Use the CAT
helper instead.
Self-closing tags can be generated with the TAG helper. The tag name must end with a "/".
{{=TAG['link/'](_href='http://web2py.com')}}
generates the following XML:
<link ref="http://web2py.com"/>
Notice that TAG
is an object, and TAG.name
or TAG['name']
is a function that returns a temporary helper class.
MENU
The MENU helper takes a list of lists or of tuples of the form of response.menu
(as described in Chapter 4) and generates a tree-like structure using unordered lists representing the menu. For example:
>>> 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>
The first item in each list/tuple is the text to be displayed for the given menu item.
The second item in each list/tuple is a boolean indicating whether that particular menu item is active (i.e., the currently selected item). When set to True, the
MENU
helper will add a "web2py-menu-active" class to the<li>
for that item (you can change the name of that class via the "li_active" argument toMENU
). Another way to specify the active url is by directly passing it toMENU
via its "active_url" argument.The third item in each list/tuple can be an HTML helper (which could include nested helpers), and the
MENU
helper will simply render that helper rather than creating its own<a>
tag.
Each menu item can have a fourth argument that is a nested submenu (and so on recursively):
>>> 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>
A menu item can also have an optional 5th element, which is a boolean. When false, the menu item is ignored by the MENU helper.
The MENU
helper takes the following optional arguments:
_class
: defaults to "web2py-menu web2py-menu-vertical" and sets the class of the outer UL elements.ul_class
: defaults to "web2py-menu-vertical" and sets the class of the inner UL elements.li_class
: defaults to "web2py-menu-expand" and sets the class of the inner LI elements.li_first
: allows to add a class to the first list element.li_last
: allows to add a class to the last list element.
MENU
takes an optional argument mobile
. When set to True
instead of building a recursive UL
menu structure it returns a SELECT
dropdown with all the menu options and a onchange
attribute that redirects to the page corresponding to the selected option. This is designed an an alternative menu representation that increases usability on small mobile devices such as phones.
Normally the menu is used in a layout with the following syntax:
{{=MENU(response.menu, mobile=request.user_agent().is_mobile)}}
In this way a mobile device is automatically detected and the menu is rendered accordingly.
BEAUTIFY
BEAUTIFY
is used to build HTML representations of compound objects, including lists, tuples and dictionaries:
{{=BEAUTIFY({"a": ["hello", XML("world")], "b": (1, 2)})}}
BEAUTIFY
returns an XML-like object serializable to XML, with a nice looking representation of its constructor argument. In this case, the XML representation of:
{"a": ["hello", XML("world")], "b": (1, 2)}
will render as:
<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>
Server-side DOM and parsing
elements
The DIV helper and all derived helpers provide the search methods element
and elements
.
element
returns the first child element matching a specified condition (or None if no match).
elements
returns a list of all matching children.
element and elements use the same syntax to specify the matching condition, which allows for three possibilities that can be mixed and matched: jQuery-like expressions, match by exact attribute value, match using regular expressions.
Here is a simple example:
>>> 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>
The un-named argument of elements
is a string, which may contain: the name of a tag, the id of a tag preceded by a pound symbol, the class preceded by a dot, the explicit value of an attribute in square brackets.
Here are 4 equivalent ways to search the previous tag by id:
d = a.elements('#target')
d = a.elements('div#target')
d = a.elements('div[id=target]')
d = a.elements('div', _id='target')
Here are 4 equivalent ways to search the previous tag by class:
d = a.elements('.abc')
d = a.elements('div.abc')
d = a.elements('div[class=abc]')
d = a.elements('div', _class='abc')
Any attribute can be used to locate an element (not just id
and class
), including multiple attributes (the function element can take multiple named arguments), but only the first matching element will be returned.
Using the jQuery syntax "div#target" it is possible to specify multiple search criteria separated by a comma:
a = DIV(SPAN('a', _id='t1'), DIV('b', _class='c2'))
d = a.elements('span#t1, div.c2')
or equivalently
a = DIV(SPAN('a', _id='t1'), DIV('b', _class='c2'))
d = a.elements('span#t1', 'div.c2')
If the value of an attribute is specified using a name argument, it can be a string or a regular expression:
a = DIV(SPAN('a', _id='test123'), DIV('b', _class='c2'))
d = a.elements('span', _id=re.compile('test\d{3}')
A special named argument of the DIV (and derived) helpers is find
. It can be used to specify a search value or a search regular expression in the text content of the tag. For example:
>>> a = DIV(SPAN('abcde'), DIV('fghij'))
>>> d = a.elements(find='bcd')
>>> print d[0]
<span>abcde</span>
or
>>> a = DIV(SPAN('abcde'), DIV('fghij'))
>>> d = a.elements(find=re.compile('fg\w{3}'))
>>> print d[0]
<div>fghij</div>
components
Here's an example of listing all elements in an html string:
>>> html = TAG('<a>xxx</a><b>yyy</b>')
>>> for item in html.components:
... print item
...
<a>xxx</a>
<b>yyy</b>
parent
and siblings
parent
returns the parent of the current element.
>>> 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>
Replacing elements
Elements that are matched can also be replaced or removed by specifying the replace
argument. Notice that a list of the original matching elements is still returned as usual.
>>> 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
can be a callable. In this case it will be passed the original element and it is expected to return the replacement element:
>>> 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>
If replace=None
, matching elements will be removed completely.
>>> a = DIV(SPAN('x'), DIV(SPAN('y'))
>>> b = a.elements('span', replace=None)
>>> print a
<div></div>
flatten
The flatten method recursively serializes the content of the children of a given element into regular text (without tags):
>>> a = DIV(SPAN('this', DIV('is', B('a'))), SPAN('test'))
>>> print a.flatten()
thisisatest
Flatten can be passed an optional argument, render
, i.e. a function that renders/flattens the content using a different protocol. Here is an example to serialize some tags into Markmin wiki syntax:
>>> 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]]
At the time of writing we provide markmin_serializer
and markdown_serializer
.
Parsing
The TAG object is also an XML/HTML parser. It can read text and convert into a tree structure of helpers. This allows manipulation using the API above:
>>> 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>
Page layout
Views can extend and include other views in a tree-like structure.
For example, we can think of a view "index.html" that extends "layout.html" and includes "body.html". At the same time, "layout.html" may include "header.html" and "footer.html".
The root of the tree is what we call a layout view. Just like any other HTML template file, you can edit it using the web2py administrative interface. The file name "layout.html" is just a convention.
Here is a minimalist page that extends the "layout.html" view and includes the "page.html" view:
{{extend 'layout.html'}}
<h1>Hello World</h1>
{{include 'page.html'}}
The extended layout file must contain an {{include}}
directive, something like:
<html>
<head>
<title>Page Title</title>
</head>
<body>
{{include}}
</body>
</html>
When the view is called, the extended (layout) view is loaded, and the calling view replaces the {{include}}
directive inside the layout. Processing continues recursively until all extend
and include
directives have been processed. The resulting template is then translated into Python code. Note, when an application is bytecode compiled, it is this Python code that is compiled, not the original view files themselves. So, the bytecode compiled version of a given view is a single .pyc file that includes the Python code not just for the original view file, but for its entire tree of extended and included views.
extend
,include
,block
andsuper
are special template directives, not Python commands.
Any content or code that precedes the {{extend ...}}
directive will be inserted (and therefore executed) before the beginning of the extended view's content/code. Although this is not typically used to insert actual HTML content before the extended view's content, it can be useful as a means to define variables or functions that you want to make available to the extended view. For example, consider a view "index.html":
{{sidebar_enabled=True}}
{{extend 'layout.html'}}
<h1>Home Page</h1>
and an excerpt from "layout.html":
{{if sidebar_enabled:}}
<div id="sidebar">
Sidebar Content
</div>
{{pass}}
Because the sidebar_enabled
assignment in "index.html" comes before the extend
, that line gets inserted before the beginning of "layout.html", making sidebar_enabled
available anywhere within the "layout.html" code (a somewhat more sophisticated version of this is used in the welcome app).
It is also worth pointing out that the variables returned by the controller function are available not only in the function's main view, but in all of its extended and included views as well.
The argument of an extend
or include
(i.e., the extended or included view name) can be a Python variable (though not a Python expression). However, this imposes a limitation -- views that use variables in extend
or include
statements cannot be bytecode compiled. As noted above, bytecode-compiled views include the entire tree of extended and included views, so the specific extended and included views must be known at compile time, which is not possible if the view names are variables (whose values are not determined until run time). Because bytecode compiling views can provide a significant speed boost, using variables in extend
and include
should generally be avoided if possible.
In some cases, an alternative to using a variable in an include
is simply to place regular {{include ...}}
directives inside an if...else
block.
{{if some_condition:}}
{{include 'this_view.html'}}
{{else:}}
{{include 'that_view.html'}}
{{pass}}
The above code does not present any problem for bytecode compilation because no variables are involved. Note, however, that the bytecode compiled view will actually include the Python code for both "this_view.html" and "that_view.html", though only the code for one of those views will be executed, depending on the value of some_condition
.
Keep in mind, this only works for include
-- you cannot place {{extend ...}}
directives inside if...else
blocks.
Layouts are used to encapsulate page commonality (headers, footers, menus), and though they are not mandatory, they will make your application easier to write and maintain. In particular, we suggest writing layouts that take advantage of the following variables that can be set in the controller. Using these well known variables will help make your layouts interchangeable:
response.title
response.subtitle
response.meta.author
response.meta.keywords
response.meta.description
response.flash
response.menu
response.files
Except for menu
and files
, these are all strings and their meaning should be obvious.
response.menu
menu is a list of 3-tuples or 4-tuples. The three elements are: the link name, a boolean representing whether the link is active (is the current link), and the URL of the linked page. For example:
response.menu = [('Google', False, 'http://www.google.com', []),
('Index', True, URL('index'), [])]
The fourth tuple element is an optional sub-menu.
response.files
is a list of CSS and JS files that are needed by your page.
We also recommend that you use:
{{include 'web2py_ajax.html'}}
in the HTML head, since this will include the jQuery libraries and define some backward-compatible JavaScript functions for special effects and Ajax. "web2py_ajax.html" includes the response.meta
tags in the view, jQuery base, the calendar datepicker, and all required CSS and JS response.files
.
Default page layout
The "views/layout.html" that ships with the web2py scaffolding application welcome (stripped down of some optional parts) is quite complex but it has the following structure:
<!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>
There are a few features of this default layout that make it very easy to use and customize:
- It is written in HTML5 and uses the "modernizr" [modernizr] library for backward compatibility. The actual layout includes some extra conditional statements required by IE and they are omitted for brevity.
- It displays both
response.title
andresponse.subtitle
which can be set in a model or a controller. If they are not set, it adopts the application name as title. - It includes the
web2py_ajax.html
file in the header which generated all the link and script import statements. - It uses a modified version of Twitter Bootstrap for flexible layouts which works on mobile devices and re-arranges columns to fit small screens.
- It uses "analytics.js" to connect to Google Analytics.
- The
{{=auth.navbar(...)}}
displays a welcome to the current user and links to the auth functions like login, logout, register, change password, etc. depending on context.auth.navbar
is a helper factory and its output can be manipulated as any other helper. It is placed in an expression to check for auth definition, the expression evaluates to '' in case auth is undefined. - The
{{=MENU(response.menu)}}
displays the menu structure as<ul>...</ul>
. {{include}}
is replaced by the content of the extending view when the page is rendered.- By default it uses a conditional three column (the left and right sidebars can be turned off by the extending views)
- It uses the following classes: page-header, main, footer.
- It contains the following blocks: head, left_sidebar, center, right_sidebar, footer.
In views, you can turn on and customize sidebars as follows:
{{left_sidebar_enabled=True}}
{{extend 'layout.html'}}
This text goes in center
{{block left_sidebar}}
This text goes in sidebar
{{end}}
Customizing the default layout
Customizing the default layout without editing is easy because the welcome application is based on Twitter Bootstrap which is well documented and supports themes. In web2py four static files which are relevant to style:
- "css/web2py.css" contains web2py specific styles
- "css/bootstrap.min.css" contains the Twitter Bootstrap CSS style [bootstrap] Bootstrap
- "css/web2py_bootstrap.css" which overrides some Bootstrap styles to conform to web2py needs.
- "js/bootstrap.min.js" which includes the libraries for menu effects, modals, panels.
To change colors and background images, try append the following code to layout.html header:
<style>
body { background: url('images/background.png') repeat-x #3A3A3A; }
a { color: #349C01; }
.page-header h1 { color: #349C01; }
.page-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>
Of course you can also completely replace the "layout.html" and "web2py.css" files with your own.
Mobile development
Although the default layout.html is designed to be mobile-friendly, one may sometimes need to use different views when a page is visited by a mobile device.
To make developing for desktop and mobile devices easier, web2py includes the @mobilize
decorator. This decorator is applied to actions that should have a normal view and a mobile view. This is demonstrated here:
from gluon.contrib.user_agent_parser import mobilize
@mobilize
def index():
return dict()
Notice that the decorator must be imported before using it in a controller. When the "index" function is called from a regular browser (desktop computer), web2py will render the returned dictionary using the view "[controller]/index.html". However, when it is called by a mobile device, the dictionary will be rendered by "[controller]/index.mobile.html". Notice that mobile views have the "mobile.html" extension.
Alternatively you can apply the following logic to make all views mobile friendly:
if request.user_agent().is_mobile:
response.view.replace('.html', '.mobile.html')
The task of creating the "*.mobile.html" views is left to the developer but we strongly suggest using the "jQuery Mobile" plugin which makes the task very easy.
Functions in views
Consider this "layout.html":
<html>
<body>
{{include}}
<div class="sidebar">
{{if 'mysidebar' in globals():}}{{mysidebar()}}{{else:}}
my default sidebar
{{pass}}
</div>
</body>
</html>
and this extending view
{{def mysidebar():}}
my new sidebar!!!
{{return}}
{{extend 'layout.html'}}
Hello World!!!
Notice the function is defined before the {{extend...}}
statement -- this results in the function being created before the "layout.html" code is executed, so the function can be called anywhere within "layout.html", even before the {{include}}
. Also notice the function is included in the extended view without the =
prefix.
The code generates the following output:
<html>
<body>
Hello World!!!
<div class="sidebar">
my new sidebar!!!
</div>
</body>
</html>
Notice that the function is defined in HTML (although it could also contain Python code) so that response.write
is used to write its content (the function does not return the content). This is why the layout calls the view function using {{mysidebar()}}
rather than {{=mysidebar()}}
. Functions defined in this way can take arguments.
Blocks in views
The main way to make a view more modular is by using {{block ...}}
s and this mechanism is an alternative to the mechanism discussed in the previous section.
To understand how this works, consider apps based on the scaffolding app welcome, which has a view layout.html. This view is extended by the view default/index.html
via {{extend 'layout.html'}}
. The contents of layout.html predefine certain blocks with certain default content, and these are therefore included into default/index.html.
You can override these default content blocks by enclosing your new content inside the same block name. The location of the block in the layout.html is not changed, but the contents is.
Here is a simplifed version. Imagine this is "layout.html":
<html>
<body>
{{include}}
<div class="sidebar">
{{block mysidebar}}
my default sidebar (this content to be replaced)
{{end}}
</div>
</body>
</html>
and this is a simple extending view default/index.html
:
{{extend 'layout.html'}}
Hello World!!!
{{block mysidebar}}
my new sidebar!!!
{{end}}
It generates the following output, where the content is provided by the over-riding block in the extending view, yet the enclosing DIV and class comes from layout.html. This allows consistency across views:
<html>
<body>
Hello World!!!
<div class="sidebar">
my new sidebar!!!
</div>
</body>
</html>
The real layout.html defines a number of useful blocks, and you can easily add more to match the layout your desire.
You can have many blocks, and if a block is present in the extended view but not in the extending view, the content of the extended view is used. Also, notice that unlike with functions, it is not necessary to define blocks before the {{extend ...}}
-- even if defined after the extend
, they can be used to make substitutions anywhere in the extended view.
Inside a block, you can use the expression {{super}}
to include the content of the parent. For example, if we replace the above extending view with:
{{extend 'layout.html'}}
Hello World!!!
{{block mysidebar}}
{{super}}
my new sidebar!!!
{{end}}
we get:
<html>
<body>
Hello World!!!
<div class="sidebar">
my default sidebar
my new sidebar!
</div>
</body>
</html>
Javascript in views
Helpers can be used within external code by placing it in a template and then including the template where needed. For example, if some javascript code is in a file "/views/my.js", then it can be included in a view file:
<script>
{{include 'my.js'}}
</script>
However, this will be inefficient if there are many lines of javascript code but only few lines of dynamically generated web2py content such as helpers. An alternative is to define the dynamically generated web2py variables in one block of javascript in the template, and then load a static javascript file that simply refers to those variables (this is how "web2py_ajax.html" works -- it defines several JS variables, which are then used by "web2py.js"). So, in the view file:
<script>
var someVar = "{{=T('some phrase to be translated')}}";
var someURL = "{{=URL('default', 'myfunction')}}";
</script>
<script src="{{=URL('static', 'js/my.js')}}"></script>
or equivalently using the web2py ASSIGNJS
helper:
<script>
{{=ASSIGNJS(someVar = T('some phrase to be translated'),
someURL = URL('default', 'myfunction'))}};
</script>
<script src="{{=URL('static', 'js/my.js')}}"></script>
then in "my.js", someVar
and someURL
can be used as normal javascript variables.