/** * Hjelpemiddel for å regne ut gjennomsnittskarakter. * @author Lærling Jørgen Heggøy Knudsen, HVL, 2023. * @note Scriptet skal kunne slippes hvor som helst i en HTML-kontainer og generere * kalkulator automatisk. */ class KarakterKalkulator { constructor() { // Legger først inn CSS for kalkulatoren. this.css = document.createElement('style'); this.css.innerText = ` #kalkulator { box-shadow: .4rem .4rem 1rem rgba(0,0,0,.3); margin-left: .8rem; padding: 1.2rem 2.4rem; border-radius: 1rem; max-width: 23rem; } #kalkulator .vekting, #kalkulator .karakter { width: 48%; } #kalkulator summary { cursor: pointer; } #kalkulator .utregning { font-weight: bold; } #kalkulator .utregning label { /* Fiks for Bouvets stilark. */ display: inline; } #kalkulator .sections section { display: flex; align-items: stretch; gap: 1em; } #kalkulator .sections section p { margin: auto 0; } `; document.head.appendChild(this.css); // Lager kalkulatoren inn i "kalkulator" hvis "kalkulator" eksisterer, ellers så kommer error melding. this.kalkulator = document.createElement('form') this.kalkulator.id = "kalkulator" document.currentScript.insertAdjacentElement('afterend', this.kalkulator) this.kalkulator.appendChild(document.currentScript) this.sections = document.createElement('div') this.sections.classList.add('sections') this.kalkulator.appendChild(this.sections) this.sections.appendChild(this.makeCalculatorRow()) this.details = document.createElement('details') this.details.style.display = 'none' this.kalkulator.insertAdjacentElement('beforeend', this.details) this.summary = document.createElement('summary') this.summary.innerText = 'Se utregningen:' this.details.appendChild(this.summary) this.delutregning = document.createElement('div') this.delutregning.id = 'delutregning' this.delutregning.style.textAlign = "right" this.delutregning.style.fontSize = "small" this.delutregning.style.fontStyle = "italic" this.details.appendChild(this.delutregning) // lager en feilmelding som skal hvises nederst på siden. this.summeringsrubrikk = document.createElement("p") this.summeringsrubrikk.id = 'summeringsrubrikk' this.kalkulator.insertAdjacentElement('beforeend', this.summeringsrubrikk) // lager en feilmelding som skal vises nederst på siden. this.feilmelding = document.createElement("p") this.feilmelding.id = 'feilmelding' this.kalkulator.insertAdjacentElement('beforeend', this.feilmelding) // lager seksjonen "output" nederst på siden. this.output = document.createElement("output") this.output.id = 'output' this.output.style.textAlign = "left" this.kalkulator.insertAdjacentElement('beforeend', this.output) this.horizontalRuler = document.createElement("hr") this.horizontalRuler.id = 'horizontalRuler' this.horizontalRuler.style.display = 'none' this.horizontalRuler.style.border = 'none' this.horizontalRuler.style.backgroundColor = '#000' this.horizontalRuler.style.height = '1px' this.details.appendChild(this.horizontalRuler) this.TallverdiForBokstavKaraterer = document.createElement("output") this.TallverdiForBokstavKaraterer.id = 'TallverdiForBokstavKaraterer' this.TallverdiForBokstavKaraterer.style.fontSize = "small" this.TallverdiForBokstavKaraterer.style.fontStyle = "italic" this.details.appendChild(this.TallverdiForBokstavKaraterer) this.bokstavOutput = document.createElement("output") this.bokstavOutput.id = 'bokstavOutput' this.bokstavOutput.style.textAlign = "right" this.kalkulator.insertAdjacentElement('beforeend', this.bokstavOutput) // Her kan bruker velge prosent uten å skrive det inn selv this.datalist = document.createElement('datalist') this.datalist.id = "vektingsVerdier" for (let index = 1; index <= 3; index++) { this.datalist.appendChild(new Option(index * 10, index * 10)) } this.datalist.appendChild(new Option("33.33", "33.33")) for (let index = 4; index <= 10; index++) { this.datalist.appendChild(new Option(index * 10, index * 10)) } this.kalkulator.appendChild(this.datalist) this.sumAllRows(); } createFractionOption(numerator, denominator) { const optionElement = document.createElement('option') optionElement.value = (numerator / denominator * 100).toLocaleString('en-US', {minimumFractionDigits: 0, maximumFractionDigits: 2}) optionElement.innerHTML = this.createFraction(numerator, denominator).innerHTML return optionElement } createFraction(numerator, denominator) { const fractionElement = document.createElement('span') const numeratorElement = document.createElement('sup') const denominatorElement = document.createElement('sub') numeratorElement.innerText = numerator denominatorElement.innerText = denominator //fractionElement.appendChild(numeratorElement) fractionElement.insertAdjacentHTML('beforeend', '⁄') fractionElement.appendChild(denominatorElement) return fractionElement } /** * Beregner summen av alle radene. */ sumAllRows() { let sections = this.kalkulator.getElementsByTagName('section') let sum = 0 for (let index = 0; index < sections.length; index++) { const section = sections[index]; const karakterinput = section.querySelector('select') const vekting = section.querySelector('input') if (karakterinput.value === "0" ) { return 0 } // Sjekker om "Vekting" har en verdi som er gyldig. if (vekting.checkValidity() && vekting.value !== "") { sum += (parseFloat(karakterinput.value) * (parseFloat(vekting.value) / 100)) } } return sum } vektingTotall() { let sections = this.kalkulator.getElementsByTagName('section') let sum = 0 for (let index = 0; index < sections.length; index++) { const section = sections[index]; const karakterinput = section.querySelector('select') const vekting = section.querySelector('input') // Sjekker om "Vekting" har en verdi som er gyldig. if (vekting.checkValidity() && vekting.value !== "") { sum +=(parseFloat(vekting.value)) } } return sum } deleteLastRowIfPercentageOver100() { let lastRow = this.sections.getElementsByTagName('section')[this.sections.getElementsByTagName('section').length - 1] let vekting = lastRow.querySelector('.vekting') let karakterinput = lastRow.querySelector('.karakter') if (vekting.value === "" && karakterinput.value === "" && this.checkPercentageOver100()) { lastRow.remove() } } checkPercentageOver100() { let sections = this.kalkulator.getElementsByTagName('section') let sum = 0 // Her hentes alle seksjonene i kalkulatoret, går gjennom hver seksjon og henter vektingen for den seksjonen. for (let index = 0; index < sections.length; index++) { const section = sections[index]; const vekting = section.querySelector('input') // Hvis vektingen er gyldig og ikke tom, legger funksjonen til verdien til summen. if (vekting.checkValidity() && vekting.value !== "") { sum += parseFloat(vekting.value) } } return (Math.round(sum) >= 100) } /** * Sjekker om du skriver en gyldig verdi i vekting. */ checkPercentage100() { let sections = this.kalkulator.getElementsByTagName('section') let sum = 0 // Her hentes alle seksjonene i kalkulatoret, går gjennom hver seksjon og henter vektingen for den seksjonen. for (let index = 0; index < sections.length; index++) { const section = sections[index]; const vekting = section.querySelector('.vekting') const karakter = section.querySelector('.karakter') // Hvis vektingen er gyldig og ikke tom, legger funksjonen til verdien til summen. if (vekting.checkValidity() && vekting.value !== "" && karakter.value !== "") { sum += parseFloat(vekting.value) } } return (Math.round(sum) === 100) } /** * Oppdaterer totalutregning i bunn av kalkulator. * @param {object} row HTML-elementet som utregningen skal foregå i. */ updateCalculation (row) { let vekting = row.querySelector('.vekting') let karakterinput = row.querySelector('.karakter') // hvis "checkPercentage100" = true, så putter den html inn i outputen. if (this.checkPercentage100()) { this.output.innerHTML = `

${String.fromCharCode((Math.round(this.sumAllRows())) / -1 + 70)}

` this.delutregning.innerText = "" if (this.sumAllRows() === 0) { this.delutregning.innerText = "Dersom resultat på en deleksamen er F, blir totalkarakter i emnet F. Alle deleksamener må bestås for å få en samlet karakter i et emne." }else{ for (let index = 0; index < this.sections.querySelectorAll('section').length; index++) { const element = this.sections.querySelectorAll('section')[index]; if(element.querySelector(".karakter").value === "" || element.querySelector(".vekting").value === ""){continue}; const delutregningrow = document.createElement('div') delutregningrow.innerHTML = `${element.querySelector(".karakter").value} * ${element.querySelector(".vekting").value} / 100 = ${(parseFloat(element.querySelector(".karakter").value) * (parseFloat(element.querySelector(".vekting").value) / 100)).toLocaleString('no-NO', {minimumFractionDigits: 0, maximumFractionDigits: 1})}` this.delutregning.appendChild(delutregningrow) } const sum = document.createElement('div') sum.innerHTML = `sum = ${this.sumAllRows().toLocaleString('no-NO', {minimumFractionDigits: 0, maximumFractionDigits: 1})}
${this.sumAllRows().toLocaleString('no-NO', {minimumFractionDigits: 0, maximumFractionDigits: 1})} ≈ ${Math.round(this.sumAllRows())}` delutregning.appendChild(sum) } this.summeringsrubrikk.innerText = '' this.feilmelding.innerText = '' this.details.style.removeProperty("display") this.horizontalRuler.style.removeProperty("display") this.TallverdiForBokstavKaraterer.innerHTML = 'Tallverdi for bokstavkarakterer:
A=5, B=4, C=3, D=2, E=1, F=0' } else { this.horizontalRuler.style.display = 'none' this.TallverdiForBokstavKaraterer.innerHTML = '' this.delutregning.innerText = '' this.output.innerText = '' this.details.style.display = 'none' this.summeringsrubrikk.innerHTML = `Vektingen utgjør totalt: ${this.vektingTotall()}%` this.feilmelding.innerText = 'Sum av vektingene må utgjøre totalt 100%' } if (!this.checkPercentageOver100() && vekting.value !== "" && karakterinput.value !== "" && this.sections.getElementsByTagName('section')[this.sections.getElementsByTagName('section').length - 1] === row) { this.sections.appendChild(this.makeCalculatorRow()) } } /** * Lager raden og inputs som bruker skal skrive i. */ makeCalculatorRow() { let row = document.createElement('section') // Her kan brukeren velge karakter. let karakterinput = document.createElement('select') karakterinput.className='karakter' karakterinput.placeholder='A-E' karakterinput.name='karakter' karakterinput.style.marginRight='0.5em' karakterinput.appendChild(new Option("Ikke satt", "")); karakterinput.appendChild(new Option("A", "5")); karakterinput.appendChild(new Option("B", "4")); karakterinput.appendChild(new Option("C", "3")); karakterinput.appendChild(new Option("D", "2")); karakterinput.appendChild(new Option("E", "1")); karakterinput.appendChild(new Option("F", "0")); karakterinput.addEventListener("change", function(event) { this.nextElementSibling.select() }) row.appendChild(karakterinput) // Her velges vekting denna karakteren har for dette emnet. let vekting = document.createElement('input') vekting.className='vekting' vekting.type='number' vekting.min=0 vekting.max=100 vekting.step="any" vekting.style.textAlign="right" vekting.placeholder='%' vekting.name='vekting' vekting.setAttribute('list', 'vektingsVerdier') row.appendChild(vekting) vekting.addEventListener("blur", function(event){ vekting.reportValidity() }) let prosentSymbol = document.createElement('p') prosentSymbol.innerText='%' row.appendChild(prosentSymbol) // Når du trykker "Enter" så lager den bare ny rad hvis du har fylt ut forrige. vekting.addEventListener("keypress", function(event) { if (event.key === "Enter") { const nextRow = this.parentElement.nextElementSibling if (nextRow !== null) { nextRow.firstElementChild.focus() } } }) // Når en av feltene blir endret så oppdaterer den kalkulasjonen. vekting.addEventListener("input", () => { this.updateCalculation(row) this.deleteLastRowIfPercentageOver100() }) karakterinput.addEventListener("input", () => { this.updateCalculation(row) this.deleteLastRowIfPercentageOver100() }) // Returnerer den ferdiglagde karakterraden. return row; } } let kalkulator = new KarakterKalkulator();