Skip to content

Mastering Odoo 19 Public Components: Elevate Your Website with Dynamic OWL

odoo 19 public components 7

Welcome, Odoo enthusiasts and web developers! Are you ready to inject unparalleled dynamism and interactivity into your Odoo website? With the release of Odoo 19, the way we build powerful web experiences is evolving. Today, we’re diving deep into a truly transformative feature: Odoo 19 Public Components. These powerful elements allow you to create rich, reactive, and engaging user interfaces directly within your Odoo website, leveraging the elegant Odoo Web Library (OWL).

This blog post will guide you through creating and utilizing public components in Odoo 19, transforming static web pages into vibrant, interactive experiences. Whether you’re a seasoned Odoo developer or just starting, this tutorial will provide a clear, step-by-step roadmap.

This article is inspired by a fantastic video tutorial on creating public components in Odoo 19. You can watch the original content for a visual guide here: https://www.youtube.com/watch?v=KWn7SSVHsAo

Why Embrace Odoo 19 Public Components?

In today’s digital landscape, user engagement is paramount. Static websites, while informative, often fall short in delivering the interactive experiences users expect. This is where Odoo 19 Public Components shine. They are essentially standard Odoo components, built with OWL, that can be seamlessly rendered and utilized directly within your Odoo website templates.

Here’s why they are a game-changer for your Odoo 19 projects:

  • Unleash Dynamism: Public components make your website pages reactive. Instead of full page reloads for every interaction, components can update specific sections of your page in real-time, providing a smoother user experience.
  • Enhanced Interactivity: From interactive forms and real-time dashboards to personalized content displays and complex data visualizations, public components enable a new level of interactivity directly on your public-facing Odoo website.
  • Code Reusability: Just like any other component, public components promote modularity. You can build a component once and reuse it across multiple web pages, reducing development time and ensuring consistency.
  • Seamless Integration: Being native Odoo components, they integrate effortlessly with Odoo’s backend services (ORM, RPC) and styling, ensuring a cohesive and performant application.
  • Modern Web Development: By leveraging OWL, Odoo aligns with modern frontend development practices, allowing developers to build sophisticated web UIs with a declarative and reactive approach.

Our goal today is to illustrate this power by creating a simple yet effective public component: a dynamic contact list on your Odoo website. This component will display all your res.partner records and, with a click, reveal their sales and purchase summaries in an interactive dialog.

Prerequisites for This Tutorial

Before we embark on this exciting journey, ensure you have the following:

  • Odoo 19 Development Environment: A running instance of Odoo 19.
  • Basic Odoo Module Structure Knowledge: Familiarity with creating Odoo modules, __manifest__.py files, and directory structures.
  • OWL (Odoo Web Library) Basics: An understanding of OWL components, setup method, useState, hooks (onWillStart), and services (useService).
  • Odoo Controllers and Templates: Knowledge of how to define routes in Odoo controllers and render QWeb templates.
  • Python for Odoo: Basic understanding of Odoo ORM methods and Python model extensions.

Core Concepts of Odoo 19 Public Components

At the heart of creating Odoo 19 Public Components lies a few key mechanisms:

  1. t-component Directive: In your QWeb templates, instead of t-name for static includes, you’ll use t-component to render a dynamic OWL component.
  2. public_components Registry: Odoo maintains a special registry for components intended for public website use. You register your OWL component here to make it discoverable by the t-component directive.
  3. Props (Properties): You can pass data from your Odoo controller (Python) to your OWL component (JavaScript) using props. This allows for context-sensitive rendering.

Now, let’s roll up our sleeves and build!

Step-by-Step Tutorial: Building Your First Odoo 19 Public Component

We’ll be creating a new Odoo module for this tutorial. Let’s call it my_public_components.

Step 1: Setting Up Your Odoo Module

First, create a new module my_public_components with the necessary __manifest__.py file.

# my_public_components/__manifest__.py
{
    'name': 'My Odoo 19 Public Components',
    'version': '1.0',
    'summary': 'Demonstration of creating public components in Odoo 19 using OWL.',
    'depends': ['base', 'website'], # 'website' dependency is crucial for public components
    'data': [
        'views/public_component_template.xml', # Our main website template
    ],
    'assets': {
        'web.assets_frontend': [ # This is where we register our component's assets
            'my_public_components/static/src/components/public_contacts/public_contacts.js',
            'my_public_components/static/src/components/sales_purchase_dialog/sales_purchase_dialog.js',
            'my_public_components/static/src/components/public_contacts/public_contacts.xml',
            'my_public_components/static/src/components/sales_purchase_dialog/sales_purchase_dialog.xml',
        ],
    },
    'installable': True,
    'application': True,
    'license': 'LGPL-3',
}

