From 3708869870736ea1c20d3aa61f655c0f757a0a93 Mon Sep 17 00:00:00 2001 From: Suherdy Yacob Date: Mon, 16 Mar 2026 16:12:54 +0700 Subject: [PATCH] refactor: Stabilize image field patching in Odoo Sign to ensure proper inheritance and improve lazy loading and upload handling. --- static/src/js/sign_image_upload.js | 85 +++++++++++------------------- 1 file changed, 32 insertions(+), 53 deletions(-) diff --git a/static/src/js/sign_image_upload.js b/static/src/js/sign_image_upload.js index 2eaeb1d..c6be760 100755 --- a/static/src/js/sign_image_upload.js +++ b/static/src/js/sign_image_upload.js @@ -4,20 +4,20 @@ import { patch } from "@web/core/utils/patch"; import { PDFIframe } from "@sign/components/sign_request/PDF_iframe"; import { SignablePDFIframe } from "@sign/components/sign_request/signable_PDF_iframe"; -console.log("Sign Image Field: Loading custom patches..."); +console.log("Sign Image Field: Initializing stabilized patches..."); /** - * Robust lazy loading trigger for image sign items. + * Shared utility for lazy loading image fields. */ const triggerLazyLoad = (el) => { if (!el) return; const lazyImg = el.querySelector('.o_sign_image_lazy'); if (lazyImg && (lazyImg.dataset.src || lazyImg.src)) { const targetSrc = lazyImg.dataset.src || lazyImg.src; - // Check if it's currently showing the transparent placeholder const currentSrc = lazyImg.getAttribute('src'); + + // Only trigger if we haven't loaded the real image yet if (!currentSrc || currentSrc.startsWith('data:image/gif') || currentSrc === '') { - console.log("Sign Image Field: Loading image data for", el); lazyImg.src = targetSrc; lazyImg.onload = () => { lazyImg.classList.remove('o_sign_image_loading'); @@ -25,7 +25,7 @@ const triggerLazyLoad = (el) => { lazyImg.style.opacity = 1; }; } else { - // Already has a real src, just ensure loading classes are gone + // Cleanup in case of re-rendering lazyImg.classList.remove('o_sign_image_loading'); el.classList.remove('o_sign_image_loading_container'); lazyImg.style.opacity = 1; @@ -34,13 +34,13 @@ const triggerLazyLoad = (el) => { }; /** - * Setup IntersectionObserver for an element. + * Shared logic for setting up an observer on any image field. */ -const setupLazyObserver = (iframeInstance, el) => { +const setupLazyObserver = (el) => { if (!el) return; - // Safety fallback: Always load after 2 seconds regardless of visibility - // This handles cases where IntersectionObserver fails in iframes or remote bundling issues + // Immediate check + safety fallback (2 seconds) + // This ensures images load even if IntersectionObserver is throttled setTimeout(() => triggerLazyLoad(el), 2000); if (window.IntersectionObserver) { @@ -51,62 +51,48 @@ const setupLazyObserver = (iframeInstance, el) => { observer.disconnect(); } }); - }, { - rootMargin: '200px' - }); + }, { rootMargin: '200px' }); observer.observe(el); + } else { + triggerLazyLoad(el); } }; -// 1. Patch PDFIframe (Base class) -// We avoid calling 'super' here because PDFIframe is a base class and has no parent. -// Odoo 19's patch system might misbehave with 'super' in base class prototype patches. -const pdfIframePatch = { +// 1. Patch PDFIframe (Used for observers and read-only views) +patch(PDFIframe.prototype, { enableCustom(signItem) { - console.log("Sign Image Field: PDFIframe.enableCustom triggered for", signItem.data.type); - if (signItem && signItem.data && signItem.data.type === 'image') { - setupLazyObserver(this, signItem.el); + // ALWAYS call the base implementation FIRST to preserve Odoo behavior + super.enableCustom(...arguments); + + // OUR LOGIC: Setup lazy loading if it's an image + if (signItem && signItem.data && signItem.data.type === 'image' && signItem.el) { + setupLazyObserver(signItem.el); } - // Instead of super.enableCustom(signItem), we check if there's an ORIGINAL to call - // Normally for PDFIframe it's an empty function. } -}; +}); -// Use a safe patch wrapper to ensure we don't break the prototype -try { - patch(PDFIframe.prototype, pdfIframePatch); -} catch (e) { - console.error("Sign Image Field: Failed to patch PDFIframe.prototype", e); -} - -// 2. Patch SignablePDFIframe (Subclass) -const signablePDFIframePatch = { +// 2. Patch SignablePDFIframe (Used for active signers) +patch(SignablePDFIframe.prototype, { enableCustom(signItem) { - console.log("Sign Image Field: SignablePDFIframe.enableCustom triggered for", signItem.data.type); - - // Setup observer BEFORE role checks so it works for observers/read-only too - if (signItem && signItem.data && signItem.data.type === 'image') { - setupLazyObserver(this, signItem.el); - } - - // Call base implementation - super.enableCustom(signItem); + // ALWAYS call the base implementation FIRST to preserve text/signature population + super.enableCustom(...arguments); + // OUR LOGIC: Setup lazy loading and upload support if (signItem && signItem.data && signItem.data.type === 'image' && signItem.el) { const el = signItem.el; const data = signItem.data; - // Re-apply responsible check for upload logic only + // Shared lazy loading observer + setupLazyObserver(el); + + // Only attach upload listeners if this person is responsible and not readonly if (this.readonly || (data.responsible > 0 && data.responsible !== this.currentRole)) { return; } el.setAttribute('data-type', 'image'); - - // Upload implementation const input = el.querySelector('.o_sign_image_upload_input'); if (input) { - // Remove any previous listeners if possible (though patch should handle it) el.addEventListener('click', (e) => { if (e.target !== input) { input.click(); @@ -134,7 +120,6 @@ const signablePDFIframePatch = { width *= MAX_SIZE / height; height = MAX_SIZE; } - canvas.width = width; canvas.height = height; const ctx = canvas.getContext('2d'); @@ -174,12 +159,6 @@ const signablePDFIframePatch = { if (item.data.type === 'image') { return item.el.dataset.value || item.data.value || false; } - return super.getSignatureValueFromElement(item); + return super.getSignatureValueFromElement(...arguments); } -}; - -try { - patch(SignablePDFIframe.prototype, signablePDFIframePatch); -} catch (e) { - console.error("Sign Image Field: Failed to patch SignablePDFIframe.prototype", e); -} +});