Prisen på en service virker ofte enkel, når man kun ser det endelige beløb.
Men hvad består prisen egentlig af?
Ved træfældning kan arbejdstid, udstyr, adgangsforhold og oprydning have betydning for den samlede udgift.
Det gør emnet interessant fra et webudviklingsperspektiv.
For hvordan bygger vi en lille prisberegner, der viser, hvordan forskellige beløb lægges sammen, uden at den giver brugeren en falsk forventning om en garanteret pris?
I denne tutorial bygger vi et enkelt værktøj med HTML og vanilla JavaScript.
Vi bruger ingen frameworks.
Vi fokuserer i stedet på tre ting:
- Tydelige beregninger
- Forståelig visning af moms
- Ærlig kommunikation om usikkerhed
Vigtigt: Beregneren er et undervisningseksempel. Brugeren indtaster selv beløbene. Den indeholder ingen officielle timepriser fra en bestemt virksomhed, og resultatet er ikke et bindende tilbud på træfældning.
1. Hvorfor bygge en prisberegner?
Forestil dig, at en boligejer i Kolding undersøger, hvad det koster at få fjernet et træ.
Vedkommende har måske fået oplyst et forventet tidsforbrug og vil gerne forstå, hvordan en pris kan opdeles.
En beregner kan gøre regnestykket mere overskueligt.
Men den skal ikke forsøge at træffe en faglig beslutning om træet.
Det er vigtigt at skelne mellem to forskellige opgaver.
Den ene er at beregne et beløb ud fra givne tal.
Den anden er at vurdere, hvor lang tid det faktisk tager at fælde et træ, og hvilke sikkerhedsforanstaltninger opgaven kræver.
JavaScript er velegnet til det første.
Det andet kræver en konkret faglig vurdering.
Derfor bygger vi ikke en automatisk prisvurdering baseret på træhøjde eller billeder.
Vi bygger et værktøj, der viser matematikken bag et eksempel.
2. Vores beregningsmodel
Vi bruger tre oplysninger:
- Et forventet antal arbejdstimer
- En indtastet timepris
- Et valgfrit beløb for ekstra ydelser
Beregningen bliver:
Arbejdsbeløb = timer × timepris
Beløb eksklusive moms = arbejdsbeløb + ekstra ydelser
Moms = beløb eksklusive moms × 0,25
Beløb inklusive moms = beløb eksklusive moms + moms
Vi bruger den almindelige danske momssats på 25 procent i eksemplet.
Det er en forenklet model.
En virkelig pris kan også afhænge af maskiner, transport, bemanding og andre forhold, som ikke er med i dette regnestykke.
3. HTML: Lad brugeren indtaste oplysningerne
Vi begynder med en enkelt formular.
Opret filen index.html.
<!doctype html> <html lang="da"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Eksempel på prisberegner</title> <style> body { font-family: system-ui, sans-serif; max-width: 680px; margin: 40px auto; padding: 0 20px; line-height: 1.6; color: #26352c; } form { display: grid; gap: 16px; margin: 25px 0; } label { display: grid; gap: 6px; font-weight: 600; } input { padding: 11px; font: inherit; border: 1px solid #a3b2a7; border-radius: 6px; } button { padding: 12px; font: inherit; font-weight: 700; background: #24583b; color: #fff; border: 0; border-radius: 6px; cursor: pointer; } button:focus-visible, input:focus-visible { outline: 3px solid #608b70; outline-offset: 3px; } #resultat { padding: 20px; background: #edf4ee; border-radius: 10px; } .total { font-size: 1.25rem; font-weight: 700; } .note { color: #4e6455; font-size: 0.95rem; } [hidden] { display: none !important; } </style> </head> <body> <main> <h1>Prisberegner</h1> <p> Indtast dine egne tal for at se et eksempel på en samlet pris. </p> <form id="prisForm"> <label for="timer"> Forventede arbejdstimer <input id="timer" name="timer" type="number" min="0" max="10000" step="0.5" required > </label> <label for="timepris"> Timepris ekskl. moms (kr.) <input id="timepris" name="timepris" type="number" min="0" max="10000000" step="0.01" required > </label> <label for="ekstra"> Ekstra ydelser ekskl. moms (kr.) <input id="ekstra" name="ekstra" type="number" min="0" max="100000000" step="0.01" value="0" required > </label> <button type="submit"> Beregn eksempel </button> </form> <p id="fejl" role="alert" hidden></p> <section id="resultat" aria-labelledby="resultatTitel" hidden > <h2 id="resultatTitel">Beregning</h2> <p id="arbejde"></p> <p id="ekstraVisning"></p> <p id="subtotal"></p> <p id="moms"></p> <p id="total" class="total"></p> </section> <p class="note"> Kun et regneeksempel. Resultatet er ikke et pristilbud. Det tager ikke højde for alle forhold ved en virkelig opgave. </p> </main> <script src="app.js"></script> </body> </html> Vi har nu en formular med tre felter.
Alle felter har tilhørende labels.
Vi bruger også type="number" og begrænser input til ikke-negative værdier.
Det giver brugeren en mere tydelig indtastningsoplevelse.
Men HTML-validering er ikke nok i sig selv.
Vi kontrollerer også tallene i JavaScript.
4. Beregn prisen med en ren funktion
Nu opretter vi filen app.js.
Vi starter med selve beregningen.
const MOMSSATS = 0.25; function beregnPris(timer, timepris, ekstra) { const tal = [timer, timepris, ekstra]; if ( tal.some( (tal) => !Number.isFinite(tal) || tal < 0 ) ) { throw new Error( "Alle beløb skal være gyldige, " + "ikke-negative tal." ); } const arbejdsbeloeb = timer * timepris; const subtotal = arbejdsbeloeb + ekstra; const moms = subtotal * MOMSSATS; const total = subtotal + moms; if ( !Number.isFinite(arbejdsbeloeb) || !Number.isFinite(subtotal) || !Number.isFinite(moms) || !Number.isFinite(total) ) { throw new Error( "Beløbet er for stort til beregning." ); } return { arbejdsbeloeb, ekstra, subtotal, moms, total }; } Funktionen modtager tre tal og returnerer et objekt.
Den arbejder ikke direkte med HTML-elementerne.
Det gør den lettere at teste.
Det er også en nyttig adskillelse mellem beregning og præsentation.
Hvis vi senere ændrer designet, behøver vi ikke ændre regnemetoden.
5. Vis danske beløb korrekt
Danske priser vises normalt med komma som decimaltegn og punktum som tusindtalsseparator.
I stedet for selv at sætte tegnene ind bruger vi browserens indbyggede internationaliseringsfunktion.
const formatter = new Intl.NumberFormat( "da-DK", { style: "currency", currency: "DKK", minimumFractionDigits: 2, maximumFractionDigits: 2 } ); function formatPris(beloeb) { return formatter.format(beloeb); } Her bruger vi Intl.NumberFormat.
Den tager hensyn til sproglige konventioner.
Det er bedre end at skrive vores egen løsning, der manuelt forsøger at placere kommaer og punktummer.
Det betyder også, at samme princip kan bruges til andre valutaer og sprog.
Bemærk, at JavaScripts almindelige decimaltal ikke er ideelle til alle former for finansielle beregninger.
Til produktionsbrug med bogføring og betaling bør beløb normalt håndteres med en kontrolleret decimalmodel eller heltal i mindste valutaenhed og med tydelige afrundingsregler.
Vores løsning er et simpelt overslag.
6. Forbind formularen til beregningen
Nu skal vi hente brugerens tal og vise resultatet.
Tilføj følgende kode nederst i app.js.
const form = document.querySelector("#prisForm"); const resultat = document.querySelector("#resultat"); const fejl = document.querySelector("#fejl"); form.addEventListener("submit", (event) => { event.preventDefault(); fejl.hidden = true; resultat.hidden = true; const timer = Number(form.elements.timer.value); const timepris = Number( form.elements.timepris.value ); const ekstra = Number(form.elements.ekstra.value); try { const pris = beregnPris( timer, timepris, ekstra ); document.querySelector("#arbejde").textContent = "Arbejde: " + formatPris(pris.arbejdsbeloeb); document.querySelector( "#ekstraVisning" ).textContent = "Ekstra ydelser: " + formatPris(pris.ekstra); document.querySelector("#subtotal").textContent = "Pris ekskl. moms: " + formatPris(pris.subtotal); document.querySelector("#moms").textContent = "Moms (25 %): " + formatPris(pris.moms); document.querySelector("#total").textContent = "Pris inkl. moms: " + formatPris(pris.total); resultat.hidden = false; } catch (error) { fejl.textContent = error.message; fejl.hidden = false; } }); Når formularen bliver sendt, stopper vi browserens normale genindlæsning.
Derefter læser vi felterne.
Tallene bliver sendt videre til vores beregningsfunktion.
Til sidst viser vi resultatet i separate linjer.
Hvis input er ugyldigt, får brugeren en fejlbesked.
På den måde bliver regnestykket lettere at forstå end ved blot at vise et enkelt stort beløb.
7. Test med illustrative tal
Vi kan nu prøve et simpelt eksempel.
Forestil dig, at brugeren indtaster:
- Arbejdstimer: 4
- Timepris: 600 kr.
- Ekstra ydelser: 800 kr.
Disse tal er udelukkende undervisningsdata.
De er ikke en virksomheds offentlige timepriser.
Beregningen giver:
| Post | Beløb |
|---|---|
| Arbejde | 2.400 kr. |
| Ekstra ydelser | 800 kr. |
| Pris ekskl. moms | 3.200 kr. |
| Moms 25 % | 800 kr. |
| Pris inkl. moms | 4.000 kr. |
Det er et meget enkelt regnestykke.
Men netop derfor er det nyttigt.
Vi kan se, hvor hvert beløb kommer fra.
Og vi kan kontrollere, om beregningen virker som forventet.
8. Skriv nogle enkle tests
En prisberegner bør ikke kun se korrekt ud.
Den skal også regne korrekt.
Vi kan teste vores funktion direkte i browserens udviklerværktøjer.
console.assert( beregnPris(4, 600, 800).total === 4000, "Den samlede pris skal være 4000 kr." ); console.assert( beregnPris(2, 500, 0).moms === 250, "Momsen skal være 250 kr." ); console.assert( beregnPris(0, 0, 0).total === 0, "Nul skal give nul." ); let fejlBlevFanget = false; try { beregnPris(-1, 500, 0); } catch { fejlBlevFanget = true; } console.assert( fejlBlevFanget, "Negative værdier skal afvises." ); Testene er små, men de kontrollerer nogle vigtige regler.
Den samlede pris skal være korrekt.
Momsen skal være korrekt.
Og negative tal må ikke accepteres.
Hvis systemet skulle udvikles videre til egentlig fakturering, ville vi have brug for mere omfattende tests og håndtering af afrunding.
9. Hvad mangler vores beregner?
Der mangler en hel del.
Det er faktisk en vigtig del af denne tutorial.
Værktøjet kan lægge tal sammen.
Men det kan ikke selv afgøre:
- Hvor mange medarbejdere opgaven kræver
- Om et træ skal fjernes i sektioner
- Om en lift er nødvendig
- Hvor vanskelige adgangsforholdene er
- Om træets tilstand kræver særlige forholdsregler
- Hvordan oprydning og transport skal planlægges
Disse forhold kræver faglig vurdering.
Hvis vi forsøgte at gætte dem ud fra træhøjde alene, kunne beregneren skabe en falsk følelse af præcision.
Det ville ikke gøre værktøjet mere hjælpsomt.
Tværtimod kunne brugeren få et misvisende billede af den faktiske udgift.
Derfor er det bedre at vise en tydelig begrænsning end at lade et tal se mere sikkert ud, end det er.
10. Hvorfor er gennemsigtighed vigtig?
Når en bruger indtaster tal i en beregner, bør det være tydeligt, hvad resultatet betyder.
Hvis resultatet kun er et eksempel, skal det stå klart.
Hvis ekstra ydelser ikke er med, skal det også være tydeligt.
Det samme gælder moms.
En beregner, der viser en pris uden at forklare, om moms er inkluderet, kan skabe unødvendig forvirring.
Derfor er vores resultat opdelt i flere linjer.
Brugeren kan se arbejdsbeløbet, ekstra ydelser, moms og den samlede pris.
Vi har samtidig skrevet, at resultatet ikke er et bindende tilbud.
Det er ikke bare en juridisk detalje.
Det er en del af god brugeroplevelse.
11. Et virkeligt eksempel: Træfældning pris i Kolding
Skovhugger Lund har base i Kolding og udfører træfældning, beskæring og træpleje i Syddanmark.
På virksomhedens prisside forklares det, at prisen på træfældning beregnes ud fra et forventet tidsforbrug.
Træets størrelse, placering, tilstand og opgavens sværhedsgrad indgår i vurderingen.
Virksomheden offentliggør også vejledende priser for forskellige typer træarbejde.
Men en konkret opgave kræver stadig en vurdering af forholdene.
På siden om træfældning pris i Kolding beskriver Skovhugger Lund, hvorfor en simpel standardpris ikke passer til alle træer.
Det er en god illustration af forskellen mellem en teknisk beregning og en faglig prisvurdering.
Vores JavaScript-beregner er et selvstændigt undervisningseksempel.
Den er ikke udviklet af Skovhugger Lund, indeholder ikke virksomhedens faktiske timepriser og kan ikke bruges til at bestille eller prissætte dens ydelser.
12. Hvordan kunne vi forbedre projektet?
Hvis vi ville gøre værktøjet mere anvendeligt, kunne vi begynde med bedre input.
Eksempelvis kunne vi tilføje en valgfri beskrivelse af opgaven.
Men vi bør ikke lade den frie tekst bestemme en automatisk pris.
I stedet kunne den bruges til at dokumentere brugerens forudsætninger.
Vi kunne også tilføje en knap, der eksporterer selve regnestykket som tekst.
Det ville gøre det lettere at gennemgå de indtastede beløb.
En anden forbedring kunne være at gemme beregningens antagelser sammen med resultatet.
Så ville brugeren tydeligt kunne se, hvilke tal der blev brugt.
Men enhver udvidelse bør stadig bevare den vigtigste regel:
Et beregnet eksempel skal ikke præsenteres som et fagligt godkendt tilbud.
13. En nyttig regel for udviklere
Det er fristende at bygge en beregner, som altid giver brugeren et svar.
Men ikke alle spørgsmål kan besvares præcist med en formel.
Nogle kræver observationer, erfaring og vurdering af konkrete forhold.
Det gælder mange servicefag.
En prisberegner kan gøre matematikken tydelig.
Den kan hjælpe brugeren med at forstå moms og omkostninger.
Og den kan vise, hvordan forskellige beløb påvirker en samlet sum.
Men den bør ikke foregive at vide mere, end den faktisk gør.
God software viser ikke kun et resultat. Den viser også, hvor resultatet kommer fra, og hvad det ikke kan fortælle os.
Opsummering
I denne tutorial har vi bygget en enkel prisberegner med HTML og vanilla JavaScript.
Vi har arbejdet med:
- Formularer og validering
- Rene beregningsfunktioner
Intl.NumberFormat- Dansk momsberegning
- Dynamisk resultatvisning
- Simple browser-tests
- Tydelig kommunikation af begrænsninger
Vi har også set, hvorfor et teknisk regnestykke ikke kan erstatte en faglig vurdering.
Det er måske den vigtigste pointe.
For en gennemsigtig beregner er ikke nødvendigvis den, der giver flest tal.
Det er den, der hjælper brugeren med at forstå, hvad tallene betyder.