Next, create the basic directory structure:
my_public_components/controllers/
my_public_components/models/
my_public_components/static/src/components/public_contacts/
my_public_components/static/src/components/sales_purchase_dialog/
my_public_components/views/

Step 2: Defining the Website Template for Public Components

This XML file will define the website page where our public component will be rendered. We’ll use the website.layout to ensure it looks like a standard Odoo website page.

<!-- my_public_components/views/public_component_template.xml -->
<odoo>
    <template id="public_component_page" name="Public Components Page" inherit_id="website.layout">
        <xpath expr="//div[@id='wrap']" position="inside">
            <div class="container my-5">
                <h1>Welcome to Your Dynamic Odoo 19 Public Components Demo!</h1>
                <p class="lead">Below you'll find an interactive list of contacts rendered by our custom OWL public component.</p>
                <!-- This is where we call our public component -->
                <!-- The 'component_props' will be passed from the Python controller -->
                <t t-component="PublicContacts" t-props="component_props"/>
            </div>
        </xpath>
    </template>
</odoo>

Here, t-component="PublicContacts" tells Odoo to look for a component named “PublicContacts” in the public_components registry. The t-props="component_props" means any data contained in the component_props variable (which our Python controller will provide) will be passed down to our OWL component. This is a crucial aspect of creating contextual Odoo 19 Public Components.

Step 3: Crafting Your Public OWL Component (JavaScript)

This is the core logic of our component. Create public_contacts.js in my_public_components/static/src/components/public_contacts/.

// my_public_components/static/src/components/public_contacts/public_contacts.js
/** @odoo-module **/

import { Component, useState, onWillStart, useService } from "@odoo/owl";
import { registry } from "@web/core/registry";
import { SalesPurchaseDialog } from "../sales_purchase_dialog/sales_purchase_dialog"; // Import our dialog component

export class PublicContacts extends Component {
    static template = "my_public_components.PublicContactsTemplate"; // Link to our XML template
    static props = {
        partnerName: { type: String, optional: true }, // Define props our component expects
    };
    static components = { SalesPurchaseDialog }; // Register dialog for use in this component's template

    setup() {
        this.state = useState({
            partners: [], // Reactive state to hold our contact list
        });

        // Use Odoo services for ORM and Dialog
        this.orm = useService("orm");
        this.dialog = useService("dialog");
        this.notification = useService("notification"); // For showing messages

        // Hook to fetch data when the component is about to start rendering
        onWillStart(async () => {
            try {
                this.state.partners = await this.orm.searchRead(
                    "res.partner",           // Model to search
                    [['is_company', '=', true]], // Only show companies for a cleaner list
                    ["id", "name"]           // Fields to read
                );
            } catch (error) {
                this.notification.add("Failed to load contacts: " + error.message, { type: "danger" });
                console.error("Error fetching partners:", error);
            }
        });
    }

    /**
     * Handles the click event on a partner info button.
     * Fetches sales and purchase info for the clicked partner and displays it in a dialog.
     * @param {Object} partner - The partner object clicked.
     */
    async onPartnerInfoClick(partner) {
        try {
            const partnerData = await this.orm.call(
                "res.partner",
                "get_sales_purchase_info", // Custom Python method on res.partner
                [partner.id]               // Arguments for the Python method
            );

            // Open our custom dialog with the fetched data
            this.dialog.add(SalesPurchaseDialog, {
                partner: partnerData,
                title: `Details for ${partner.name}`,
            });
        } catch (error) {
            this.notification.add("Failed to fetch partner info: " + error.message, { type: "danger" });
            console.error("Error fetching partner info:", error);
        }
    }
}

// Crucially, register our component as a public component
// The first argument "PublicContacts" must match the name used in t-component in the XML template
registry.category("public_components").add("PublicContacts", PublicContacts);

Step 4: Designing the Component’s View (XML)

This template defines the HTML structure for our PublicContacts component. Create public_contacts.xml in my_public_components/static/src/components/public_contacts/.

