Skip to content

Master Odoo OWL Public Components: A Powerful 7-Step Tutorial for Dynamic Web Experiences

  • owl
odoo owl public components

Master Odoo OWL Public Components: A Powerful 7-Step Tutorial for Dynamic Web Experiences

Welcome, Odoo developers! Are you ready to elevate your Odoo website and portal interfaces beyond standard functionalities? Do you want to build truly interactive, dynamic, and public-facing web experiences? Then mastering Odoo OWL public components is your next essential step. This comprehensive guide, inspired by the insights from the Odoo OWL Live MasterClass - Exploring New Public Components Part 2 (watch the full session here: https://www.youtube.com/watch?v=49YcoZNfw5M), will walk you through the entire process, from module setup to deployment, ensuring you unlock the full power of OWL in a public context.

The Power of Odoo OWL Public Components

In the world of Odoo development, creating engaging and responsive user interfaces is paramount. While Odoo’s core functionalities are robust, custom requirements often demand more dynamic and interactive elements. This is where Odoo OWL public components come into play. OWL (Odoo Web Library) is Odoo’s modern, reactive JavaScript framework, and when combined with the “public” access setting, it allows you to build sophisticated components that can be used by anyone visiting your Odoo website or portal, even unauthenticated users.

Imagine building a custom dashboard for your website visitors, a dynamic course catalog, or an interactive form that fetches real-time data from your Odoo backend. All these and more are perfectly achievable with Odoo OWL public components. They empower you to extend Odoo’s frontend capabilities significantly, offering a fluid and modern user experience.

Why Mastering Odoo OWL Public Components is Crucial

Adopting OWL components for public-facing modules offers several compelling advantages:

  • Enhanced User Experience: Create highly interactive and responsive interfaces that improve user engagement and satisfaction.
  • Modern Web Development: Leverage a reactive framework that aligns with current web development best practices, making your code cleaner and more maintainable.
  • Seamless Integration: OWL components integrate natively with Odoo’s QWeb templating engine and backend services, providing a cohesive development experience.
  • Scalability and Reusability: Design modular components that can be easily reused across different parts of your website or portal, promoting scalability.
  • Public Access: Specifically designed for scenarios where data and interactions are needed for unauthenticated users, expanding the reach and utility of your Odoo applications.

This tutorial will not only show you how to implement these components but also provide crucial tips and best practices to avoid common pitfalls, making your development journey smoother and more efficient.

Setting the Stage: Prerequisites & Environment

Before we dive into the steps, ensure you have the following:

  • Basic Odoo Development Knowledge: Familiarity with Odoo module structure, Python, JavaScript, and XML.
  • Odoo Version: Odoo 16 or higher is recommended to take full advantage of the latest OWL features.
  • OWL Basics: A fundamental understanding of the OWL framework, including concepts like components, reactive state, and lifecycle hooks. If you’re new to OWL, consider checking out the official Odoo documentation on OWL.
  • Odoo Website Module: The website module should be installed and configured in your Odoo instance.
  • Development Tools: A good code editor like Visual Studio Code and browser developer tools for debugging.

Let’s get started on building your first powerful Odoo OWL public components!

7-Step Tutorial: Building a Dynamic Course List Component

We’ll create a module that displays a list of courses from the website_slides module on a public portal page. This will demonstrate how to fetch data from the backend using an OWL component.

Step 1: Module Setup and Directory Structure

First, create a new Odoo module. For this example, let’s call it my_public_component_module. The directory structure is crucial for Odoo to correctly load your assets.

my_public_component_module/
├── __init__.py
├── __manifest__.py
├── controllers/
│   ├── __init__.py
│   └── main.py              # Python controller for backend data
├── models/
│   └── models.py            # You might have Odoo models here, but optional for this example
├── views/
│   └── portal_templates.xml # XML file to inherit and place your component
└── static/
    └── src/
        ├── js/
        │   └── components/
        │       ├── course_list.js  # Your OWL component's JavaScript
        │       └── course_list.xml # Your OWL component's QWeb template
        └── css/
            └── main.css      # Optional: for custom styling

Content for my_public_component_module/__init__.py (empty):

# Nothing needed here for this example, or import models/controllers as needed.

Content for my_public_component_module/models/__init__.py (empty):

# Nothing needed here for this example.

Step 2: Module Manifest (__manifest__.py)

This file tells Odoo about your module and its dependencies, and importantly, registers your frontend assets.

{
    'name': 'My Public OWL Components',
    'version': '1.0',
    'summary': 'Demonstrates Odoo OWL Public Components for website/portal',
    'description': """
        This module provides an example of how to create and integrate 
        Odoo OWL public components into the Odoo frontend (website/portal) 
        to display dynamic data.
    """,
    'author': 'Your Name/Company',
    'website': 'https://yourwebsite.com',
    'license': 'LGPL-3',
    'depends': ['base', 'website', 'website_slides'], # Crucial dependencies
    'data': [
        'views/portal_templates.xml',
    ],
    'assets': {
        'web.assets_frontend': [
            'my_public_component_module/static/src/js/components/*.js',
            'my_public_component_module/static/src/js/components/*.xml',
            'my_public_component_module/static/src/css/*.css', # Include CSS if you have it
        ],
    },
    'installable': True,
    'application': True,
    'auto_install': False,
}

Key Points in the Manifest:

  • depends: Ensure website and website_slides (if you are inheriting) are listed. These provide the necessary framework and models.
  • assets: This section is vital. web.assets_frontend ensures your JavaScript and XML files are loaded on the website. The wildcards *.js and *.xml conveniently include all files in those directories.

Step 3: Creating the Controller (controllers/main.py)

Our Python controller will serve the course data to our frontend OWL component.

Content for my_public_component_module/controllers/__init__.py:

from . import main

Content for my_public_component_module/controllers/main.py:

from odoo import http
from odoo.http import route, request
import json # Important for JSON responses

class PublicCourseController(http.Controller):
    @route('/my_portal/get_public_courses', type='json', auth='public', website=True)
    def get_public_courses(self, **kwargs):
        """
        Retrieves a list of public courses.
        This endpoint is accessible to unauthenticated users.
        """
        # A partner_id might be passed if we want to filter by user subscriptions,
        # but for a purely public component, it might not be strictly necessary.
        # However, the context mentions passing partner_id, so we'll allow it.
        partner_id = kwargs.get('partner_id')
        domain = [('website_published', '=', True)]

        if partner_id:
            # Example: Fetch courses subscribed by a specific partner if needed.
            # For this example, we'll just show all public courses.
            # In a real scenario, you might filter 'slide.channel' subscriptions.
            pass # Or adjust domain, e.g., domain.append(('partner_ids', 'in', int(partner_id)))

        # Fetch published slides (courses)
        slides = request.env['slide.slide'].sudo().search(domain)
        
        courses_data = []
        for slide in slides:
            courses_data.append({
                'id': slide.id,
                'name': slide.name,
                'description': slide.description,
                'url': slide.website_url, # Link to the course page
            })
        
        # Return the data in JSON format
        return courses_data

Controller Explained:

  • @route Decorator:
    • '/my_portal/get_public_courses': The URL path for this endpoint.
    • type='json': Specifies that this route expects and returns JSON data.
    • auth='public': Crucial for Odoo OWL public components. This allows any user, authenticated or not, to access this route.
    • website=True: Marks this route as being part of the website, which can affect things like website layout rendering (though not directly for a JSON endpoint).
  • get_public_courses Method:
    • Uses request.env['slide.slide'].sudo().search(domain) to fetch publicly published courses. sudo() is used to bypass access rights if the public user doesn’t have direct access, which is often the case for public endpoints.
    • The results are formatted into a list of dictionaries, which Python’s json library handles perfectly for the type='json' route.

Step 4: Creating the OWL Component (static/src/js/components/course_list.js)

This JavaScript file defines your OWL component, manages its state, and communicates with the backend.

/** @odoo-module **/

import { Component, useState, onMounted } from "@odoo/owl";
import { useService } from "@web/core/utils/hooks";
import { registry } from "@web/core/registry"; // Important for public components

export class PublicCourseList extends Component {
    setup() {
        this.state = useState({
            courses: [],
            isLoading: true,
            error: null,
            totalCourses: 0,
        });
        this.rpc = useService("rpc"); // Access Odoo's RPC service

        // Fetch courses when the component is mounted
        onMounted(async () => {
            await this.loadCourses();
        });
    }

    async loadCourses() {
        this.state.isLoading = true;
        this.state.error = null;
        try {
            // Making an asynchronous call to our Python controller
            // The context mentions passing partner_id; for this example, we'll pass a dummy one
            // or you could dynamically get it if the user is logged in, even with 'auth=public'.
            const partnerIdFromDOM = document.querySelector('[data-partner-id]')?.dataset.partnerId || 0;
            const fetchedCourses = await this.rpc("/my_portal/get_public_courses", { 
                partner_id: partnerIdFromDOM 
            });
            this.state.courses = fetchedCourses;
            this.state.totalCourses = fetchedCourses.length;

            // Example of manipulating DOM directly (as discussed in context) - generally avoid this
            // but useful for quick updates or integrating with legacy elements.
            const totalCoursesElement = document.getElementById('my_public_component_total_courses');
            if (totalCoursesElement) {
                totalCoursesElement.textContent = `Total Public Courses: ${this.state.totalCourses}`;
            }

        } catch (error) {
            console.error("Failed to load Odoo OWL public components (courses):", error);
            this.state.error = "Could not load courses. Please try again later.";
        } finally {
            this.state.isLoading = false;
        }
    }
}

// Associate the component with its QWeb template
PublicCourseList.template = "my_public_component_module.PublicCourseListTemplate";
// If your component uses other OWL components, declare them here.
PublicCourseList.components = {};

// Register the component under the 'public_components' category
registry.category("public_components").add("my_public_component_module.PublicCourseList", PublicCourseList);

OWL Component Explained:

  • import Statements: Essential OWL hooks (Component, useState, onMounted) and the useService hook for RPC calls. registry is key for making it a public component.
  • setup() Method: This is where you initialize your component.
    • this.state = useState(...): Defines reactive state variables (courses, isLoading, error, totalCourses). Changes to these will automatically trigger re-renders.
    • this.rpc = useService("rpc"): Provides access to Odoo’s RPC service for making backend calls.
    • onMounted(async () => { ... }): A lifecycle hook that runs once the component is mounted to the DOM. It’s the ideal place to fetch initial data.
  • async loadCourses() Method:
    • An async function to handle the RPC call. Always use async/await for asynchronous operations to prevent blocking the UI.
    • await this.rpc(...): Makes the call to our Python controller. We pass an optional partner_id as suggested by the context, which could be fetched from a DOM element (data-partner-id).
    • Updates this.state.courses and this.state.totalCourses with the fetched data.
    • Direct DOM Manipulation (Caution): The context mentioned manipulating DOM elements with JavaScript. While OWL prefers a reactive approach, document.getElementById('my_public_component_total_courses').textContent = ... shows how you could do it. This is generally discouraged for data managed by OWL’s state, but it’s an example for specific needs outside of OWL’s direct reactivity.
    • Includes try...catch for robust error handling.
  • PublicCourseList.template: Links this JavaScript component to its QWeb XML template. The name must be an exact match.
  • registry.category("public_components").add(...): This line is paramount! It registers your PublicCourseList component as an accessible public component in Odoo, allowing you to embed it in public QWeb templates.

Step 5: Creating the XML Template (static/src/js/components/course_list.xml)

This QWeb template defines the visual structure of your OWL component.

<?xml version="1.0" encoding="UTF-8"?>
<templates>
    <t t-name="my_public_component_module.PublicCourseListTemplate" owl="1">
        <div class="o_public_course_list_component">
            <div t-if="state.isLoading" class="alert alert-info">Loading courses...</div>
            <div t-if="state.error" class="alert alert-danger"><t t-esc="state.error"/></div>
            
            <h3 id="my_public_component_total_courses">Total Public Courses: <t t-esc="state.totalCourses"/></h3>

            <div t-if="!state.isLoading and !state.error">
                <t t-if="state.courses and state.courses.length > 0">
                    <div class="row">
                        <t t-foreach="state.courses" t-as="course" t-key="course.id">
                            <div class="col-md-4 mb-3">
                                <div class="card shadow-sm h-100">
                                    <div class="card-body">
                                        <h5 class="card-title"><t t-esc="course.name"/></h5>
                                        <p class="card-text"><t t-esc="course.description"/></p>
                                        <a t-att-href="course.url" class="btn btn-primary btn-sm mt-2">View Course</a>
                                    </div>
                                    <div class="card-footer text-muted">
                                        Course ID: <t t-esc="course.id"/>
                                    </div>
                                </div>
                            </div>
                        </t>
                    </div>
                </t>
                <t t-else="">
                    <p class="alert alert-warning">No public courses available at the moment.</p>
                </t>
            </div>
        </div>
    </t>
</templates>

QWeb Template Explained:

  • t-name: Must exactly match the PublicCourseList.template property in your JavaScript.
  • owl="1": Declares this as an OWL template.
  • Conditional Rendering (t-if, t-else): Displays different content based on state.isLoading, state.error, or state.courses availability.
  • Looping (t-foreach): Iterates over state.courses to display each course dynamically.
  • Data Display (t-esc): Safely outputs data from your component’s state.
  • Dynamic Attributes (t-att-): This is a key insight from the context. To set dynamic HTML attributes, like href on an <a> tag, use t-att-attribute_name="expression". For instance, t-att-href="course.url" dynamically sets the href attribute to the url property of each course object. This is essential for dynamic elements that need to pull data from your component’s state.

Step 6: Inheriting and Implementing the Component in a Public View (views/portal_templates.xml)

Now, we’ll embed our OWL component into an existing Odoo website page. We’ll inherit the website.homepage template as an example.

<odoo>
    <template id="portal_my_public_courses" inherit_id="website.homepage" name="My Public Courses Section">
        <xpath expr="//div[@id='wrap']" position="inside">
            <section class="s_text_block pt48 pb48" data-snippet="s_text_block" data-name="Text Block">
                <div class="container o_container_small">
                    <div class="row">
                        <div class="col-lg-12">
                            <h2 class="text-center mb-4">Explore Our Odoo OWL Public Components Courses!</h2>
                            <!-- This div can pass dynamic data if needed, or simply act as a target -->
                            <div id="public_course_list_container" data-partner-id="<t t-esc="request.env.user.partner_id.id if request.env.user.partner_id else 0"/>">
                                <!-- Your OWL component will be rendered here -->
                                <t t-component="my_public_component_module.PublicCourseList"/>
                            </div>
                        </div>
                    </div>
                    <div class="row mt-5">
                        <div class="col-12 text-center">
                            <p id="my_public_component_total_courses" class="text-muted">Total Public Courses: 0</p>
                        </div>
                    </div>
                </div>
            </section>
        </xpath>
    </template>
</odoo>

Template Inheritance Explained:

  • inherit_id="website.homepage": We’re extending Odoo’s default homepage.
  • xpath expr="//div[@id='wrap']" position="inside": This XPath expression targets a specific element on the homepage (<div id="wrap">) and inserts our new content inside it.
  • <t t-component="my_public_component_module.PublicCourseList"/>: This is how you embed your registered OWL public component! The value of t-component must exactly match the name you used when registering the component in the registry in course_list.js.
  • data-partner-id: As per the context, we’re using a data- attribute to pass potential dynamic information (like a partner_id if the user is logged in) from QWeb to the OWL component, which can then read it via document.querySelector('[data-partner-id]')?.dataset.partnerId.

Step 7: Testing and Troubleshooting

You’ve built your Odoo OWL public components! Now it’s time to see them in action.

  1. Update Module List: In Odoo, go to Apps -> Update Apps List.
  2. Install/Upgrade Module: Find My Public OWL Components and install it. If you made changes to an already installed module, upgrade it.
  3. Restart Odoo Server: Always restart your Odoo server after making changes to Python files, manifest, or asset definitions.
  4. Access Your Website: Navigate to your Odoo website’s homepage. You should see your new course list.
  5. Browser Developer Tools:
    • Console (F12): This is your best friend for debugging. Check for JavaScript errors. The console.log statements in your course_list.js will appear here, confirming if your files are loaded and your RPC calls are successful.
    • Network Tab: Verify that the /my_portal/get_public_courses RPC call is being made and returning the expected data. Look for a successful 200 status code.
    • Elements Tab: Inspect the HTML structure to ensure your component is rendered as expected.
  6. Odoo Server Logs: Check your Odoo server logs for any Python errors from your controller.

Important Troubleshooting Tips (from the Live MasterClass):

  • Incremental Development: When starting a new component, begin with a simple “Hello World” in your JavaScript and XML. Confirm it loads before adding complex logic. This helps isolate issues.
  • Naming Consistency: The context strongly emphasizes that names must match precisely:
    • PublicCourseList.template in JS must match t-name in XML.
    • The component name in registry.category("public_components").add() must match the name used in <t t-component="..."> in your QWeb view. Even a capitalization error can cause it to fail silently or with hard-to-diagnose errors.
  • Asynchronous (Async/Await): Remember that RPC calls are asynchronous. Always use async on your methods and await when calling this.rpc to correctly handle promises and prevent race conditions or unexpected behavior.
  • Copilot Caution: Be careful with AI code suggestions like Copilot. As highlighted in the context, it sometimes suggests changes (e.g., turning a function into a static property) that are incorrect or introduce subtle bugs. If you’re encountering strange issues, try disabling it temporarily or double-checking its suggestions against official documentation.
  • t-att- for Dynamic Attributes: Recall that directly assigning dynamic values to attributes like <div partner-id="{{ partner_id }}"> is not how QWeb works for dynamic data. You must use t-att-attribute_name="expression" to ensure attributes are evaluated dynamically.
  • Dependencies: If you’re inheriting from existing Odoo components or modules (like website_slides), ensure all necessary dependencies are declared in __manifest__.py. Missing dependencies are a common cause of errors.

Conclusion

Congratulations! You’ve successfully built and integrated dynamic Odoo OWL public components into your Odoo website. This powerful approach allows you to create highly interactive and engaging user interfaces, opening up a world of possibilities for custom public-facing features. By diligently following these steps, paying close attention to naming conventions, asynchronous operations, and leveraging browser debugging tools, you are well on your way to becoming an Odoo frontend master. Keep experimenting, keep building, and unleash the full potential of OWL in Odoo!


Discover more from teguhteja.id

Subscribe to get the latest posts sent to your email.

Tags:

Leave a Reply

WP Twitter Auto Publish Powered By : XYZScripts.com