Widgets¶
Widgets are small self-contained components that can be reused across the same web page or across multiple pages.
A widget typically has a state (its value) a configuration (its params) a template that describes what should be displayed, one ore more resources (javascript or CSS) needed during display and might have some logic that has to be executed every time the widget is displayed.
Using Widgets¶
A typical widget will look like:
class MyWidget(tw2.core.Widget):
template = "mypackage.widgets.templates.mywidget"
Which will look for a template named mywidget.html
into the templates
python package within the widgets
package of the mypackage
application. The extension
expected for the file depends on the template engine used:
- mako:
.mako
- kajiki:
.kajiki
- jinja:
.jinja
- genshi:
.genshi
The template engine used to render the provided template
depends on the default_engine
option provided when
configuring tw2.core.middleware.TwMiddleware
.
In case you don’t want to save the template into a separate
file you can also set the inline_engine_name
option
to one of the template engines and provide the template
as a string:
class HelloWidgetTemplate(tw2.core.Widget):
inline_engine_name = "kajiki"
template = """
<i>Hello <span py:for="i in range(1, 4)">${i}, </span></i>
"""
Displaying a widget is as simple as calling the Widget.display()
:
HelloWidgetTemplate.display()
Widget value¶
Each Widget has a special paramter, which is value
. This parameter
contains the current state of the widget. Value will usually be
a single value or a dictionary containing multiple values
(in case of tw2.core.widgets.CompoundWidget
).
You can use the value to drive what the widget should show once displayed:
class HelloWidgetValue(tw2.core.Widget):
inline_engine_name = "kajiki"
template = """
<i>Hello ${w.value}</i>
"""
>>> HelloWidgetValue.display(value='World')
Markup('<i>Hello World</i>')
tw2.core.CompoundWidget
can contain multiple subwidgets
(children) and their value is typically a dict
with values
for each one of the children:
class CompoundHello(tw2.core.CompoundWidget):
inline_engine_name = "kajiki"
template = """
<div py:for="c in w.children">
${c.display()}
</div>
"""
name = HelloWidgetValue()
greeter = tw2.core.Widget(inline_engine_name="kajiki",
template="<span>From ${w.value}</span>")
>>> CompoundHello(value=dict(name="Mario", greeter="Luigi")).display()
Markup('<div><span>Hello Mario</span></div><div><span>From Luigi</span></div>')
Children of a compound widget (like Forms) can be accessed
both as a list iterating over w.children
or by name using
w.children.childname
.
Parameters¶
Widgets might require more information than just their value to display,
or might allow more complex kind of configurations. The options required
to configure the widget are provided through tw2.core.Param
objects
that define which options each widget supports.
If you want your widget to be configurable, you can make available one or more options to your Widget and allow any user to set them as they wish:
class HelloWidgetParam(tw2.core.Widget):
inline_engine_name = "kajiki"
template = """
<i>Hello ${w.name}</i>
"""
name = tw2.core.Param(description="Name of the greeted entity")
The parameters can be provided any time by changing configuration of a widget:
>>> w = HelloWidgetParam(name="Peach")
>>> w.display()
Markup('<i>Hello Peach</i>')
>>> w2 = w(name="Toad")
>>> w2.display()
Markup('<i>Hello Toad</i>')
Or can be provided at display
time itself:
>>> HelloWidgetParam.display(name="Peach")
Markup('<i>Hello Peach</i>')
Deferred Parameters¶
When a widget requires a parameter that is not available before
display time. That parameter can be set to a tw2.core.Deferred
object.
Deferred objects will accept any callable and before the widget is displayed the callable will be executed to fetch the actual value for the widget:
>>> singleselect = SingleSelectField(options=tw2.core.Deferred(lambda: [1,2,3]))
>>> singleselect.options
<Deferred: <Deferred>>
>>> singleselect.display()
Markup('<select ><option value=""></option>\n <option value="1">1</option>\n <option value="2">2</option>\n <option value="3">3</option>\n</select>')
Deferred
is typically used when loading data from the content of a database
to ensure that the content is the one available at the time the widget is
displayed and not the one that was available when the application started:
>>> userpicker = twf.SingleSelectField(
... options=twc.Deferred(lambda: [(u.user_id, u.display_name) for u in model.DBSession.query(model.User)])
... )
>>> userpicker.display()
Markup('<select ><option value=""></option>\n <option value="1">Example manager</option>\n <option value="2">Example editor</option>\n</select>')
Builtin Widgets¶
The tw2.core
packages comes with the basic buildin blocks needed
to create your own custom widgets.
-
class
tw2.core.widgets.
Widget
(**kw)[source]¶ Base class for all widgets.
-
classmethod
req
(**kw)[source]¶ Generate an instance of the widget.
Return the validated widget for this request if one exists.
-
classmethod
post_define
()[source]¶ This is a class method, that is called when a subclass of this Widget is created. Process static configuration here. Use it like this:
class MyWidget(LeafWidget): @classmethod def post_define(cls): id = getattr(cls, 'id', None) if id and not id.startswith('my'): raise pm.ParameterError("id must start with 'my'")
post_define should always cope with missing data - the class may be an abstract class. There is no need to call super(), the metaclass will do this automatically.
-
classmethod
get_link
()[source]¶ Get the URL to the controller . This is called at run time, not startup time, so we know the middleware if configured with the controller path. Note: this function is a temporary measure, a cleaner API for this is planned.
-
prepare
()[source]¶ This is an instance method, that is called just before the Widget is displayed. Process request-local configuration here. For efficiency, widgets should do as little work as possible here. Use it like this:
class MyWidget(Widget): def prepare(self): super(MyWidget, self).prepare() self.value = 'My: ' + str(self.value)
-
generate_output
(displays_on)[source]¶ Generate the actual output text for this widget.
By default this renders the widget’s template. Subclasses can override this method for purely programmatic output.
- displays_on
- The name of the template engine this widget is being displayed inside.
Use it like this:
class MyWidget(LeafWidget): def generate_output(self, displays_on): return "<span {0}>{1}</span>".format(self.attrs, self.text)
-
classmethod
-
class
tw2.core.widgets.
LeafWidget
(**kw)[source]¶ A widget that has no children; this is the most common kind, e.g. form fields.
-
class
tw2.core.widgets.
CompoundWidget
(**kw)[source]¶ A widget that has an arbitrary number of children, this is common for layout components, such as
tw2.forms.TableLayout
.
-
class
tw2.core.widgets.
RepeatingWidget
(**kw)[source]¶ A widget that has a single child, which is repeated an arbitrary number of times, such as
tw2.forms.GridLayout
.
-
class
tw2.core.widgets.
DisplayOnlyWidget
(**kw)[source]¶ A widget that has a single child. The parent widget is only used for display purposes; it does not affect value propagation or validation. This is used by widgets like
tw2.forms.FieldSet
.-
classmethod
post_define
()[source]¶ This is a class method, that is called when a subclass of this Widget is created. Process static configuration here. Use it like this:
class MyWidget(LeafWidget): @classmethod def post_define(cls): id = getattr(cls, 'id', None) if id and not id.startswith('my'): raise pm.ParameterError("id must start with 'my'")
post_define should always cope with missing data - the class may be an abstract class. There is no need to call super(), the metaclass will do this automatically.
-
prepare
()[source]¶ This is an instance method, that is called just before the Widget is displayed. Process request-local configuration here. For efficiency, widgets should do as little work as possible here. Use it like this:
class MyWidget(Widget): def prepare(self): super(MyWidget, self).prepare() self.value = 'My: ' + str(self.value)
-
classmethod
-
class
tw2.core.widgets.
Page
(**kw)[source]¶ An HTML page. This widget includes a
request()
method that serves the page.-
classmethod
post_define
()[source]¶ This is a class method, that is called when a subclass of this Widget is created. Process static configuration here. Use it like this:
class MyWidget(LeafWidget): @classmethod def post_define(cls): id = getattr(cls, 'id', None) if id and not id.startswith('my'): raise pm.ParameterError("id must start with 'my'")
post_define should always cope with missing data - the class may be an abstract class. There is no need to call super(), the metaclass will do this automatically.
-
classmethod
-
class
tw2.core.
Param
(description=Default, default=Default, request_local=Default, attribute=Default, view_name=Default)[source]¶ A parameter for a widget.
- description
- A string to describe the parameter. When overriding a parameter
description, the string can include
$$
to insert the previous description. - default
- The default value for the parameter. If no defalt is specified, the parameter is a required parameter. This can also be specified explicitly using tw.Required.
- request_local
- Can the parameter be overriden on a per-request basis? (default: True)
- attribute
- Should the parameter be automatically included as an attribute? (default: False)
- view_name
- The name used for the attribute. This is useful for attributes like class which are reserved names in Python. If this is None, the name is used. (default: None)
The class takes care to record which arguments have been explictly specifed, even if to their default value. If a parameter from a base class is updated in a subclass, arguments that have been explicitly specified will override the base class.
Resources¶
ToscaWidgets comes with resources management for widgets too.
Some widgets might be complex enough that they need external resources to work properly. Typically those are CSS stylesheets or Javascript functions.
The need for those can be specified in the Widget.resources
param, which is a list of resources the widget needs to work properly
The tw2.core.middleware.TwMiddleware
takes care of serving
all the resources needed by a widget through a tw2.core.resources.ResourcesApp
.
There is not need to setup such application manually, having a TwMiddleware
in place will provide support for resources too.
When a widget is being prepared for display, all resources that it requires
(as specified by tw2.core.Widget.resources
)
are registered into the current request and while the response page output
goes through the middleware it will be edited to add the links (or content)
of those resources as specified by their location.
Note
If a resource was already injected into the page during current request
and another widget requires it, it won’t be injected twice. ToscaWidgets
is able to detect that it’s the same resource (thanks to the resource id
)
and only inject that once.
To add resources to a widget simply specify them in tw2.core.Widget.resources
:
class HelloWidgetClass(twc.Widget):
inline_engine_name = "kajiki"
template = """
<i class="${w.css_class}">Hello ${w.name}</i>
"""
name = twc.Param(description="Name of the greeted entity")
css_class = twc.Param(description="Class used to display content", default="red")
resources = [
twc.CSSSource(src="""
.red { color: red; }
.green { color: green; }
.blue { color: blue; }
""")
]
Once the page where the widget is displayed is rendered, you will see that it begins with:
<!DOCTYPE html>
<html>
<head><style type="text/css">
.red { color: red; }
.green { color: green; }
.blue { color: blue; }
</style>
<meta content="width=device-width, initial-scale=1.0" name="viewport">
<meta charset="utf-8">
Which contains the CSS resource you specified as a dependency of your widget.
In case you are using a solution to package your resources into bundles like
WebPack, WebAssets or similar, you might want to disable resources injection
using inject_resoures=False
option provided to tw2.core.middleware.TwMiddleware
to avoid injecting resources that were already packed into your bundle.
Builtin Resource Types¶
-
class
tw2.core.resources.
ResourceBundle
(**kw)[source]¶ Just a list of resources.
Use it as follows:
>>> jquery_ui = ResourceBundle(resources=[jquery_js, jquery_css]) >>> jquery_ui.inject()
-
class
tw2.core.resources.
Resource
(**kw)[source]¶ A resource required by a widget being displayed.
location
states where the resource should be injected into the page. Can be any ofhead
,headbottom
,bodytop
orbodybottom
orNone
.
-
class
tw2.core.resources.
Link
(**kw)[source]¶ A link to a file.
The
link
parameter can be used to specify the explicit link to a URL.If omitted, the link will be built to serve
filename
frommodname
as a resource coming from a python distribution.-
classmethod
guess_modname
()[source]¶ Try to guess my modname.
If I wasn’t supplied any modname, take a guess by stepping back up the frame stack until I find something not in tw2.core
-
classmethod
post_define
()[source]¶ This is a class method, that is called when a subclass of this Widget is created. Process static configuration here. Use it like this:
class MyWidget(LeafWidget): @classmethod def post_define(cls): id = getattr(cls, 'id', None) if id and not id.startswith('my'): raise pm.ParameterError("id must start with 'my'")
post_define should always cope with missing data - the class may be an abstract class. There is no need to call super(), the metaclass will do this automatically.
-
classmethod
-
class
tw2.core.resources.
DirLink
(**kw)[source]¶ A whole directory as a resource.
Unlike
JSLink
andCSSLink
, this resource doesn’t inject anything on the page.. but it does register all resources under the marked directory to be served by the middleware.This is useful if you have a css file that pulls in a number of other static resources like icons and images.
-
class
tw2.core.resources.
JSLink
(**kw)[source]¶ A JavaScript source file.
By default is injected in whatever default place is specified by the middleware.
-
class
tw2.core.resources.
CSSLink
(**kw)[source]¶ A CSS style sheet.
By default it’s injected at the top of the head node.