(()=>{ const cont = `

About

Check out the README.md file to learn more about the configuration structure.

Usage

  1. Either configure the calculator using the text box below or load one from the existing JSON files to generate one.
  2. Click Generate.
  3. Enter the input values.

Configuration

Load config from file

`; document.querySelector('.grade-calc').innerHTML = cont; })(); // eh function generate() { let calcSection = document.querySelector(".calculator-container"); calcSection.innerHTML = ""; try { conf = JSON.parse(document.getElementById("json").value); } catch (e) { console.log(e); calcSection.innerHTML = e; return; } let cb = (out) => { for (let o of out) { calcSection.appendChild(o); } } gc = new __GradeCalc(conf, cb); calcSection.appendChild(gc.elemTotal); } function loadConfig(filename) { var client = new XMLHttpRequest(); client.open('GET', filename); client.onreadystatechange = function () { document.getElementById("json").value = (client.responseText); } client.send(); } class __GradeCalc { maxscore = 0; sections = []; inputSection = []; outputSection = []; fields = []; grades = []; ugrades = []; both = false; totalOutput = null; constructor(config, outCallback) { this.totalOutput = document.createElement("div"); let dConfig = JSON.parse(JSON.stringify(config)); // dirty clone let sanConfig = []; for (let conf of dConfig) { if (conf.percentage === undefined || conf.name === undefined) continue; if (conf.title === undefined) conf.title = conf.name[0].toUpperCase() + conf.name.slice(1); sanConfig.push(conf); } this.config = sanConfig; for (let [i, conf] of this.config.entries()) { this.maxscore += conf.percentage; this.inputSection[i] = []; this.outputSection[i] = document.createElement("div"); if (conf.bothMethods) { this.both = true; } this.sections[i] = (this.createSection(i)); } for (let [k, v] of this.fields.entries()) { for (let field of v) { this.addInputEventListener(k, field); } } outCallback(this.sections); } createSection(id) { let conf = this.config[id]; var section = document.createElement("div"); section.classList.add(conf.name); var heading = document.createElement("h2"); heading.innerHTML = `${conf.title} (${conf.percentage}%)`; section.appendChild(heading); if (conf.info !== undefined) section.appendChild(document.createTextNode(conf.info)); this.fields[id] = []; if (conf.points !== undefined) { for (var i = 0; i < conf.points.length; i++) { section.appendChild(this.createInputSection(id, i)); } } else { section.appendChild(this.createInputSection(id, 0, true)); } section.appendChild(this.outputSection[id]); return section; } createInputSection(sectId, inputId, soleInput = false) { let conf = this.config[sectId]; let inputSection = document.createElement("div"); inputSection.classList.add("input-section"); let label = document.createElement("label"); if (soleInput) label.innerHTML = `${conf.title} Score: `; else label.innerHTML = `${conf.title} ${inputId + 1} Score: `; let field = document.createElement("input"); field.classList.add(`input`); field.classList.add(`${conf.name}-score`); this.fields[sectId][inputId] = field; let suffix = (soleInput) ? "%" : ` / ${conf.points[inputId]} pts`; inputSection.appendChild(label); inputSection.appendChild(field); inputSection.appendChild(document.createTextNode(suffix)); this.inputSection[sectId][inputId] = inputSection; return inputSection; } addInputEventListener(id, field, event = "keyup") { let conf = this.config[id]; field.addEventListener(event, () => { if (conf.output !== undefined && conf.output) this.showSectionGrade(id); this.showTotalGrade(); }); } calculateSectionGrade(id, unweighted = false) { let conf = this.config[id]; let fields = this.fields[id]; if (fields === undefined) return; if (conf.points === undefined) { return parseFloat(fields[0].value); } let total = 0; if (unweighted) { let counter = 0; for (let [i, field] of fields.entries()) { let val = parseFloat(field.value); if (isNaN(val)) continue; total += val / conf.points[i]; counter++; } return (total / counter * 100); } total = fields.reduce((acc, cur) => { let c = parseFloat(cur.value); if (isNaN(c)) return acc; return acc + parseFloat(c); }, 0); let max_total = 0; for (let [i, field] of conf.points.entries()) { if (isNaN(parseFloat(fields[i].value))) continue; max_total += field; } return (total / max_total * 100); } showSectionGrade(id) { let conf = this.config[id]; let grade = this.calculateSectionGrade(id); let ugrade = this.calculateSectionGrade(id, true); this.grades[id] = grade * parseFloat(conf.percentage) / 100; this.ugrades[id] = ugrade * parseFloat(conf.percentage) / 100; grade = !isNaN(grade) ? grade.toFixed(2) : "..."; ugrade = !isNaN(ugrade) ? ugrade.toFixed(2) : "..."; if (conf.bothMethods) { this.outputSection[id].innerHTML = `Score (weighted): ${grade}%
Score (unweighted): ${ugrade}%`; return; } this.outputSection[id].innerHTML = `Score: ${grade}`; } showTotalGrade() { for (let [k, conf] of this.config.entries()) { if (!conf.output) { this.grades[k] = this.calculateSectionGrade(k) * parseFloat(conf.percentage) / 100; this.ugrades[k] = this.calculateSectionGrade(k, true) * parseFloat(conf.percentage) / 100; } } let grade = this.grades.reduce((a, c) => { if (isNaN(c)) return a; return a + c }, 0); let ugrade = this.ugrades.reduce((a, c) => { if (isNaN(c)) return a; return a + c }, 0); grade = !isNaN(grade) ? grade.toFixed(2) : "..."; ugrade = !isNaN(ugrade) ? ugrade.toFixed(2) : "..."; if (this.both) { this.totalOutput.innerHTML = `Total Score (weighted): ${grade}%
Total Score (unweighted): ${ugrade}%`; return; } this.totalOutput.innerHTML = `Total Score: ${grade}%`; } get elemTotal() { return this.totalOutput; } }