<!-- my_public_components/static/src/components/public_contacts/public_contacts.xml -->
<templates>
    <t t-name="my_public_components.PublicContactsTemplate">
        <div class="container odoo-public-component-contacts">
            <h2 class="mb-3">Contact List <t t-if="props.partnerName">(Hello, <t t-out="props.partnerName"/>!)</t></h2>
            <p t-if="state.partners.length === 0" class="text-muted">No contacts found.</p>
            <div t-else="" class="list-group">
                <t t-foreach="state.partners" t-as="partner" t-key="partner.id">
                    <div class="list-group-item list-group-item-action d-flex justify-content-between align-items-center">
                        <span class="fw-bold"><t t-out="partner.name"/></span>
                        <button t-on-click.prevent="() => onPartnerInfoClick(partner)" class="btn btn-sm btn-info">
                            Show Info
                        </button>
                    </div>
                </t>
            </div>
        </div>
    </t>
</templates>

Notice how t-out="props.partnerName" directly displays the partnerName passed from the controller, demonstrating dynamic data flow for Odoo 19 Public Components. Also, t-on-click is used to bind JavaScript methods to HTML elements.

Step 5: Implementing the Dialog Component (JavaScript & XML)

We’ll create a separate, simple OWL component for the sales and purchase dialog. This promotes modularity and keeps our main component clean.

Create sales_purchase_dialog.js in my_public_components/static/src/components/sales_purchase_dialog/.

// my_public_components/static/src/components/sales_purchase_dialog/sales_purchase_dialog.js
/** @odoo-module **/

import { Component } from "@odoo/owl";

export class SalesPurchaseDialog extends Component {
    static template = "my_public_components.SalesPurchaseDialogTemplate";
    static props = ["partner", "close"]; // 'partner' prop for data, 'close' prop for dialog dismissal
}

And its corresponding XML template sales_purchase_dialog.xml in the same directory:

<!-- my_public_components/static/src/components/sales_purchase_dialog/sales_purchase_dialog.xml -->
<templates>
    <t t-name="my_public_components.SalesPurchaseDialogTemplate">
        <div role="dialog" class="modal-dialog">
            <div class="modal-content">
                <header class="modal-header">
                    <h4 class="modal-title">Sales and Purchase Information for <t t-out="props.partner.name"/></h4>
                    <button type="button" class="btn-close" aria-label="Close" t-on-click="props.close"/>
                </header>
                <main class="modal-body">
                    <p t-if="!props.partner.sales_info and !props.partner.purchase_info" class="text-muted">
                        No sales or purchase information available for this partner.
                    </p>

                    <div t-if="props.partner.sales_info">
                        <h5>Sales Summary:</h5>
                        <ul class="list-unstyled">
                            <t t-foreach="Object.keys(props.partner.sales_info)" t-as="state" t-key="state">
                                <li><strong><t t-out="state"/>:</strong> <t t-out="props.partner.sales_info[state].toFixed(2)"/></li>
                            </t>
                        </ul>
                    </div>

                    <div t-if="props.partner.purchase_info" class="mt-3">
                        <h5>Purchase Summary:</h5>
                        <ul class="list-unstyled">
                            <t t-foreach="Object.keys(props.partner.purchase_info)" t-as="state" t-key="state">
                                <li><strong><t t-out="state"/>:</strong> <t t-out="props.partner.purchase_info[state].toFixed(2)"/></li>
                            </t>
                        </ul>
                    </div>
                </main>
                <footer class="modal-footer">
                    <button type="button" class="btn btn-primary" t-on-click="props.close">Close</button>
                </footer>
            </div>
        </div>
    </t>
</templates>

Step 6: Creating the Odoo Controller

This Python controller will handle the web request, fetch data, and render our main website template, passing any necessary props to our public component. Create main.py in my_public_components/controllers/.

# my_public_components/controllers/main.py
from odoo import http
from odoo.http import request

class PublicComponentController(http.Controller):
    @http.route('/my_public_component_page', type='http', auth="public", website=True)
    def my_public_component_page(self, **kwargs):
        """
        Renders the public component page and passes user-specific props.
        """
        partner_name = "Guest" # Default name for external users

        if request.env.user._is_internal():
            # If an internal Odoo user is logged in
            partner_name = f"Employee {request.env.user.partner_id.name}"
        elif request.env.user.partner_id and not request.env.user.partner_id._is_public():
            # If a portal user is logged in
            partner_name = request.env.user.partner_id.name

        # Prepare the props dictionary to be passed to the OWL component
        values = {
            'component_props': {
                'partnerName': partner_name,
            },
        }
        # Render our main website template, which in turn renders our public component
        return request.render('my_public_components.public_component_page', values)

Remember to add from . import main in my_public_components/controllers/__init__.py.

Step 7: Enhancing Your Odoo Model with Business Logic

Our public component needs a way to fetch sales and purchase data. We’ll add a custom method to the res.partner model. Create partner.py in my_public_components/models/.

