@ -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> |