School ICT Self Study
πŸ“ View in Your Language:

Loading Voting System...

Admin Setup

1. **Upload VoteData.txt:** Please upload the configuration file with the required format **(School Name, Event, Limit, Password, Names)**.

How to Vote:

  1. Enter your **Full Name** in the box below.
  2. Click **Start Voting** to confirm you haven't voted yet.
  3. Select up to the maximum number of candidates specified.
  4. Click **Cast Your Votes**.
  5. If another person needs to vote on the same computer, click the **"Vote for Another Person"** button that appears.

Your name must be unique; you cannot vote more than once per event.

'); printWindow.document.close(); printWindow.focus(); // Delay print command to ensure content is loaded in the new window setTimeout(() => { printWindow.print(); }, 300); }// FUNCTION: Copies a formatted message and the link to the clipboard window.generateAndCopyShareMessage = function() { const shareLink = shareLinkOutput.value; const school = config.schoolName; const event = config.eventName;const shareMessage = `πŸ—³οΈ VOTE NOW! πŸ—³οΈ School: ${school} Event: ${event}Please click the link below or scan the QR code to cast your vote. You can only vote once. Link: ${shareLink}`;const tempTextarea = document.createElement('textarea'); tempTextarea.value = shareMessage; document.body.appendChild(tempTextarea);tempTextarea.select(); document.execCommand('copy');document.body.removeChild(tempTextarea); alert("Sharing message and link copied to clipboard! You can now paste it into WhatsApp or Email."); }// FUNCTION: Copies Event ID to Clipboard window.copyEventIdToClipboard = function() { if (eventID) { navigator.clipboard.writeText(eventID).then(() => { alert(`Event ID: ${eventID} copied to clipboard!`); }); } }// FUNCTION: Creates the sample data file for download window.downloadSampleFile = function() { const sampleContent = `# School/Firm Name Pilana Vidyarthodaya M V - Pilana, Galle # Event Name School Prefect Election 2026 # Voting Limit 5 # Results Password 123456 # Student Names (List one name per line Like Bellow) 1. Aravinda Ranasinghe (12A) 2. Dinusha Jayawardena (10B) 3. Tharaka Bandara(10B) 4. Malithi Seneviratne (10B) 5. Sithum Kariyawasam (12A) 6. Niroshan Gunaratne (9C) 7. Sanuda Rajapaksha (9C) 8. Oshini Perera 9. Pasindu Dissanayake(12A) 10. Nethmi Fernando (9C) 11. Heshan Karunaratne 12. Kaveesha Samarasinghe(12A) 13. Ranil Wickremesinghe 14. Amaya De Silva 15. Chathura Senanayake 16. Dilani Premathilaka 17. Gayan Fernando 18. Harshi Wijesinghe 19. Isuru Jayasinghe 20. Janani Kaldera `; const blob = new Blob([sampleContent], { type: 'text/plain' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'VoteData.txt'; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); }// FUNCTION: Resets the form for the next voter in the SAME event window.resetForNewVoter = function() { currentVoterId = null; nameInput.value = ''; form.style.display = 'none'; statusDiv.style.display = 'none'; newVoterActions.style.display = 'none'; nameArea.style.display = 'block'; document.querySelectorAll('.vote-checkbox').forEach(cb => cb.checked = false); startBtn.removeAttribute('disabled'); startBtn.textContent = 'Start Voting'; displayStatus(`Ready for the next voter for ${config.schoolName}.`, 'info'); statusDiv.style.display = 'block'; } // FUNCTION: Resets the entire page for a DIFFERENT event/school (Called by main title/admin button) window.resetForNewEvent = function() { eventID = null; config.students = {}; // Strips the ?id=... from the URL and forces a clean reload to Admin Setup mode window.location.href = window.location.origin + window.location.pathname; }// FUNCTION: Prompts admin for Event ID to view results later window.promptForEventId = function() { const inputId = prompt("Enter the unique Event ID (e.g., stpeterscollege_1761298371785) to load the event and view results:"); if (inputId) { // Redirects the current page to the URL with the new ID window.location.href = window.location.origin + window.location.pathname + `?id=${inputId}`; } }function generateCheckboxes(studentNames) { let html = ''; let studentData = {}; studentNames.forEach((name, index) => { const id = `StudentID_${String(index + 1).padStart(2, '0')}`; studentData[id] = name;html += `
`; }); config.students = studentData; studentCheckboxList.innerHTML = html; } function startVotingFlow() { const urlHasId = window.location.search.includes('id=');if (urlHasId) { // If ID is present, we are in the event. Show voter/admin controls. configAreaWrapper.style.display = 'block'; adminHeader.style.display = 'block'; nameArea.style.display = 'block'; // If share link is visible, hide the upload panel if (shareLinkArea.style.display === 'block') { configUploadArea.style.display = 'none'; // Generate QR code now that link is ready generateQRCode(shareLinkOutput.value); // Display raw ID for saving eventIdDisplay.textContent = eventID; } else { configUploadArea.style.display = 'none'; shareLinkArea.style.display = 'none'; } } else { // Initial load without ID: Admin Mode (Show setup) configAreaWrapper.style.display = 'block'; nameArea.style.display = 'none'; configUploadArea.style.display = 'block'; adminHeader.style.display = 'block'; shareLinkArea.style.display = 'none'; }// --- Update the Main UI Elements --- eventTitle.textContent = `Voting System`; // Main Title Fix schoolNameDisplay.textContent = `${config.schoolName} - ${config.eventName}`; // Prominent School/Event display in the voting card limitInfo.textContent = `Select up to ${config.voteLimit} students. You can only vote once with your name.`; resultsHeader.textContent = `Real-Time Results: ${config.schoolName} - ${config.eventName}`; generateCheckboxes(Object.values(config.students)); attachVoteLimitListener(config.voteLimit); setupRealTimeResults(config.students); // Ensure results are hidden on page load unless manually opened resultsDisplay.style.display = 'none'; } // ========================================================== // INITIAL LOAD CHECK // ========================================================== function loadInitialConfig() { // Toggle Collapsible Headers document.getElementById('admin-header').addEventListener('click', function() { adminContent.style.maxHeight = adminContent.style.maxHeight ? null : adminContent.scrollHeight + "px"; });document.getElementById('voter-guide-header').addEventListener('click', function() { voterContent.style.maxHeight = voterContent.style.maxHeight ? null : voterContent.scrollHeight + "px"; }); // Load event config from URL const urlParams = new URLSearchParams(window.location.search); const idFromURL = urlParams.get('id');if (idFromURL) { eventID = idFromURL; const configRef = ref(database, 'events/' + eventID + '/config'); get(configRef).then(snapshot => { if (snapshot.exists()) { const loadedConfig = snapshot.val(); config.schoolName = loadedConfig.schoolName; config.eventName = loadedConfig.eventName; config.voteLimit = loadedConfig.voteLimit; config.resultsPassword = loadedConfig.resultsPassword; config.students = loadedConfig.students; startVotingFlow(); } else { configStatus.textContent = `Error: Event ID "${eventID}" not found in database. Upload new config.`; configStatus.style.display = 'block'; configUploadArea.style.display = 'block'; shareLinkArea.style.display = 'none'; configAreaWrapper.style.display = 'block'; nameArea.style.display = 'none'; adminHeader.style.display = 'block'; } }).catch(error => { configStatus.textContent = `Error connecting to Firebase. Check console.`; configStatus.style.display = 'block'; configAreaWrapper.style.display = 'block'; nameArea.style.display = 'none'; adminHeader.style.display = 'block'; });} else { // Initial load without ID: Admin Mode (Show setup) configAreaWrapper.style.display = 'block'; nameArea.style.display = 'none'; configUploadArea.style.display = 'block'; adminHeader.style.display = 'block'; } }// ========================================================== // B. CONFIG FILE UPLOAD HANDLER // ==========================================================const handleFileUpload = (event) => { const file = event.target.files[0]; if (!file) return;configStatus.style.display = 'block'; configStatus.textContent = "Reading file...";const reader = new FileReader(); reader.onload = function(e) { try { const rawContent = e.target.result.trim().split('\n').map(line => line.trim()); const dataLines = rawContent.filter(line => !line.startsWith('#') && line.length > 0); if (dataLines.length < 5) { configStatus.textContent = "Error: File format invalid. Expected at least 5 data lines (School Name, Event, Limit, Password, and 1+ Name)."; event.target.value = ''; return; }const schoolName = dataLines[0]; const eventName = dataLines[1]; const voteLimit = parseInt(dataLines[2]); const resultsPassword = dataLines[3]; const studentNamesArray = dataLines.slice(4);// --- VALIDATION --- if (isNaN(voteLimit) || voteLimit <= 0) { configStatus.textContent = "Error: Voting Limit must be a positive number (1 or greater)."; event.target.value = ''; return; } if (studentNamesArray.length < 1) { configStatus.textContent = "Error: Student Names list is empty."; event.target.value = ''; return; } // --- END VALIDATION --- let studentsMap = {}; studentNamesArray.forEach((name, index) => { const id = `StudentID_${String(index + 1).padStart(2, '0')}`; studentsMap[id] = name; }); config.schoolName = schoolName; config.eventName = eventName; config.voteLimit = voteLimit; config.resultsPassword = resultsPassword; config.students = studentsMap; eventID = generateEventID(schoolName);// Save Configuration to Firebase const configRef = ref(database, 'events/' + eventID + '/config'); set(configRef, config).then(() => { configStatus.textContent = "Configuration saved successfully. Generating link...";const shareLink = `${window.location.origin}${window.location.pathname}?id=${eventID}`; shareLinkOutput.value = shareLink; // Show Share Link Area and hide upload configUploadArea.style.display = 'none'; shareLinkArea.style.display = 'block'; configStatus.style.display = 'none';window.history.pushState({}, config.eventName, shareLink);startVotingFlow(); }).catch(error => { configStatus.textContent = `Error saving config to Firebase: ${error.message}`; console.error(error); event.target.value = ''; });} catch (error) { configStatus.textContent = `Fatal Error reading file: ${error.message}`; console.error("File processing error:", error); event.target.value = ''; } }; configFileInput.addEventListener('change', handleFileUpload); configFileInput.value = ''; // Reset input value before reading reader.readAsText(file); };configFileInput.addEventListener('change', handleFileUpload);// ========================================================== // C. VOTER NAME INPUT AND VALIDATION // ========================================================== startBtn.addEventListener('click', async () => { const name = nameInput.value.trim(); if (name.length < 3) { alert('Please enter a valid name (at least 3 characters).'); return; }const key = createVoterKey(name); const singleVoterRef = ref(database, 'events/' + eventID + '/voters/' + key); startBtn.setAttribute('disabled', 'disabled'); startBtn.textContent = 'Checking...';try { const snapshot = await get(singleVoterRef);if (snapshot.exists()) { nameArea.style.display = 'none'; form.style.display = 'none'; displayStatus(`⚠️ **Sorry, ${name}.** A vote has already been recorded for this event under this name. You cannot vote again.`, 'warning'); newVoterActions.style.display = 'block'; } else { currentVoterId = key; nameArea.style.display = 'none'; form.style.display = 'block'; displayStatus('', 'info'); welcomeMessage.textContent = `Welcome, ${name}! Select up to ${config.voteLimit} students.`; submitBtn.removeAttribute('disabled'); } } catch (error) { console.error("Error checking voter status:", error); displayStatus('❌ **System Error:** Could not connect to the database. Check your **Firebase Rules** and network connection.', 'error'); } finally { startBtn.removeAttribute('disabled'); startBtn.textContent = 'Start Voting'; } });// ========================================================== // D. VOTE LIMIT ATTACHMENT FUNCTION // ========================================================== function attachVoteLimitListener(limit) { const checkboxes = document.querySelectorAll('.vote-checkbox'); checkboxes.forEach(checkbox => { checkbox.addEventListener('change', () => { const checkedBoxes = document.querySelectorAll('.vote-checkbox:checked'); if (checkedBoxes.length > limit) { checkbox.checked = false; displayStatus(`⚠️ **Limit Reached:** You can only vote for a maximum of ${limit} students.`, 'warning'); } else if (checkedBoxes.length > 0) { submitBtn.removeAttribute('disabled'); statusDiv.style.display = 'none'; } else { submitBtn.setAttribute('disabled', 'disabled'); statusDiv.style.display = 'none'; } }); }); }// ========================================================== // E. HANDLE FORM SUBMISSION // ========================================================== form.addEventListener('submit', function(e) { e.preventDefault(); if (!currentVoterId || !eventID) { displayStatus('Error: System not configured. Please reload.', 'error'); return; }submitBtn.setAttribute('disabled', 'disabled'); submitBtn.textContent = 'Submitting...'; statusDiv.style.display = 'none';const checkedBoxes = document.querySelectorAll('.vote-checkbox:checked'); if (checkedBoxes.length === 0 || checkedBoxes.length > config.voteLimit) { displayStatus(`Please select between 1 and ${config.voteLimit} students.`, 'warning'); submitBtn.removeAttribute('disabled'); submitBtn.textContent = 'Cast Your Votes'; return; }const votePromises = [];checkedBoxes.forEach(checkbox => { const studentId = checkbox.value; const voteRef = ref(database, 'events/' + eventID + '/votes/' + studentId);const promise = runTransaction(voteRef, (currentData) => { return (currentData || 0) + 1; }); votePromises.push(promise); });const voterRecordRef = ref(database, 'events/' + eventID + '/voters/' + currentVoterId); const recordVoterPromise = set(voterRecordRef, { name: nameInput.value.trim(), timestamp: new Date().toISOString(), votes: checkedBoxes.length }); votePromises.push(recordVoterPromise);Promise.all(votePromises) .then(() => { form.style.display = 'none'; displayStatus('πŸŽ‰ **Success!** Your votes have been recorded.', 'success'); newVoterActions.style.display = 'block'; }) .catch(error => { console.error("Voting failed: ", error); displayStatus('❌ **Error:** Could not record vote. Please try again.', 'error'); submitBtn.removeAttribute('disabled'); submitBtn.textContent = 'Cast Your Votes'; }); });// ========================================================== // F. DISPLAY REAL-TIME RESULTS (ADMIN PASSWORD) // ========================================================== showResultsBtn.addEventListener('click', () => { const password = prompt("Enter Results Password to view:"); if (password === config.resultsPassword) { resultsDisplay.style.display = 'block'; } else { alert("Incorrect password."); } });function setupRealTimeResults(studentNames) { const votesRef = ref(database, 'events/' + eventID + '/votes'); onValue(votesRef, (snapshot) => { const votesData = snapshot.val() || {}; let resultsHtml = ''; const studentResults = Object.keys(studentNames).map(id => { const voteCount = votesData[id] || 0; return { id: id, name: studentNames[id], votes: voteCount }; });studentResults.sort((a, b) => b.votes - a.votes);studentResults.forEach((student) => { const rowStyle = student.votes > 0 ? 'background-color: #f0f0f0;' : 'background-color: white;';resultsHtml += `${student.name}${student.votes} `; });resultsBody.innerHTML = resultsHtml; }); } // FUNCTION: Allows Admin to load a previously shared Event ID window.promptForEventId = function() { const inputId = prompt("Enter the unique Event ID (e.g., stpeterscollege_1761298371785) to load the event and view results:"); if (inputId) { // Redirects the current page to the URL with the new ID window.location.href = window.location.origin + window.location.pathname + `?id=${inputId}`; } }// ========================================================== // G. RUN INITIAL CHECK // ========================================================== document.addEventListener('DOMContentLoaded', () => { loadInitialConfig(); });
Select Language:
Select Font Size: