Building blocks¶
Building blocks, also known as snippets, are how users design and layout pages. They are important XML elements of your design.
The building blocks are classified into four categories:
Structure blocks: to give a basic structure to the website
Feature blocks: to describe the features of a product or service
Dynamic Content blocks: blocks that are animated or interact with the backend
Inner Content blocks: blocks used inside other building blocks
At the end of this chapter, you will be able to create custom snippets and to add them into a dedicated category.
File structure¶
The layout’s file structure is the following.
views
├── snippets
│ └── options.xml
│ └── s_snippet_name.xml
The styles” file structure is the following.
static
├── src
│ └── snippets
│ └── s_snippet_name
│ └── 000.js
│ └── 000.scss
│ └── 000.xml
│ └── options.js
Pour plus d'infos
Demo page
Demo data have to be installed to access this page:
https://your-database.com/website/demo/snippets
Layout¶
Snippets are editable by the user using the Website Builder. Some Bootstrap classes are important as they trigger some Website Builder options.
Wrapper¶
The standard main container of any snippet is a section
. Any section element can be edited like a
block of content that you can move or duplicate.
<section class="s_snippet_name" data-name="..." data-snippet="...">
<!-- Content -->
</section>
For inner content snippets, any other HTML tag can be used.
<div class="s_snippet_name" data-name="..." data-snippet="...">
<!-- Content -->
</div>
Attribute |
Description |
---|---|
class |
Unique class name for this snippet |
data-name |
Displayed in the right panel as the name of the snippet. If not found, it will fall back to Block. |
data-snippet |
Used by the system to identify the snippet |
The system automatically adds the data-name
and data-snippet
attributes during the drag and
drop based on the template’s name.
Avertissement
Those attributes should be specifically added when a snippet is declared on a theme page.
Avertissement
Avoid adding a section
tag inside another section
tag: this will trigger twice the Website
Builder’s options. You can use inner content snippets instead.
Astuce
To write the content of a static page with standard snippets, there are two possible approaches:
- Pre-build the custom static pages with the Website Builder: Drag & drop snippets, then
copy/paste the code into your file and clean it up.
- Code everything directly: But beware of compatibility with the Website Builder. It
requires certain classes, names, id, data, etc. to work properly. A good practice is to seek out the snippets code created in standard code in the Odoo source files. Pay attention that the Website Builder sometimes adds classes to the snippets after dropped in the page.
Elements¶
There is a list of « features » we can enable/disable by using specific CSS classes.
Sizing¶
Any large Bootstrap columns directly descending from a .row
element (respecting Bootstrap
structure) will be triggered by the Website Builder to make them resizable.
.row > .col-lg-*
Add padding on columns and <section>
.
class="pt80 pb80"
Note
pb*
and pt*
are the Odoo classes used to control the handlers. Their values are
increased by multiples of 8, till a max of 256 (0, 8, 16, 24, 32, 40, 48, …).
Enable the columns selector.
<div class="container s_allow_columns">
Disable the columns amount option.
<div class="row s_nb_column_fixed">
Disable the size option for all child columns.
<div class="row s_col_no_resize">
Disable the size option for one specific column.
<div class="col-lg-* s_col_no_resize">
Colors¶
Add a background based on the color palette for columns and <section>
.
class="o_cc o_cc*"
Disable the background color option for all columns.
<div class="row s_col_no_bgcolor">
Disable the background color option of one specific column.
<div class="col-lg-* s_col_no_bgcolor">
Add a black color filter with an opacity of 50%.
<section>
<div class="o_we_bg_filter bg-black-50"/>
<div class="container">
<!-- Content -->
</div>
</section>
Add a white color filter with an opacity of 85%.
<section>
<div class="o_we_bg_filter bg-white-85"/>
<div class="container">
<!-- Content -->
</div>
</section>
Add a custom color filter.
<section>
<div class="o_we_bg_filter" style="background-color: rgba(39, 110, 114, 0.54) !important;"/>
<div class="container">
<!-- Content -->
</div>
</section>
Add a custom gradient filter.
<section>
<div class="o_we_bg_filter" style="background-image: linear-gradient(135deg, rgba(255, 204, 51, 0.5) 0%, rgba(226, 51, 255, 0.5) 100%) !important;"/>
<div class="container">
<!-- Content -->
</div>
</section>
Features¶
Make an element not editable.
<div class="o_not_editable">
Make an element not removable.
<div class="oe_unremovable">
Add a background image and have it centered.
<div class="oe_img_bg o_bg_img_center" style="background-image: url('...')">
Add parallax effect.
<section class="parallax s_parallax_is_fixed s_parallax_no_overflow_hidden" data-scroll-background-ratio="1">
<span class="s_parallax_bg oe_img_bg o_bg_img_center" style="background-image: url('...'); background-position: 50% 75%;"/>
<div class="container">
<!-- Content -->
</div>
</section>
Note
A video background can be set on a section. Refer to the « Media » chapter of this documentation.
Grid layout¶
Grid Layout is a powerful and flexible layout system in CSS that enables users to design complex building block layouts with ease.
Enable the Grid Layout by adding the o_grid_mode
class on the row
. The number of rows in your
grid is defined in the data-row-count
attribute. The grid always contains 12 columns.
Use
<div class="row o_grid_mode" data-row-count="13">
<!-- Content -->
</div>
Add items in the grid with the o_grid_item
class. If the grid item contains an image, use the
o_grid_item_image
class.
<div class="row o_grid_mode" data-row-count="13">
<div class="o_grid_item g-height-* g-col-lg-*" style="grid-area: 2 / 1 / 7 / 8; z-index: 3;">
<!-- Content -->
</div>
<div class="o_grid_item o_grid_item_image g-height-* g-col-lg-*" style="grid-area: 1 / 6 / 9 / 13; z-index: 2;">
<img src="..." alt="..." >
</div>
</div>
The dimensions and position of a grid item are defined by the grid-area that can be explicitly set
in the style
attribute along with the z-index.
The g-height-*
and g-col-lg-*
classes are generated by the Website Builder for editing purposes.
Compatibility system¶
- When a snippet has a
data-vcss
,data-vjs
and/ordata-vxml
attribute, it means it is an updated version, not the original one.
<section class="s_snippet_name" data-vcss="001" data-vxml="001" data-js="001">
<!-- Content -->
</section>
These data attributes indicate to the system which file version to load for that
snippet (e.g., 001.js
, 002.scss
).
Custom snippet¶
To create a custom snippet, create first the snippet template. Then, add it to the list and make it available via the Website Builder.
Template¶
Declaration
/website_airproof/views/snippets/s_airproof_snippet.xml
¶<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="s_airproof_snippet" name="...">
<section class="s_airproof_snippet">
<!-- Content -->
</section>
</template>
</odoo>
Avertissement
data-name
and data-snippet
attributes have to be specified when a snippet is declared on a
theme page. Otherwise, the snippet won’t be recognised by the Website Builder and issues might
occur whenever a database upgrade is done. Additionally, remember that the name attribute is
shown as the name of your custom snippet in the Blocks section of the options panel.
Astuce
Use Bootstrap native classes as much as possible.
Prefix all your custom classes.
Use underscore lowercase notation to name classes, e.g.,
.x_nav
,.x_nav_item
.Avoid using ID attribute within your
section
as several instances of a snippet may appear throughout the page (An ID attribute has to be unique on a page).
Add your custom snippet to the list of standard snippets, so the user can drag and drop it on the page, directly from the edit panel.
/website_airproof/views/snippets/options.xml
¶<template id="snippets" inherit_id="website.snippets" name="Airproof - Custom Snippets">
<xpath expr="//*[@id='default_snippets']" position="before">
<t id="x_theme_snippets">
<div id="x_theme_snippets_category" class="o_panel">
<div class="o_panel_header">Theme</div>
<div class="o_panel_body">
<t t-snippet="website_airproof.s_airproof_snippet" t-thumbnail="/website_airproof/static/src/img/wbuilder/s_airproof_snippet.svg">
<keywords>Snippet</keywords>
</t>
</div>
</div>
</t>
</xpath>
</template>
Attribute |
Description |
---|---|
t-snippet |
The template to use |
t-thumbnail |
The path to the snippet thumbnail |
<keywords> |
Keywords entered by the user in the search field in the Snippets panel |
Options¶
Options allow users to edit a snippet’s appearance/behavior using the Website Builder. You can create snippet options easily and automatically add them to the Website Builder.
Pour plus d'infos
Template¶
There are a bunch of commands to set the options of a custom snippet. These options can be created
into /website_airproof/views/snippets/s_airproof_snippet.xml
.
/website_airproof/views/snippets/s_airproof_snippet.xml
¶<template id="s_airproof_snippet_options" inherit_id="website.snippet_options" name="Airproof - Snippets Options">
<xpath expr="." position="inside">
<!-- Options -->
</xpath>
</template>
Then insert the different available options:
/website_airproof/views/snippets/s_airproof_snippet.xml
¶<template id="s_airproof_snippet_options" inherit_id="website.snippet_options" name="Airproof - Snippets Options">
<xpath expr="." position="inside">
<div data-selector=".s_airproof_snippet">
<we-select string="Layout">
<we-button data-select-class="">Default</we-button>
<we-button data-select-class="s_airproof_snippet_portrait">Portrait</we-button>
<we-button data-select-class="s_airproof_snippet_square">Square</we-button>
<we-button data-select-class="s_airproof_snippet_landscape">Landscape</we-button>
</we-select>
<we-title>Space</we-title>
<we-button-group string="Before">
<we-button data-select-class="mt-0">1</we-button>
<we-button data-select-class="mt-3">2</we-button>
<we-button data-select-class="mt-5">3</we-button>
</we-button-group>
</div>
</xpath>
</template>
Binding¶
These options use CSS selectors (class, XML tag, id, etc).
data-selector¶
Options are wrapped in groups. Groups can have properties that define how the included options interact with the user interface.
data-selector
binds all the options included in the group to a particular element matching the
selector value (CSS class, ID, etc). The option will appear when the matching selector is selected.
<div data-selector="section, h1, .custom_class, #custom_id">
It can be used in combination with other attributes like data-target
, data-exclude
or
data-apply-to
.
data-target¶
data-target=""
allows to apply the option to a child element of the data-selector=""
.
<div
data-selector=".s_airproof_snippet"
data-target=".row">
data-exclude¶
data-exclude=""
allows to exclude some particular selectors from the rule.
<ul>
tag (without .navbar-nav
class) is selected¶<div
data-selector="ul"
data-exclude=".navbar-nav">
data-drop-in¶
data-drop-in
defines the list of elements where the snippet can be dropped into.
<div data-selector=".s_airproof_snippet" data-drop-in=".x_custom_location">
data-drop-near¶
data-drop-near
defines the list of elements where the snippet can be dropped beside.
<div data-selector=".s_airproof_snippet_card" data-drop-near=".card">
data-js¶
data-js
binds a custom JavaScript methods.
<div data-selector=".s_airproof_snippet" data-js="CustomMethodName">
Layout & fields¶
<we-title>
¶
Add titles between options to categorize them.
<we-title>Option subtitle 1</we-title>