# my_public_components/models/partner.py
from odoo import models, fields, api

class Partner(models.Model):
    _inherit = 'res.partner'

    @api.model
    def get_sales_purchase_info(self, partner_id):
        """
        RPC method to get sales and purchase summary for a given partner.
        This method is called from the OWL public component.
        """
        partner = self.browse(partner_id)
        if not partner:
            return {}

        sales = self.env['sale.order'].search([('partner_id', '=', partner.id)])
        purchases = self.env['purchase.order'].search([('partner_id', '=', partner.id)])

        sales_by_state = {}
        for sale in sales:
            # Group sales by state and sum their total amounts
            sales_by_state.setdefault(sale.state, 0.0)
            sales_by_state[sale.state] += sale.amount_total

        purchases_by_state = {}
        for purchase in purchases:
            # Group purchases by state and sum their total amounts
            purchases_by_state.setdefault(purchase.state, 0.0)
            purchases_by_state[purchase.state] += purchase.amount_total

        return {
            'id': partner.id,
            'name': partner.name,
            'sales_info': sales_by_state,
            'purchase_info': purchases_by_state
        }

Don’t forget to add from . import partner in my_public_components/models/__init__.py.

Step 8: Deploying and Using in the Website Builder

After creating all the files and restarting your Odoo instance (with -u my_public_components), follow these steps:

  1. Access the Page: Navigate to http://your-odoo-instance:8069/my_public_component_page (replace with your actual Odoo URL and port).
  2. Verify Functionality: You should see the “Welcome to Your Dynamic Odoo 19 Public Components Demo!” header, followed by a list of companies (partners). Click the “Show Info” button next to any contact to see the sales and purchase summary in a modal dialog.
  3. Website Builder Integration (Optional but powerful):
    • Go to your Odoo website’s backend (/web).
    • Navigate to Website -> Pages.
    • Create a new page or edit an existing one.
    • Click “Edit” in the top right corner of the page.
    • Find the “Structure” tab in the right-hand panel.
    • Drag and drop an “HTML Editor” snippet onto your page.
    • In the editor, add the following code:
      xml <t t-call="my_public_components.public_component_page"/>
    • Save your changes. Now, your dynamic public component is part of your website page, manageable directly through the website builder!

This powerful integration showcases how Odoo 19 Public Components aren’t just for developers; they empower content managers to embed rich, interactive features without touching complex code.

Key Takeaways and Best Practices for Odoo 19 Public Components

You’ve just created a dynamic and interactive web element using Odoo 19 Public Components! Here are some important considerations and best practices to keep in mind for your future projects:

  • Modularity is Key: Break down complex functionalities into smaller, reusable OWL components. This improves maintainability and readability.
  • Reactive State Management: Leverage useState effectively to manage your component’s data. Remember, any changes to useState variables will trigger a re-render of the relevant parts of your component.
  • Efficient Data Fetching: Use onWillStart or onMounted hooks for initial data loading. For dynamic, real-time updates, consider using services or RPC calls judiciously to avoid performance bottlenecks.
  • Security First: When making RPC calls from frontend components, always validate and sanitize data on the backend. Public components, by definition, are accessible to anyone, so never expose sensitive data or allow unauthenticated arbitrary operations.
  • Error Handling: Implement robust try-catch blocks for asynchronous operations (like ORM calls) to provide meaningful feedback to users and prevent unexpected crashes. The notification service is excellent for this.
  • Performance Optimization: For components with large datasets, consider pagination, lazy loading, or debouncing user input to keep your website fast and responsive.
  • OWL Documentation: The official OWL documentation (refer to the latest Odoo version for updated links) is an invaluable resource for understanding all its features and best practices.
  • Odoo Documentation: For deeper insights into Odoo’s web development, refer to the official Odoo documentation.

Conclusion

Odoo 19 Public Components mark a significant step forward in web development within the Odoo ecosystem. They empower developers to craft dynamic, interactive, and visually engaging web experiences that were previously more challenging to achieve. By following this tutorial, you’ve gained practical experience in building and integrating these powerful components, laying a strong foundation for future innovations.

The ability to create reactive elements directly on your Odoo website opens up a world of possibilities, from personalized dashboards for portal users to sophisticated data visualization tools for public consumption.

What exciting interactive features will you build next with Odoo 19 Public Components? Share your ideas and questions in the comments below! Don’t forget to subscribe for more in-depth Odoo tutorials and insights. Happy coding!


Discover more from teguhteja.id

Subscribe to get the latest posts sent to your email.

Leave a Reply

WP Twitter Auto Publish Powered By : XYZScripts.com