`;
gradeEntries.appendChild(newEntry);
// Show remove buttons if there's more than one entry
updateRemoveButtons();
});
// Add new weighted grade entry
addWeightedEntryBtn.addEventListener('click', function() {
const newEntry = document.createElement('div');
newEntry.className = 'weighted-entry';
newEntry.innerHTML = `
`;
weightedEntries.appendChild(newEntry);
// Show remove buttons if there's more than one entry
updateRemoveButtons();
});
// Remove grade entry
document.addEventListener('click', function(e) {
if (e.target.classList.contains('remove-entry-btn')) {
e.target.closest('.grade-entry, .weighted-entry').remove();
updateRemoveButtons();
}
});
// Update remove buttons visibility
function updateRemoveButtons() {
const gradeEntryButtons = document.querySelectorAll('#grade-entries .remove-entry-btn');
const weightedEntryButtons = document.querySelectorAll('#weighted-entries .remove-entry-btn');
gradeEntryButtons.forEach(btn => {
btn.style.display = gradeEntries.children.length > 1 ? 'flex' : 'none';
});
weightedEntryButtons.forEach(btn => {
btn.style.display = weightedEntries.children.length > 1 ? 'flex' : 'none';
});
}
// Calculate grade
calculateBtn.addEventListener('click', function() {
const type = calculationType.value;
let isValid = true;
// Validate inputs based on calculation type
if (type === 'simple') {
isValid = validateSimpleGrade();
} else if (type === 'multiple') {
isValid = validateMultipleGrades();
} else if (type === 'weighted') {
isValid = validateWeightedGrades();
}
if (!isValid) return;
// Calculate based on type
let percentage, details;
if (type === 'simple') {
const result = calculateSimpleGrade();
percentage = result.percentage;
details = result.details;
} else if (type === 'multiple') {
const result = calculateMultipleGrades();
percentage = result.percentage;
details = result.details;
} else if (type === 'weighted') {
const result = calculateWeightedGrades();
percentage = result.percentage;
details = result.details;
}
// Display results
displayResults(percentage, details);
});
// Reset form
resetBtn.addEventListener('click', function() {
// Clear all inputs
document.querySelectorAll('.form-input').forEach(input => {
input.value = '';
input.classList.remove('error');
});
// Clear error messages
document.querySelectorAll('.error-message').forEach(msg => {
msg.style.display = 'none';
});
// Reset to simple grade form with one entry
calculationType.value = 'simple';
simpleGradeForm.classList.add('active');
multipleGradeForm.classList.remove('active');
weightedGradeForm.classList.remove('active');
// Remove additional entries
while (gradeEntries.children.length > 1) {
gradeEntries.lastChild.remove();
}
while (weightedEntries.children.length > 1) {
weightedEntries.lastChild.remove();
}
updateRemoveButtons();
// Hide results
resultsSection.style.display = 'none';
});
// Validation functions
function validateSimpleGrade() {
const pointsEarned = document.getElementById('points-earned').value;
const totalPoints = document.getElementById('total-points').value;
let isValid = true;
if (!pointsEarned || isNaN(pointsEarned) {
showError(document.getElementById('points-earned'), 'Please enter a valid points earned value');
isValid = false;
} else if (parseFloat(pointsEarned) < 0) {
showError(document.getElementById('points-earned'), 'Points cannot be negative');
isValid = false;
}
if (!totalPoints || isNaN(totalPoints)) {
showError(document.getElementById('total-points'), 'Please enter a valid total points value');
isValid = false;
} else if (parseFloat(totalPoints) <= 0) {
showError(document.getElementById('total-points'), 'Total points must be greater than 0');
isValid = false;
}
if (isValid && parseFloat(pointsEarned) > parseFloat(totalPoints)) {
showError(document.getElementById('points-earned'), 'Points earned cannot exceed total points');
isValid = false;
}
return isValid;
}
function validateMultipleGrades() {
const entries = document.querySelectorAll('#grade-entries .grade-entry');
let isValid = true;
entries.forEach((entry, index) => {
const earned = entry.querySelector('.earned-points').value;
const total = entry.querySelector('.total-points').value;
if (!earned || isNaN(earned)) {
showError(entry.querySelector('.earned-points'), `Please enter valid points for entry ${index + 1}`);
isValid = false;
} else if (parseFloat(earned) < 0) {
showError(entry.querySelector('.earned-points'), `Points cannot be negative in entry ${index + 1}`);
isValid = false;
}
if (!total || isNaN(total)) {
showError(entry.querySelector('.total-points'), `Please enter valid total points for entry ${index + 1}`);
isValid = false;
} else if (parseFloat(total) <= 0) {
showError(entry.querySelector('.total-points'), `Total points must be greater than 0 in entry ${index + 1}`);
isValid = false;
}
if (isValid && parseFloat(earned) > parseFloat(total)) {
showError(entry.querySelector('.earned-points'), `Points earned cannot exceed total points in entry ${index + 1}`);
isValid = false;
}
});
return isValid;
}
function validateWeightedGrades() {
const entries = document.querySelectorAll('#weighted-entries .weighted-entry');
let isValid = true;
let totalWeight = 0;
entries.forEach((entry, index) => {
const earned = entry.querySelector('.earned-points').value;
const total = entry.querySelector('.total-points').value;
const weight = entry.querySelector('.weight-value').value;
if (!earned || isNaN(earned)) {
showError(entry.querySelector('.earned-points'), `Please enter valid points for entry ${index + 1}`);
isValid = false;
} else if (parseFloat(earned) < 0) {
showError(entry.querySelector('.earned-points'), `Points cannot be negative in entry ${index + 1}`);
isValid = false;
}
if (!total || isNaN(total)) {
showError(entry.querySelector('.total-points'), `Please enter valid total points for entry ${index + 1}`);
isValid = false;
} else if (parseFloat(total) <= 0) {
showError(entry.querySelector('.total-points'), `Total points must be greater than 0 in entry ${index + 1}`);
isValid = false;
}
if (!weight || isNaN(weight)) {
showError(entry.querySelector('.weight-value'), `Please enter valid weight for entry ${index + 1}`);
isValid = false;
} else if (parseFloat(weight) <= 0) {
showError(entry.querySelector('.weight-value'), `Weight must be greater than 0 in entry ${index + 1}`);
isValid = false;
}
if (isValid && parseFloat(earned) > parseFloat(total)) {
showError(entry.querySelector('.earned-points'), `Points earned cannot exceed total points in entry ${index + 1}`);
isValid = false;
}
if (isValid) {
totalWeight += parseFloat(weight);
}
});
if (isValid && Math.abs(totalWeight - 100) > 0.1) {
alert('Total weight must equal 100%. Current total: ' + totalWeight.toFixed(1) + '%');
isValid = false;
}
return isValid;
}
function showError(input, message) {
input.classList.add('error');
let errorMsg = input.nextElementSibling;
if (!errorMsg || !errorMsg.classList.contains('error-message')) {
errorMsg = document.createElement('div');
errorMsg.className = 'error-message';
input.parentNode.insertBefore(errorMsg, input.nextSibling);
}
errorMsg.textContent = message;
errorMsg.style.display = 'block';
}
// Calculation functions
function calculateSimpleGrade() {
const earned = parseFloat(document.getElementById('points-earned').value);
const total = parseFloat(document.getElementById('total-points').value);
const percentage = (earned / total) * 100;
const details = [{
name: 'Single Grade',
earned: earned,
total: total,
percentage: percentage,
weight: 100
}];
return { percentage, details };
}
function calculateMultipleGrades() {
const entries = document.querySelectorAll('#grade-entries .grade-entry');
let totalEarned = 0;
let totalPossible = 0;
const details = [];
entries.forEach(entry => {
const earned = parseFloat(entry.querySelector('.earned-points').value);
const total = parseFloat(entry.querySelector('.total-points').value);
const percentage = (earned / total) * 100;
totalEarned += earned;
totalPossible += total;
details.push({
name: 'Grade Entry ' + (details.length + 1),
earned: earned,
total: total,
percentage: percentage,
weight: (total / totalPossible) * 100
});
});
const percentage = (totalEarned / totalPossible) * 100;
return { percentage, details };
}
function calculateWeightedGrades() {
const entries = document.querySelectorAll('#weighted-entries .weighted-entry');
let weightedSum = 0;
const details = [];
entries.forEach(entry => {
const earned = parseFloat(entry.querySelector('.earned-points').value);
const total = parseFloat(entry.querySelector('.total-points').value);
const weight = parseFloat(entry.querySelector('.weight-value').value);
const percentage = (earned / total) * 100;
const weightedValue = percentage * (weight / 100);
weightedSum += weightedValue;
details.push({
name: 'Weighted Entry ' + (details.length + 1),
earned: earned,
total: total,
percentage: percentage,
weight: weight,
weightedValue: weightedValue
});
});
const percentage = weightedSum;
return { percentage, details };
}
// Display results
function displayResults(percentage, details) {
// Round to 2 decimal places
percentage = Math.round(percentage * 100) / 100;
// Update percentage display
finalPercentage.textContent = percentage + '%';
// Determine letter grade
const grade = getLetterGrade(percentage);
letterGrade.textContent = grade.letter;
letterGrade.className = 'letter-grade ' + grade.className;
// Update progress bar
gradeProgressBar.style.width = percentage + '%';
gradeProgressBar.style.backgroundColor = grade.color;
// Generate detailed results table
let tableHTML = '
Name | Earned | Total | % | ';
if (calculationType.value === 'weighted') {
tableHTML += 'Weight | Weighted % | ';
}
tableHTML += '
';
details.forEach(detail => {
tableHTML += `
${detail.name} |
${detail.earned} |
${detail.total} |
${Math.round(detail.percentage * 100) / 100}% | `;
if (calculationType.value === 'weighted') {
tableHTML += `${detail.weight}% |
${Math.round(detail.weightedValue * 100) / 100}% | `;
}
tableHTML += '
';
});
tableHTML += '
';
detailedResults.innerHTML = tableHTML;
// Show results section
resultsSection.style.display = 'block';
}
function getLetterGrade(percentage) {
if (percentage >= 90) {
return { letter: 'A', color: '#4CAF50', className: 'grade-a' };
} else if (percentage >= 80) {
return { letter: 'B', color: '#00A8A3', className: 'grade-b' };
} else if (percentage >= 70) {
return { letter: 'C', color: '#FF9800', className: 'grade-c' };
} else if (percentage >= 60) {
return { letter: 'D', color: '#F44336', className: 'grade-d' };
} else {
return { letter: 'F', color: '#D32F2F', className: 'grade-f' };
}
}
});