In Odoo 18, snippets are powerful tools for extending the website builder, allowing developers to create reusable, dynamic content blocks. With the introduction of OWL (Odoo Web Library), Odoo snippets have become more interactive and performant, leveraging modern JavaScript frameworks. In this blog, we'll walk through the process of creating a custom product snippet in Odoo 18 using OWL components. This snippet will display a list of products with their names, prices, images, and reference codes.
By the end of this guide, you'll have a fully functional snippet that fetches product data from the backend and renders it dynamically on the website. Let's get started!
Prerequisites
Before diving into the code, ensure you have the following:
* Odoo 18 installed and running.
* Basic knowledge of Odoo module development.
* Familiarity with JavaScript, OWL, and XML.
* A custom Odoo module (e.g., your_module_name) where you'll add the snippet.
For this tutorial, we'll assume you're working in a module called your_module_name.
Step 1: Module Structure
To create a custom snippet, your module should have the following structure:
/your_module_name/
+-- __init__.py
+-- __manifest__.py
+-- controllers/
¦ +-- main.py
+-- static/
¦ +-- src/
¦ +-- js/
¦ ¦ +-- snippet.js
¦ +-- images/
¦ +-- snippets/
¦ +-- banner.jpg
+-- views/
¦ +-- snippet_views.xml
Here's what each component does:
* controllers/main.py: Defines the backend logic to fetch product data.
* static/src/js/snippet.js: Contains the OWL component for the frontend logic.
* views/snippet_views.xml: Defines the snippet template and integrates it into the website builder.
* static/src/images/snippets/banner.jpg: A thumbnail image for the snippet in the website builder.
Update your __manifest__.py to include dependencies and assets:
python
Copy
# -*- coding: utf-8 -*-
{
'name': 'your_module_name',
'depends': [
'base', 'website',
],
'data': [
'views/snippets/snippet_template.xml'
],
'assets': {
'web.assets_frontend': [
'your_module_name/static/src/js/*',
'your_module_name/static/src/xml/*'
]
},
}
Step 2: Creating the Controller
The controller handles the backend logic, fetching product data to be displayed in the snippet. Create a file controllers/main.py with the following code:
# -*- coding: utf-8 -*-
from odoo import http
from odoo.http import request
class WebsiteSnippetOwl(http.Controller):
@http.route('/get_product_details', type='json', auth='public', methods=['POST'])
def get_product_details(self):
""" Function to get product details for the snippet"""
return request.env['product.product'].search_read([('image_1920', '!=', False)],
['name', 'lst_price', 'default_code', 'image_1920'], limit=8)
Explanation:
* The @http.route decorator defines a JSON endpoint /get_product_details accessible publicly.
* The search_read method retrieves up to 8 products with images, returning their name, lst_price, default_code, and image_1920 fields.
* The auth='public' ensures the endpoint is accessible to all website visitors.
Step 3: Creating the OWL Component
The OWL component handles the frontend logic, fetching data from the controller and rendering it. Create a file static/src/js/snippet.js with the following code:
/** @odoo-module **/
import { rpc } from "@web/core/network/rpc";
import { registry } from "@web/core/registry";
import { useState, Component, onWillStart } from "@odoo/owl";
export class WebOwlComponentSnippet extends Component {
static template = "your_module_name.snippet_xml";
setup() {
this.state = useState({
data: [],
});
onWillStart(async () => {
this.state.data = await rpc("/get_product_details");
});
}
}
registry.category("public_components").add("your_module_name.snippet", WebOwlComponentSnippet);
Explanation:
* Imports: We import rpc for making backend calls, registry to register the component, and OWL utilities (useState, Component, onWillStart).
* Component: WebOwlComponentSnippet extends Component and defines the template Website_snippet_owl.snippet.
* Setup: The setup method initializes a reactive state object with an empty data array. The onWillStart hook fetches product data from the /get_product_details endpoint before rendering.
* Registry: The component is registered in the public_components category, making it accessible in the website builder.
Step 4: Creating the OWL Template
The OWL template defines the HTML structure for rendering the product data. Create a file static/src/xml/snippet.xml (or include it in your module's views) with the following code:
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="your_module_name.snippet_xml">
<div class="row body">
<t t-foreach="state.data" t-as="each" t-key="each.id">
<div class="col-lg-4">
<div class="card">
<div class="card-body">
<h5 class="card-title"><t t-esc="each.name"/></h5>
<div class="row">
<div class="col-6">
<p class="card-text"><t t-esc="each.default_code"/></p>
</div>
<div class="col-6">
<img class="card-img-top o_img_product_square o_img_product_cover h-auto"
t-attf-src="data:image/jpeg;base64,{{each.image_1920}}"/>
</div>
</div>
<a href="#" class="btn btn-primary">Go somewhere</a>
</div>
</div>
</div>
</t>
</div>
</t>
</templates>
Explanation:
* Template: The t-name attribute defines the template name (Website_snippet_owl.snippet), matching the component's static template.
* Loop: The t-foreach directive loops through state.data, rendering a card for each product.
* Data Binding: Product details (name, default_code, image_1920) are displayed using t-esc. The image is rendered as a base64-encoded JPEG.
* Styling: Bootstrap classes (row, col-lg-4, card, etc.) are used for a responsive grid layout.
Step 5: Creating the Snippet View
The snippet view integrates the OWL component into the website builder, making it draggable and configurable. Create a file views/snippet_views.xml with the following code:
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="product_snippet_template" name="Product Snippet">
<section class="product_section">
<div class="container">
<h1>Latest Products</h1>
<owl-component name="your_module_name.snippet"/>
</div>
</section>
</template>
<template id="product_component_snippet" inherit_id="website.snippets" name="Category Highlight Snippet">
<xpath expr="//snippets[@id='snippet_groups']
" position="inside">
<t t-snippet="your_module_name.product_snippet_template"
t-thumbnail="/your_module_name/static/src/images/snippets/banner.jpg"/>
</xpath>
</template>
</odoo>
Explanation:
* Snippet Template: The product_snippet_template defines the outer structure of the snippet, including a heading and the OWL component (<owl-component>).
* Snippet Integration: The product_component_snippet template inherits website.snippets and adds the snippet to the website builder's "Structure" category.
* Thumbnail: The t-thumbnail attribute specifies a preview image for the snippet in the website builder.
Note: Replace your_module_name with your actual module name and ensure the thumbnail image exists at the specified path.
Conclusion
Creating a custom snippet in Odoo 18 using OWL components is a powerful way to enhance the website builder with dynamic, interactive content. By combining a backend controller, an OWL component, and a snippet view, you can create reusable blocks like the product snippet we built in this guide. OWL's reactive nature and Odoo's website builder make it easy to create modern, performant snippets tailored to your needs.
To read more about How to Use OWL Components on the Website Odoo 18, refer to our blog How to Use OWL Components on the Website Odoo 18.