Site pages and sections
A site page is a page made of widgets. Its web template declares named sections with render_section, editors fill those sections with widgets, and Raytha renders each widget through its own template. This page covers the template side: sections, the grid, the two functions, and how drafts reach the public site.
The parts
| Part | What it is | Who edits it |
|---|---|---|
| Site page | A title, a route path, one web template, and a draft and a published copy of its widgets. | Editors, in the admin |
| Web template | Liquid that surrounds the widgets and declares the sections. Layouts and inheritance work as in any other web template. | Theme authors |
| Section | A named slot, such as main or sidebar. Each render_section("name") call in the template is one section. | Theme authors |
| Widget | One block in a section, with a type, a set of settings, and a position in the 12-column grid. | Editors |
| Widget template | The Liquid and settings form for a widget type. See Widget templates. | Theme authors |
A minimal page template
<section class="py-5">
<div class="container">
{{ render_section("main") }}
</div>
</section>
Pick this template for a site page, add a widget to main, and the page renders:
<section class="py-5">
<div class="container">
<div class="row">
<div class="col-md-12">
... the widget's HTML ...
</div>
</div>
</div>
</section>
The template is the body of the page. The base layout above it supplies <html>, the header and the footer. A site page template can inherit from a layout like any other template, and render_section can sit in the layout itself.
Raytha ships four site page templates in every theme:
| Developer name | Sections |
|---|---|
raytha_html_page_fullwidth | main |
raytha_html_page_sidebar | main (8 columns), sidebar (4 columns) |
raytha_html_page_multi | hero, features, content, cta |
raytha_html_home | hero, features, content, cta |
How sections are found
The admin does not ask you to register sections. When an editor opens a page, Raytha scans the page's template, and every template it inherits from, for render_section("name") and get_section("name") calls. Each distinct name becomes a section the editor can fill.
- The name must be a quoted string literal, single or double quotes.
render_section(my_variable)works at render time but the editor never offers that section. - Calls inside
{% comment %}blocks and HTML comments<!-- -->are ignored. Mention a section in a comment freely. - A template with no calls gets one section named
main. - Use the same case in the template and on the page.
render_section("Main")does not find widgets saved inmain.
render_section
render_section("name") returns the HTML of every widget in that section, wrapped in Bootstrap-style rows and columns. A section with no widgets, and a call outside a site page, return an empty string.
| Named argument | Default | Effect |
|---|---|---|
wrap | true | With false, no row or column elements are written and every other option is ignored. Widgets appear in grid order. |
row_class | row | The class of each row element. |
col_class | col-md-{columnSpan} | The class of every column element. Set it and the span is no longer written into the markup. |
row_id, col_id | none | An id attribute on every row or column. |
row_attributes, col_attributes | none | Extra attribute text, for example row_attributes="data-aos='fade-up'". |
{{ render_section("main", wrap=false) }}
{{ render_section("features", row_class="row g-4", col_class="col-12 col-md-4") }}
The options are applied to every row and column of the call. row_id on a section with three rows writes three elements with the same id.
get_section
When you need markup that render_section cannot produce, use get_section("name"). It returns a list of widgets, ordered by row and then column. Each one has the widget already rendered into content.
| Member | Value |
|---|---|
id, type | The widget instance id, and the widget template's developer name. |
content | The widget's rendered HTML. If it failed, an HTML comment naming the widget type and the error. |
settings | The raw settings. Read them as block.settings.headline. |
row, column, column_span | Numbers. Rows start at 0. |
css_class, html_id, custom_attributes | Strings. Empty when the editor set nothing. |
is_row_start, is_row_end | Booleans. True for the first and last widget of a row. |
Rebuild the grid yourself with your own classes:
{% for block in get_section("features") %}
{% if block.is_row_start %}<div class="feature-row">{% endif %}
<div class="feature feature-{{ block.type }}" data-span="{{ block.column_span }}">
{{ block.content }}
</div>
{% if block.is_row_end %}</div>{% endif %}
{% endfor %}
Or skip the grid and place the pieces anywhere:
{% assign blocks = get_section("sidebar") %}
{% if blocks.size > 0 %}
<aside class="sidebar">
{% for block in blocks %}
<div class="sidebar-block">{{ block.content }}</div>
{% endfor %}
</aside>
{% endif %}
Both functions are defined only while a site page is being rendered. Calling them in a detail or list template returns nothing. Calling them inside a widget template also returns nothing, so widgets cannot nest.
The grid
Every widget has a position in its section:
| Property | Range | Meaning |
|---|---|---|
row | 0 or more | Widgets with the same row are written inside one row element. Rows are sorted ascending. |
column | 0 to 11 | The sort order inside a row. It does not become an offset. |
columnSpan | 1 to 12 | The width, written as col-md-N. The default is 12. |
cssClass, htmlId, customAttributes | text | Passed to the widget as widget.css_class, widget.html_id and widget.custom_attributes. The widget's own template decides whether to print them. |
Three cards on one row use row 0 for all of them, column 0, 4 and 8, and columnSpan 4. Two widgets in the same row with a total span under 12 leave the rest of the row empty. The columns do not center themselves.
The default column class is Bootstrap's. If your theme does not load Bootstrap, pass col_class and row_class, or use get_section.
Draft and published
A site page keeps two copies of its widgets. Editors work on the draft. The public site renders the published copy.
- An unpublished page answers with the 404 template.
- Saving widgets changes the draft only. The live page changes when the page is published.
- A signed-in admin who can manage site pages can view the draft by adding
?previewDraft=trueto the page's URL. Everyone else gets the published copy and ignores the parameter. - The page that is set as the home page cannot be unpublished or deleted. Set another page as the home page first.
Widget templates are different. A change to a widget template applies to the published page at once, because the widget is rendered from its template on every request.
Choosing the template
A page can only use a web template that belongs to the active theme, and it can only place widget types that exist in the active theme. Switching the active theme does not move existing pages. A page that points at the old theme's template keeps rendering with that template's source but looks up widgets in the new theme. After a switch, re-select the template for each page and check every one.
Gotchas
- The default base layout writes
<title>fromTarget.PrimaryField, which a site page does not have. A site page therefore gets only the organization name as its title. In your own layout, use{% if Target.Title %}{{ Target.Title | escape }} | {% endif %}{{ CurrentOrganization.OrganizationName | escape }}. wrap=falsemust be the booleanfalse. The textwrap="false"is a non-empty string and still wraps.- A widget that fails to render does not fail the page. Its place in the HTML holds the comment
<!-- Widget rendering error: type - message -->. View source after every change. The message can include template text, so do not leave a broken widget on a public page. - Removing a section from a template does not remove its widgets. They stay on the page and are not rendered until the section returns.
- An editor cannot delete a widget template that a page still uses, built-in or not.