@ -0,0 +1,47 @@ |
|||
.. image:: https://img.shields.io/badge/license-LGPL--3-green.svg |
|||
:target: https://www.gnu.org/licenses/lgpl-3.0-standalone.html |
|||
:alt: License: LGPL-3 |
|||
|
|||
Fountain Widget |
|||
=============== |
|||
* This module introduce a new widget that is used to select the many2one field in fountain manner. |
|||
|
|||
Configuration |
|||
============= |
|||
No additional configurations needed. |
|||
|
|||
License |
|||
------- |
|||
Lesser General Public License, Version 3 (LGPL v3). |
|||
|
|||
(https://www.gnu.org/licenses/lgpl-3.0-standalone.html) |
|||
|
|||
Company |
|||
------- |
|||
* `Cybrosys Techno Solutions <https://cybrosys.com/>`__ |
|||
|
|||
Credits |
|||
------- |
|||
Developer: (V18) Manasa T P ,Contact : odoo@cybrosys.com |
|||
|
|||
Contacts |
|||
-------- |
|||
* Mail Contact : odoo@cybrosys.com |
|||
* Website : https://cybrosys.com |
|||
|
|||
Bug Tracker |
|||
----------- |
|||
Bugs are tracked on GitHub Issues. In case of trouble, please check there if your issue has already been reported. |
|||
|
|||
Maintainer |
|||
========== |
|||
.. image:: https://cybrosys.com/images/logo.png |
|||
:target: https://cybrosys.com |
|||
|
|||
This module is maintained by Cybrosys Technologies. |
|||
|
|||
For support and more information, please visit https://www.cybrosys.com |
|||
|
|||
Further information |
|||
=================== |
|||
HTML Description: `<static/description/index.html>`__ |
@ -0,0 +1,21 @@ |
|||
# -*- coding: utf-8 -*- |
|||
############################################################################### |
|||
# |
|||
# Cybrosys Technologies Pvt. Ltd. |
|||
# |
|||
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
|||
# Author: Manasa T P (odoo@cybrosys.com) |
|||
# |
|||
# You can modify it under the terms of the GNU LESSER |
|||
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3. |
|||
# |
|||
# This program is distributed in the hope that it will be useful, |
|||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. |
|||
# |
|||
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE |
|||
# (LGPL v3) along with this program. |
|||
# If not, see <http://www.gnu.org/licenses/>. |
|||
# |
|||
############################################################################### |
@ -0,0 +1,53 @@ |
|||
# -*- coding: utf-8 -*- |
|||
############################################################################### |
|||
# |
|||
# Cybrosys Technologies Pvt. Ltd. |
|||
# |
|||
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
|||
# Author: Manasa T P (odoo@cybrosys.com) |
|||
# |
|||
# You can modify it under the terms of the GNU LESSER |
|||
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3. |
|||
# |
|||
# This program is distributed in the hope that it will be useful, |
|||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. |
|||
# |
|||
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE |
|||
# (LGPL v3) along with this program. |
|||
# If not, see <http://www.gnu.org/licenses/>. |
|||
# |
|||
############################################################################### |
|||
{ |
|||
"name": "Fountain Widget", |
|||
"version": "18.0.1.0.0", |
|||
"category": 'Extra Tools', |
|||
"summary": "This widget is used to select the many2one field in fountain " |
|||
"manner.", |
|||
"description": "This is a widget for many2one fields. By using this " |
|||
"widget we can select record in a fountain manner. It " |
|||
"generates dynamic dropdown menus with hierarchical options " |
|||
"and thus Supports parent-child relationships between " |
|||
"dropdown options.", |
|||
"author": "Cybrosys Techno Solutions", |
|||
'company': 'Cybrosys Techno Solutions', |
|||
'maintainer': 'Cybrosys Techno Solutions', |
|||
'website': 'https://www.cybrosys.com', |
|||
"depends": ['base'], |
|||
'assets': { |
|||
'web.assets_backend': [ |
|||
'fountain_widget_many2one/static/src/js/fountain_widget_many2one.js', |
|||
'fountain_widget_many2one/static/src/xml/fountain_widget_many2one.xml', |
|||
'fountain_widget_many2one/static/src/css/fountain_widget_many2one.css', |
|||
'fountain_widget_many2one/static/src/css/fountain_widget_many2one.scss', |
|||
'fountain_widget_many2one/static/src/xml/fountain_widget_component.xml', |
|||
'fountain_widget_many2one/static/src/js/fountain_widget_component.js', |
|||
], |
|||
}, |
|||
'images': ['static/description/banner.jpg'], |
|||
'license': 'LGPL-3', |
|||
'installable': True, |
|||
'auto_install': False, |
|||
'application': False, |
|||
} |
@ -0,0 +1,6 @@ |
|||
## Module <fountain_widget_many2one> |
|||
#### 17.04.2025 |
|||
#### Version 18.0.1.0.0 |
|||
##### ADD |
|||
|
|||
- Initial Commit for Fountain Widget |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 628 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 210 KiB |
After Width: | Height: | Size: 209 KiB |
After Width: | Height: | Size: 109 KiB |
After Width: | Height: | Size: 495 B |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 624 B |
After Width: | Height: | Size: 136 KiB |
After Width: | Height: | Size: 214 KiB |
After Width: | Height: | Size: 36 KiB |
After Width: | Height: | Size: 3.6 KiB |
After Width: | Height: | Size: 310 B |
After Width: | Height: | Size: 929 B |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 542 B |
After Width: | Height: | Size: 576 B |
After Width: | Height: | Size: 733 B |
After Width: | Height: | Size: 4.3 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 4.0 KiB |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 738 KiB |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 911 B |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 600 B |
After Width: | Height: | Size: 673 B |
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 462 B |
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 926 B |
After Width: | Height: | Size: 9.0 KiB |
After Width: | Height: | Size: 23 KiB |
After Width: | Height: | Size: 7.0 KiB |
After Width: | Height: | Size: 878 B |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 653 B |
After Width: | Height: | Size: 800 B |
After Width: | Height: | Size: 905 B |
After Width: | Height: | Size: 189 KiB |
After Width: | Height: | Size: 4.3 KiB |
After Width: | Height: | Size: 839 B |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 5.9 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 34 KiB |
After Width: | Height: | Size: 26 KiB |
After Width: | Height: | Size: 3.8 KiB |
After Width: | Height: | Size: 23 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 2.3 KiB |
After Width: | Height: | Size: 427 B |
After Width: | Height: | Size: 627 B |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 988 B |
After Width: | Height: | Size: 3.7 KiB |
After Width: | Height: | Size: 5.0 KiB |
After Width: | Height: | Size: 875 B |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 767 KiB |
After Width: | Height: | Size: 116 KiB |
After Width: | Height: | Size: 100 KiB |
After Width: | Height: | Size: 89 KiB |
After Width: | Height: | Size: 126 KiB |
After Width: | Height: | Size: 76 KiB |
After Width: | Height: | Size: 95 KiB |
After Width: | Height: | Size: 154 KiB |
After Width: | Height: | Size: 81 KiB |
After Width: | Height: | Size: 78 KiB |
After Width: | Height: | Size: 84 KiB |
After Width: | Height: | Size: 86 KiB |
After Width: | Height: | Size: 80 KiB |
After Width: | Height: | Size: 388 KiB |
After Width: | Height: | Size: 880 KiB |
After Width: | Height: | Size: 702 KiB |
After Width: | Height: | Size: 40 KiB |
@ -0,0 +1,11 @@ |
|||
.divMargin{ |
|||
margin-right: 11px; |
|||
border-right: 1px solid black; |
|||
padding-right: 10px; |
|||
} |
|||
.dropdown-item-text { |
|||
display: block; |
|||
padding: 3px 20px; |
|||
color: #495057; |
|||
position: relative; |
|||
} |
@ -0,0 +1,8 @@ |
|||
.icon_style{ |
|||
&::after{ |
|||
content: "\f061"; |
|||
font-family: 'FontAwesome'; |
|||
position: absolute; |
|||
right: 0px; |
|||
} |
|||
} |
@ -0,0 +1,46 @@ |
|||
/** @odoo-module **/ |
|||
import { Dropdown } from "@web/core/dropdown/dropdown"; |
|||
import { onMounted, onWillUnmount, useRef } from "@odoo/owl"; |
|||
|
|||
// Extending the Dropdown and adding the inputData into the props
|
|||
export class FountainDropdown extends Dropdown { |
|||
setup() { |
|||
super.setup(); |
|||
this.state.open = false; // Default state is closed
|
|||
|
|||
// Use OWL's useRef to reference the root element
|
|||
this.rootRef = useRef("root"); |
|||
|
|||
// Add click outside detection
|
|||
this.handleClickOutside = (event) => { |
|||
if ( |
|||
this.rootRef.el && |
|||
!this.rootRef.el.contains(event.target) && |
|||
this.state.open |
|||
) { |
|||
this.state.open = false; // Close dropdown if clicked outside
|
|||
} |
|||
}; |
|||
|
|||
onMounted(() => { |
|||
// Add event listener when component is mounted
|
|||
document.addEventListener('click', this.handleClickOutside); |
|||
}); |
|||
|
|||
onWillUnmount(() => { |
|||
// Remove event listener when component is unmounted
|
|||
document.removeEventListener('click', this.handleClickOutside); |
|||
}); |
|||
} |
|||
|
|||
onClick(event) { |
|||
event.preventDefault(); // Prevent default behavior
|
|||
this.state.open = !this.state.open; // Toggle dropdown visibility
|
|||
} |
|||
} |
|||
|
|||
FountainDropdown.template = "fountain.Dropdown"; |
|||
FountainDropdown.props = { |
|||
...Dropdown.props, |
|||
inputData: { type: Object, optional: true } |
|||
}; |
@ -0,0 +1,87 @@ |
|||
/** @odoo-module **/ |
|||
import { Component, useEffect, useState, onWillStart } from "@odoo/owl"; |
|||
import { registry } from "@web/core/registry"; |
|||
import { standardFieldProps } from "@web/views/fields/standard_field_props"; |
|||
import { FountainDropdown } from "./fountain_widget_component"; |
|||
import { useService } from "@web/core/utils/hooks"; |
|||
|
|||
export class FountainWidget extends Component { |
|||
static template = "FountainWidgetField"; |
|||
static components = { |
|||
FountainDropdown, |
|||
}; |
|||
setup() { |
|||
this.orm = useService('orm'); |
|||
useEffect(() => { this.state.inputData = this.props.record.data[this.props.name] }, () => [this.props]); |
|||
this.state = useState({ |
|||
childMenu: {}, |
|||
inputData: this.props.record.data[this.props.name], |
|||
open: false // Control dropdown visibility
|
|||
}); |
|||
onWillStart(async () => { |
|||
this.state.category = await this.orm.searchRead(this.props.record.fields.categ_id.relation, []); |
|||
}); |
|||
this.index = 0; // Track the current depth of the dropdown
|
|||
|
|||
// Bind the onClickDropDown method to ensure `this` refers to the component instance
|
|||
this.onClickDropDown = this.onClickDropDown.bind(this); |
|||
} |
|||
|
|||
get inputData() { |
|||
return { |
|||
input: this.state.inputData ? this.state.inputData[1] : '' |
|||
}; |
|||
} |
|||
|
|||
get parentMenu() { |
|||
return this.state.category.filter(item => !item.parent_id); |
|||
} |
|||
|
|||
get childKeys() { |
|||
return Object.keys(this.state.childMenu); |
|||
} |
|||
|
|||
onClickDropDown(parent_id, index = 0) { |
|||
// Ensure index is a number, default to 0 if undefined
|
|||
index = Number(index) || 0; |
|||
|
|||
if (this.index >= index) { |
|||
let childLength = this.childKeys.length; |
|||
while (index < childLength) { |
|||
const keyIndex = this.childKeys[index]; |
|||
delete this.state.childMenu[keyIndex]; |
|||
childLength--; |
|||
} |
|||
} |
|||
let selectedCategory = this.state.category.find((item) => item.id === parent_id); |
|||
let obj = this.state.category.filter(item => item.parent_id && item.parent_id[0] === parent_id); |
|||
|
|||
if (!this.state.childMenu.hasOwnProperty(parent_id) && obj.length > 0) { |
|||
this.state.childMenu[parent_id] = obj; |
|||
this.index = index + 1; // Increment index for nested levels
|
|||
} |
|||
|
|||
if (this.props.record && this.props.record.update) { |
|||
this.state.inputData = [parent_id, selectedCategory.complete_name]; |
|||
this.props.record.update({ [this.props.name]: [parent_id, selectedCategory.complete_name] }); |
|||
} else { |
|||
console.error('Update function not found in record props:', this.props); |
|||
} |
|||
this.state.open = false; // Close dropdown after selection
|
|||
return obj; |
|||
} |
|||
|
|||
static props = { |
|||
...standardFieldProps, |
|||
inputData: { type: Object, optional: true, default: {} }, |
|||
}; |
|||
} |
|||
|
|||
// Define the widget field with the correct structure
|
|||
export const FountainWidgetField = { |
|||
component: FountainWidget, |
|||
supportedTypes: ["many2one"], |
|||
}; |
|||
|
|||
// Register the widget in the field registry
|
|||
registry.category("fields").add("fountain_widget", FountainWidgetField); |
@ -0,0 +1,37 @@ |
|||
<?xml version="1.0" encoding="UTF-8" ?> |
|||
<templates id="template" xml:space="preserve"> |
|||
<!-- Changing the button into an input --> |
|||
<t t-name="fountain.Dropdown" owl="1"> |
|||
<div class="o-dropdown dropdown" |
|||
t-att-class="props.class" |
|||
t-attf-class=" |
|||
{{ directionCaretClass || ''}} |
|||
{{ state.open ? 'show' : ''}} |
|||
{{ !showCaret ? 'o-dropdown--no-caret' : '' }}" |
|||
t-ref="root"> |
|||
<input style="width:100%;" |
|||
class="dropdown-toggle" |
|||
t-att-class="{ |
|||
[props.togglerClass]: props.togglerClass, |
|||
'dropdown-item': parentDropdown }" |
|||
t-on-click.stop="onClick" |
|||
t-att-title="props.title" |
|||
t-att-value="props.inputData.input || ''" |
|||
t-att-data-tooltip="props.tooltip" |
|||
t-att-data-hotkey="props.hotkey" |
|||
t-att-aria-expanded="state.open ? 'true' : 'false'" |
|||
t-att-tabindex="props.skipTogglerTabbing ? -1 : 0" |
|||
t-ref="togglerRef"> |
|||
<t t-slot="default"/> |
|||
</input> |
|||
<div t-if="state.open" |
|||
class="o-dropdown--menu dropdown-menu d-block" |
|||
t-att-class="props.menuClass" |
|||
role="menu" |
|||
t-ref="menuRef" |
|||
style="min-width: unset;"> |
|||
<t t-slot="default"/> |
|||
</div> |
|||
</div> |
|||
</t> |
|||
</templates> |
@ -0,0 +1,37 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<templates> |
|||
<t t-name="FountainWidgetField" owl="1"> |
|||
<!-- Template containing the dropdown component --> |
|||
<FountainDropdown inputData="inputData"> |
|||
<div style="display: flex;"> |
|||
<div class="divMargin"> |
|||
<t t-foreach="parentMenu" t-as="category" t-key="category.id"> |
|||
<t t-if="category.child_id.length > 0"> |
|||
<t t-set="parent_class" t-value="'icon_style'"/> |
|||
</t> |
|||
<option class="dropdown-item-text" |
|||
t-on-click.stop.prevent="() => onClickDropDown(category.id, 0)" |
|||
t-attf-class="#{parent_class}"> |
|||
<t t-esc="category.name"/> |
|||
</option> |
|||
<li class="dropdown-divider" role="separator"/> |
|||
</t> |
|||
</div> |
|||
<div t-foreach="childKeys" t-as="cKeys" t-key="cKeys"> |
|||
<t t-foreach="state.childMenu[cKeys]" t-as="child" t-key="child.id"> |
|||
<t t-if="child.child_id.length > 0"> |
|||
<t t-set="child_class" t-value="'icon_style'"/> |
|||
</t> |
|||
<option class="dropdown-item-text" |
|||
t-on-click.stop.prevent="() => onClickDropDown(child.id, cKeys_index + 1)" |
|||
t-attf-class="#{child_class}"> |
|||
<t t-esc="child.name"/> |
|||
</option> |
|||
<t t-set="child_class" t-value=""/> |
|||
<li class="dropdown-divider" role="separator"/> |
|||
</t> |
|||
</div> |
|||
</div> |
|||
</FountainDropdown> |
|||
</t> |
|||
</templates> |