Containers
Some components can contain other components. We call these containers. (A Form is also a container.)
The best way to see how each container behaves is to experiment - drop a container into a Form and drop several TextAreas into it. Play around and see how many ways you can arrange things.
Controlling Containers from code
You can add any component to a container in Python by calling the method
add_component(component, container_properties…)
.
For example, the following code sets up an XYPanel that is 400x400 pixels, adds it to the current Form, and then adds two components to it:
xyp = XYPanel(width=400, height=400)
self.add_component(xyp)
lbl = Label(text="Hello")
xyp.add_component(lbl, x=10, y=10)
btn = Button(text="Click me!")
xyp.add_component(btn, x=10, y=100)
To remove a component from its parent container, call its remove_from_parent()
method.
btn.remove_from_parent()
Alternatively, you can remove all components from a particular container by calling clear()
on the container:
my_container.clear()
To get a list of all components currently in the container, call get_components()
:
components = my_container.get_components()
print(f"{len(components)} components")
To raise an event on all children of a component without referencing the children directly, call raise_event_on_children()
:
my_container.raise_event_on_children("x-custom-event", foo=42)
For more on adding, removing and getting components within containers, see the Containers overview.
Container Properties
Container Properties are properties that control the relationship between a component and the container it is in; the argument names and their meanings are specific to the type of container. They are visible in the Properties panel in the Anvil Editor.
They can also be passed as keyword arguments to add_component
. For example, Data Row Panels inside a Data Grid have a pinned
property that determines whether the Data Row Panel is visible on every page of a paginated Data Grid:
self.data_grid_1.add_component(DataRowPanel(), pinned=True)
ColumnPanel
This container allows you to drag and drop components into rows and columns. The ColumnPanel is the default layout for Anvil Forms.
Components added at runtime using the add_component
method will each be added to their own row.
In this respect, the ColumnPanel behaves the same as the LinearPanel at runtime. There are no special arguments
to pass to add_component
.
Like the LinearPanel
, the ColumnPanel will expand to fit its contents.
Components in a ColumnPanel have several container properties, which can be set in the Properties dialog:
full_width_row
: WhenTrue
, this row of components will stretch to the full width of the screen. (Default:False
)row_background
: Set to a CSS color for the background of this row. Note that the background stretches to the full with of the browser. (Default:None
)
cp = ColumnPanel(background='yellow')
# Add a TextBox to the Column Panel and give it the full_width_row container property
cp.add_component(TextBox(text='enter some text', full_width_row=True))
RichText
RichText components are containers for other components, which can also display text in one of three formats:
- Markdown (
format='markdown'
): Full Markdown syntax is supported. - Plain text (
format='plain_text'
): Content will be presented as-is without any formatting. - HTML (
format='restricted_html'
): A safe subset of HTML is supported (the input is sanitised, so it is safe to display user-supplied data here). See below for details.
c = RichText(content="## This is a title\n\nAnd _this_ is some **text**.")
You can enable language specific code highlighting by including highlight.js from CDN in your native libraries.
RichText components can also hold other components. Define named slots in the RichText content
property by adding the slot name in curly braces; for example, {slot_name}
. Slot names can only contain alphanumeric characters, dashes, underscores or spaces.
Other components can then be placed into these slots using the drag-and-drop Designer:
Or add components in code, by passing the slot’s name as the slot
container property.
rt = RichText(content="Enter your name: {name} {go}")
rt.add_component(TextBox(), slot="name")
rt.add_component(Button(), slot="go")
Slots can also be populated by setting the data
property of the RichText component to a dict
with keys corresponding to the slots.
The items in this dict
will then be inserted into the named slots appropriately. Anvil Components will be displayed in their slots, and other Python objects will be subject to f-string formatting.
rt = RichText(content="Personal details: {name} {age:.1f} {submit}")
rt.data = {"name": "Zaphod Beeblebrox", "age": 42, "submit": Button(text="Submit")}
Named slots work in all three format modes - markdown
, restricted_html
, and plain_text
.
To display a literal {
or }
in a RichText component, use {{
and }}
, respectively.
rt = RichText(content="This is a {slot}. This is some text in {{curly braces}}.")
Alternately, set the enable_slots
property of the RichText container to False
, and curly braces will be displayed as-is. It’s recommended that enable_slots
be set to False
whenever a RichText component will display untrusted input, to prevent curly braces from being omitted.
RichText HTML Sanitisation
When a Richtext’s format
property is restricted_html
, its content
is sanitised by restricting it only to certain tags and attributes.
The only permissible tags are
<h1>
,<h2>
,<h3>
,<h4>
,<h5>
,<h6>
,<hgroup>
<col>
,<colgroup>
<table>
,<tbody>
,<td>
,<tfoot>
,<th>
,<thead>
,<tr>
<b>
,<i>
,<s>
,<u>
<footer>
,<header>
<main>
<a>
<abbr>
<address>
<article>
<aside>
<bdi>
<bdo>
<blockquote>
<br>
<caption>
<cite>
<code>
<data>
<dd>
<dfn>
<div>
<dl>
<dt>
<em>
<figcaption>
<figure>
<hr>
<kbd>
<li>
<mark>
<nav>
<ol>
<p>
<pre>
<q>
<rb>
<rp>
<rt>
<rtc>
<ruby>
<samp>
<section>
<small>
<span>
<strong>
<sub>
<sup>
<time>
<ul>
<var>
<wbr>
There’s also only a few permissible attributes. First, href
in <a>
tags, and src
attribute in <img>
tags. Both of these then have their arguments restricted to safe targets:
- relative URLs starting with
/
or_/theme
- absolute URLs starting with
http://
orhttps://
The style
attribute is available for all permissible tags, but can only be used with a restricted list of safe arguments. These are:
margin
,margin-left
,margin-top
,margin-bottom
,margin-right
padding
,padding-left
,padding-top
,padding-bottom
,padding-right
font-size
,font-family
,font-weight
,font-style
border
background-color
color
FlowPanel
This container allows you to display components in lines with wrapping. Each component will only take up the horizontal space that it needs.
Some components, like Buttons, dictate their own width when placed inside FlowPanels, based on their content. For components like TextBoxes, that don’t have a width specified by their content, you can drag the handle to set the width you require.
fp = FlowPanel(align="center", spacing="small")
# A button determines its own width
fp.add_component(Button(text="Click me"))
# You set the width of a TextBox explicitly
fp.add_component(TextBox(), width=100)
If a component doesn’t fit on the same row as the previous component, it will wrap onto a new line.
To control the space between components, set the spacing
property of the FlowPanel.
Like many other containers, the FlowPanel will expand vertically to fit its contents.
By default, components are added at the end of a FlowPanel. If you pass an index
parameter to add_component()
, the component will be inserted at the specified position.
Index 0 is the first element in the panel, 1 is the second, etc.
fp = FlowPanel()
fp.add_component(Button(text="Button"))
# Display the label to the left of the button:
fp.add_component(Label(text="Click here:"), index=0)
DataRowPanel
For full documentation on the DataRowPanel, see the section on Data Grids
The DataRowPanel is a special Anvil container, designed to work inside the DataGrid component. In particular, DataRowPanels inherit the column-based layout of their parent DataGrids, so they can arrange their child components appropriately. There are two main features of DataRowPanels that differentiate them from other Anvil containers:
- DataRowPanels have a ‘slot’ for each column of their parent Data Grid, meaning other Anvil components can be dropped into particular columns. They also behave like LinearPanels, in that components placed outside the column-specific slots will take up the full width of the container. This is useful for section headers and other advanced layouts.
- DataRowPanels can automatically display data in the column-based layout of their parent DataGrid. This is done by setting the
item
property of the DataRowPanel to a dictionary with the same keys as thedata_keys
of the Data Grid columns.
For more information, see the documentation for the DataGrid
component, or our DataGrid
Tutorials
LinearPanel
This container arranges its child components vertically, each filling the whole width of the panel by default.
It doesn’t require any extra arguments for add_component
: each component is added to the bottom of the LinearPanel.
lp = LinearPanel()
lp.add_component(Label(text="Hello"))
lp.add_component(Button(text="Click me"))
The LinearPanel will expand vertically to fit its contents.
By default, components are added at the end of a LinearPanel.
If you add an index
parameter to the add_component()
call, the component will be added at that index. Index 0 is the first element in the panel, 1 is the second, etc.
# self.linear_panel_1 is a LinearPanel
lp.add_component(Button(text="Button"))
# This label will be added above the Button.
lp.add_component(Label(text="Label"), index=0)
XYPanel
This container allows its child components to be placed at a specific position inside the container. Positions are measured in pixels from the top-left of the panel. To help with positioning components, you can get the width of an XYPanel
by calling the get_width()
function.
Arguments to add_component
:
x
: How far across the component is from the left-hand edgey
: How far down the component is from the top edgewidth
: Optional, the width of the component
xy_panel = XYPanel(width=400, height=400)
btn = Button(text="Click me!")
xy_panel.add_component(btn, x=10, y=100)
GridPanel
This container lays out components on a grid. Each row has twelve columns, and a component can span multiple columns. Components occupying the same columns in different rows will be aligned.
gp = GridPanel()
gp.add_component(Label(text="Name:"),
row="MyRow", col_xs=0, width_xs=2)
gp.add_component(TextBox(),
row="MyRow", col_xs=2, width_xs=6)
gp.add_component(Button(text="Submit"),
row="MyOtherRow", col_xs=3, width_xs=2)
Arguments to add_component
:
row
: The name of the row to which this component will be added. If this is the first component with this row name, a new row will be created at the bottom of the GridPanel.col_xs
: What’s the first column this component occupies? (0-11, default 0)width_xs
: How many columns does this component occupy? (1-12, default 12)
Do you still have questions?
Our Community Forum is full of helpful information and Anvil experts.