<we-row>
¶
Create a row in which elements is displayed next to each other.
<we-row string="My option">
<we-select>...</we-select>
<we-button-group>...</we-button-group>
</we-row>
The perfect example for this case is the Animation row:

<we-select>
¶
Formats the option as a dropdown list. Add string=""
to indicate the field name.
<we-select string="Layout">...</we-select>

<we-checkbox>
¶
Formats the option as a toggle switch.
<we-checkbox
string="Tooltip"
data-select-class="s_airproof_snippet_tooltip" />

<we-range>
¶
Formats the option as a slider.
<we-range
string="Images Spacing"
data-select-class="o_spc-none|o_spc-small|o_spc-medium|o_spc-big" />
Each step of the range is separated by a |
. Here, each class name corresponds to a step.

<we-input>
¶
Formats the option as a text field.
data-unit
, data-save-unit
and data-step
are optional¶<we-input
string="Speed"
data-unit="s"
data-save-unit="ms"
data-step="0.1" />

<we-input>
comes with optional attributes particularly useful in specific case:
Attribute |
Description |
---|---|
|
Shows the expected unit of measure. |
|
Set the unit of measure to which the value entered by the user is converted and saved. |
|
Set the numerical value by which the field can be incremented. |
<we-colorpicker>
¶
Formats the option as a color/gradient to choose from.
<we-colorpicker
string="Color filter"
data-select-style="true"
data-css-property="background-color"
data-color-prefix="bg-"
data-apply-to=".s_map_color_filter" />

