Web development in Odoo has significantly evolved with the adoption of Owl (Odoo Web Library) components in Odoo 18. This modernized approach leverages a component-based architecture, seamlessly integrating with Odoo’s backend to enable the creation of dynamic and interactive web applications.
With Odoo 18, the use of Owl components in websites and portal pages has expanded, moving away from conventional development methods. This enhancement allows developers to build more advanced and interactive user interfaces while preserving Odoo’s core principles of simplicity and efficiency.
Why Choose Owl Components?
Owl components bring several key benefits to Odoo website development:
- Component-Based Architecture
* Modular and reusable code for efficient development
* Simplified maintenance and updates
* Clear separation of logic and presentation
- Improved Interactivity
* Real-time updates for a dynamic user experience
* Seamless client-side interactions
* Enhanced usability for complex features
- Modern Development Approach
* Intuitive syntax familiar to JavaScript developers
* Integrated state management for streamlined data handling
* A robust templating system for efficient UI development
Understanding the Ecosystem
Before implementing Owl components, it's essential to understand their role within the Odoo website framework:
* Frontend Assets – Owl components are included in the web.assets_frontend bundle, ensuring seamless integration with other frontend resources.
* Public Components Registry – Components are registered in the public_components registry, making them accessible across the website.
* Integration Points – Owl components can be embedded in both website pages and portal views, enabling dynamic and interactive UI elements.
Key Considerations
While Owl components are powerful, they come with important considerations:
1. Performance Impact
* Client-side rendering implications
* Initial load time considerations
* Browser resource usage
2. SEO Implications
* Search engine crawling behavior
* Content indexing challenges
* Best practices for visibility
3. User Experience
* Layout shift management
* Loading state handling
* Accessibility considerations
Sample Implementation:
We'll create a partner listing page with a Kanban view using Owl components. This implementation will include:
1. Website menu creation
2. Backend controller
3. Owl component for partner display
4. Templates and styling
Step 1: Module Structure
First, create a new module with this structure:
partner_website_listing/
+-- __init__.py
+-- __manifest__.py
+-- controllers/
¦ +-- __init__.py
¦ +-- partner_controller.py
+-- static/
¦ +-- src/
¦ +-- components/
¦ ¦ +-- partner_listing.js
¦ ¦ +-- partner_listing.xml
¦ +-- scss/
¦ +-- partner_listing.scss
+-- views/
+-- templates.xml
+-- website_menu.xml
Step 2: Module Manifest:
{
'name': 'Website Partner Listing',
'version': '1.0',
'category': 'Website',
'summary': 'Display partners in website with kanban view',
'depends': ['website', 'contacts'],
'data': [
'views/website_menu.xml',
'views/templates.xml',
],
'assets': {
'web.assets_frontend': [
'partner_website_listing/static/src/components/**/*',
'partner_website_listing/static/src/scss/**/*',
],
},
'application': False,
'installable': True,
}
Step 3: Create Website Menu
views/website_menu.xml
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="menu_website_partner_listing" model="website.menu">
<field name="name">Partners</field>
<field name="url">/partners</field>
<field name="parent_id" ref="website.main_menu"/>
<field name="sequence" type="int">50</field>
</record>
</data>
</odoo>
Step 4: Backend Controller
controllers/partner_controller.py
from odoo import http
from odoo.http import request
class PartnerController(http.Controller):
@http.route('/partners', type='http', auth='public', website=True)
def partner_listing_page(self, **kwargs):
return request.render('partner_website_listing.partner_listing_page')
@http.route('/partners/data', type='json', auth='public', website=True)
def get_partners(self):
partners = request.env['res.partner'].sudo().search([])
return {
'partners': [{
'id': partner.id,
'name': partner.name,
'image_url': f'/web/image/res.partner/{partner.id}/image_1024',
'email': partner.email,
'phone': partner.phone,
'website': partner.website,
'city': partner.city,
'country': partner.country_id.name if partner.country_id else '',
} for partner in partners]
}
Step 5: Owl Component
static/src/components/partner_listing.js
/** @odoo-module **/
import { Component, useState, onWillStart } from "@odoo/owl";
import { registry } from "@web/core/registry";
import { rpc } from "@web/core/network/rpc";
export class PartnerListing extends Component {
static template = "partner_website_listing.PartnerListing";
setup() {
this.state = useState({
partners: [],
isLoading: true,
error: null,
filter: '',
view: 'kanban' // or 'list'
});
this.loadPartners();
}
async loadPartners() {
try {
const result = await rpc(
'/partners/data'
);
this.state.partners = result.partners;
} catch (error) {
this.state.error = error.message;
} finally {
this.state.isLoading = false;
}
}
get filterPartners() {
const searchTerm = this.state.filter.toLowerCase();
return this.state.partners.filter(partner =>
partner.name.toLowerCase().includes(searchTerm) ||
partner.city?.toLowerCase().includes(searchTerm) ||
partner.country?.toLowerCase().includes(searchTerm)
);
}
getAddress (partner) {
const address = [partner.city, partner.country].filter(Boolean).join(', ')
return address
}
toggleView() {
this.state.view = this.state.view === 'kanban' ? 'list' : 'kanban';
}
}
registry.category("public_components").add(
"partner_website_listing.PartnerListing",
PartnerListing
);
Step 6: Component Template
static/src/components/partner_listing.xml
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="partner_website_listing.PartnerListing">
<div class="partner-listing">
<!-- Search and View Toggle -->
<div class="controls mb-4 d-flex">
<div class="input-group w-50">
<input
type="text"
class="form-control"
placeholder="Search partners..."
t-model="state.filter"/>
</div>
<div>
<button
class="btn ms-2" t-att-class="state.view === 'kanban' and 'btn-primary'"
t-on-click="toggleView"
t-att-disabled="state.view === 'kanban'">
<i class="fa fa-th-large me-2"/> Kanban View
</button>
<button
class="btn ms-2" t-att-class="state.view === 'list' and 'btn-primary'"
t-on-click="toggleView"
t-att-disabled="state.view === 'list'">
<i class="fa fa-list me-2"/> List View
</button>
</div>
</div>
<!-- Loading State -->
<div t-if="state.isLoading" class="text-center py-5">
<div class="spinner-border" role="status">
<span class="visually-hidden">Loading...</span>
</div>
</div>
<!-- Error State -->
<div t-if="state.error" class="alert alert-danger" role="alert">
<t t-esc="state.error"/>
</div>
<!-- Kanban View -->
<div t-if="state.view === 'kanban'" class="row">
<t t-foreach="filterPartners" t-as="partner" t-key="partner.id">
<div class="col-sm-6 col-lg-4 mb-4">
<div class="card h-100">
<img t-att-src="partner.image_url"
class="card-img-top partner-image"
t-att-alt="partner.name"/>
<div class="card-body">
<h5 class="card-title" t-esc="partner.name"/>
<p t-if="partner.city || partner.country"
class="card-text">
<i class="fa fa-map-marker"/>
<t t-esc="getAddress(partner)"/>
</p>
<p t-if="partner.email" class="card-text">
<i class="fa fa-envelope"/>
<a t-att-href="'mailto:' + partner.email"
t-esc="partner.email"/>
</p>
<p t-if="partner.phone" class="card-text">
<i class="fa fa-phone"/>
<a t-att-href="'tel:' + partner.phone"
t-esc="partner.phone"/>
</p>
<a t-if="partner.website"
t-att-href="partner.website"
class="btn btn-primary mt-2"
target="_blank">
Visit Website
</a>
</div>
</div>
</div>
</t>
</div>
<!-- List View -->
<div t-if="state.view === 'list'" class="table-responsive">
<table class="table">
<thead>
<tr>
<th>Name</th>
<th>Location</th>
<th>Contact</th>
<th>Website</th>
</tr>
</thead>
<tbody>
<t t-foreach="filterPartners" t-as="partner" t-key="partner.id">
<tr>
<td>
<img t-att-src="partner.image_url"
class="partner-list-image me-2"
t-att-alt="partner.name"/>
<span t-esc="partner.name"/>
</td>
<t t-esc="getAddress(partner)"/>
<td>
<div t-if="partner.email">
<a t-att-href="'mailto:' + partner.email"
t-esc="partner.email"/>
</div>
<div t-if="partner.phone">
<a t-att-href="'tel:' + partner.phone"
t-esc="partner.phone"/>
</div>
</td>
<td>
<a t-if="partner.website"
t-att-href="partner.website"
target="_blank">
Visit
</a>
</td>
</tr>
</t>
</tbody>
</table>
</div>
</div>
</t>
</templates>
Step 7: Website Template
<!-- views/templates.xml -->
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="partner_listing_page" name="Partners">
<t t-call="website.layout">
<div class="container py-5">
<h1 class="mb-4">Our Partners</h1>
<owl-component name="partner_website_listing.PartnerListing"/>
</div>
</t>
</template>
</odoo>
Step 8: Styling
static/src/scss/partner_listing.scss
.partner-listing {
.partner-image {
height: 200px;
object-fit: cover;
}
.partner-list-image {
width: 50px;
height: 50px;
object-fit: cover;
border-radius: 25px;
}
.card {
transition: transform 0.2s;
&:hover {
transform: translateY(-5px);
}
}
.fa {
width: 20px;
}
}
Conclusion
Integrating Owl components into Odoo’s website and portal can greatly improve user experience by enabling real-time interactivity and dynamic content updates. However, careful planning is essential to prevent issues like layout shifts and potential SEO challenges. Understanding the best use cases for Owl components allows you to maximize their benefits without compromising usability or search engine visibility.
Key Takeaways
- Owl Component Setup:
* Create an Owl component and register it in the public_components registry.
* Include it in the web.assets_frontend bundle to enable functionality on the website or portal.
* Use the <owl-component> tag to mount it on the desired page.
- Potential Challenges:
* Layout Shifts: Prevent disruptions by allocating fixed spaces or carefully positioning components.
* SEO Considerations: Since Owl components rely on JavaScript rendering, they may affect search engine indexing. Use them in areas where SEO is not a priority.
- Best Use Cases for Owl Components:
* Ideal for user portals or private pages where SEO is less critical.
* Best suited for interactive interfaces requiring real-time user input and dynamic content updates.
By following these best practices, you can harness the power of Owl components to create engaging, responsive, and user-friendly Odoo web experiences.
To read more about An Overview of OWL Components in Odoo 18, refer to our blog An Overview of OWL Components in Odoo 18.