The evolution of web development in Odoo has taken a significant leap forward with the introduction of Owl components in Odoo 17. This modern approach to frontend development combines the flexibility of component-based architecture with the robustness of Odoo's backend framework, opening new possibilities for creating dynamic and interactive web experiences.
Odoo 17 introduces enhanced support for Owl (Odoo Web Library) components in websites and portal pages, marking a significant shift from traditional website development approaches. This integration allows developers to create more sophisticated, interactive user interfaces while maintaining Odoo's signature simplicity and efficiency.
Why Owl Components?
Owl components offer several compelling advantages for Odoo website development:
1. Component-Based Architecture
* Modular and reusable code structure
* Easier maintenance and updates
* Clear separation of concerns
2. Enhanced Interactivity
* Real-time user interface updates
* Smooth client-side interactions
* Better user experience for complex features
3. Modern Development Experience
* Familiar syntax for JavaScript developers
* Built-in state management
* Powerful templating system
Understanding the Ecosystem
Before diving into implementation, it's important to understand where Owl components fit in the Odoo website ecosystem:
* Frontend Assets: Owl components become part of the web.assets_frontend bundle
* Public Components Registry: Components are registered in the public_components registry
* Integration Points: Components can be embedded in both website pages and portal views
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";
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 this.env.services.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
Incorporating Owl components into Odoo’s portal and website can significantly enhance the user experience by enabling real-time interactivity and dynamic content rendering. However, their use should be carefully planned to avoid issues such as layout shifts and potential SEO drawbacks. By understanding the scenarios where Owl components are most beneficial, you can effectively leverage their capabilities without compromising user experience or search engine visibility.
Key Points to Remember:
1. Owl Component Setup:
* Create an Owl component and register it in the public_components registry.
* Add it to the web.assets_frontend bundle for it to work on the portal or website.
* Use an <owl-component> tag to mount it on a desired page.
2. Potential Drawbacks:
* Layout Shifts: Ensure the placement of Owl components does not disrupt surrounding elements by reserving fixed spaces or positioning components thoughtfully.
* SEO Considerations: Since Owl components rely on JavaScript rendering, they might affect how search engines index content. Use them only when SEO is not a priority.
3. When to Use Owl Components:
* Use them for user portals or private pages where SEO isn’t a concern.
* Prefer Owl components for highly interactive interfaces that benefit from real-time user inputs.
By following these guidelines, you can take advantage of Owl’s dynamic features while maintaining a polished and user-friendly portal or website experience.
To read more about How to Periodically Assign Leads Based on Rules in Odoo 17 CRM, refer to our blog How to Periodically Assign Leads Based on Rules in Odoo 17 CRM.