Attribute |
Description |
---|---|
|
Refers to |
|
Define the CSS property targeted by the colorpicker. |
|
Define the prefix applied to the CSS class returned. |
|
Set the element on which the color is applied. |
Methods¶
Beside binding options allowing to select, target or exclude an element. Option fields have several useful data attributes refering to standard JavaScript methods.
For example, data-select-class
refers to the JavaScript method named selectClass
.
Built-in methods¶
Selection¶
There are several built-in methods available. They are callable by using the related data attribute directly into the XML template.
Data attributes |
Description |
---|---|
|
Allows to select one and only one class in the option classes set and set it on the associated snippet. |
|
Allows to select a value and set it on the associated snippet as an attribute. The attribute
name is given by the |
|
Allows to select a value and set it on the associated snippet as a property. The attribute
name is given by the |
|
Allows to select a value and set it on the associated snippet as a CSS style. The attribute
name is given by the |
|
Enable the selection of a color palette.
Only for |
Events¶
There are also built-in methods directly linked to events the Website Builder listens to:
Name |
Description |
---|---|
start |
Occurs when the publisher selects the snippet for the first time in an editing session or when the snippet is drag-and-dropped on the page. |
destroy |
Occurs after the publisher has saved the page. |
onFocus |
Occurs each time the snippet is selected by the user or when the snippet is drag-and-dropped on the page. |
onBlur |
Occurs when a snippet loses focus. |
onClone |
Occurs just after a snippet is duplicated. |
onRemove |
Occurs just before the snippet is removed. |
onBuilt |
Occurs just after the snippet is drag-and-dropped on a drop zone. When this event is triggered, the content is already inserted in the page. |
cleanForSave |
Occurs before the publisher saves the page. |
Custom methods¶
To create custom JavaScript methods, a link between the options group and the custom methods has to
be created. To do so, a JavaScript class has to be created and called in the XML template with
data-js
.
Add the data-js
attribute to your options group:
<template id="s_airproof_snippet_options" inherit_id="website.snippet_options" name="Airproof - Snippets Options">
<xpath expr="." position="inside">
<div data-selector=".s_airproof_snippet" data-js="airproofSnippet">
// Options
</div>
</xpath>
</template>
Then, the class can be created in a JavaScript file:
/website_airproof/static/src/s_airproof_snippet/options.js
¶/** @odoo-module */
import options from 'web_editor.snippets.options';
const AirproofSnippet = options.Class.extend({
// Built-in method example
start: function() {
//...
}
// Custom method example
customMethodName: function() {
//...
}
});
options.registry.AirproofSnippet = AirproofSnippet;
export default AirproofSnippet;
Finally, the custom method can be called on your custom option through the XML template:
<template id="s_airproof_snippet_options" inherit_id="website.snippet_options" name="Airproof - Snippets Options">
<xpath expr="." position="inside">
<div data-selector=".s_airproof_snippet" data-js="airproofSnippet">
<we-checkbox data-custom-method-name="" />
</div>
</xpath>
</template>
Dynamic Content templates¶
By default, Dynamic Content blocks have a selection of templates available in the Website Builder. Custom templates can also be added to the list automatically by use the same naming convention for the template id attribute.
Call the template¶
The selected dynamic snippet replace the <div class="dynamic_snippet_template"/>
placeholder by
the right template based on the data-template-key
and the custom CSS class:
<section
data-snippet="s_blog_posts"
data-name="Blog Posts"
class="s_blog_post_airproof s_dynamic_snippet_blog_posts s_blog_posts_effect_marley s_dynamic pb32 o_cc o_cc2 o_dynamic_empty"
data-template-key="website_airproof.dynamic_filter_template_blog_post_airproof"
data-filter-by-blog-id="-1"
data-number-of-records="3"
data-number-of-elements="3"
>
<div class="container o_not_editable">
<div class="css_non_editable_mode_hidden">
<div class="missing_option_warning alert alert-info rounded-0 fade show d-none d-print-none">
Your Dynamic Snippet will be displayed here... This message is displayed because you did not provided both a filter and a template to use.<br/>
</div>
</div>
<div class="dynamic_snippet_template"/>
</div>
</section>
Examples¶
/website_airproof/views/snippets/options.xml
¶<template id="dynamic_filter_template_blog_post_airproof" name="...">
<div t-foreach="records" t-as="data" class="s_blog_posts_post">
<t t-set="record" t-value="data['_record']"/>
<!-- Content -->
</div>
</template>
Attribute |
Description |
---|---|
id |
The ID of the template. Has to start with |
name |
Human-readable name of the template |
/website_airproof/views/snippets/options.xml
¶<template id="dynamic_filter_template_product_product_airproof" name="...">
<t t-foreach="records" t-as="data" data-number-of-elements="4" data-number-of-elements-sm="1" data-number-of-elements-fetch="8">
<t t-set="record" t-value="data['_record']"/>
<!-- Content -->
</t>
</template>
Attribute |
Description |
---|---|
id |
The ID of the template. Has to start with |
name |
Human-readable name of the template |
data-number-of-elements |
Number of products per slide on desktop |
data-number-of-elements-sm |
Number of products per slide on mobile |
data-number-of-elements-fetch |
The total amount of fetched products |
/website_airproof/views/snippets/options.xml
¶<template id="dynamic_filter_template_event_event_airproof" name="...">
<div t-foreach="records" t-as="data" class="s_events_event">
<t t-set="record" t-value="data['_record']._set_tz_context()"/>
<!-- Content -->
</div>
</template>
Attribute |
Description |
---|---|
id |
The ID of the template. Has to start with |
name |
Human-readable name of the template |