Consider the following code in a template:
[[=DIV('this', 'is', 'a', 'test', _id='123', _class='myclass')]]
it is rendered as:
<div id="123" class="myclass">thisisatest</div>
You can easily test the rendering of these commands by copying the _scaffold app (see copying-the-scaffold-app
) and then editing the file new_app/template/index.html
.
DIV
is a helper class, i.e., something that can be used to build HTML programmatically. It corresponds to the HTML <div>
tag.
Helpers can have:
- positional arguments interpreted as objects contained between the open and close tags, like
thisisatest
in the previous example - named arguments (start with an underscore) interpreted as HTML tag attributes (without the underscore), like
_class
and_id
in the previous example - named arguments (start without an underscore), in this case 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 are the current set of helpers available within the YATL module:
A
, BEAUTIFY
, BODY
, CAT
, CODE
, DIV
, EM
, FORM
, H1
, H2
, H3
, H4
, H5
, H6
, HEAD
, HTML
, IMG
, INPUT
, LABEL
, LI
, METATAG
, OL
, OPTION
, P
, PRE
, SELECT
, SPAN
, STRONG
, TABLE
, TAG
, TAGGER
, THEAD
, TBODY
, TD
, TEXTAREA
, TH
, TT
, TR
, UL
, XML
, xmlescape
, I
, META
, LINK
, TITLE
, STYLE
, SCRIPT
Helpers can be used to build complex expressions, that can then be serialized to XML. For example:
[[=DIV(STRONG(I("hello ", "<world>")), _class="myclass")]]
is rendered:
<div class="myclass"><strong><i>hello <world></i></strong></div>
Helpers can also be serialized into strings, equivalently, with the __str__
and the xml
methods. This can be manually tested directly with a Python shell or by using the shell command option
of py4web and then:
>>> from yatl.helpers import *
>>>
>>> str(DIV("hello world"))
'<div>hello world</div>'
>>> DIV("hello world").xml()
'<div>hello world</div>'
The helpers mechanism in py4web 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(STRONG('x'))
>>> a[0][0] = 'y'
>>> print(a)
<div><span>yb</span><strong>x</strong></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.children
, and the complete set of attributes can be accessed via a dictionary called a.attributes
. So, a[i]
is equivalent to a.children[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 can 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>
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
is an helper 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("<strong>hello</strong>"))
<div><strong>hello</strong></div>
by using XML
you can prevent escaping:
>>> print(DIV(XML("<strong>hello</strong>")))
<div><strong>hello</strong></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. In this case the py4web 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/',
'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'table', 'tr', 'td',
'div', 'strong', 'span'],
allowed_attributes={'a': ['href', 'title', 'target'],
'img': ['src', 'alt'], 'blockquote': ['type'], 'td': ['colspan']})
This helper is used to build links.
>>> print(A('<click>', XML('<strong>me</strong>'),
_href='http://www.py4web.com'))
<a href="http://www.py4web.com"><click><strong>me</strong></a>
This helper makes the body of a page.
>>> print(BODY('<hello>', XML('<strong>world</strong>'), _bgcolor='red'))
<body bgcolor="red"><hello><strong>world</strong></body>
This helper concatenates other helpers.
>>> print(CAT('Here is a ', A('link', _href='target'), ', and here is some ', STRONG('bold text'), '.'))
Here is a <a href="target">link</a>, and here is some <strong>bold text</strong>.
This is the content division element.
>>> print(DIV('<hello>', XML('<strong>world</strong>'), _class='test', _id=0))
<div id="0" class="test"><hello><strong>world</strong></div>
Emphasizes its content.
>>> print(EM('<hello>', XML('<strong>world</strong>'), _class='test', _id=0))
<em id="0" class="test"><hello><strong>world</strong></em>
Use this helper to make a FORM for user input. Forms will be later discussed in detail in the dedicated Forms
chapter.
>>> print(FORM(INPUT(_type='submit'), _action='', _method='post'))
<form action="" method="post"><input type="submit"/></form>
These helpers are for paragraph headings and subheadings.
>>> print(H1('<hello>', XML('<strong>world</strong>'), _class='test', _id=0))
<h1 id="0" class="test"><hello><strong>world</strong></h1>
For tagging the HEAD of an HTML page.
>>> print(HEAD(TITLE('<hello>', XML('<strong>world</strong>'))))
<head><title><hello><strong>world</strong></title></head>
For tagging an HTML page.
>>> print(HTML(BODY('<hello>', XML('<strong>world</strong>'))))
<html><body><hello><strong>world</strong></body></html>
This helper makes its contents italic.
>>> print(I('<hello>', XML('<strong>world</strong>'), _class='test', _id=0))
<i id="0" class="test"><hello><strong>world</strong></i>
It can be used to embed images into HTML.
>>> print(IMG(_src='http://example.com/image.png', _alt='test'))
<img alt="test" src="http://example.com/image.png"/>
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="/default/index"><img alt="My Logo" src="/static/logo.png"/></a>
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 name="test" value="a"/>
For radio buttons use the _checked
attribute:
>>> for v in ['a', 'b', 'c']:
... print(INPUT(_type='radio', _name='test', _value=v, _checked=v=='b'), v)
...
<input name="test" type="radio" value="a"/> a
<input checked="checked" name="test" type="radio" value="b"/> b
<input name="test" type="radio" value="c"/> c
and similarly for checkboxes:
>>> print(INPUT(_type='checkbox', _name='test', _value='a', _checked=True))
<input checked="checked" name="test" type="checkbox" value="a"/>
>>> print(INPUT(_type='checkbox', _name='test', _value='a', _checked=False))
<input name="test" type="checkbox" value="a"/>
It is used to create a LABEL tag for an INPUT field.
>>> print(LABEL('<hello>', XML('<strong>world</strong>'), _class='test', _id=0))
<label id="0" class="test"><hello><strong>world</strong></label>
It makes a list item and should be contained in a UL
or OL
tag.
>>> print(LI('<hello>', XML('<strong>world</strong>'), _class='test', _id=0))
<li id="0" class="test"><hello><strong>world</strong></li>
It stands for ordered list. The list should contain LI tags.
>>> print(OL(LI('<hello>'), LI(XML('<strong>world</strong>')), _class='test', _id=0))
<ol class="test" id="0"><li><hello></li><li><strong>world</strong></li></ol>
This should only be used as argument of a SELECT
.
>>> print(OPTION('<hello>', XML('<strong>world</strong>'), _value='a'))
<option value="a"><hello><strong>world</strong></option>
For selected options use the _selected
attribute:
>>> print(OPTION('Thank You', _value='ok', _selected=True))
<option selected="selected" value="ok">Thank You</option>
This is for tagging a paragraph.
>>> print(P('<hello>', XML('<strong>world</strong>'), _class='test', _id=0))
<p id="0" class="test"><hello><strong>world</strong></p>
Generates a <pre>...</pre>
tag for displaying pre-formatted text. The CODE
helper is generally preferable for code listings.
>>> print(SELECT(OPTION('first', _value='1'), OPTION('second', _value='2'), _class='test', _id=0))
<pre id="0" class="test"><hello><strong>world</strong></pre>
This is for include or link a script, such as JavaScript.
>>> print(SCRIPT('console.log("hello world");', _type='text/javascript'))
<script type="text/javascript">console.log("hello world");</script>
Makes a <select>...</select>
tag. This is used with the OPTION
helper.
>>> print(SELECT(OPTION('first', _value='1'), OPTION('second', _value='2'),
... _class='test', _id=0))
<select class="test" id="0"><option value="1">first</option><option value="2">second</option></select>
Similar to DIV
but used to tag inline (rather than block) content.
>>> print(SPAN('<hello>', XML('<strong>world</strong>'), _class='test', _id=0))
<span id="0" class="test"><hello><strong>world</strong></span>
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>
These tags (along with the optional THEAD
and TBODY
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.
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(*map(TD, table[0])), TR(*map(TD, 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(*map(TD, rows)) for rows in table]))
<table><tr><td>a</td><td>b</td></tr><tr><td>c</td><td>d</td></tr></table>
This is used to tag rows contained in the table body, as opposed to header or footer rows. It is optional.
>>> print(TBODY(TR(TD('<hello>')), _class='test', _id=0))
<tbody id="0" class="test"><tr><td><hello></td></tr></tbody>
This helper makes a <textarea>...</textarea>
tag.
>>> print(TEXTAREA('<hello>', XML('<strong>world</strong>'), _class='test',
... _cols="40", _rows="10"))
<textarea class="test" cols="40" rows="10"><hello><strong>world</strong></textarea>
This is used instead of TD
in table headers.
>>> print(TH('<hello>', XML('<strong>world</strong>'), _class='test', _id=0))
<th id="0" class="test"><hello><strong>world</strong></th>
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>
This is used to tag the title of a page in an HTML header.
>>> print(TITLE('<hello>', XML('<strong>world</strong>')))
<title><hello><strong>world</strong></title>
Tags text as typewriter (monospaced) text.
>>> print(TT('<hello>', XML('<strong>world</strong>'), _class='test', _id=0))
<tt id="0" class="test"><hello><strong>world</strong></tt>
It stands for unordered list. The list should contain LI tags.
>>> print(UL(LI('<hello>'), LI(XML('<strong>world</strong>')), _class='test', _id=0))
<ul class="test" id="0"><li><hello></li><li><strong>world</strong></li></ul>
The URL helper is not part of yatl package, instead it is provided by py4web.
Sometimes you need to generate custom XML tags*. For this purpose py4web 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')]]
Self-closing tags can be generated with the TAG helper. The tag name must end with a “/”.
[[=TAG['link/'](_href='http://py4web.com')]]
generates the following XML:
<link ref="http://py4web.com"/>
Notice that TAG
is an object, and TAG.name
or TAG['name']
is a function that returns an helper instance.
BEAUTIFY
is used to build HTML representations of compound objects, including lists, tuples and dictionaries:
[[=BEAUTIFY({"a": ["hello", STRONG("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", STRONG("world")], "b": (1, 2)}
will render as:
<table><tbody>
<tr><th>a</th><td><ul><li>hello</li><li><strong>world</strong></li></ul></td></tr>
<tr><th>b</th><td>(1, 2)</td></tr>
</tbody></table>
As we've already seen the helpers mechanism in py4web also provides a server-side representation of the document object model (DOM).
Each helper object keep the list of its components into the children
attribute.
>>> CAT('hello', STRONG('world')).children
['hello', <yatl.helpers.TAGGER object at 0x7fa533ff7640>]
To help searching into the DOM, all helpers have a find
method with the following signature:
def find(self, query=None, **kargs)
that returns all the components matching supplied arguments.
A very simple query
can be a tag name:
>>> a = DIV(DIV(SPAN('x'), 3, DIV(SPAN('y'))))
>>> for c in a.find('span', first_only=True): c[0]='z'
>>> print(a) # We should .xml() here instead of print
<div><div><span>z</span>3<div><span>y</span></div></div></div>
>>> for c in a.find('span'): c[0]='z'
>>> print(a)
<div><div><span>z</span>3<div><span>z</span></div></div></div>
It also supports a syntax compatible with jQuery, accepting the following expressions:
- jQuery Multiple Selector, e.g. “selector1, selector2, selectorN”,
- jQuery Descendant Selector, e.g. “ancestor descendant”,
- jQuery ID Selector, e.g. “#id”,
- jQuery Class Selector, e.g. “.class”, and
- jQuery Attribute Equals Selector, e.g. “[name=value]”, notice that here the value must be unquoted.
Here are some examples:
>>> a = DIV(SPAN(A('hello', **{'_id': '1-1', '_u:v': '$'})), P('world', _class='this is a test'))
>>> for e in a.find('div a#1-1, p.is'): print(e)
<a id="1-1" u:v="$">hello</a>
<p class="this is a test">world</p>
>>> for e in a.find('#1-1'): print(e)
<a id="1-1" u:v="$">hello</a>
>>> a.find('a[u:v=$]')[0].xml()
'<a id="1-1" u:v="$">hello</a>'
>>> a = FORM(INPUT(_type='text'), SELECT(OPTION(0)), TEXTAREA())
>>> for c in a.find('input, select, textarea'): c['_disabled'] = True
>>> a.xml()
'<form><input disabled="disabled" type="text"/><select disabled="disabled"><option>0</option></select><textarea disabled="disabled"></textarea></form>'
>>> for c in a.find('input, select, textarea'): c['_disabled'] = False
>>> a.xml()
'<form><input type="text"/><select><option>0</option></select><textarea></textarea></form>'
Elements that are matched can also be replaced or removed by specifying a replace
argument (note, a list of the original matching elements is still returned as usual).
>>> a = DIV(DIV(SPAN('x', _class='abc'), DIV(SPAN('y', _class='abc'), SPAN('z', _class='abc'))))
>>> b = a.find('span.abc', replace=P('x', _class='xyz'))
>>> print(a)
<div><div><p class="xyz">x</p><div><p class="xyz">x</p><p class="xyz">x</p></div></div></div>
replace
can be a callable, which will be passed the original element and should return a new element to replace it.
>>> a = DIV(DIV(SPAN('x', _class='abc'), DIV(SPAN('y', _class='abc'), SPAN('z', _class='abc'))))
>>> b = a.find('span.abc', replace=lambda el: P(el[0], _class='xyz'))
>>> print(a)
<div><div><p class="xyz">x</p><div><p class="xyz">y</p><p class="xyz">z</p></div></div></div>
If replace=None
, matching elements will be removed completely.
>>> a = DIV(DIV(SPAN('x', _class='abc'), DIV(SPAN('y', _class='abc'), SPAN('z', _class='abc'))))
>>> b = a.find('span', text='y', replace=None)
>>> print(a)
<div><div><span class="abc">x</span><div><span class="abc"></span><span class="abc">z</span></div></div></div>
If a text
argument is specified, elements will be searched for text components that match text, and any matching text components will be replaced (text
is ignored if replace
is not also specified, use a find
argument when you only need searching for textual elements).
Like the find
argument, text
can be a string or a compiled regex.
>>> a = DIV(DIV(SPAN('x', _class='abc'), DIV(SPAN('y', _class='abc'), SPAN('z', _class='abc'))))
>>> b = a.find(text=re.compile('x|y|z'), replace='hello')
>>> print(a)
<div><div><span class="abc">hello</span><div><span class="abc">hello</span><span class="abc">hello</span></div></div></div>
If other attributes are specified along with text
, then only components that match the specified attributes will be searched for text.
>>> a = DIV(DIV(SPAN('x', _class='abc'), DIV(SPAN('y', _class='efg'), SPAN('z', _class='abc'))))
>>> b = a.find('span.efg', text=re.compile('x|y|z'), replace='hello')
>>> print(a)
<div><div><span class="abc">x</span><div><span class="efg">hello</span><span class="abc">z</span></div></div></div>
Normally all the code should be called from the controller program, and only the necessary data is passed to the template in order to be displayed. But sometimes it's useful to pass variables or even use a python function as a helper called from a template.
In this case you can use the fixture Inject
from py4web.utils.factories.
This is a simple example for injecting a variable:
from py4web.utils.factories import Inject
my_var = "Example variable to be passed to a Template"
...
@unauthenticated("index", "index.html")
@action.uses(Inject(my_var=my_var))
def index():
...
Then in index.html
you can use the injected variable:
[[=my_var]]
You can also use Inject
to add variables to the auth.enable line; in this way auth forms would have access to that variable.
auth.enable(uses=(session, T, db, Inject(TIMEOFFSET=settings.TIMEOFFSET)))
A more complex usage of Inject is for passing python functions to templates. For example if your helper function is called sidebar_menu and it's inside the libs/helpers.py module of your app, you could use this in controllers.py:
from py4web.utils.factories import Inject
from .libs.helpers import sidebar_menu
@action(...)
@action.uses("index.html", Inject(sidebar_menu=sidebar_menu))
def index(): ....
OR
from py4web.utils.factories import Inject
from .libs import helpers
@action(...)
@action.uses(Inject(**vars(helpers)), "index.html")
def index(): ....
Then you can import the needed code in the index.html template in a clean way:
[[=sidebar_menu]]