Initial commit — Rosary Presenter App
Full source for loveandrosary.com: slide-based Rosary/novena/Divine Mercy Chaplet presentation tool with multi-user roles, SVG bead ring, audio uploads, donate strip, and public session profiles. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,186 @@
|
||||
/**
|
||||
* assets/js/setup.js
|
||||
* Setup form interactivity: conditional field visibility + form submission.
|
||||
*/
|
||||
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
const form = document.getElementById('session-form');
|
||||
const occasionSel = document.getElementById('occasion');
|
||||
const photoInput = document.getElementById('photo');
|
||||
const uploadStatus = document.getElementById('upload-status');
|
||||
const submitBtn = document.getElementById('submit-btn');
|
||||
const msgBox = document.getElementById('form-message');
|
||||
const nameHelp = document.getElementById('name-help');
|
||||
|
||||
// Are we editing an existing session?
|
||||
const isEditMode = !!form.querySelector('[name="id"]');
|
||||
|
||||
// Fields to show/hide by occasion (field IDs without the "field-" prefix)
|
||||
const OCCASION_FIELDS = {
|
||||
novena_deceased: ['novena_day', 'novena_mystery_mode', 'subject_name', 'subject_pronoun', 'subject_dates'],
|
||||
divine_mercy_novena:['novena_day'],
|
||||
memorial: ['subject_name', 'subject_pronoun', 'subject_dates'],
|
||||
general_rosary: [],
|
||||
};
|
||||
|
||||
const mysteryStandardWrap = document.getElementById('field-mystery_set_standard');
|
||||
|
||||
function toggleFields() {
|
||||
const occasion = occasionSel.value;
|
||||
const show = OCCASION_FIELDS[occasion] || [];
|
||||
const isNovenaDeceased = (occasion === 'novena_deceased');
|
||||
const isDivineMercy = (occasion === 'divine_mercy_novena');
|
||||
const isAnyNovena = isNovenaDeceased || isDivineMercy;
|
||||
|
||||
// Standard mystery dropdown: hide for both novena types
|
||||
if (mysteryStandardWrap) {
|
||||
mysteryStandardWrap.style.display = isAnyNovena ? 'none' : '';
|
||||
const sel = document.getElementById('mystery_set');
|
||||
if (sel) {
|
||||
if (isAnyNovena) {
|
||||
sel.removeAttribute('required');
|
||||
if (!sel.value) sel.value = 'sorrowful';
|
||||
} else {
|
||||
sel.setAttribute('required', '');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Show/hide conditional fields
|
||||
document.querySelectorAll('.conditional-field').forEach(el => {
|
||||
const fieldName = el.id.replace('field-', '');
|
||||
// novena_mystery_mode only shows for novena_deceased, not divine_mercy_novena
|
||||
if (fieldName === 'novena_mystery_mode' && !isNovenaDeceased) {
|
||||
el.style.display = 'none';
|
||||
el.querySelectorAll('input, select').forEach(input => input.removeAttribute('required'));
|
||||
return;
|
||||
}
|
||||
const visible = show.includes(fieldName);
|
||||
el.style.display = visible ? '' : 'none';
|
||||
el.querySelectorAll('input, select').forEach(input => {
|
||||
if (!visible) input.removeAttribute('required');
|
||||
});
|
||||
});
|
||||
|
||||
// Update submit button label
|
||||
if (submitBtn) {
|
||||
if (isNovenaDeceased) {
|
||||
submitBtn.textContent = submitBtn.dataset.labelNovenaDeceased;
|
||||
} else if (isDivineMercy) {
|
||||
submitBtn.textContent = submitBtn.dataset.labelDivineMercy;
|
||||
} else {
|
||||
submitBtn.textContent = submitBtn.dataset.labelDefault;
|
||||
}
|
||||
}
|
||||
|
||||
// Update session name help text
|
||||
if (nameHelp) {
|
||||
nameHelp.style.display = isAnyNovena && !isEditMode ? '' : 'none';
|
||||
}
|
||||
}
|
||||
|
||||
occasionSel.addEventListener('change', toggleFields);
|
||||
toggleFields(); // run on page load (handles edit mode pre-selection)
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Photo upload on file selection
|
||||
// ------------------------------------------------------------------
|
||||
var photoUploading = false;
|
||||
|
||||
if (photoInput) {
|
||||
photoInput.addEventListener('change', async function () {
|
||||
if (!this.files || !this.files[0]) return;
|
||||
|
||||
const file = this.files[0];
|
||||
uploadStatus.style.display = '';
|
||||
uploadStatus.className = 'upload-status uploading';
|
||||
uploadStatus.textContent = 'Uploading\u2026';
|
||||
|
||||
// Disable submit while upload is in progress
|
||||
photoUploading = true;
|
||||
if (submitBtn) submitBtn.disabled = true;
|
||||
|
||||
const fd = new FormData();
|
||||
fd.append('photo', file);
|
||||
|
||||
try {
|
||||
const res = await fetch(BASE_URL + '/api/upload_photo.php', { method: 'POST', body: fd });
|
||||
const data = await res.json();
|
||||
|
||||
if (data.error) {
|
||||
uploadStatus.className = 'upload-status error';
|
||||
uploadStatus.textContent = 'Upload failed: ' + data.error;
|
||||
} else {
|
||||
document.getElementById('photo_path').value = data.path;
|
||||
// Clear the file input so the file is not re-sent with the main form
|
||||
photoInput.value = '';
|
||||
uploadStatus.className = 'upload-status success';
|
||||
uploadStatus.textContent = 'Photo uploaded successfully.';
|
||||
}
|
||||
} catch (err) {
|
||||
uploadStatus.className = 'upload-status error';
|
||||
uploadStatus.textContent = 'Network error during upload.';
|
||||
} finally {
|
||||
photoUploading = false;
|
||||
if (submitBtn) submitBtn.disabled = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Form submission via fetch
|
||||
// ------------------------------------------------------------------
|
||||
form.addEventListener('submit', async function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
if (!form.checkValidity()) {
|
||||
form.reportValidity();
|
||||
return;
|
||||
}
|
||||
|
||||
submitBtn.disabled = true;
|
||||
submitBtn.textContent = 'Saving\u2026';
|
||||
showMessage('', '');
|
||||
|
||||
const fd = new FormData(form);
|
||||
|
||||
try {
|
||||
const res = await fetch(BASE_URL + '/api/save_session.php', { method: 'POST', body: fd });
|
||||
const data = await res.json();
|
||||
|
||||
if (data.error) {
|
||||
showMessage('error', data.error);
|
||||
submitBtn.disabled = false;
|
||||
const occ = occasionSel.value;
|
||||
submitBtn.textContent = isEditMode
|
||||
? submitBtn.dataset.labelDefault
|
||||
: (occ === 'novena_deceased' ? submitBtn.dataset.labelNovenaDeceased
|
||||
: occ === 'divine_mercy_novena' ? submitBtn.dataset.labelDivineMercy
|
||||
: submitBtn.dataset.labelDefault);
|
||||
} else if (data.novena) {
|
||||
// All 9 days created — go to admin dashboard
|
||||
window.location.href = BASE_URL + '/admin/?novena_created=' + data.ids.length;
|
||||
} else {
|
||||
// Single session — go to presentation
|
||||
window.location.href = BASE_URL + '/present.php?id=' + data.id;
|
||||
}
|
||||
} catch (err) {
|
||||
showMessage('error', 'Network error. Please try again.');
|
||||
submitBtn.disabled = false;
|
||||
submitBtn.textContent = submitBtn.dataset.labelDefault;
|
||||
}
|
||||
});
|
||||
|
||||
function showMessage(type, text) {
|
||||
if (!text) {
|
||||
msgBox.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
msgBox.style.display = '';
|
||||
msgBox.className = 'alert alert-' + type;
|
||||
msgBox.textContent = text;
|
||||
}
|
||||
|
||||
})();
|
||||
Reference in New Issue
Block a user