From 38bcbd90c776833e32eed4ee6fad06e1a69fb491 Mon Sep 17 00:00:00 2001 From: killercow Date: Mon, 12 Jan 2026 13:55:00 +0000 Subject: [PATCH] Fix: photo upload on iOS and remove empty photo message from email - Photo placeholder now uses label element for better iOS support - Input gets unique ID for label association - Removed 'Geen foto's toegevoegd' message from email when no photos - Added null checks for preview element - Preview now explicitly set to display:block when photo loaded --- examples/inventory_corporate.html | 55 ++++++++++++++++++++++--------- examples/inventory_minimal.html | 55 ++++++++++++++++++++++--------- examples/inventory_modern.html | 55 ++++++++++++++++++++++--------- src/templates.py | 55 ++++++++++++++++++++++--------- 4 files changed, 160 insertions(+), 60 deletions(-) diff --git a/examples/inventory_corporate.html b/examples/inventory_corporate.html index 118be88..5496187 100644 --- a/examples/inventory_corporate.html +++ b/examples/inventory_corporate.html @@ -1120,27 +1120,54 @@ input.invalid, select.invalid, textarea.invalid { document.querySelectorAll('.photo-container').forEach(container => { const input = container.querySelector('input[type="file"]'); const preview = container.querySelector('.photo-preview'); - const fieldId = preview.id; + const placeholder = container.querySelector('.photo-placeholder'); + const fieldId = preview ? preview.id : null; const maxWidth = parseInt(container.dataset.maxWidth) || 1200; const maxHeight = parseInt(container.dataset.maxHeight) || 1200; + // Make the placeholder clickable with a label + if (placeholder && input) { + // Create a unique ID for the input + const inputId = 'photo-input-' + (fieldId || Math.random().toString(36).substr(2, 9)); + input.id = inputId; + + // Wrap placeholder in a label for better iOS support + const label = document.createElement('label'); + label.setAttribute('for', inputId); + label.style.cursor = 'pointer'; + label.style.display = 'block'; + label.innerHTML = placeholder.innerHTML; + placeholder.innerHTML = ''; + placeholder.appendChild(label); + } + + // Also handle click on container (for desktop) container.addEventListener('click', function(e) { - if (e.target.tagName !== 'BUTTON') { + // Don't trigger if clicking on buttons or if already handled by label + if (e.target.tagName === 'BUTTON' || e.target.tagName === 'LABEL' || e.target.closest('label')) { + return; + } + if (input) { input.click(); } }); - input.addEventListener('change', function(e) { - const file = e.target.files[0]; - if (!file) return; - - processImage(file, maxWidth, maxHeight, function(dataUrl) { - preview.src = dataUrl; - container.classList.add('has-photo'); - hasChanges = true; - saveData(); + if (input) { + input.addEventListener('change', function(e) { + const file = e.target.files[0]; + if (!file) return; + + processImage(file, maxWidth, maxHeight, function(dataUrl) { + if (preview) { + preview.src = dataUrl; + preview.style.display = 'block'; + } + container.classList.add('has-photo'); + hasChanges = true; + saveData(); + }); }); - }); + } }); } @@ -1284,7 +1311,7 @@ input.invalid, select.invalid, textarea.invalid { body += '\n========================\n'; - // Add photos section if there are any + // Add photos section only if there are photos if (photos.length > 0) { body += '\nFOTO BIJLAGEN (BASE64)\n'; body += '========================\n'; @@ -1299,8 +1326,6 @@ input.invalid, select.invalid, textarea.invalid { body += photo.data + '\n'; body += '<<< EINDE BASE64 <<<\n\n'; }); - } else { - body += 'Geen foto\'s toegevoegd aan dit formulier.\n'; } const mailto = 'mailto:' + encodeURIComponent(CONFIG.export.mailto.to) + diff --git a/examples/inventory_minimal.html b/examples/inventory_minimal.html index cb86e84..488ce79 100644 --- a/examples/inventory_minimal.html +++ b/examples/inventory_minimal.html @@ -1151,27 +1151,54 @@ select { document.querySelectorAll('.photo-container').forEach(container => { const input = container.querySelector('input[type="file"]'); const preview = container.querySelector('.photo-preview'); - const fieldId = preview.id; + const placeholder = container.querySelector('.photo-placeholder'); + const fieldId = preview ? preview.id : null; const maxWidth = parseInt(container.dataset.maxWidth) || 1200; const maxHeight = parseInt(container.dataset.maxHeight) || 1200; + // Make the placeholder clickable with a label + if (placeholder && input) { + // Create a unique ID for the input + const inputId = 'photo-input-' + (fieldId || Math.random().toString(36).substr(2, 9)); + input.id = inputId; + + // Wrap placeholder in a label for better iOS support + const label = document.createElement('label'); + label.setAttribute('for', inputId); + label.style.cursor = 'pointer'; + label.style.display = 'block'; + label.innerHTML = placeholder.innerHTML; + placeholder.innerHTML = ''; + placeholder.appendChild(label); + } + + // Also handle click on container (for desktop) container.addEventListener('click', function(e) { - if (e.target.tagName !== 'BUTTON') { + // Don't trigger if clicking on buttons or if already handled by label + if (e.target.tagName === 'BUTTON' || e.target.tagName === 'LABEL' || e.target.closest('label')) { + return; + } + if (input) { input.click(); } }); - input.addEventListener('change', function(e) { - const file = e.target.files[0]; - if (!file) return; - - processImage(file, maxWidth, maxHeight, function(dataUrl) { - preview.src = dataUrl; - container.classList.add('has-photo'); - hasChanges = true; - saveData(); + if (input) { + input.addEventListener('change', function(e) { + const file = e.target.files[0]; + if (!file) return; + + processImage(file, maxWidth, maxHeight, function(dataUrl) { + if (preview) { + preview.src = dataUrl; + preview.style.display = 'block'; + } + container.classList.add('has-photo'); + hasChanges = true; + saveData(); + }); }); - }); + } }); } @@ -1315,7 +1342,7 @@ select { body += '\n========================\n'; - // Add photos section if there are any + // Add photos section only if there are photos if (photos.length > 0) { body += '\nFOTO BIJLAGEN (BASE64)\n'; body += '========================\n'; @@ -1330,8 +1357,6 @@ select { body += photo.data + '\n'; body += '<<< EINDE BASE64 <<<\n\n'; }); - } else { - body += 'Geen foto\'s toegevoegd aan dit formulier.\n'; } const mailto = 'mailto:' + encodeURIComponent(CONFIG.export.mailto.to) + diff --git a/examples/inventory_modern.html b/examples/inventory_modern.html index c8127ff..c7ac6fb 100644 --- a/examples/inventory_modern.html +++ b/examples/inventory_modern.html @@ -1182,27 +1182,54 @@ input.invalid, select.invalid, textarea.invalid { document.querySelectorAll('.photo-container').forEach(container => { const input = container.querySelector('input[type="file"]'); const preview = container.querySelector('.photo-preview'); - const fieldId = preview.id; + const placeholder = container.querySelector('.photo-placeholder'); + const fieldId = preview ? preview.id : null; const maxWidth = parseInt(container.dataset.maxWidth) || 1200; const maxHeight = parseInt(container.dataset.maxHeight) || 1200; + // Make the placeholder clickable with a label + if (placeholder && input) { + // Create a unique ID for the input + const inputId = 'photo-input-' + (fieldId || Math.random().toString(36).substr(2, 9)); + input.id = inputId; + + // Wrap placeholder in a label for better iOS support + const label = document.createElement('label'); + label.setAttribute('for', inputId); + label.style.cursor = 'pointer'; + label.style.display = 'block'; + label.innerHTML = placeholder.innerHTML; + placeholder.innerHTML = ''; + placeholder.appendChild(label); + } + + // Also handle click on container (for desktop) container.addEventListener('click', function(e) { - if (e.target.tagName !== 'BUTTON') { + // Don't trigger if clicking on buttons or if already handled by label + if (e.target.tagName === 'BUTTON' || e.target.tagName === 'LABEL' || e.target.closest('label')) { + return; + } + if (input) { input.click(); } }); - input.addEventListener('change', function(e) { - const file = e.target.files[0]; - if (!file) return; - - processImage(file, maxWidth, maxHeight, function(dataUrl) { - preview.src = dataUrl; - container.classList.add('has-photo'); - hasChanges = true; - saveData(); + if (input) { + input.addEventListener('change', function(e) { + const file = e.target.files[0]; + if (!file) return; + + processImage(file, maxWidth, maxHeight, function(dataUrl) { + if (preview) { + preview.src = dataUrl; + preview.style.display = 'block'; + } + container.classList.add('has-photo'); + hasChanges = true; + saveData(); + }); }); - }); + } }); } @@ -1346,7 +1373,7 @@ input.invalid, select.invalid, textarea.invalid { body += '\n========================\n'; - // Add photos section if there are any + // Add photos section only if there are photos if (photos.length > 0) { body += '\nFOTO BIJLAGEN (BASE64)\n'; body += '========================\n'; @@ -1361,8 +1388,6 @@ input.invalid, select.invalid, textarea.invalid { body += photo.data + '\n'; body += '<<< EINDE BASE64 <<<\n\n'; }); - } else { - body += 'Geen foto\'s toegevoegd aan dit formulier.\n'; } const mailto = 'mailto:' + encodeURIComponent(CONFIG.export.mailto.to) + diff --git a/src/templates.py b/src/templates.py index adb3877..86bdc42 100644 --- a/src/templates.py +++ b/src/templates.py @@ -1386,27 +1386,54 @@ JAVASCRIPT = """ document.querySelectorAll('.photo-container').forEach(container => { const input = container.querySelector('input[type="file"]'); const preview = container.querySelector('.photo-preview'); - const fieldId = preview.id; + const placeholder = container.querySelector('.photo-placeholder'); + const fieldId = preview ? preview.id : null; const maxWidth = parseInt(container.dataset.maxWidth) || 1200; const maxHeight = parseInt(container.dataset.maxHeight) || 1200; + // Make the placeholder clickable with a label + if (placeholder && input) { + // Create a unique ID for the input + const inputId = 'photo-input-' + (fieldId || Math.random().toString(36).substr(2, 9)); + input.id = inputId; + + // Wrap placeholder in a label for better iOS support + const label = document.createElement('label'); + label.setAttribute('for', inputId); + label.style.cursor = 'pointer'; + label.style.display = 'block'; + label.innerHTML = placeholder.innerHTML; + placeholder.innerHTML = ''; + placeholder.appendChild(label); + } + + // Also handle click on container (for desktop) container.addEventListener('click', function(e) { - if (e.target.tagName !== 'BUTTON') { + // Don't trigger if clicking on buttons or if already handled by label + if (e.target.tagName === 'BUTTON' || e.target.tagName === 'LABEL' || e.target.closest('label')) { + return; + } + if (input) { input.click(); } }); - input.addEventListener('change', function(e) { - const file = e.target.files[0]; - if (!file) return; - - processImage(file, maxWidth, maxHeight, function(dataUrl) { - preview.src = dataUrl; - container.classList.add('has-photo'); - hasChanges = true; - saveData(); + if (input) { + input.addEventListener('change', function(e) { + const file = e.target.files[0]; + if (!file) return; + + processImage(file, maxWidth, maxHeight, function(dataUrl) { + if (preview) { + preview.src = dataUrl; + preview.style.display = 'block'; + } + container.classList.add('has-photo'); + hasChanges = true; + saveData(); + }); }); - }); + } }); } @@ -1550,7 +1577,7 @@ JAVASCRIPT = """ body += '\\n========================\\n'; - // Add photos section if there are any + // Add photos section only if there are photos if (photos.length > 0) { body += '\\nFOTO BIJLAGEN (BASE64)\\n'; body += '========================\\n'; @@ -1565,8 +1592,6 @@ JAVASCRIPT = """ body += photo.data + '\\n'; body += '<<< EINDE BASE64 <<<\\n\\n'; }); - } else { - body += 'Geen foto\\'s toegevoegd aan dit formulier.\\n'; } const mailto = 'mailto:' + encodeURIComponent(CONFIG.export.mailto.to) +