Add Odex Sidebar Backend Theme 2 with collapsible menu and configuration options

This commit is contained in:
Altahir Hassan 2026-01-05 15:59:28 +04:00
parent 575bb504ee
commit 144fffbb1a
11 changed files with 403 additions and 22 deletions

View File

@ -1 +0,0 @@
# -*- coding: utf-8 -*-

View File

@ -0,0 +1,142 @@
========================
Odex Sidebar Backend Theme v2
========================
Overview
========
This module provides a modern, collapsible sidebar menu system that replaces the default Odex app menu bar with an enhanced navigation experience. It delivers an improved user interface with better space management and responsive design.
Features
========
* **Collapsible/Expandable Sidebar Menu**: Toggle the sidebar to maximize screen space for your work
* **Smooth Navigation**: Seamlessly navigate across all Odex applications and menus
* **Responsive Design**: Adapts beautifully to different screen sizes (desktop, tablet, mobile)
* **Persistent State**: Sidebar collapse/expand state is saved and persists across user sessions
* **Enable/Disable Toggle**: Control the sidebar feature through the Settings panel
* **Optimized Performance**: Minimal performance impact with efficient menu rendering
Installation
============
1. Download or clone this module into your Odex addons directory
2. Restart the Odex server
3. Navigate to **Apps** and search for "Odex Sidebar Backend Theme 2"
4. Click **Install**
Dependencies
============
This module requires:
* Odex 18.0 (or compatible version)
* web module
* base module
Configuration
=============
After installation, you can configure the sidebar menu feature:
1. Navigate to **Settings > General Settings** (or **Settings > Sidebar Menu** if available)
2. Find the **Sidebar Menu** section
3. Enable or disable the sidebar menu feature as needed
4. Click **Save**
The setting is automatically saved and persists across all user sessions.
Usage
=====
Once enabled:
* Click the **menu icon** (≡) at the top of the sidebar to collapse/expand it
* The sidebar state is automatically saved for your next session
* All applications and menu items are accessible through the sidebar
* The responsive design automatically adjusts on smaller screens
Module Structure
================
::
odex_sidebar_backend_theme2/
├── __init__.py # Python initialization
├── __manifest__.py # Module metadata
├── views/
│ └── res_config_settings.xml # Settings configuration
├── static/
│ └── src/
│ ├── scss/
│ │ └── sidebar_menu.scss # Sidebar styling
│ ├── js/
│ │ ├── sidebar_menu.js # Main sidebar logic
│ │ ├── sidebar_css_loader.js # CSS loading functionality
│ │ ├── menu_item.js # Menu item handling
│ │ └── navbar_patch.js # Navigation bar customizations
│ └── xml/
│ ├── sidebar_menu_template.xml # Sidebar template
│ ├── menu_item_template.xml # Menu item template
│ └── navbar_patch.xml # Navbar patches
└── README.rst # This file
Technologies
=============
* JavaScript (ES6+)
* SCSS/CSS
* XML (Odex QWeb templates)
* Python
Browser Support
===============
* Chrome/Edge 90+
* Firefox 88+
* Safari 14+
* Mobile browsers (iOS Safari, Chrome Mobile)
Troubleshooting
===============
**Sidebar not appearing:**
- Clear your browser cache
- Log out and log back in
- Check that the module is enabled in Settings
**Sidebar not saving state:**
- Ensure cookies are enabled in your browser
- Check browser console for JavaScript errors (F12)
**Performance issues:**
- Clear browser cache and local storage
- Try disabling and re-enabling the module
Support
=======
For issues, questions, or feature requests, please contact Epxert Ltd.
* Website: https://www.exp-sa.com
* Email: support@exp-sa.com
License
=======
This module is licensed under the LGPL-3 License. See the LICENSE file for details.
Authors
=======
* Epxert Ltd. (https://www.exp-sa.com)
Changelog
=========
**Version 1.0.0** (Initial Release)
- Initial release of Odex Sidebar Backend Theme 2
- Collapsible sidebar menu functionality
- Persistent state management
- Responsive design support
- Enable/disable toggle in settings

View File

@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
from . import models

View File

@ -1,26 +1,46 @@
# -*- coding: utf-8 -*-
{
'name': 'Odex Sidebar Backend Theme2',
'version': '18.0.1.0.0',
'category': 'Web',
'summary': 'Custom menu for navigating all Odoo apps and menus smoothly',
'name': 'Odex Sidebar Backend Theme 2',
'version': '1.0.0',
'category': 'Web/Themes',
'summary': 'Enhanced collapsible sidebar menu for Odex backend navigation',
'description': """
Replace the default Odoo app menu bar with a collapsible sidebar menu.
Odex Sidebar Backend Theme 2
=============================
This module provides a modern, collapsible sidebar menu system that replaces
the default Odex app menu bar with an enhanced navigation experience.
Key Features:
- Collapsible/expandable sidebar menu for better space management
- Smooth navigation across all Odex applications and menus
- Responsive design that adapts to different screen sizes
- Persistent sidebar state (collapse/expand) across sessions
- Enable/disable sidebar via Settings panel
- Minimal performance impact with optimized menu rendering
Configuration:
Navigate to Settings > Sidebar Menu to enable or disable the sidebar menu feature.
The setting is automatically saved and persists across all user sessions.
""",
'author': 'Your Company',
'website': 'https://www.yourcompany.com',
'depends': ['web'],
'data': [],
'author': 'Epxert Ltd.',
'website': 'https://www.exp-sa.com',
'license': 'LGPL-3',
'depends': [
'web',
'base',
],
'data': [
'views/res_config_settings.xml',
],
'assets': {
'web.assets_backend': [
'odex_sidebar_backend_theme2/static/src/scss/sidebar_menu.scss',
'odex_sidebar_backend_theme2/static/src/xml/sidebar_menu_template.xml',
'odex_sidebar_backend_theme2/static/src/xml/menu_item_template.xml',
'odex_sidebar_backend_theme2/static/src/js/sidebar_menu.js',
'odex_sidebar_backend_theme2/static/src/js/sidebar_css_loader.js',
'odex_sidebar_backend_theme2/static/src/js/menu_item.js',
'odex_sidebar_backend_theme2/static/src/xml/navbar_patch.xml',
'odex_sidebar_backend_theme2/static/src/js/navbar_patch.js',
],
@ -28,5 +48,4 @@
'installable': True,
'application': False,
'auto_install': False,
'license': 'LGPL-3',
}

View File

@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
from . import res_config_settings

View File

@ -0,0 +1,90 @@
# -*- coding: utf-8 -*-
from odoo import fields,api, models
class ResConfigSettings(models.TransientModel):
_inherit = 'res.config.settings'
sidebar_menu_enable = fields.Boolean(
config_parameter='odex_sidebar_backend_theme2.sidebar_menu_enable',
string='Enable Sidebar Menu',
help='Enable or disable the sidebar menu in the backend'
)
disable_nav_menu_section = fields.Boolean(
config_parameter='odex_sidebar_backend_theme2.disable_nav_menu_section',
string='Disable Navigation Menu Section',
help='Enable or disable the top navigation bar menu section in the backend interface'
)
sidebar_menu_icon = fields.Binary(
string="Sidebar Icon",
help="Upload an icon for the sidebar menu.",
)
# set default value for the setting
def get_values(self):
res = super(ResConfigSettings, self).get_values()
IrConfigParam = self.env['ir.config_parameter'].sudo()
sidebar_menu_enable = IrConfigParam.get_param('odex_sidebar_backend_theme2.sidebar_menu_enable')
disable_nav_menu_section = IrConfigParam.get_param('odex_sidebar_backend_theme2.disable_nav_menu_section')
res.update(
sidebar_menu_enable=sidebar_menu_enable == 'True',
disable_nav_menu_section=disable_nav_menu_section == 'True',
sidebar_menu_icon=IrConfigParam.get_param('odex_sidebar_backend_theme2.sidebar_menu_icon')
)
return res
def _generate_sidebar_css(self):
"""Generate CSS rules for sidebar menu state"""
if self.disable_nav_menu_section:
return """
/* Sidebar Menu Disabled - Hide Top Menu Sections */
.o_main_navbar .o_menu_sections {
{
display: none !important;
visibility: hidden !important;
}
"""
return ""
# save the setting value
def set_values(self):
super(ResConfigSettings, self).set_values()
IrConfigParam = self.env['ir.config_parameter'].sudo()
IrConfigParam.set_param(
'odex_sidebar_backend_theme2.sidebar_menu_enable',
str(self.sidebar_menu_enable)
)
IrConfigParam.set_param(
'odex_sidebar_backend_theme2.disable_nav_menu_section',
str(self.disable_nav_menu_section)
)
IrConfigParam.set_param(
'odex_sidebar_backend_theme2.sidebar_menu_icon',
self.sidebar_menu_icon or ''
)
if self.sidebar_menu_icon:
# Store the image URL in config parameter
image_url = f"/web/image/res.config.settings/{self.id}/sidebar_menu_icon"
self.env['ir.config_parameter'].sudo().set_param('odex_sidebar_backend_theme2.sidebar_menu_icon_url', image_url)
else:
self.env['ir.config_parameter'].sudo().set_param('odex_sidebar_backend_theme2.sidebar_menu_icon_url', '')
# Generate and store CSS for sidebar state
css_code = self._generate_sidebar_css()
IrConfigParam.set_param(
'odex_sidebar_backend_theme2.sidebar_css',
css_code
)
return True
@api.model
def get_sidebar_setting(self):
IrConfigParam = self.env['ir.config_parameter'].sudo()
sidebar_enabled = IrConfigParam.get_param('odex_sidebar_backend_theme2.sidebar_menu_enable') == 'True'
sidebar_icon_url = IrConfigParam.get_param('odex_sidebar_backend_theme2.sidebar_menu_icon_url')
return {'sidebar_enabled': sidebar_enabled, 'sidebar_icon_url': sidebar_icon_url}

View File

@ -0,0 +1,54 @@
/** @odoo-module **/
import { useService } from "@web/core/utils/hooks";
import { registry } from "@web/core/registry";
/**
* Load and inject sidebar CSS rules based on configuration
*/
export function loadSidebarCSS() {
// Get the RPC service
const rpc = useService("rpc");
const loadCSS = async () => {
try {
// Fetch the stored CSS from config parameter
const css = await rpc(
'/web/dataset/call_kw/ir.config_parameter/get_param',
{
model: 'ir.config_parameter',
method: 'get_param',
args: ['odex_sidebar_backend_theme2.sidebar_css'],
kwargs: {},
}
);
if (css && css.trim()) {
// Create a style element and inject the CSS
const style = document.createElement('style');
style.type = 'text/css';
style.id = 'sidebar-dynamic-css';
style.innerHTML = css;
document.head.appendChild(style);
console.log('Sidebar CSS injected successfully');
}
} catch (error) {
console.error('Error loading sidebar CSS:', error);
}
};
// Load CSS when DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', loadCSS);
} else {
loadCSS();
}
}
// Initialize on module load
registry.category("web_tour.tours").add("sidebar_css_loader", {
steps: [],
});
// Auto-load CSS on page load
// loadSidebarCSS();

View File

@ -4,6 +4,7 @@ import { Component, onMounted, useEffect, useState } from "@odoo/owl";
import { registry } from "@web/core/registry";
import { useService } from "@web/core/utils/hooks";
import { MenuItem } from "./menu_item";
import { rpc } from "@web/core/network/rpc"
export class SidebarMenu extends Component {
static template = "odex_sidebar_backend_theme2.SidebarMenu"
@ -14,13 +15,18 @@ export class SidebarMenu extends Component {
this.menuService = useService("menu");
this.busService = useService("bus_service");
this.actionService = useService("action");
this.rpc = rpc;
this.state = useState({
menus: [],
isOpen: true,
isCollapsed: false
isCollapsed: false,
sidebarEnabled: false,
sidebarMenuIconUrl: null,
});
this.loadSidebarSetting()
// =================== JavaScript Control Starts Here ===================
const applyLayoutChanges = (isOpen) => {
@ -92,6 +98,33 @@ export class SidebarMenu extends Component {
});
}
async loadSidebarSetting() {
try {
const result = await this.rpc('/web/dataset/call_kw', {
model: 'res.config.settings',
method: 'get_sidebar_setting',
args: [],
kwargs: {},
});
// Convert string result to boolean
this.state.sidebarEnabled = result.sidebar_enabled !== 'False' && result.sidebar_enabled !== false && result.sidebar_enabled !== '';
// If sidebar is disabled, close it
if (!this.state.sidebarEnabled) {
this.state.isOpen = false;
}
// Load sidebar menu icon URL
this.state.sidebarMenuIconUrl = result.sidebar_icon_url || '/odex_sidebar_backend_theme2/static/src/img/logo1.png';
} catch (error) {
console.error('Error loading sidebar setting:', error);
// Default to enabled if setting cannot be loaded
this.state.sidebarEnabled = true;
}
}
loadMenus() {
const allMenus = this.menuService.getAll();
const clonedMenus = structuredClone(allMenus);

View File

@ -461,11 +461,6 @@
display: none !important;
}
/* Hide submenus inside the top bar */
.o_main_navbar .o_menu_sections {
display: none !important;
}
.o_web_client {
margin-left: 0 !important;
transition: all 0.3s ease-in-out;

View File

@ -1,11 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="odex_sidebar_backend_theme2.SidebarMenu" owl="1">
<div class="custom_sidebar" t-att-class="{ 'is-open': state.isOpen, 'is-collapsed': state.isCollapsed }">
<div class="custom_sidebar" t-att-class="{ 'is-open': state.isOpen, 'is-collapsed': state.isCollapsed, 'is-disabled': !state.sidebarEnabled }" t-if="state.sidebarEnabled">
<!-- Sidebar Header -->
<header class="sidebar-header">
<a href="/" class="header-logo" t-att-title="state.isCollapsed ? 'Home' : ''">
<img src="/odex_sidebar_backend_theme2/static/src/img/logo1.png" alt="CodingNepal" />
<img t-att-src="state.sidebarMenuIconUrl" alt="CodingNepal" />
</a>
<button class="sidebar-toggler" t-on-click="() => this.toggleCollapse()">
<span class="material-symbols-rounded">

View File

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="res_config_settings_view_form_inherit_sidebar" model="ir.ui.view">
<field name="name">res.config.settings.form.inherit.sidebar</field>
<field name="model">res.config.settings</field>
<field name="inherit_id" ref="base.res_config_settings_view_form" />
<field name="arch" type="xml">
<xpath expr="//form" position="inside">
<app data-string="Odex Sidebar Menu" string="Odex Sidebar Menu"
name="odex_sidebar_backend_theme2">
<block title="Odex Sidebar Menu" name="odex_sidebar_settings_block">
<setting>
<div class="content-group">
<div class="row mt16">
<label for="sidebar_menu_enable" />
<div class="text-muted">
Enable or disable the sidebar menu in the backend
interface
</div>
<field name="sidebar_menu_enable" />
</div>
<div class="row mt16">
<label for="disable_nav_menu_section" />
<div class="text-muted">
Enable or disable the top navigation bar menu section
in the backend interface
</div>
<field name="disable_nav_menu_section" />
</div>
<div class="row mt16">
<label for="sidebar_menu_icon" />
<field name="sidebar_menu_icon" widget="image"
options="{'size': [128, 128]}"/>
</div>
</div>
</setting>
</block>
</app>
</xpath>
</field>
</record>
</odoo>