fix bug overlap with OWL DOM
This commit is contained in:
parent
086bd6a944
commit
ea0fee1b45
@ -1,109 +1,146 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
console.log('Loading comprehensive decimal observer...');
|
||||
console.log('Loading safe decimal observer...');
|
||||
|
||||
// Function to apply decimal styling to any element
|
||||
function applyDecimalStyling(element) {
|
||||
if (!element || !element.textContent) return;
|
||||
// Function to safely apply decimal styling to any element
|
||||
function safelyApplyDecimalStyling(element) {
|
||||
if (!element || !element.textContent || !document.contains(element)) return false;
|
||||
|
||||
// Skip if already processed
|
||||
if (element.querySelector('.o_decimal') || element.classList.contains('o_decimal')) return;
|
||||
// Skip if already processed or if Owl is updating this element
|
||||
if (element.querySelector('.o_decimal') ||
|
||||
element.classList.contains('o_decimal') ||
|
||||
element.hasAttribute('data-decimal-processed') ||
|
||||
element.hasAttribute('data-owl-updating')) return false;
|
||||
|
||||
const text = element.textContent.trim();
|
||||
if (!text) return;
|
||||
|
||||
// Pattern for decimal numbers (including currency formats)
|
||||
const patterns = [
|
||||
// Standard decimal: 123.45, 1,234.56
|
||||
/^(.*)(\d{1,3}(?:[.,]\d{3})*)[.,](\d+)(.*)$/,
|
||||
// Currency with Rp: Rp 6,210,000.00
|
||||
/^(.*Rp\s*)(\d{1,3}(?:[.,]\d{3})*)[.,](\d+)(.*)$/,
|
||||
// Simple decimal: 240.000
|
||||
/^(.*)(\d+)[.](\d+)(.*)$/
|
||||
];
|
||||
|
||||
for (const pattern of patterns) {
|
||||
const match = text.match(pattern);
|
||||
if (match) {
|
||||
const [, prefix, integerPart, decimalPart, suffix] = match;
|
||||
|
||||
console.log('Decimal Observer: Styling', text);
|
||||
|
||||
// Create new HTML with decimal styling
|
||||
const newHTML = `${prefix || ''}${integerPart}<span class="o_decimal">.${decimalPart}</span>${suffix || ''}`;
|
||||
element.innerHTML = newHTML;
|
||||
return true;
|
||||
try {
|
||||
const text = element.textContent.trim();
|
||||
if (!text) return false;
|
||||
|
||||
// Mark as processed to avoid conflicts
|
||||
element.setAttribute('data-decimal-processed', 'true');
|
||||
|
||||
// Pattern for decimal numbers (including currency formats)
|
||||
const patterns = [
|
||||
// Currency with Rp: Rp 6,210,000.00
|
||||
/^(.*Rp\s*)(\d{1,3}(?:[.,]\d{3})*)[.,](\d+)(.*)$/,
|
||||
// Standard decimal: 123.45, 1,234.56
|
||||
/^(.*)(\d{1,3}(?:[.,]\d{3})*)[.,](\d+)(.*)$/,
|
||||
// Simple decimal: 240.000
|
||||
/^(.*)(\d+)[.](\d+)(.*)$/
|
||||
];
|
||||
|
||||
for (const pattern of patterns) {
|
||||
const match = text.match(pattern);
|
||||
if (match) {
|
||||
const [, prefix, integerPart, decimalPart, suffix] = match;
|
||||
|
||||
console.log('Safe Decimal Observer: Styling', text);
|
||||
|
||||
// Double-check the element hasn't changed before updating
|
||||
if (element.textContent.trim() === text && document.contains(element)) {
|
||||
const newHTML = `${prefix || ''}${integerPart}<span class="o_decimal">.${decimalPart}</span>${suffix || ''}`;
|
||||
element.innerHTML = newHTML;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle whole numbers in quantity contexts
|
||||
if (text.match(/^\d+$/) && (
|
||||
element.closest('[name="quantity"]') ||
|
||||
element.closest('.o_field_float') ||
|
||||
element.closest('td') && element.closest('table')
|
||||
)) {
|
||||
console.log('Decimal Observer: Adding decimals to whole number', text);
|
||||
element.innerHTML = `${text}<span class="o_decimal">.000</span>`;
|
||||
return true;
|
||||
|
||||
// Handle whole numbers in quantity contexts (be more selective)
|
||||
if (text.match(/^\d+$/) && text.length > 0 && (
|
||||
element.closest('[name="quantity"]') ||
|
||||
(element.closest('td') && element.closest('table') && text.length <= 6) // Only for reasonable quantities
|
||||
)) {
|
||||
console.log('Safe Decimal Observer: Adding decimals to whole number', text);
|
||||
|
||||
// Double-check before updating
|
||||
if (element.textContent.trim() === text && document.contains(element)) {
|
||||
element.innerHTML = `${text}<span class="o_decimal">.000</span>`;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.warn('Safe decimal styling error (non-critical):', error);
|
||||
// Remove the processing flag if there was an error
|
||||
element.removeAttribute('data-decimal-processed');
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Process all elements in a container
|
||||
function processContainer(container) {
|
||||
if (!container) return;
|
||||
// Safe processing of containers
|
||||
function safelyProcessContainer(container) {
|
||||
if (!container || !document.contains(container)) return;
|
||||
|
||||
// Find all text-containing elements
|
||||
const elements = container.querySelectorAll('td, span, div, .o_field_monetary, .o_field_float');
|
||||
|
||||
elements.forEach(element => {
|
||||
// Only process leaf elements (no child elements with text)
|
||||
if (element.children.length === 0 ||
|
||||
(element.children.length === 1 && element.children[0].tagName === 'SPAN')) {
|
||||
applyDecimalStyling(element);
|
||||
}
|
||||
});
|
||||
try {
|
||||
// Find all text-containing elements, but be more selective
|
||||
const elements = container.querySelectorAll('td:not([data-decimal-processed]), span:not([data-decimal-processed]), .o_field_monetary:not([data-decimal-processed]), .o_field_float:not([data-decimal-processed])');
|
||||
|
||||
elements.forEach(element => {
|
||||
// Only process leaf elements and skip if Owl is managing them
|
||||
if ((element.children.length === 0 ||
|
||||
(element.children.length === 1 && element.children[0].tagName === 'SPAN')) &&
|
||||
!element.hasAttribute('data-owl-updating')) {
|
||||
safelyApplyDecimalStyling(element);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.warn('Safe container processing error (non-critical):', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Observer for dynamic content
|
||||
const observer = new MutationObserver((mutations) => {
|
||||
mutations.forEach((mutation) => {
|
||||
if (mutation.type === 'childList') {
|
||||
mutation.addedNodes.forEach((node) => {
|
||||
if (node.nodeType === Node.ELEMENT_NODE) {
|
||||
// Process the new node and its children
|
||||
processContainer(node);
|
||||
// More conservative observer
|
||||
const safeObserver = new MutationObserver((mutations) => {
|
||||
// Debounce the processing to avoid conflicts
|
||||
clearTimeout(safeObserver.timeout);
|
||||
safeObserver.timeout = setTimeout(() => {
|
||||
try {
|
||||
mutations.forEach((mutation) => {
|
||||
if (mutation.type === 'childList') {
|
||||
mutation.addedNodes.forEach((node) => {
|
||||
if (node.nodeType === Node.ELEMENT_NODE && document.contains(node)) {
|
||||
// Use requestAnimationFrame to ensure we run after Owl updates
|
||||
requestAnimationFrame(() => {
|
||||
safelyProcessContainer(node);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.warn('Safe observer error (non-critical):', error);
|
||||
}
|
||||
});
|
||||
}, 200); // Debounce for 200ms
|
||||
});
|
||||
|
||||
// Start observing when DOM is ready
|
||||
function startObserver() {
|
||||
console.log('Decimal Observer: Starting comprehensive observation');
|
||||
// Start observing safely
|
||||
function startSafeObserver() {
|
||||
console.log('Safe Decimal Observer: Starting conservative observation');
|
||||
|
||||
// Process existing content
|
||||
processContainer(document.body);
|
||||
// Process existing content safely
|
||||
requestAnimationFrame(() => {
|
||||
safelyProcessContainer(document.body);
|
||||
});
|
||||
|
||||
// Start observing for new content
|
||||
observer.observe(document.body, {
|
||||
// Start observing for new content with reduced frequency
|
||||
safeObserver.observe(document.body, {
|
||||
childList: true,
|
||||
subtree: true
|
||||
});
|
||||
|
||||
// Also process content periodically for dynamic updates
|
||||
// Periodic processing with longer intervals to reduce conflicts
|
||||
setInterval(() => {
|
||||
processContainer(document.body);
|
||||
}, 2000);
|
||||
requestAnimationFrame(() => {
|
||||
safelyProcessContainer(document.body);
|
||||
});
|
||||
}, 5000); // Every 5 seconds instead of 2
|
||||
}
|
||||
|
||||
// Start immediately or when DOM is ready
|
||||
// Start safely
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', startObserver);
|
||||
document.addEventListener('DOMContentLoaded', startSafeObserver);
|
||||
} else {
|
||||
startObserver();
|
||||
startSafeObserver();
|
||||
}
|
||||
|
||||
console.log('Comprehensive decimal observer loaded');
|
||||
console.log('Safe decimal observer loaded');
|
||||
@ -7,103 +7,129 @@ import { formatMonetary, formatFloat } from "@web/views/fields/formatters";
|
||||
|
||||
console.log('Loading Web Decimal Style formatters patch...');
|
||||
|
||||
// Patch Components for Form Views
|
||||
patch(MonetaryField.prototype, {
|
||||
get formattedValue() {
|
||||
if (this.props.inputType === "number" && !this.props.readonly && this.value) {
|
||||
return this.value;
|
||||
}
|
||||
const res = formatMonetary(this.value, {
|
||||
digits: this.currencyDigits,
|
||||
currencyId: this.currencyId,
|
||||
noSymbol: !this.props.readonly || this.props.hideSymbol,
|
||||
});
|
||||
|
||||
// For readonly fields, apply decimal styling via DOM manipulation
|
||||
if (this.props.readonly) {
|
||||
setTimeout(() => {
|
||||
const element = this.el?.querySelector('.o_field_monetary');
|
||||
if (element && !element.querySelector('.o_decimal')) {
|
||||
const text = element.textContent;
|
||||
const match = text?.match(/^(.*)(\d{1,3}(?:[.,]\d{3})*)[.,](\d+)(.*)$/);
|
||||
if (match) {
|
||||
const [, prefix, integerPart, decimalPart, suffix] = match;
|
||||
element.innerHTML = `${prefix}${integerPart}<span class="o_decimal">.${decimalPart}</span>${suffix}`;
|
||||
}
|
||||
}
|
||||
}, 50);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
});
|
||||
|
||||
patch(FloatField.prototype, {
|
||||
get formattedValue() {
|
||||
if (
|
||||
!this.props.formatNumber ||
|
||||
(this.props.inputType === "number" && !this.props.readonly && this.value)
|
||||
) {
|
||||
return this.value;
|
||||
}
|
||||
const options = {
|
||||
digits: this.props.digits,
|
||||
field: this.props.record.fields[this.props.name],
|
||||
};
|
||||
let res;
|
||||
if (this.props.humanReadable && !this.state.hasFocus) {
|
||||
res = formatFloat(this.value, {
|
||||
...options,
|
||||
humanReadable: true,
|
||||
decimals: this.props.decimals,
|
||||
// Safe field patching with error handling
|
||||
try {
|
||||
// Patch Components for Form Views
|
||||
patch(MonetaryField.prototype, {
|
||||
get formattedValue() {
|
||||
if (this.props.inputType === "number" && !this.props.readonly && this.value) {
|
||||
return this.value;
|
||||
}
|
||||
const res = formatMonetary(this.value, {
|
||||
digits: this.currencyDigits,
|
||||
currencyId: this.currencyId,
|
||||
noSymbol: !this.props.readonly || this.props.hideSymbol,
|
||||
});
|
||||
} else {
|
||||
res = formatFloat(this.value, { ...options, humanReadable: false });
|
||||
}
|
||||
|
||||
// For readonly fields, apply decimal styling via DOM manipulation
|
||||
if (this.props.readonly) {
|
||||
setTimeout(() => {
|
||||
const element = this.el?.querySelector('.o_field_float');
|
||||
if (element && !element.querySelector('.o_decimal')) {
|
||||
const text = element.textContent;
|
||||
const match = text?.match(/^(.*)(\d+)[.](\d+)(.*)$/);
|
||||
if (match) {
|
||||
const [, prefix, integerPart, decimalPart, suffix] = match;
|
||||
element.innerHTML = `${prefix}${integerPart}<span class="o_decimal">.${decimalPart}</span>${suffix}`;
|
||||
}
|
||||
}
|
||||
}, 50);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
});
|
||||
|
||||
// Enhanced Registry Patching for List Views
|
||||
const formattersRegistry = registry.category("formatters");
|
||||
|
||||
// Store original formatters
|
||||
const originalFormatters = new Map();
|
||||
|
||||
function patchFormatter(name) {
|
||||
if (formattersRegistry.contains(name) && !originalFormatters.has(name)) {
|
||||
const original = formattersRegistry.get(name);
|
||||
originalFormatters.set(name, original);
|
||||
|
||||
formattersRegistry.add(name, (...args) => {
|
||||
const res = original(...args);
|
||||
|
||||
// Return the result as-is, let DOM observer handle styling
|
||||
// This avoids markup escaping issues
|
||||
// For readonly fields, apply decimal styling via safe DOM manipulation
|
||||
if (this.props.readonly) {
|
||||
// Use a longer timeout to avoid Owl lifecycle conflicts
|
||||
setTimeout(() => {
|
||||
try {
|
||||
const element = this.el?.querySelector('.o_field_monetary');
|
||||
if (element && !element.querySelector('.o_decimal') && document.contains(element)) {
|
||||
const text = element.textContent;
|
||||
const match = text?.match(/^(.*)(\d{1,3}(?:[.,]\d{3})*)[.,](\d+)(.*)$/);
|
||||
if (match && element.textContent === text) { // Double-check content hasn't changed
|
||||
const [, prefix, integerPart, decimalPart, suffix] = match;
|
||||
element.innerHTML = `${prefix}${integerPart}<span class="o_decimal">.${decimalPart}</span>${suffix}`;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Monetary field decimal styling error (non-critical):', error);
|
||||
}
|
||||
}, 150); // Longer delay to avoid Owl conflicts
|
||||
}
|
||||
|
||||
return res;
|
||||
}, { force: true });
|
||||
|
||||
console.log('Web Decimal Style: Patched formatter', name);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
patch(FloatField.prototype, {
|
||||
get formattedValue() {
|
||||
if (
|
||||
!this.props.formatNumber ||
|
||||
(this.props.inputType === "number" && !this.props.readonly && this.value)
|
||||
) {
|
||||
return this.value;
|
||||
}
|
||||
const options = {
|
||||
digits: this.props.digits,
|
||||
field: this.props.record.fields[this.props.name],
|
||||
};
|
||||
let res;
|
||||
if (this.props.humanReadable && !this.state.hasFocus) {
|
||||
res = formatFloat(this.value, {
|
||||
...options,
|
||||
humanReadable: true,
|
||||
decimals: this.props.decimals,
|
||||
});
|
||||
} else {
|
||||
res = formatFloat(this.value, { ...options, humanReadable: false });
|
||||
}
|
||||
|
||||
// For readonly fields, apply decimal styling via safe DOM manipulation
|
||||
if (this.props.readonly) {
|
||||
setTimeout(() => {
|
||||
try {
|
||||
const element = this.el?.querySelector('.o_field_float');
|
||||
if (element && !element.querySelector('.o_decimal') && document.contains(element)) {
|
||||
const text = element.textContent;
|
||||
const match = text?.match(/^(.*)(\d+)[.](\d+)(.*)$/);
|
||||
if (match && element.textContent === text) { // Double-check content hasn't changed
|
||||
const [, prefix, integerPart, decimalPart, suffix] = match;
|
||||
element.innerHTML = `${prefix}${integerPart}<span class="o_decimal">.${decimalPart}</span>${suffix}`;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Float field decimal styling error (non-critical):', error);
|
||||
}
|
||||
}, 150);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.warn('Field patching error (non-critical):', error);
|
||||
}
|
||||
|
||||
// Patch all numeric formatters
|
||||
['float', 'monetary', 'percentage', 'float_factor', 'float_time'].forEach(patchFormatter);
|
||||
// Safe Registry Patching for List Views
|
||||
try {
|
||||
const formattersRegistry = registry.category("formatters");
|
||||
|
||||
// Store original formatters
|
||||
const originalFormatters = new Map();
|
||||
|
||||
function safelyPatchFormatter(name) {
|
||||
if (formattersRegistry.contains(name) && !originalFormatters.has(name)) {
|
||||
try {
|
||||
const original = formattersRegistry.get(name);
|
||||
originalFormatters.set(name, original);
|
||||
|
||||
formattersRegistry.add(name, (...args) => {
|
||||
try {
|
||||
const res = original(...args);
|
||||
// Return the result as-is, let DOM observer handle styling
|
||||
// This avoids markup escaping issues and Owl conflicts
|
||||
return res;
|
||||
} catch (error) {
|
||||
console.warn(`Formatter ${name} error (non-critical):`, error);
|
||||
return original(...args); // Fallback to original
|
||||
}
|
||||
}, { force: true });
|
||||
|
||||
console.log('Web Decimal Style: Safely patched formatter', name);
|
||||
} catch (error) {
|
||||
console.warn(`Error patching formatter ${name} (non-critical):`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Patch all numeric formatters safely
|
||||
['float', 'monetary', 'percentage', 'float_factor', 'float_time'].forEach(safelyPatchFormatter);
|
||||
} catch (error) {
|
||||
console.warn('Registry patching error (non-critical):', error);
|
||||
}
|
||||
|
||||
console.log('Web Decimal Style formatters patch loaded successfully');
|
||||
|
||||
@ -5,47 +5,71 @@ import { ListRenderer } from "@web/views/list/list_renderer";
|
||||
|
||||
console.log('Loading inventory decimal patch...');
|
||||
|
||||
// Post-render DOM manipulation approach to avoid markup escaping issues
|
||||
// Safe DOM manipulation approach to avoid Owl lifecycle conflicts
|
||||
patch(ListRenderer.prototype, {
|
||||
async render() {
|
||||
const result = await super.render();
|
||||
|
||||
// After rendering, find and modify decimal numbers in the DOM
|
||||
setTimeout(() => {
|
||||
this.applyDecimalStyling();
|
||||
}, 100);
|
||||
// Schedule decimal styling after Owl completes its rendering cycle
|
||||
this.scheduleDecimalStyling();
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
scheduleDecimalStyling() {
|
||||
// Use requestAnimationFrame to ensure we run after Owl's DOM updates
|
||||
requestAnimationFrame(() => {
|
||||
try {
|
||||
this.applyDecimalStyling();
|
||||
} catch (error) {
|
||||
console.warn('Decimal styling error (non-critical):', error);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
applyDecimalStyling() {
|
||||
if (!this.el) return;
|
||||
if (!this.el || !document.contains(this.el)) return;
|
||||
|
||||
// Find all cells in the list table
|
||||
const cells = this.el.querySelectorAll('td, .o_data_cell');
|
||||
// Find all cells in the list table, but be more careful about DOM manipulation
|
||||
const cells = this.el.querySelectorAll('td:not([data-decimal-processed]), .o_data_cell:not([data-decimal-processed])');
|
||||
|
||||
cells.forEach(cell => {
|
||||
// Skip if already processed
|
||||
if (cell.querySelector('.o_decimal')) return;
|
||||
|
||||
const text = cell.textContent?.trim();
|
||||
if (!text) return;
|
||||
|
||||
// Check for decimal numbers (including currency)
|
||||
const decimalMatch = text.match(/^(.*)(\d+)([.,])(\d+)(.*)$/);
|
||||
if (decimalMatch) {
|
||||
const [, prefix, integerPart, decimalPoint, decimalPart, suffix] = decimalMatch;
|
||||
try {
|
||||
// Skip if cell is being updated by Owl
|
||||
if (cell.hasAttribute('data-owl-updating')) return;
|
||||
|
||||
console.log('Applying DOM decimal styling to:', text);
|
||||
// Mark as processed to avoid double processing
|
||||
cell.setAttribute('data-decimal-processed', 'true');
|
||||
|
||||
// Create new HTML structure
|
||||
const newHTML = `${prefix}${integerPart}<span class="o_decimal">${decimalPoint}${decimalPart}</span>${suffix}`;
|
||||
cell.innerHTML = newHTML;
|
||||
}
|
||||
// Handle whole numbers by adding .000
|
||||
else if (text.match(/^\d+$/)) {
|
||||
console.log('Adding decimals to whole number:', text);
|
||||
cell.innerHTML = `${text}<span class="o_decimal">.000</span>`;
|
||||
const text = cell.textContent?.trim();
|
||||
if (!text) return;
|
||||
|
||||
// Check for decimal numbers (including currency)
|
||||
const decimalMatch = text.match(/^(.*)(\d+)([.,])(\d+)(.*)$/);
|
||||
if (decimalMatch) {
|
||||
const [, prefix, integerPart, decimalPoint, decimalPart, suffix] = decimalMatch;
|
||||
|
||||
console.log('Applying safe DOM decimal styling to:', text);
|
||||
|
||||
// Create new HTML structure safely
|
||||
const newHTML = `${prefix}${integerPart}<span class="o_decimal">${decimalPoint}${decimalPart}</span>${suffix}`;
|
||||
|
||||
// Only update if the content hasn't changed
|
||||
if (cell.textContent.trim() === text) {
|
||||
cell.innerHTML = newHTML;
|
||||
}
|
||||
}
|
||||
// Handle whole numbers by adding .000
|
||||
else if (text.match(/^\d+$/) && text.length > 0) {
|
||||
console.log('Adding decimals to whole number safely:', text);
|
||||
|
||||
// Only update if the content hasn't changed
|
||||
if (cell.textContent.trim() === text) {
|
||||
cell.innerHTML = `${text}<span class="o_decimal">.000</span>`;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Error processing cell (non-critical):', error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user