<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Skovhugger Lund</title>
    <description>The latest articles on DEV Community by Skovhugger Lund (@skovhuggerlund).</description>
    <link>https://dev.to/skovhuggerlund</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4242558%2Fdce3d23e-4f1e-4418-af4a-7ac09972c362.jpg</url>
      <title>DEV Community: Skovhugger Lund</title>
      <link>https://dev.to/skovhuggerlund</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/skovhuggerlund"/>
    <language>en</language>
    <item>
      <title>Byg en gennemsigtig prisberegner med JavaScript</title>
      <dc:creator>Skovhugger Lund</dc:creator>
      <pubDate>Fri, 13 Nov 2026 11:41:46 +0000</pubDate>
      <link>https://dev.to/skovhuggerlund/byg-en-gennemsigtig-prisberegner-med-javascript-4g74</link>
      <guid>https://dev.to/skovhuggerlund/byg-en-gennemsigtig-prisberegner-med-javascript-4g74</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjo198v5g3tn0xx7tescc.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjo198v5g3tn0xx7tescc.jpg" alt=" " width="680" height="382"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Prisen på en service virker ofte enkel, når man kun ser det endelige beløb.&lt;/p&gt;

&lt;p&gt;Men hvad består prisen egentlig af?&lt;/p&gt;

&lt;p&gt;Ved træfældning kan arbejdstid, udstyr, adgangsforhold og oprydning have betydning for den samlede udgift.&lt;/p&gt;

&lt;p&gt;Det gør emnet interessant fra et webudviklingsperspektiv.&lt;/p&gt;

&lt;p&gt;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?&lt;/p&gt;

&lt;p&gt;I denne tutorial bygger vi et enkelt værktøj med HTML og vanilla JavaScript.&lt;/p&gt;

&lt;p&gt;Vi bruger ingen frameworks.&lt;/p&gt;

&lt;p&gt;Vi fokuserer i stedet på tre ting:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tydelige beregninger&lt;/li&gt;
&lt;li&gt;Forståelig visning af moms&lt;/li&gt;
&lt;li&gt;Ærlig kommunikation om usikkerhed&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  1. Hvorfor bygge en prisberegner?
&lt;/h2&gt;

&lt;p&gt;Forestil dig, at en boligejer i Kolding undersøger, hvad det koster at få fjernet et træ.&lt;/p&gt;

&lt;p&gt;Vedkommende har måske fået oplyst et forventet tidsforbrug og vil gerne forstå, hvordan en pris kan opdeles.&lt;/p&gt;

&lt;p&gt;En beregner kan gøre regnestykket mere overskueligt.&lt;/p&gt;

&lt;p&gt;Men den skal ikke forsøge at træffe en faglig beslutning om træet.&lt;/p&gt;

&lt;p&gt;Det er vigtigt at skelne mellem to forskellige opgaver.&lt;/p&gt;

&lt;p&gt;Den ene er at beregne et beløb ud fra givne tal.&lt;/p&gt;

&lt;p&gt;Den anden er at vurdere, hvor lang tid det faktisk tager at fælde et træ, og hvilke sikkerhedsforanstaltninger opgaven kræver.&lt;/p&gt;

&lt;p&gt;JavaScript er velegnet til det første.&lt;/p&gt;

&lt;p&gt;Det andet kræver en konkret faglig vurdering.&lt;/p&gt;

&lt;p&gt;Derfor bygger vi ikke en automatisk prisvurdering baseret på træhøjde eller billeder.&lt;/p&gt;

&lt;p&gt;Vi bygger et værktøj, der viser matematikken bag et eksempel.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Vores beregningsmodel
&lt;/h2&gt;

&lt;p&gt;Vi bruger tre oplysninger:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Et forventet antal arbejdstimer&lt;/li&gt;
&lt;li&gt;En indtastet timepris&lt;/li&gt;
&lt;li&gt;Et valgfrit beløb for ekstra ydelser&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Beregningen bliver:&lt;/p&gt;

&lt;p&gt;Arbejdsbeløb = timer × timepris&lt;/p&gt;

&lt;p&gt;Beløb eksklusive moms = arbejdsbeløb + ekstra ydelser&lt;/p&gt;

&lt;p&gt;Moms = beløb eksklusive moms × 0,25&lt;/p&gt;

&lt;p&gt;Beløb inklusive moms = beløb eksklusive moms + moms&lt;/p&gt;

&lt;p&gt;Vi bruger den almindelige danske momssats på 25 procent i eksemplet.&lt;/p&gt;

&lt;p&gt;Det er en forenklet model.&lt;/p&gt;

&lt;p&gt;En virkelig pris kan også afhænge af maskiner, transport, bemanding og andre forhold, som ikke er med i dette regnestykke.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. HTML: Lad brugeren indtaste oplysningerne
&lt;/h2&gt;

&lt;p&gt;Vi begynder med en enkelt formular.&lt;/p&gt;

&lt;p&gt;Opret filen &lt;code&gt;index.html&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!doctype html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"da"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"utf-8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"viewport"&lt;/span&gt;
        &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"width=device-width, initial-scale=1"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Eksempel på prisberegner&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;style&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;body&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;system-ui&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sans-serif&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;max-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;680px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;40px&lt;/span&gt; &lt;span class="n"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="mi"&gt;20px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;line-height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.6&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mh"&gt;#26352c&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nt"&gt;form&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;gap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;16px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;25px&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nt"&gt;label&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;gap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;6px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;font-weight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;600&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;11px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;font&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;inherit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1px&lt;/span&gt; &lt;span class="n"&gt;solid&lt;/span&gt; &lt;span class="mh"&gt;#a3b2a7&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;6px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;12px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;font&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;inherit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;font-weight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;700&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mh"&gt;#24583b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mh"&gt;#fff&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;6px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;pointer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nt"&gt;focus-visible&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nt"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nt"&gt;focus-visible&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;outline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3px&lt;/span&gt; &lt;span class="n"&gt;solid&lt;/span&gt; &lt;span class="mh"&gt;#608b70&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;outline-offset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nn"&gt;#resultat&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mh"&gt;#edf4ee&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nc"&gt;.total&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.25rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;font-weight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;700&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nc"&gt;.note&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mh"&gt;#4e6455&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.95rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;hidden&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;none&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;important&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;main&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Prisberegner&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;
      Indtast dine egne tal for at se
      et eksempel på en samlet pris.
    &lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"prisForm"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"timer"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        Forventede arbejdstimer
        &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt;
          &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"timer"&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"timer"&lt;/span&gt;
          &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"number"&lt;/span&gt;
          &lt;span class="na"&gt;min&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt;
          &lt;span class="na"&gt;max&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"10000"&lt;/span&gt;
          &lt;span class="na"&gt;step&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"0.5"&lt;/span&gt;
          &lt;span class="na"&gt;required&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;

      &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"timepris"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        Timepris ekskl. moms (kr.)
        &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt;
          &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"timepris"&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"timepris"&lt;/span&gt;
          &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"number"&lt;/span&gt;
          &lt;span class="na"&gt;min&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt;
          &lt;span class="na"&gt;max&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"10000000"&lt;/span&gt;
          &lt;span class="na"&gt;step&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"0.01"&lt;/span&gt;
          &lt;span class="na"&gt;required&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;

      &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"ekstra"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        Ekstra ydelser ekskl. moms (kr.)
        &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt;
          &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"ekstra"&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"ekstra"&lt;/span&gt;
          &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"number"&lt;/span&gt;
          &lt;span class="na"&gt;min&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt;
          &lt;span class="na"&gt;max&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"100000000"&lt;/span&gt;
          &lt;span class="na"&gt;step&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"0.01"&lt;/span&gt;
          &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt;
          &lt;span class="na"&gt;required&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;

      &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        Beregn eksempel
      &lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"fejl"&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"alert"&lt;/span&gt; &lt;span class="na"&gt;hidden&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/p&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;section&lt;/span&gt;
      &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"resultat"&lt;/span&gt;
      &lt;span class="na"&gt;aria-labelledby&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"resultatTitel"&lt;/span&gt;
      &lt;span class="na"&gt;hidden&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;h2&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"resultatTitel"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Beregning&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;

      &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"arbejde"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"ekstraVisning"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"subtotal"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"moms"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"total"&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"total"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/section&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"note"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      Kun et regneeksempel.
      Resultatet er ikke et pristilbud.
      Det tager ikke højde for alle
      forhold ved en virkelig opgave.
    &lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/main&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;script&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"app.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Vi har nu en formular med tre felter.&lt;/p&gt;

&lt;p&gt;Alle felter har tilhørende labels.&lt;/p&gt;

&lt;p&gt;Vi bruger også &lt;code&gt;type="number"&lt;/code&gt; og begrænser input til ikke-negative værdier.&lt;/p&gt;

&lt;p&gt;Det giver brugeren en mere tydelig indtastningsoplevelse.&lt;/p&gt;

&lt;p&gt;Men HTML-validering er ikke nok i sig selv.&lt;/p&gt;

&lt;p&gt;Vi kontrollerer også tallene i JavaScript.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Beregn prisen med en ren funktion
&lt;/h2&gt;

&lt;p&gt;Nu opretter vi filen &lt;code&gt;app.js&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Vi starter med selve beregningen.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="no"&gt;MOMSSATS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.25&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;beregnPris&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timepris&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ekstra&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;tal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;timer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timepris&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ekstra&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;tal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isFinite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;tal&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s2"&gt;"Alle beløb skal være gyldige, "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
      &lt;span class="s2"&gt;"ikke-negative tal."&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;arbejdsbeloeb&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;timer&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;timepris&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;subtotal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;arbejdsbeloeb&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;ekstra&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;moms&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;subtotal&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="no"&gt;MOMSSATS&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;subtotal&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;moms&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isFinite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arbejdsbeloeb&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
    &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isFinite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subtotal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
    &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isFinite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;moms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
    &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isFinite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s2"&gt;"Beløbet er for stort til beregning."&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;arbejdsbeloeb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ekstra&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;subtotal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;moms&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;total&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Funktionen modtager tre tal og returnerer et objekt.&lt;/p&gt;

&lt;p&gt;Den arbejder ikke direkte med HTML-elementerne.&lt;/p&gt;

&lt;p&gt;Det gør den lettere at teste.&lt;/p&gt;

&lt;p&gt;Det er også en nyttig adskillelse mellem beregning og præsentation.&lt;/p&gt;

&lt;p&gt;Hvis vi senere ændrer designet, behøver vi ikke ændre regnemetoden.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Vis danske beløb korrekt
&lt;/h2&gt;

&lt;p&gt;Danske priser vises normalt med komma som decimaltegn og punktum som tusindtalsseparator.&lt;/p&gt;

&lt;p&gt;I stedet for selv at sætte tegnene ind bruger vi browserens indbyggede internationaliseringsfunktion.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;formatter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Intl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;NumberFormat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="s2"&gt;"da-DK"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"currency"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;currency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"DKK"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;minimumFractionDigits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;maximumFractionDigits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;formatPris&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;beloeb&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;formatter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;beloeb&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Her bruger vi &lt;code&gt;Intl.NumberFormat&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Den tager hensyn til sproglige konventioner.&lt;/p&gt;

&lt;p&gt;Det er bedre end at skrive vores egen løsning, der manuelt forsøger at placere kommaer og punktummer.&lt;/p&gt;

&lt;p&gt;Det betyder også, at samme princip kan bruges til andre valutaer og sprog.&lt;/p&gt;

&lt;p&gt;Bemærk, at JavaScripts almindelige decimaltal ikke er ideelle til alle former for finansielle beregninger.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Vores løsning er et simpelt overslag.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Forbind formularen til beregningen
&lt;/h2&gt;

&lt;p&gt;Nu skal vi hente brugerens tal og vise resultatet.&lt;/p&gt;

&lt;p&gt;Tilføj følgende kode nederst i &lt;code&gt;app.js&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"#prisForm"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;resultat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"#resultat"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;fejl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"#fejl"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"submit"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="n"&gt;fejl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hidden&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;resultat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hidden&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;timer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;elements&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;timepris&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;elements&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timepris&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;ekstra&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;elements&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ekstra&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;pris&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;beregnPris&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="n"&gt;timer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;timepris&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;ekstra&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"#arbejde"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
      &lt;span class="s2"&gt;"Arbejde: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
      &lt;span class="nf"&gt;formatPris&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pris&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;arbejdsbeloeb&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s2"&gt;"#ekstraVisning"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
      &lt;span class="s2"&gt;"Ekstra ydelser: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
      &lt;span class="nf"&gt;formatPris&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pris&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ekstra&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"#subtotal"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
      &lt;span class="s2"&gt;"Pris ekskl. moms: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
      &lt;span class="nf"&gt;formatPris&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pris&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;subtotal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"#moms"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
      &lt;span class="s2"&gt;"Moms (25 %): "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
      &lt;span class="nf"&gt;formatPris&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pris&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;moms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"#total"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
      &lt;span class="s2"&gt;"Pris inkl. moms: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
      &lt;span class="nf"&gt;formatPris&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pris&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="n"&gt;resultat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hidden&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;fejl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;fejl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hidden&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Når formularen bliver sendt, stopper vi browserens normale genindlæsning.&lt;/p&gt;

&lt;p&gt;Derefter læser vi felterne.&lt;/p&gt;

&lt;p&gt;Tallene bliver sendt videre til vores beregningsfunktion.&lt;/p&gt;

&lt;p&gt;Til sidst viser vi resultatet i separate linjer.&lt;/p&gt;

&lt;p&gt;Hvis input er ugyldigt, får brugeren en fejlbesked.&lt;/p&gt;

&lt;p&gt;På den måde bliver regnestykket lettere at forstå end ved blot at vise et enkelt stort beløb.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Test med illustrative tal
&lt;/h2&gt;

&lt;p&gt;Vi kan nu prøve et simpelt eksempel.&lt;/p&gt;

&lt;p&gt;Forestil dig, at brugeren indtaster:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Arbejdstimer: 4&lt;/li&gt;
&lt;li&gt;Timepris: 600 kr.&lt;/li&gt;
&lt;li&gt;Ekstra ydelser: 800 kr.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Disse tal er udelukkende undervisningsdata.&lt;/p&gt;

&lt;p&gt;De er ikke en virksomheds offentlige timepriser.&lt;/p&gt;

&lt;p&gt;Beregningen giver:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Post&lt;/th&gt;
&lt;th&gt;Beløb&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Arbejde&lt;/td&gt;
&lt;td&gt;2.400 kr.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ekstra ydelser&lt;/td&gt;
&lt;td&gt;800 kr.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pris ekskl. moms&lt;/td&gt;
&lt;td&gt;3.200 kr.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Moms 25 %&lt;/td&gt;
&lt;td&gt;800 kr.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pris inkl. moms&lt;/td&gt;
&lt;td&gt;4.000 kr.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Det er et meget enkelt regnestykke.&lt;/p&gt;

&lt;p&gt;Men netop derfor er det nyttigt.&lt;/p&gt;

&lt;p&gt;Vi kan se, hvor hvert beløb kommer fra.&lt;/p&gt;

&lt;p&gt;Og vi kan kontrollere, om beregningen virker som forventet.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Skriv nogle enkle tests
&lt;/h2&gt;

&lt;p&gt;En prisberegner bør ikke kun se korrekt ud.&lt;/p&gt;

&lt;p&gt;Den skal også regne korrekt.&lt;/p&gt;

&lt;p&gt;Vi kan teste vores funktion direkte i browserens udviklerværktøjer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nf"&gt;beregnPris&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;600&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;800&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;4000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s2"&gt;"Den samlede pris skal være 4000 kr."&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nb"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nf"&gt;beregnPris&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;moms&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;250&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s2"&gt;"Momsen skal være 250 kr."&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nb"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nf"&gt;beregnPris&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s2"&gt;"Nul skal give nul."&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="n"&gt;fejlBlevFanget&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;beregnPris&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;fejlBlevFanget&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nb"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;fejlBlevFanget&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s2"&gt;"Negative værdier skal afvises."&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Testene er små, men de kontrollerer nogle vigtige regler.&lt;/p&gt;

&lt;p&gt;Den samlede pris skal være korrekt.&lt;/p&gt;

&lt;p&gt;Momsen skal være korrekt.&lt;/p&gt;

&lt;p&gt;Og negative tal må ikke accepteres.&lt;/p&gt;

&lt;p&gt;Hvis systemet skulle udvikles videre til egentlig fakturering, ville vi have brug for mere omfattende tests og håndtering af afrunding.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Hvad mangler vores beregner?
&lt;/h2&gt;

&lt;p&gt;Der mangler en hel del.&lt;/p&gt;

&lt;p&gt;Det er faktisk en vigtig del af denne tutorial.&lt;/p&gt;

&lt;p&gt;Værktøjet kan lægge tal sammen.&lt;/p&gt;

&lt;p&gt;Men det kan ikke selv afgøre:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hvor mange medarbejdere opgaven kræver&lt;/li&gt;
&lt;li&gt;Om et træ skal fjernes i sektioner&lt;/li&gt;
&lt;li&gt;Om en lift er nødvendig&lt;/li&gt;
&lt;li&gt;Hvor vanskelige adgangsforholdene er&lt;/li&gt;
&lt;li&gt;Om træets tilstand kræver særlige forholdsregler&lt;/li&gt;
&lt;li&gt;Hvordan oprydning og transport skal planlægges&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Disse forhold kræver faglig vurdering.&lt;/p&gt;

&lt;p&gt;Hvis vi forsøgte at gætte dem ud fra træhøjde alene, kunne beregneren skabe en falsk følelse af præcision.&lt;/p&gt;

&lt;p&gt;Det ville ikke gøre værktøjet mere hjælpsomt.&lt;/p&gt;

&lt;p&gt;Tværtimod kunne brugeren få et misvisende billede af den faktiske udgift.&lt;/p&gt;

&lt;p&gt;Derfor er det bedre at vise en tydelig begrænsning end at lade et tal se mere sikkert ud, end det er.&lt;/p&gt;

&lt;h2&gt;
  
  
  10. Hvorfor er gennemsigtighed vigtig?
&lt;/h2&gt;

&lt;p&gt;Når en bruger indtaster tal i en beregner, bør det være tydeligt, hvad resultatet betyder.&lt;/p&gt;

&lt;p&gt;Hvis resultatet kun er et eksempel, skal det stå klart.&lt;/p&gt;

&lt;p&gt;Hvis ekstra ydelser ikke er med, skal det også være tydeligt.&lt;/p&gt;

&lt;p&gt;Det samme gælder moms.&lt;/p&gt;

&lt;p&gt;En beregner, der viser en pris uden at forklare, om moms er inkluderet, kan skabe unødvendig forvirring.&lt;/p&gt;

&lt;p&gt;Derfor er vores resultat opdelt i flere linjer.&lt;/p&gt;

&lt;p&gt;Brugeren kan se arbejdsbeløbet, ekstra ydelser, moms og den samlede pris.&lt;/p&gt;

&lt;p&gt;Vi har samtidig skrevet, at resultatet ikke er et bindende tilbud.&lt;/p&gt;

&lt;p&gt;Det er ikke bare en juridisk detalje.&lt;/p&gt;

&lt;p&gt;Det er en del af god brugeroplevelse.&lt;/p&gt;

&lt;h2&gt;
  
  
  11. Et virkeligt eksempel: Træfældning pris i Kolding
&lt;/h2&gt;

&lt;p&gt;Skovhugger Lund har base i Kolding og udfører træfældning, beskæring og træpleje i Syddanmark.&lt;/p&gt;

&lt;p&gt;På virksomhedens prisside forklares det, at prisen på træfældning beregnes ud fra et forventet tidsforbrug.&lt;/p&gt;

&lt;p&gt;Træets størrelse, placering, tilstand og opgavens sværhedsgrad indgår i vurderingen.&lt;/p&gt;

&lt;p&gt;Virksomheden offentliggør også vejledende priser for forskellige typer træarbejde.&lt;/p&gt;

&lt;p&gt;Men en konkret opgave kræver stadig en vurdering af forholdene.&lt;/p&gt;

&lt;p&gt;På siden om &lt;a href="https://www.skovhuggerlund.dk/priser/" rel="noopener noreferrer"&gt;træfældning pris i Kolding&lt;/a&gt; beskriver Skovhugger Lund, hvorfor en simpel standardpris ikke passer til alle træer.&lt;/p&gt;

&lt;p&gt;Det er en god illustration af forskellen mellem en teknisk beregning og en faglig prisvurdering.&lt;/p&gt;

&lt;p&gt;Vores JavaScript-beregner er et selvstændigt undervisningseksempel.&lt;/p&gt;

&lt;p&gt;Den er ikke udviklet af Skovhugger Lund, indeholder ikke virksomhedens faktiske timepriser og kan ikke bruges til at bestille eller prissætte dens ydelser.&lt;/p&gt;

&lt;h2&gt;
  
  
  12. Hvordan kunne vi forbedre projektet?
&lt;/h2&gt;

&lt;p&gt;Hvis vi ville gøre værktøjet mere anvendeligt, kunne vi begynde med bedre input.&lt;/p&gt;

&lt;p&gt;Eksempelvis kunne vi tilføje en valgfri beskrivelse af opgaven.&lt;/p&gt;

&lt;p&gt;Men vi bør ikke lade den frie tekst bestemme en automatisk pris.&lt;/p&gt;

&lt;p&gt;I stedet kunne den bruges til at dokumentere brugerens forudsætninger.&lt;/p&gt;

&lt;p&gt;Vi kunne også tilføje en knap, der eksporterer selve regnestykket som tekst.&lt;/p&gt;

&lt;p&gt;Det ville gøre det lettere at gennemgå de indtastede beløb.&lt;/p&gt;

&lt;p&gt;En anden forbedring kunne være at gemme beregningens antagelser sammen med resultatet.&lt;/p&gt;

&lt;p&gt;Så ville brugeren tydeligt kunne se, hvilke tal der blev brugt.&lt;/p&gt;

&lt;p&gt;Men enhver udvidelse bør stadig bevare den vigtigste regel:&lt;/p&gt;

&lt;p&gt;Et beregnet eksempel skal ikke præsenteres som et fagligt godkendt tilbud.&lt;/p&gt;

&lt;h2&gt;
  
  
  13. En nyttig regel for udviklere
&lt;/h2&gt;

&lt;p&gt;Det er fristende at bygge en beregner, som altid giver brugeren et svar.&lt;/p&gt;

&lt;p&gt;Men ikke alle spørgsmål kan besvares præcist med en formel.&lt;/p&gt;

&lt;p&gt;Nogle kræver observationer, erfaring og vurdering af konkrete forhold.&lt;/p&gt;

&lt;p&gt;Det gælder mange servicefag.&lt;/p&gt;

&lt;p&gt;En prisberegner kan gøre matematikken tydelig.&lt;/p&gt;

&lt;p&gt;Den kan hjælpe brugeren med at forstå moms og omkostninger.&lt;/p&gt;

&lt;p&gt;Og den kan vise, hvordan forskellige beløb påvirker en samlet sum.&lt;/p&gt;

&lt;p&gt;Men den bør ikke foregive at vide mere, end den faktisk gør.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;God software viser ikke kun et resultat. Den viser også, hvor resultatet kommer fra, og hvad det ikke kan fortælle os.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Opsummering
&lt;/h2&gt;

&lt;p&gt;I denne tutorial har vi bygget en enkel prisberegner med HTML og vanilla JavaScript.&lt;/p&gt;

&lt;p&gt;Vi har arbejdet med:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Formularer og validering&lt;/li&gt;
&lt;li&gt;Rene beregningsfunktioner&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Intl.NumberFormat&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Dansk momsberegning&lt;/li&gt;
&lt;li&gt;Dynamisk resultatvisning&lt;/li&gt;
&lt;li&gt;Simple browser-tests&lt;/li&gt;
&lt;li&gt;Tydelig kommunikation af begrænsninger&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Vi har også set, hvorfor et teknisk regnestykke ikke kan erstatte en faglig vurdering.&lt;/p&gt;

&lt;p&gt;Det er måske den vigtigste pointe.&lt;/p&gt;

&lt;p&gt;For en gennemsigtig beregner er ikke nødvendigvis den, der giver flest tal.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Det er den, der hjælper brugeren med at forstå, hvad tallene betyder.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>html</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Byg en tilgængelig informationsside med HTML og CSS</title>
      <dc:creator>Skovhugger Lund</dc:creator>
      <pubDate>Fri, 13 Nov 2026 11:09:48 +0000</pubDate>
      <link>https://dev.to/skovhuggerlund/byg-en-tilgaengelig-informationsside-med-html-og-css-2mnn</link>
      <guid>https://dev.to/skovhuggerlund/byg-en-tilgaengelig-informationsside-med-html-og-css-2mnn</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw5wfhp3i0h3sx2kl979c.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw5wfhp3i0h3sx2kl979c.jpg" alt=" " width="680" height="383"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Forestil dig, at en boligforening skal have fjernet et stort træ fra et fællesareal.&lt;/p&gt;

&lt;p&gt;Beboerne skal vide, hvornår arbejdet finder sted, hvilke områder der bliver berørt, og hvem de kan kontakte.&lt;/p&gt;

&lt;p&gt;Det er en lille kommunikationsopgave, men den er oplagt til at lære noget om moderne HTML.&lt;/p&gt;

&lt;p&gt;For hvordan bygger man en informationsside, som er nem at læse på mobil, fungerer med tastatur og giver mening for en skærmlæser?&lt;/p&gt;

&lt;p&gt;I denne tutorial bygger vi en enkel side med &lt;strong&gt;semantisk HTML og almindelig CSS&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Ingen JavaScript-frameworks. Ingen eksterne komponentbiblioteker.&lt;/p&gt;

&lt;p&gt;Og faktisk behøver vi slet ikke JavaScript.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Eksemplet handler om formidling af planlagt arbejde. Det er ikke et system til at vurdere træers stabilitet, godkende afspærringer eller planlægge sikkerheden ved træfældning.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  1. Hvad skal siden kunne?
&lt;/h2&gt;

&lt;p&gt;Vi tager udgangspunkt i et tænkt eksempel.&lt;/p&gt;

&lt;p&gt;En boligforening i Kolding har planlagt professionel træfældning af et træ på et fællesareal.&lt;/p&gt;

&lt;p&gt;Beboerne skal kunne finde fire oplysninger:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hvad arbejdet handler om&lt;/li&gt;
&lt;li&gt;Hvornår det forventes at foregå&lt;/li&gt;
&lt;li&gt;Hvilke praktiske ændringer der er planlagt&lt;/li&gt;
&lt;li&gt;Hvem de kan kontakte ved spørgsmål&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Derudover ønsker vi, at siden fungerer godt på små skærme.&lt;/p&gt;

&lt;p&gt;Indholdet skal være let at læse, og besøgende skal kunne åbne supplerende information med tastaturet.&lt;/p&gt;

&lt;p&gt;Vi bygger det hele i én HTML-fil, så eksemplet er let at teste.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Start med semantisk HTML
&lt;/h2&gt;

&lt;p&gt;En webside kan bygges med mange &lt;code&gt;div&lt;/code&gt;-elementer.&lt;/p&gt;

&lt;p&gt;Men HTML har allerede elementer, der beskriver indholdets funktion.&lt;/p&gt;

&lt;p&gt;Vi bruger blandt andet:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;header&lt;/code&gt; til sidens introduktion&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;main&lt;/code&gt; til hovedindholdet&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;section&lt;/code&gt; til selvstændige afsnit&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;time&lt;/code&gt; til tidsoplysninger&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;details&lt;/code&gt; og &lt;code&gt;summary&lt;/code&gt; til supplerende information&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;footer&lt;/code&gt; til afsluttende oplysninger&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Det gør dokumentet mere forståeligt for både udviklere og hjælpemidler.&lt;/p&gt;

&lt;p&gt;Opret en fil med navnet &lt;code&gt;index.html&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!doctype html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"da"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"utf-8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"viewport"&lt;/span&gt;
        &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"width=device-width, initial-scale=1"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Information om planlagt træarbejde&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"description"&lt;/span&gt;
        &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Praktisk information om planlagt træarbejde på et fællesareal."&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"style.css"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;header&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"site-header"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"container"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"eyebrow"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Beboerinformation&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Planlagt træarbejde på fællesarealet&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;
        Her finder du den praktiske information
        om det kommende arbejde.
      &lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/header&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;main&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"main-content"&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"container"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;section&lt;/span&gt; &lt;span class="na"&gt;aria-labelledby&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"overview-title"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;h2&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"overview-title"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Overblik&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;
        Der er planlagt arbejde med et træ
        på ejendommens fællesareal.
      &lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/section&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/main&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;footer&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"site-footer"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"container"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Eksempel på en informationsside.&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/footer&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bemærk, at &lt;code&gt;lang="da"&lt;/code&gt; står på &lt;code&gt;html&lt;/code&gt;-elementet.&lt;/p&gt;

&lt;p&gt;Det fortæller browseren og relevante hjælpemidler, at sidens primære sprog er dansk.&lt;/p&gt;

&lt;p&gt;Vi bruger også kun ét &lt;code&gt;h1&lt;/code&gt;-element og en naturlig rækkefølge af overskrifter.&lt;/p&gt;

&lt;p&gt;Det er et godt udgangspunkt for en tydelig dokumentstruktur.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Vis tidspunktet med det rigtige HTML-element
&lt;/h2&gt;

&lt;p&gt;En dato kan selvfølgelig skrives som almindelig tekst.&lt;/p&gt;

&lt;p&gt;Men HTML har også elementet &lt;code&gt;time&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Det gør det muligt at angive datoen i et maskinlæsbart format.&lt;/p&gt;

&lt;p&gt;Tilføj denne sektion inde i &lt;code&gt;main&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;section&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"notice"&lt;/span&gt; &lt;span class="na"&gt;aria-labelledby&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"when-title"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;h2&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"when-title"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Hvornår foregår arbejdet?&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;
    Forventet arbejdsdag:
    &lt;span class="nt"&gt;&amp;lt;time&lt;/span&gt; &lt;span class="na"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"2027-04-15"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      15. april 2027
    &lt;span class="nt"&gt;&amp;lt;/time&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;
    Forventet tidsrum:
    &lt;span class="nt"&gt;&amp;lt;time&lt;/span&gt; &lt;span class="na"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"09:00"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;09.00&lt;span class="nt"&gt;&amp;lt;/time&amp;gt;&lt;/span&gt;
    til
    &lt;span class="nt"&gt;&amp;lt;time&lt;/span&gt; &lt;span class="na"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"14:00"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;14.00&lt;span class="nt"&gt;&amp;lt;/time&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"notice-note"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    Tidspunktet er foreløbigt og skal bekræftes
    af den ansvarlige for ejendommen.
  &lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/section&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Datoen er kun eksempeldata, ikke en virkelig aftale.&lt;/p&gt;

&lt;p&gt;I et rigtigt projekt skal den ansvarlige person bekræfte både dato og tidspunkt, før siden bliver offentliggjort.&lt;/p&gt;

&lt;p&gt;En vigtig detalje er, at maskinlæsbar HTML ikke gør oplysningerne korrekte.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;datetime&lt;/code&gt; beskriver datoens format.&lt;/p&gt;

&lt;p&gt;Det kontrollerer ikke, om arbejdet faktisk er planlagt.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Beskriv praktiske ændringer tydeligt
&lt;/h2&gt;

&lt;p&gt;En informationsside bør ikke gøre det svært for læseren at finde det vigtigste.&lt;/p&gt;

&lt;p&gt;Forestil dig, at en parkeringsplads eller en sti bliver påvirket af arbejdet.&lt;/p&gt;

&lt;p&gt;Så skal oplysningerne stå tydeligt.&lt;/p&gt;

&lt;p&gt;Vi kan bruge en almindelig liste.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;section&lt;/span&gt; &lt;span class="na"&gt;aria-labelledby&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"access-title"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;h2&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"access-title"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Praktiske forhold&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;
    Følgende forhold forventes at gælde,
    hvis arbejdsplanen godkendes:
  &lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;ul&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"info-list"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&lt;/span&gt;
      En del af fællesarealet kan være afspærret.
    &lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&lt;/span&gt;
      Enkelte adgangsveje kan blive påvirket.
    &lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&lt;/span&gt;
      Beboerne får besked om eventuelle
      ændringer i parkeringen.
    &lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&lt;/span&gt;
      Afspærringer skal respekteres.
    &lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;
    Den endelige afspærring og adgangsløsning
    fastlægges af de ansvarlige fagfolk
    og relevante parter.
  &lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/section&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Det er vigtigt ikke at lade denne type eksempeltekst fremstå som en godkendt sikkerhedsplan.&lt;/p&gt;

&lt;p&gt;En hjemmeside kan informere om en plan.&lt;/p&gt;

&lt;p&gt;Den kan ikke erstatte den konkrete vurdering af arbejdsområdet.&lt;/p&gt;

&lt;p&gt;Hvis en adgangsvej bliver ændret, skal informationen stemme med den faktiske, godkendte løsning.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Brug details og summary uden JavaScript
&lt;/h2&gt;

&lt;p&gt;Nogle oplysninger behøver ikke være synlige hele tiden.&lt;/p&gt;

&lt;p&gt;Det kan eksempelvis være en forklaring af, hvad beboerne skal gøre, hvis arbejdsdatoen ændrer sig.&lt;/p&gt;

&lt;p&gt;Her er &lt;code&gt;details&lt;/code&gt; og &lt;code&gt;summary&lt;/code&gt; nyttige.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;section&lt;/span&gt; &lt;span class="na"&gt;aria-labelledby&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"faq-title"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;h2&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"faq-title"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Ofte stillede spørgsmål&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;details&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;summary&amp;gt;&lt;/span&gt;Kan tidspunktet blive ændret?&lt;span class="nt"&gt;&amp;lt;/summary&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;
      Ja. Hvis planen ændres, skal informationen
      på siden opdateres.
    &lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/details&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;details&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;summary&amp;gt;&lt;/span&gt;Må beboerne bruge fællesarealet?&lt;span class="nt"&gt;&amp;lt;/summary&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;
      Følg de konkrete oplysninger fra
      ejendommens ansvarlige og respekter
      eventuelle afspærringer.
    &lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/details&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;details&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;summary&amp;gt;&lt;/span&gt;Hvem kan svare på spørgsmål?&lt;span class="nt"&gt;&amp;lt;/summary&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;
      Kontakt ejendommens administrator
      via de officielle kontaktoplysninger.
    &lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/details&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/section&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Browseren håndterer selv åbning og lukning.&lt;/p&gt;

&lt;p&gt;Det betyder, at vi ikke behøver skrive JavaScript til en almindelig informationsboks.&lt;/p&gt;

&lt;p&gt;De indbyggede elementer giver også en tastaturbetjent kontrol.&lt;/p&gt;

&lt;p&gt;Det er ofte bedre end at opfinde en klikbar &lt;code&gt;div&lt;/code&gt; og derefter forsøge at tilføje alle de manglende funktioner.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Tilføj et synligt kontaktområde
&lt;/h2&gt;

&lt;p&gt;Når noget ændrer sig på en ejendom, kan beboerne have spørgsmål.&lt;/p&gt;

&lt;p&gt;Derfor bør kontaktoplysninger være lette at finde.&lt;/p&gt;

&lt;p&gt;Vi laver en enkel kontaktsektion.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;section&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"contact-box"&lt;/span&gt;
         &lt;span class="na"&gt;aria-labelledby&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"contact-title"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;h2&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"contact-title"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Spørgsmål om arbejdet?&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;
    Kontakt ejendommens administrator
    for oplysninger om den planlagte opgave.
  &lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;strong&amp;gt;&lt;/span&gt;Kontakt:&lt;span class="nt"&gt;&amp;lt;/strong&amp;gt;&lt;/span&gt;
    Ejendommens administration
  &lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;
    De officielle kontaktoplysninger
    indsættes, når de er bekræftet.
  &lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/section&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I dette eksempel indsætter vi ikke et opdigtet telefonnummer eller en e-mailadresse.&lt;/p&gt;

&lt;p&gt;Det er bedre at vise tydeligt, at oplysningerne mangler, end at opfinde en kontaktperson.&lt;/p&gt;

&lt;p&gt;I et rigtigt system bør siden ikke publiceres som færdig beboerinformation, før de nødvendige kontaktoplysninger er bekræftet.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Gør designet responsivt med CSS
&lt;/h2&gt;

&lt;p&gt;Nu har vi en grundlæggende HTML-struktur.&lt;/p&gt;

&lt;p&gt;Næste skridt er at gøre siden behagelig at læse på både mobil og computer.&lt;/p&gt;

&lt;p&gt;Opret filen &lt;code&gt;style.css&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nt"&gt;root&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;color-scheme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;light&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nv"&gt;--page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mh"&gt;#f6f8f5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nv"&gt;--surface&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mh"&gt;#ffffff&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nv"&gt;--text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mh"&gt;#223228&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nv"&gt;--muted&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mh"&gt;#526658&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nv"&gt;--accent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mh"&gt;#235d40&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nv"&gt;--border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mh"&gt;#d6e2d8&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nv"&gt;--notice&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mh"&gt;#edf5ee&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;box-sizing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;border-box&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nt"&gt;body&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;--page&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;--text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;system-ui&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sans-serif&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;line-height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.7&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.container&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="kt"&gt;%&lt;/span&gt; &lt;span class="n"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;2rem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;780px&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;margin-inline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.site-header&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;3.5rem&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;--accent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;white&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.eyebrow&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.85rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;font-weight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;700&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;letter-spacing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.08em&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;text-transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;uppercase&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nt"&gt;h1&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;clamp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2rem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5vw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3rem&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;line-height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nt"&gt;h2&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.5rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;line-height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nt"&gt;main&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;padding-block&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2rem&lt;/span&gt; &lt;span class="mi"&gt;4rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nt"&gt;section&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;margin-block&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.5rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.5rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;--surface&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1px&lt;/span&gt; &lt;span class="n"&gt;solid&lt;/span&gt; &lt;span class="nf"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;--border&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.notice&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;--notice&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.notice-note&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;--muted&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.info-list&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;padding-left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.5rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.info-list&lt;/span&gt; &lt;span class="nt"&gt;li&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nt"&gt;li&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;margin-top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.75rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nt"&gt;details&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;border-bottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1px&lt;/span&gt; &lt;span class="n"&gt;solid&lt;/span&gt; &lt;span class="nf"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;--border&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.9rem&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nt"&gt;summary&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;pointer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;font-weight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;700&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nt"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nt"&gt;focus-visible&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;outline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3px&lt;/span&gt; &lt;span class="n"&gt;solid&lt;/span&gt; &lt;span class="nf"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;--accent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;outline-offset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.contact-box&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;border-left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5px&lt;/span&gt; &lt;span class="n"&gt;solid&lt;/span&gt; &lt;span class="nf"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;--accent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.site-footer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.5rem&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;--muted&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border-top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1px&lt;/span&gt; &lt;span class="n"&gt;solid&lt;/span&gt; &lt;span class="nf"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;--border&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;@media&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;max-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;600px&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nc"&gt;.site-header&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2rem&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;section&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.1rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;CSS'en bruger relative størrelser og en fleksibel container.&lt;/p&gt;

&lt;p&gt;Derfor kan siden tilpasse sig forskellige skærmbredder uden vandret scrolling fra selve layoutet.&lt;/p&gt;

&lt;p&gt;Vi bruger &lt;code&gt;clamp()&lt;/code&gt; til overskriften, så den bliver større på brede skærme og mindre på mobil.&lt;/p&gt;

&lt;p&gt;Derudover har vi en tydelig fokusmarkering på &lt;code&gt;summary&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Det hjælper brugere, som navigerer med tastaturet.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Test siden uden mus
&lt;/h2&gt;

&lt;p&gt;En hurtig tilgængelighedstest kræver ikke et avanceret værktøj.&lt;/p&gt;

&lt;p&gt;Åbn siden i browseren, og prøv at bruge den uden mus.&lt;/p&gt;

&lt;p&gt;Tryk på &lt;code&gt;Tab&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Når fokus når til en af vores &lt;code&gt;summary&lt;/code&gt;-overskrifter, bør den kunne aktiveres med tastaturet.&lt;/p&gt;

&lt;p&gt;Kontrollér også, at fokusmarkeringen er synlig.&lt;/p&gt;

&lt;p&gt;Læs siden i en smal mobilvisning.&lt;/p&gt;

&lt;p&gt;Er teksten stadig let at læse?&lt;/p&gt;

&lt;p&gt;Bliver noget vigtigt skjult?&lt;/p&gt;

&lt;p&gt;Er overskrifterne logisk organiseret?&lt;/p&gt;

&lt;p&gt;Det er en god start.&lt;/p&gt;

&lt;p&gt;Men en fuld tilgængelighedsvurdering kræver mere end en kort tastaturtest. Skærmlæsertest, zoom, kontrast og andre forhold bør også kontrolleres i et rigtigt projekt.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Hvorfor bør vi undgå farver som eneste signal?
&lt;/h2&gt;

&lt;p&gt;Forestil dig, at vi viser en grøn boks for almindelig information og en rød boks for ændringer.&lt;/p&gt;

&lt;p&gt;Hvis farven er det eneste, der forklarer betydningen, kan nogle brugere have svært ved at forstå informationen.&lt;/p&gt;

&lt;p&gt;Derfor bør vigtige meddelelser også have tydelig tekst.&lt;/p&gt;

&lt;p&gt;For eksempel:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"notice-note"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;strong&amp;gt;&lt;/span&gt;Ændret tidspunkt:&lt;span class="nt"&gt;&amp;lt;/strong&amp;gt;&lt;/span&gt;
  Den tidligere planlagte dato gælder ikke længere.
  Et nyt tidspunkt bliver oplyst senere.
&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Her behøver læseren ikke kunne se en bestemt farve for at forstå beskeden.&lt;/p&gt;

&lt;p&gt;Den samme tilgang kan bruges til fejl, advarsler og statusoplysninger.&lt;/p&gt;

&lt;h2&gt;
  
  
  10. Kan siden opdatere sig selv?
&lt;/h2&gt;

&lt;p&gt;Ikke med den løsning, vi har bygget her.&lt;/p&gt;

&lt;p&gt;Siden er statisk.&lt;/p&gt;

&lt;p&gt;Hvis datoen ændrer sig, skal den ansvarlige opdatere HTML-indholdet og offentliggøre den nye version.&lt;/p&gt;

&lt;p&gt;Det er ikke nødvendigvis en ulempe.&lt;/p&gt;

&lt;p&gt;For en lille boligforening med få planlagte opgaver kan en simpel statisk side være lettere at vedligeholde end et stort system.&lt;/p&gt;

&lt;p&gt;Hvis man senere ønsker et administrationspanel eller automatisk opdatering, kan projektet udvides.&lt;/p&gt;

&lt;p&gt;Men først skal det være klart, hvem der må ændre oplysningerne, og hvordan ændringer bliver godkendt.&lt;/p&gt;

&lt;p&gt;Automatisering er ikke en fordel, hvis den gør det lettere at offentliggøre forkerte oplysninger.&lt;/p&gt;

&lt;h2&gt;
  
  
  11. Hvorfor passer eksemplet til træfældning?
&lt;/h2&gt;

&lt;p&gt;Når træer skal fjernes tæt på boliger, er det vigtigt, at arbejdet planlægges efter de konkrete forhold.&lt;/p&gt;

&lt;p&gt;Skovhugger Lund er en træservicevirksomhed med base i Kolding.&lt;/p&gt;

&lt;p&gt;Virksomheden arbejder med træfældning, beskæring og træpleje for private kunder, virksomheder samt grundejer- og boligforeninger.&lt;/p&gt;

&lt;p&gt;På siden om &lt;a href="https://www.skovhuggerlund.dk/traefaeldning/" rel="noopener noreferrer"&gt;professionel træfældning i Kolding&lt;/a&gt; beskriver virksomheden forskellige arbejdsmetoder, herunder retningsbestemt fældning, klatring og brug af lift.&lt;/p&gt;

&lt;p&gt;Det viser, hvorfor en beboerinformation ikke bør fastlåse oplysninger om maskiner, adgang eller arbejdstid, før de konkrete forhold er vurderet.&lt;/p&gt;

&lt;p&gt;En informationsside kan gøre det nemmere at formidle beslutningerne.&lt;/p&gt;

&lt;p&gt;Men den skal bygge på bekræftede oplysninger.&lt;/p&gt;

&lt;p&gt;Vores kode er et selvstændigt undervisningseksempel. Den beskriver ikke en eksisterende digital løsning hos Skovhugger Lund.&lt;/p&gt;

&lt;h2&gt;
  
  
  12. Hvad ville jeg forbedre i en rigtig løsning?
&lt;/h2&gt;

&lt;p&gt;Hvis informationssiden skulle bruges i praksis, ville jeg begynde med indholdet.&lt;/p&gt;

&lt;p&gt;Hvem godkender datoen?&lt;/p&gt;

&lt;p&gt;Hvem skal informeres, hvis den ændrer sig?&lt;/p&gt;

&lt;p&gt;Og hvem sikrer, at siden ikke indeholder forældede oplysninger?&lt;/p&gt;

&lt;p&gt;Jeg ville også teste siden med forskellige skærmlæsere og kontrollere farvekontrast, tekstforstørrelse og tastaturnavigation.&lt;/p&gt;

&lt;p&gt;Hvis siden blev udvidet til flere ejendomme, kunne man overveje en fælles indholdsmodel, så hver meddelelse har samme struktur.&lt;/p&gt;

&lt;p&gt;Men det er vigtigt ikke at gøre løsningen mere kompleks end nødvendigt.&lt;/p&gt;

&lt;p&gt;En lille, gennemarbejdet informationsside kan være mere nyttig end et stort system med mange funktioner, som ingen vedligeholder.&lt;/p&gt;

&lt;h2&gt;
  
  
  Afslutning
&lt;/h2&gt;

&lt;p&gt;Vi har nu bygget en enkel informationsside med:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Semantisk HTML&lt;/li&gt;
&lt;li&gt;Maskinlæsbare tidsoplysninger&lt;/li&gt;
&lt;li&gt;En responsiv CSS-struktur&lt;/li&gt;
&lt;li&gt;Tastaturbetjente informationsbokse&lt;/li&gt;
&lt;li&gt;Tydelig tekst til praktiske meddelelser&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Og vi gjorde det uden JavaScript.&lt;/p&gt;

&lt;p&gt;Det er en nyttig påmindelse om, at moderne webudvikling ikke altid behøver begynde med et framework.&lt;/p&gt;

&lt;p&gt;Nogle gange er den bedste løsning almindelig HTML, gennemtænkt CSS og indhold, som mennesker faktisk kan bruge.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;God webudvikling handler ikke kun om, at en side ser flot ud. Den handler om, at informationen er tydelig, tilgængelig og til at stole på.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>html</category>
      <category>css</category>
      <category>webdev</category>
      <category>a11y</category>
    </item>
    <item>
      <title>TypeScript i praksis: Datamodeller til stubfræsning</title>
      <dc:creator>Skovhugger Lund</dc:creator>
      <pubDate>Fri, 13 Nov 2026 10:28:36 +0000</pubDate>
      <link>https://dev.to/skovhuggerlund/typescript-i-praksis-datamodeller-til-stubfraesning-24hm</link>
      <guid>https://dev.to/skovhuggerlund/typescript-i-praksis-datamodeller-til-stubfraesning-24hm</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy7rq5d85zhpxd1keov2b.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy7rq5d85zhpxd1keov2b.jpg" alt=" " width="680" height="453"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Forestil dig en virksomhed, som modtager forespørgsler om fjernelse af træstubbe.&lt;/p&gt;

&lt;p&gt;Én kunde har en enkelt stub midt i græsplænen. En anden har fem stubbe langs en gammel hæk. En tredje ønsker også at få undersøgt rødder, der ligger i vejen for en ny sti.&lt;/p&gt;

&lt;p&gt;Hvordan kan vi beskrive disse opgaver i et program uden at miste vigtige oplysninger?&lt;/p&gt;

&lt;p&gt;Vi kan begynde med TypeScript.&lt;/p&gt;

&lt;p&gt;I denne tutorial bygger vi en lille datamodel, validerer indholdet og skriver automatiske tests.&lt;/p&gt;

&lt;p&gt;Vi beregner ikke priser og afgør ikke, om maskinarbejde kan udføres sikkert. De beslutninger kræver faglige oplysninger, som vores eksempel ikke har.&lt;/p&gt;

&lt;p&gt;Målet er blot at skabe &lt;strong&gt;pålidelige administrative data&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Hvorfor bruge TypeScript?
&lt;/h2&gt;

&lt;p&gt;JavaScript er fleksibelt.&lt;/p&gt;

&lt;p&gt;Det kan være en fordel, men også en udfordring, hvis mange oplysninger skal registreres på samme måde.&lt;/p&gt;

&lt;p&gt;Forestil dig, at et program modtager følgende objekt:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```javascript id="st01"&lt;br&gt;
const job = {&lt;br&gt;
  area: "Kolding",&lt;br&gt;
  stumpCount: "three",&lt;br&gt;
  access: "unknown"&lt;br&gt;
};&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Her er `stumpCount` en tekst, selv om vi forventer et tal.

Det kan skabe problemer senere.

TypeScript kan hjælpe os med at beskrive den forventede struktur og opdage mange fejl under udviklingen.

Men der er en vigtig begrænsning:

**TypeScript-typer kontrollerer ikke automatisk data, der kommer udefra, mens programmet kører.**

Derfor skal vi både bruge typer og almindelig validering.

## 2. Definér opgavens oplysninger

Vi starter med en model til registrering af træstubbe.

Opret en fil med navnet `stump-jobs.ts`.



```typescript id="st02"
type AccessType =
  | "open"
  | "limited"
  | "unknown";

type GroundUse =
  | "lawn"
  | "planting"
  | "paving"
  | "undecided";

type StumpJob = {
  id: string;
  area: string;
  stumpCount: number;
  access: AccessType;
  futureUse: GroundUse;
  undergroundServicesChecked: boolean;
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Modellen indeholder seks felter.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;id&lt;/code&gt; identificerer registreringen.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;area&lt;/code&gt; beskriver det overordnede geografiske område.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;stumpCount&lt;/code&gt; fortæller, hvor mange stubbe der er registreret.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;access&lt;/code&gt; beskriver de foreløbige adgangsforhold.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;futureUse&lt;/code&gt; viser, hvad kunden ønsker at bruge området til bagefter.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;undergroundServicesChecked&lt;/code&gt; angiver, om oplysninger om nedgravede installationer er blevet gennemgået administrativt.&lt;/p&gt;

&lt;p&gt;Det sidste felt er vigtigt at forstå korrekt.&lt;/p&gt;

&lt;p&gt;En værdi på &lt;code&gt;true&lt;/code&gt; betyder ikke, at stedet er sikkert at fræse. Det betyder kun, at den definerede kontrol er registreret som gennemført.&lt;/p&gt;

&lt;p&gt;Den faktiske vurdering kræver en separat arbejdsproces.&lt;/p&gt;
&lt;h2&gt;
  
  
  3. Opret et eksempel på en opgave
&lt;/h2&gt;

&lt;p&gt;Nu kan vi oprette en registrering med den nye type.&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```typescript id="st03"&lt;br&gt;
const exampleJob: StumpJob = {&lt;br&gt;
  id: "JOB-001",&lt;br&gt;
  area: "Kolding",&lt;br&gt;
  stumpCount: 3,&lt;br&gt;
  access: "limited",&lt;br&gt;
  futureUse: "lawn",&lt;br&gt;
  undergroundServicesChecked: false&lt;br&gt;
};&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Denne opgave indeholder tre stubbe.

Adgangsforholdene er foreløbigt beskrevet som begrænsede.

Kunden ønsker at bruge området til græsplæne.

Og oplysninger om nedgravede installationer er endnu ikke markeret som gennemgået.

Det er fiktive data, som kun bruges i tutorialen.

## 4. Hvorfor skal vi validere data igen?

Forestil dig, at en anden del af programmet sender dette:



```typescript id="st04"
const incomingData: unknown = {
  id: "JOB-002",
  area: "Kolding",
  stumpCount: -4,
  access: "very easy",
  futureUse: "lawn",
  undergroundServicesChecked: "yes"
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Objektet indeholder flere problemer.&lt;/p&gt;

&lt;p&gt;Antallet af stubbe er negativt.&lt;/p&gt;

&lt;p&gt;Adgangstypen findes ikke på vores liste.&lt;/p&gt;

&lt;p&gt;Og den sidste værdi er en tekst i stedet for en boolean.&lt;/p&gt;

&lt;p&gt;Når data kommer fra en formular, en fil eller en API, bør vi behandle dem som &lt;code&gt;unknown&lt;/code&gt;, indtil de er kontrolleret.&lt;/p&gt;

&lt;p&gt;Det er netop det, vi skal gøre nu.&lt;/p&gt;
&lt;h2&gt;
  
  
  5. Byg en valideringsfunktion
&lt;/h2&gt;

&lt;p&gt;Først opretter vi en type til resultatet.&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```typescript id="st05"&lt;br&gt;
type ValidationResult =&lt;br&gt;
  | {&lt;br&gt;
      valid: true;&lt;br&gt;
      data: StumpJob;&lt;br&gt;
    }&lt;br&gt;
  | {&lt;br&gt;
      valid: false;&lt;br&gt;
      errors: string[];&lt;br&gt;
    };&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Dette er en *discriminated union*.

Når `valid` er `true`, ved TypeScript, at resultatet indeholder `data`.

Når `valid` er `false`, indeholder det en liste over fejl.

Nu skriver vi selve funktionen:



```typescript id="st06"
function validateStumpJob(
  input: unknown
): ValidationResult {
  const errors: string[] = [];

  if (
    typeof input !== "object" ||
    input === null ||
    Array.isArray(input)
  ) {
    return {
      valid: false,
      errors: ["Opgaven skal være et objekt."]
    };
  }

  const item = input as Record&amp;lt;string, unknown&amp;gt;;

  if (
    typeof item.id !== "string" ||
    item.id.trim().length === 0
  ) {
    errors.push("Opgaven mangler et gyldigt ID.");
  }

  if (
    typeof item.area !== "string" ||
    item.area.trim().length === 0
  ) {
    errors.push("Område mangler.");
  }

  if (
    typeof item.stumpCount !== "number" ||
    !Number.isInteger(item.stumpCount) ||
    item.stumpCount &amp;lt; 1
  ) {
    errors.push(
      "Antal stubbe skal være et positivt heltal."
    );
  }

  const accessValues = [
    "open",
    "limited",
    "unknown"
  ];

  if (!accessValues.includes(String(item.access))) {
    errors.push("Ugyldig adgangstype.");
  }

  const groundUses = [
    "lawn",
    "planting",
    "paving",
    "undecided"
  ];

  if (!groundUses.includes(String(item.futureUse))) {
    errors.push("Ugyldig anvendelse af området.");
  }

  if (
    typeof item.undergroundServicesChecked !==
    "boolean"
  ) {
    errors.push(
      "Kontrolstatus skal være true eller false."
    );
  }

  if (errors.length &amp;gt; 0) {
    return {
      valid: false,
      errors
    };
  }

  return {
    valid: true,
    data: item as StumpJob
  };
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Funktionen kontrollerer hvert felt.&lt;/p&gt;

&lt;p&gt;Hvis noget mangler eller har en ugyldig værdi, returnerer den en fejl.&lt;/p&gt;

&lt;p&gt;Hvis alle oplysninger opfylder vores model, returnerer den det validerede objekt.&lt;/p&gt;

&lt;p&gt;Bemærk forskellen mellem en korrekt datastruktur og en korrekt faglig beslutning.&lt;/p&gt;

&lt;p&gt;Funktionen kontrollerer kun strukturen.&lt;/p&gt;

&lt;p&gt;Den kan ikke vide, om den registrerede information stemmer med de faktiske forhold i haven.&lt;/p&gt;
&lt;h2&gt;
  
  
  6. Test resultatet
&lt;/h2&gt;

&lt;p&gt;Vi starter med det gyldige eksempel.&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```typescript id="st07"&lt;br&gt;
const result = validateStumpJob(exampleJob);&lt;/p&gt;

&lt;p&gt;if (result.valid) {&lt;br&gt;
  console.log(&lt;br&gt;
    &lt;code&gt;Registreret ${result.data.stumpCount} stubbe&lt;/code&gt;&lt;br&gt;
  );&lt;br&gt;
} else {&lt;br&gt;
  console.log(result.errors);&lt;br&gt;
}&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


For vores eksempel bliver resultatet:



```text id="st08"
Registreret 3 stubbe
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Hvis vi bruger de ugyldige data, får vi i stedet en liste med fejl.&lt;/p&gt;

&lt;p&gt;Det gør det nemmere at give præcis feedback til den del af programmet, der sender oplysningerne.&lt;/p&gt;
&lt;h2&gt;
  
  
  7. Tilføj en funktion til administrative opfølgninger
&lt;/h2&gt;

&lt;p&gt;En registreret opgave kan være teknisk gyldig, selv om nogle oplysninger endnu mangler at blive undersøgt.&lt;/p&gt;

&lt;p&gt;For eksempel kan adgangsforholdene være ukendte.&lt;/p&gt;

&lt;p&gt;Det betyder ikke, at registreringen skal afvises.&lt;/p&gt;

&lt;p&gt;I stedet kan vi oprette en liste over administrative opfølgninger.&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```typescript id="st09"&lt;br&gt;
function getFollowUps(job: StumpJob): string[] {&lt;br&gt;
  const followUps: string[] = [];&lt;/p&gt;

&lt;p&gt;if (job.access === "unknown") {&lt;br&gt;
    followUps.push(&lt;br&gt;
      "Adgangsforhold skal beskrives."&lt;br&gt;
    );&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;if (job.access === "limited") {&lt;br&gt;
    followUps.push(&lt;br&gt;
      "Maskinadgang skal vurderes af fagperson."&lt;br&gt;
    );&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;if (job.futureUse === "undecided") {&lt;br&gt;
    followUps.push(&lt;br&gt;
      "Kundens planer for området skal afklares."&lt;br&gt;
    );&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;if (!job.undergroundServicesChecked) {&lt;br&gt;
    followUps.push(&lt;br&gt;
      "Oplysninger om nedgravede installationer " +&lt;br&gt;
      "skal gennemgås."&lt;br&gt;
    );&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;return followUps;&lt;br&gt;
}&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Funktionen returnerer ikke en pris.

Den vælger heller ikke maskine, arbejdsdybde eller sikkerhedsafstande.

Den gør kun opmærksom på oplysninger, der kræver yderligere behandling.

For vores eksempel får vi to opfølgninger:



```typescript id="st10"
console.log(getFollowUps(exampleJob));
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Resultat:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="st11"&lt;br&gt;
[&lt;br&gt;
  "Maskinadgang skal vurderes af fagperson.",&lt;br&gt;
  "Oplysninger om nedgravede installationer skal gennemgås."&lt;br&gt;
]&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Det er et eksempel på automatisering, der hjælper en medarbejder uden at erstatte vedkommendes vurdering.

## 8. Skriv automatiske tests

Nu skal vi sikre, at vores funktioner opfører sig som forventet.

Vi holder eksemplet enkelt og bruger en lille testfunktion uden eksterne testbiblioteker.



```typescript id="st12"
function assert(
  condition: boolean,
  message: string
): void {
  if (!condition) {
    throw new Error(`Test fejlede: ${message}`);
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Herefter tilføjer vi vores tests.&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```typescript id="st13"&lt;br&gt;
const validResult = validateStumpJob(exampleJob);&lt;/p&gt;

&lt;p&gt;assert(&lt;br&gt;
  validResult.valid,&lt;br&gt;
  "En gyldig opgave skal accepteres."&lt;br&gt;
);&lt;/p&gt;

&lt;p&gt;const invalidCount = validateStumpJob({&lt;br&gt;
  ...exampleJob,&lt;br&gt;
  stumpCount: -2&lt;br&gt;
});&lt;/p&gt;

&lt;p&gt;assert(&lt;br&gt;
  !invalidCount.valid,&lt;br&gt;
  "Et negativt antal skal afvises."&lt;br&gt;
);&lt;/p&gt;

&lt;p&gt;const invalidAccess = validateStumpJob({&lt;br&gt;
  ...exampleJob,&lt;br&gt;
  access: "very easy"&lt;br&gt;
});&lt;/p&gt;

&lt;p&gt;assert(&lt;br&gt;
  !invalidAccess.valid,&lt;br&gt;
  "Ukendte adgangstyper skal afvises."&lt;br&gt;
);&lt;/p&gt;

&lt;p&gt;const followUps = getFollowUps(exampleJob);&lt;/p&gt;

&lt;p&gt;assert(&lt;br&gt;
  followUps.length === 2,&lt;br&gt;
  "Eksemplet skal give to opfølgninger."&lt;br&gt;
);&lt;/p&gt;

&lt;p&gt;console.log("Alle tests bestået.");&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Hvis alle tests passerer, bliver resultatet:



```text id="st14"
Alle tests bestået.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Hvis en test fejler, bliver der kastet en fejl.&lt;/p&gt;

&lt;p&gt;Det er en enkel måde at lære automatiseret testning på.&lt;/p&gt;

&lt;p&gt;I en større applikation kan man bruge et egentligt testbibliotek, men principperne er de samme:&lt;/p&gt;

&lt;p&gt;Definér den forventede adfærd, og kontrollér den automatisk.&lt;/p&gt;
&lt;h2&gt;
  
  
  9. Sådan kører du eksemplet
&lt;/h2&gt;

&lt;p&gt;Du skal have Node.js og TypeScript installeret.&lt;/p&gt;

&lt;p&gt;Opret et lille projekt:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```bash id="st15"&lt;br&gt;
mkdir stump-jobs&lt;br&gt;
cd stump-jobs&lt;br&gt;
npm init -y&lt;br&gt;
npm install --save-dev typescript&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Gem alle TypeScript-eksemplerne i samme fil, `stump-jobs.ts`, i den rækkefølge, de er vist.

De ugyldige eksempeldata kan bruges direkte som testinput.

Kompilér filen:



```bash id="st16"
npx tsc stump-jobs.ts --target es2020 --module commonjs --strict
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Kør derefter den genererede JavaScript-fil:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```bash id="st17"&lt;br&gt;
node stump-jobs.js&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Du bør se output fra både registreringen, opfølgningerne og testene.

Før koden bruges i et rigtigt system, bør tests og produktionskode placeres i separate filer.

## 10. Hvorfor må vi ikke automatisere arbejdsmetoden?

Nu har vi en datamodel, som kan håndtere oplysninger om stubfræsning.

Det kunne være fristende at udvide den med regler som:

"Hvis stubben er lille, skal der bruges en bestemt maskine."

Eller:

"Hvis der er god adgang, kan arbejdet udføres uden yderligere kontrol."

Men den slags regler ville være misvisende.

Størrelse og adgang fortæller ikke hele historien.

Der kan være kabler og rør under jorden, sten omkring stubben eller levende trærødder tæt på arbejdsområdet.

Derfor bør et almindeligt administrationssystem ikke automatisk fastslå, at en opgave er sikker.

Det bør i stedet gøre det tydeligt, hvilke oplysninger der er registreret, og hvad der kræver en faglig vurdering.

**Et gyldigt objekt er ikke det samme som en sikker arbejdsplads.**

## 11. En virkelig branchekontekst

Skovhugger Lund er en træservicevirksomhed med base i Kolding.

Virksomheden tilbyder blandt andet stubfræsning, rodfræsning og fjernelse af gamle hækstubbe i Syddanmark.

Opgaverne kan variere fra én enkelt stub i en privat have til større områder med mange stubbe og rødder.

På virksomhedens side om [stubfræsning i Kolding](https://www.skovhuggerlund.dk/stubfraesning/) beskrives både de forskellige maskintyper og muligheden for efterbehandling af området.

Det er et godt eksempel på, hvorfor en datamodel kan indeholde oplysninger om antal, adgang og fremtidig anvendelse.

Disse felter kan hjælpe med at organisere en forespørgsel, men de kan ikke alene afgøre, hvordan arbejdet skal udføres.

Tutorialen er et selvstændigt programmeringseksempel. Den beskriver ikke et system, som Skovhugger Lund bruger i dag.

## 12. Hvad ville jeg forbedre i en rigtig applikation?

Vores model er lille og overskuelig, men en produktionsløsning ville kræve mere arbejde.

Jeg ville især se på fire områder.

**Servervalidering:** Alle data, der kommer fra brugere eller eksterne tjenester, skal valideres på serveren.

**Databeskyttelse:** Et egentligt kundesystem skal håndtere personoplysninger sikkert og kun opbevare nødvendige data.

**Adgangskontrol:** Det skal være tydeligt, hvem der må oprette, læse og ændre opgaver.

**Sporbarhed:** Ændringer i administrative statusser bør kunne dokumenteres, så medarbejdere kan forstå, hvad der er blevet registreret.

Man kan også overveje at bruge et bibliotek til skemavalidering, hvis datamodellen bliver større.

Men det er en god øvelse først at forstå valideringen uden ekstra afhængigheder.

## Afslutning

Vi har nu oprettet en typesikker datamodel, bygget en valideringsfunktion og skrevet automatiske tests.

Eksemplet viser, hvordan TypeScript kan hjælpe med at skabe mere ensartede registreringer.

Det viser også, hvorfor et godt system skal kende sine egne begrænsninger.

Software kan sikre, at et antal er positivt, og at en opgavetype findes på en godkendt liste.

Men den kan ikke ud fra disse oplysninger alene vurdere jordforhold, maskinsikkerhed eller arbejdsmetoder.

Den forskel er vigtig.

**God kode organiserer det, vi ved, og gør det tydeligt, hvad vi stadig mangler at undersøge.**
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>typescript</category>
      <category>tutorial</category>
      <category>webdev</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Byg en enkel formular til træbeskæring med JavaScript</title>
      <dc:creator>Skovhugger Lund</dc:creator>
      <pubDate>Fri, 13 Nov 2026 09:48:01 +0000</pubDate>
      <link>https://dev.to/skovhuggerlund/byg-en-enkel-formular-til-traebeskaering-med-javascript-46d8</link>
      <guid>https://dev.to/skovhuggerlund/byg-en-enkel-formular-til-traebeskaering-med-javascript-46d8</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0dg3kjfd56nz5e43uczr.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0dg3kjfd56nz5e43uczr.jpg" alt=" " width="680" height="383"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;En kunde skriver: "Vi har et træ, der skal beskæres."&lt;/p&gt;

&lt;p&gt;Det lyder som en enkel henvendelse. Men for virksomheden mangler der stadig flere oplysninger.&lt;/p&gt;

&lt;p&gt;Hvor står træet? Hvilken type arbejde drejer det sig om? Hvad ønsker kunden at få ændret?&lt;/p&gt;

&lt;p&gt;En digital formular kan hjælpe med at samle svarene.&lt;/p&gt;

&lt;p&gt;I denne tutorial bygger vi en lille JavaScript-løsning til at kontrollere og organisere forespørgsler om træbeskæring.&lt;/p&gt;

&lt;p&gt;Vi bruger almindelig HTML og JavaScript uden eksterne biblioteker.&lt;/p&gt;

&lt;p&gt;Eksemplet handler om &lt;strong&gt;datahåndtering&lt;/strong&gt;, ikke om at automatisere faglige beslutninger om træers sundhed eller sikkerhed.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Hvad skal formularen kunne?
&lt;/h2&gt;

&lt;p&gt;Vi forestiller os en mindre træservicevirksomhed i Kolding.&lt;/p&gt;

&lt;p&gt;Virksomheden modtager forespørgsler om blandt andet beskæring, kroneløft og vurdering af beskadigede træer.&lt;/p&gt;

&lt;p&gt;Vores eksempel skal kunne:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Registrere kundens område.&lt;/li&gt;
&lt;li&gt;Lade brugeren vælge en opgavetype.&lt;/li&gt;
&lt;li&gt;Modtage en kort beskrivelse.&lt;/li&gt;
&lt;li&gt;Kontrollere obligatoriske oplysninger.&lt;/li&gt;
&lt;li&gt;Oprette et struktureret JavaScript-objekt.&lt;/li&gt;
&lt;li&gt;Vise en bekræftelse uden at sende data til en server.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Vi gemmer ikke navn, adresse eller telefonnummer i denne demonstration.&lt;/p&gt;

&lt;p&gt;Det gør eksemplet lettere at teste uden rigtige personoplysninger.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Start med en enkel HTML-formular
&lt;/h2&gt;

&lt;p&gt;Opret filen &lt;code&gt;index.html&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!doctype html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"da"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"UTF-8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"viewport"&lt;/span&gt;
        &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"width=device-width, initial-scale=1"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Registrering af træopgave&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;main&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Beskriv din træopgave&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"treeForm"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"area"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Område&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;select&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"area"&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"area"&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;option&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Vælg område&lt;span class="nt"&gt;&amp;lt;/option&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;option&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Kolding"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Kolding&lt;span class="nt"&gt;&amp;lt;/option&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;option&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Fredericia"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Fredericia&lt;span class="nt"&gt;&amp;lt;/option&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;option&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Vejle"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Vejle&lt;span class="nt"&gt;&amp;lt;/option&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;option&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Middelfart"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Middelfart&lt;span class="nt"&gt;&amp;lt;/option&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/select&amp;gt;&lt;/span&gt;

      &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"service"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Opgavetype&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;select&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"service"&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"service"&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;option&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Vælg opgave&lt;span class="nt"&gt;&amp;lt;/option&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;option&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Beskæring"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Beskæring&lt;span class="nt"&gt;&amp;lt;/option&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;option&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Kroneløft"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Kroneløft&lt;span class="nt"&gt;&amp;lt;/option&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;option&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Vurdering"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Faglig vurdering&lt;span class="nt"&gt;&amp;lt;/option&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/select&amp;gt;&lt;/span&gt;

      &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"details"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Beskrivelse&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;textarea&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"details"&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"details"&lt;/span&gt;
        &lt;span class="na"&gt;minlength&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"15"&lt;/span&gt;
        &lt;span class="na"&gt;maxlength&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"1000"&lt;/span&gt;
        &lt;span class="na"&gt;required&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;gt;&amp;lt;/textarea&amp;gt;&lt;/span&gt;

      &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        Kontrollér oplysninger
      &lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"feedback"&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"status"&lt;/span&gt;
       &lt;span class="na"&gt;aria-live&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"polite"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/main&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;script&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"app.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Formularen bruger almindelige HTML-felter.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;required&lt;/code&gt; fortæller browseren, at et felt skal udfyldes.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;minlength&lt;/code&gt; og &lt;code&gt;maxlength&lt;/code&gt; begrænser længden af beskrivelsen.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;aria-live="polite"&lt;/code&gt; gør det muligt for understøttede hjælpemidler at opfange ændringer i statusbeskeden.&lt;/p&gt;

&lt;p&gt;Dette er en lille demonstration, ikke et færdigt tilgængelighedsdesign. En rigtig løsning bør også testes med tastatur og skærmlæser.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Beskriv data med et enkelt objekt
&lt;/h2&gt;

&lt;p&gt;Når formularen bliver udfyldt, vil vi omsætte værdierne til et ensartet objekt.&lt;/p&gt;

&lt;p&gt;Det kan eksempelvis se sådan ud:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;area&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"Kolding"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"Beskæring"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;details&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"En gren vokser tæt på terrassen."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"Afventer faglig vurdering"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bemærk feltet &lt;code&gt;status&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Vi registrerer ikke, at træet er farligt, eller at en bestemt beskæringsmetode er korrekt.&lt;/p&gt;

&lt;p&gt;Vi registrerer kun, at forespørgslen kræver faglig vurdering.&lt;/p&gt;

&lt;p&gt;Det er en vigtig designbeslutning.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Valider oplysningerne med JavaScript
&lt;/h2&gt;

&lt;p&gt;Opret nu filen &lt;code&gt;app.js&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Vi starter med en funktion, der kontrollerer vores data.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;allowedAreas&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="s2"&gt;"Kolding"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s2"&gt;"Fredericia"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s2"&gt;"Vejle"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s2"&gt;"Middelfart"&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;allowedServices&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="s2"&gt;"Beskæring"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s2"&gt;"Kroneløft"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s2"&gt;"Vurdering"&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;validateRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;allowedAreas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;area&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Vælg et gyldigt område."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;allowedServices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Vælg en gyldig opgavetype."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;details&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="s2"&gt;"string"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
    &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;details&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s2"&gt;"Beskrivelsen skal være mindst 15 tegn."&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;details&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s2"&gt;"string"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
    &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;details&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s2"&gt;"Beskrivelsen må højst være 1000 tegn."&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;valid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;errors&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Vi bruger &lt;code&gt;Set&lt;/code&gt;, fordi vi ønsker at kontrollere, om en værdi findes på en liste over tilladte muligheder.&lt;/p&gt;

&lt;p&gt;Det er enklere end at skrive mange individuelle betingelser.&lt;/p&gt;

&lt;p&gt;Funktionen returnerer både et resultat og eventuelle fejl.&lt;/p&gt;

&lt;p&gt;Den kan derfor bruges af en formular, en test eller en fremtidig API-integration.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Håndtér formularens submit-event
&lt;/h2&gt;

&lt;p&gt;Nu skal vi forbinde HTML og JavaScript.&lt;/p&gt;

&lt;p&gt;Tilføj dette nederst i &lt;code&gt;app.js&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"#treeForm"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;feedback&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"#feedback"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"submit"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;formData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;FormData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;area&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"area"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"service"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;details&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="n"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"details"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"Afventer faglig vurdering"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;validateRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;valid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;feedback&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
      &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;" "&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;feedback&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="s2"&gt;"Oplysningerne er kontrolleret. "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="s2"&gt;"De er ikke blevet sendt eller gemt."&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Her kan en rigtig applikation senere&lt;/span&gt;
  &lt;span class="c1"&gt;// integreres med en sikker backend.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;event.preventDefault()&lt;/code&gt; forhindrer formularens normale indsendelse.&lt;/p&gt;

&lt;p&gt;Vi bruger &lt;code&gt;FormData&lt;/code&gt; til at hente værdierne.&lt;/p&gt;

&lt;p&gt;Dernæst kalder vi valideringsfunktionen.&lt;/p&gt;

&lt;p&gt;Hvis oplysningerne er gyldige, viser programmet en bekræftelse.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Der bliver ikke sendt nogen oplysninger til en server.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Derfor skal denne demonstration heller ikke beskrives som et fungerende bookingsystem.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Test valideringen
&lt;/h2&gt;

&lt;p&gt;En god formular skal ikke kun virke, når brugeren gør alt korrekt.&lt;/p&gt;

&lt;p&gt;Den skal også håndtere fejl.&lt;/p&gt;

&lt;p&gt;Vi kan teste funktionen direkte i browserens konsol.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;testRequest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;area&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"Kolding"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"Beskæring"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;details&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"Kort"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"Afventer faglig vurdering"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nb"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;validateRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;testRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Resultatet bliver:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;valid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s2"&gt;"Beskrivelsen skal være mindst 15 tegn."&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ændr derefter beskrivelsen:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="n"&gt;testRequest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;details&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="s2"&gt;"En gren hænger lavt over indkørslen."&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nb"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;validateRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;testRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nu bliver resultatet:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;valid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Vi har dermed en grundlæggende kontrol, som også kan testes uden at udfylde formularen manuelt.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Gør fejlbeskederne nyttige
&lt;/h2&gt;

&lt;p&gt;En god fejlbesked fortæller brugeren, hvad der skal ændres.&lt;/p&gt;

&lt;p&gt;Sammenlign disse to beskeder.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Uklar besked:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Ugyldige data.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Mere hjælpsom besked:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Beskrivelsen skal være mindst 15 tegn.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Den sidste besked gør det lettere at rette fejlen.&lt;/p&gt;

&lt;p&gt;I en større løsning kan man også vise fejlbeskeder ved de enkelte felter og sørge for korrekt tilknytning med &lt;code&gt;aria-describedby&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Man bør ikke kun bruge farver til at vise fejl.&lt;/p&gt;

&lt;p&gt;Teksten skal også forklare problemet.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Hvorfor bør vi ikke beregne træets risiko automatisk?
&lt;/h2&gt;

&lt;p&gt;Forestil dig, at en bruger skriver:&lt;/p&gt;

&lt;p&gt;"Træet hælder mod huset."&lt;/p&gt;

&lt;p&gt;Det kan være en vigtig observation.&lt;/p&gt;

&lt;p&gt;Men teksten fortæller ikke, om træet er ustabilt.&lt;/p&gt;

&lt;p&gt;Et træ kan have vokset med en naturlig hældning i mange år.&lt;/p&gt;

&lt;p&gt;Et andet træ kan have ændret hældning pludseligt på grund af problemer omkring rødderne.&lt;/p&gt;

&lt;p&gt;Forskellen kan være meget vigtig.&lt;/p&gt;

&lt;p&gt;Derfor bør en simpel formular ikke omsætte bestemte ord til konklusioner som:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Træet er sikkert.&lt;/li&gt;
&lt;li&gt;Træet er farligt.&lt;/li&gt;
&lt;li&gt;Træet skal fældes.&lt;/li&gt;
&lt;li&gt;En bestemt gren skal fjernes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Det kræver en faglig vurdering af de konkrete forhold.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Software kan indsamle observationer. Det betyder ikke, at softwaren kan vurdere træets stabilitet.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Ved mistanke om akut fare bør brugeren heller ikke være afhængig af en almindelig onlineformular. En virkelig løsning skal tydeligt oplyse, hvordan sådanne situationer håndteres.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Hvad med databeskyttelse?
&lt;/h2&gt;

&lt;p&gt;Vores demonstration bruger kun område, opgavetype og beskrivelse.&lt;/p&gt;

&lt;p&gt;Men et frit tekstfelt kan stadig indeholde personoplysninger.&lt;/p&gt;

&lt;p&gt;En bruger kan eksempelvis skrive en privat adresse eller et telefonnummer i beskrivelsen.&lt;/p&gt;

&lt;p&gt;Derfor bør man ikke automatisk logge eller offentliggøre formularens indhold.&lt;/p&gt;

&lt;p&gt;Hvis løsningen senere skal håndtere rigtige henvendelser, bliver der behov for flere overvejelser:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sikker overførsel med HTTPS&lt;/li&gt;
&lt;li&gt;Serverbaseret validering&lt;/li&gt;
&lt;li&gt;Passende adgangskontrol&lt;/li&gt;
&lt;li&gt;Begrænset opbevaring af personoplysninger&lt;/li&gt;
&lt;li&gt;Retningslinjer for sletning&lt;/li&gt;
&lt;li&gt;Et relevant behandlingsgrundlag&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Browserbaseret validering er nyttig for brugeroplevelsen, men den kan omgås.&lt;/p&gt;

&lt;p&gt;En backend skal derfor altid validere oplysningerne igen.&lt;/p&gt;

&lt;h2&gt;
  
  
  10. Den virkelige kontekst: Træbeskæring i Kolding
&lt;/h2&gt;

&lt;p&gt;En træservicevirksomhed kan modtage meget forskellige henvendelser.&lt;/p&gt;

&lt;p&gt;En kunde ønsker måske fri passage under et træ. En anden vil have vurderet en beskadiget gren.&lt;/p&gt;

&lt;p&gt;Det er ikke nødvendigvis samme type opgave.&lt;/p&gt;

&lt;p&gt;Skovhugger Lund er en træservicevirksomhed med base i Kolding. Virksomheden tilbyder blandt andet kroneløft, beskæring ved klatring og beskæring fra lift.&lt;/p&gt;

&lt;p&gt;På virksomhedens side om &lt;a href="https://www.skovhuggerlund.dk/beskaering/" rel="noopener noreferrer"&gt;beskæring af træer i Kolding&lt;/a&gt; kan man læse om arbejdet og de faglige hensyn, som indgår i professionel træpleje.&lt;/p&gt;

&lt;p&gt;Det gør branchen til et godt eksempel på, hvorfor software skal skelne mellem en kundes beskrivelse og en professionel vurdering.&lt;/p&gt;

&lt;p&gt;En formular kan hjælpe med at samle oplysninger.&lt;/p&gt;

&lt;p&gt;Men den kan ikke alene fastlægge beskæringsmetode, pris eller sikkerhed.&lt;/p&gt;

&lt;p&gt;Dette tutorial er et selvstændigt softwareeksempel. Det er ikke en beskrivelse af Skovhugger Lunds faktiske interne systemer.&lt;/p&gt;

&lt;h2&gt;
  
  
  11. Hvad kunne være næste udviklingstrin?
&lt;/h2&gt;

&lt;p&gt;Hvis vi skulle videreudvikle løsningen, ville jeg prioritere tre forbedringer.&lt;/p&gt;

&lt;h3&gt;
  
  
  A. Bedre tilgængelighed
&lt;/h3&gt;

&lt;p&gt;Felterne skal være lette at bruge med tastatur og skærmlæser.&lt;/p&gt;

&lt;p&gt;Det er også vigtigt at teste fejlbeskeder og fokusstyring.&lt;/p&gt;

&lt;h3&gt;
  
  
  B. En sikker backend
&lt;/h3&gt;

&lt;p&gt;En rigtig løsning skal have servervalidering, adgangskontrol og en gennemtænkt håndtering af data.&lt;/p&gt;

&lt;h3&gt;
  
  
  C. En tydelig arbejdsgang
&lt;/h3&gt;

&lt;p&gt;En forespørgsel bør kunne bevæge sig gennem administrative statusser som:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Modtaget
    |
    v
Afventer faglig vurdering
    |
    v
Tilbud under udarbejdelse
    |
    v
Afsluttet
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Det er administrative trin.&lt;/p&gt;

&lt;p&gt;De siger ikke noget om, hvorvidt træet er sikkert, eller om en bestemt arbejdsmetode er forsvarlig.&lt;/p&gt;

&lt;h2&gt;
  
  
  Afslutning
&lt;/h2&gt;

&lt;p&gt;Vi har bygget en lille HTML- og JavaScript-formular, som kan indsamle, kontrollere og strukturere oplysninger om træopgaver.&lt;/p&gt;

&lt;p&gt;Løsningen bruger ingen eksterne biblioteker.&lt;/p&gt;

&lt;p&gt;Den er enkel at forstå og kan testes direkte i browseren.&lt;/p&gt;

&lt;p&gt;Det vigtigste er dog ikke mængden af kode.&lt;/p&gt;

&lt;p&gt;Det er grænsen mellem det, programmet ved, og det, et menneske skal vurdere.&lt;/p&gt;

&lt;p&gt;God software hjælper med at organisere arbejdet.&lt;/p&gt;

&lt;p&gt;Den foregiver ikke at have faglig viden, som ikke findes i dataene.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>tutorial</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Fra have til data: Organisér træfældningsopgaver med CSV og Python</title>
      <dc:creator>Skovhugger Lund</dc:creator>
      <pubDate>Fri, 13 Nov 2026 09:02:58 +0000</pubDate>
      <link>https://dev.to/skovhuggerlund/fra-have-til-data-organiser-traefaeldningsopgaver-med-csv-og-python-3lma</link>
      <guid>https://dev.to/skovhuggerlund/fra-have-til-data-organiser-traefaeldningsopgaver-med-csv-og-python-3lma</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1keh39ty3rgb9flfqqk9.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1keh39ty3rgb9flfqqk9.jpg" alt=" " width="680" height="383"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;En træfældningsopgave begynder ofte med en enkel besked:&lt;/p&gt;

&lt;p&gt;"Vi har et stort træ i haven. Kan I komme og se på det?"&lt;/p&gt;

&lt;p&gt;Men hvad sker der, når virksomheden får mange henvendelser?&lt;/p&gt;

&lt;p&gt;Nogle handler om almindelig træfældning. Andre drejer sig om beskæring, stormskader eller stubfræsning. Oplysningerne kan hurtigt ende i forskellige e-mails, noter og regneark.&lt;/p&gt;

&lt;p&gt;Her kan selv et lille digitalt system gøre en forskel.&lt;/p&gt;

&lt;p&gt;I denne guide bygger vi et enkelt eksempel med CSV og Python. Målet er ikke at erstatte professionel planlægning eller vurdering af træer. Vi vil blot vise, hvordan data kan organiseres, så administrative opgaver bliver lettere at overskue.&lt;/p&gt;

&lt;p&gt;Du behøver ingen database eller avanceret software for at komme i gang.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Hvilke oplysninger er nyttige?
&lt;/h2&gt;

&lt;p&gt;Forestil dig en mindre virksomhed, der arbejder med træfældning i Kolding.&lt;/p&gt;

&lt;p&gt;Når en kunde kontakter virksomheden, er det nyttigt at registrere, hvilken type opgave henvendelsen handler om.&lt;/p&gt;

&lt;p&gt;Et simpelt system kunne indeholde disse felter:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Felt&lt;/th&gt;
&lt;th&gt;Formål&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;opgave_id&lt;/td&gt;
&lt;td&gt;Unikt nummer for opgaven&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;omraade&lt;/td&gt;
&lt;td&gt;Område eller by&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ydelse&lt;/td&gt;
&lt;td&gt;Type af arbejde&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;adgang&lt;/td&gt;
&lt;td&gt;Foreløbig beskrivelse af adgang&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;status&lt;/td&gt;
&lt;td&gt;Administrativ status&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;vurdering&lt;/td&gt;
&lt;td&gt;Om faglig vurdering mangler&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Det er vigtigt at skelne mellem oplysninger fra kunden og en egentlig faglig vurdering.&lt;/p&gt;

&lt;p&gt;En kunde kan eksempelvis skrive, at et træ ser ustabilt ud. Det betyder ikke, at systemet må registrere træet som farligt.&lt;/p&gt;

&lt;p&gt;Det kræver en konkret undersøgelse af kvalificerede fagfolk.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Opret en CSV-fil
&lt;/h2&gt;

&lt;p&gt;CSV er et enkelt tekstformat, der kan bruges til at gemme tabeldata.&lt;/p&gt;

&lt;p&gt;Vi kalder filen &lt;code&gt;traeopgaver.csv&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Eksemplet bruger semikolon som separator. Det er praktisk i mange danske regnearksmiljøer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;opgave_id;omraade;ydelse;adgang;status;vurdering
101;Kolding;Træfældning;Begrænset;Ny;Mangler
102;Fredericia;Beskæring;God;Planlagt;Udført
103;Kolding;Stubfræsning;Begrænset;Ny;Mangler
104;Vejle;Træfældning;God;Afsluttet;Udført
105;Kolding;Træpleje;Ukendt;Ny;Mangler
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Alle poster er opdigtede eksempeldata. De beskriver ikke faktiske kunder eller opgaver hos Skovhugger Lund.&lt;/p&gt;

&lt;p&gt;Fordelen ved denne struktur er, at oplysningerne kan læses af både mennesker og programmer.&lt;/p&gt;

&lt;p&gt;Man kan åbne filen i et regneark eller behandle den med Python.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Læs data med Python
&lt;/h2&gt;

&lt;p&gt;Python har et indbygget CSV-modul.&lt;/p&gt;

&lt;p&gt;Derfor behøver vi ikke installere ekstra pakker.&lt;/p&gt;

&lt;p&gt;Opret filen &lt;code&gt;analyse.py&lt;/code&gt; i samme mappe som CSV-filen.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="n"&gt;csv&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;"traeopgaver.csv"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"r"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"utf-8-sig"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;newline&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;fil&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;reader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;csv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DictReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;delimiter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;";"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;opgaver&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;opgave&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;opgaver&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;opgave&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"opgave_id"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;opgave&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"omraade"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;opgave&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"ydelse"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;opgave&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Koden gør tre ting:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Åbner CSV-filen.&lt;/li&gt;
&lt;li&gt;Læser hver række som en dictionary.&lt;/li&gt;
&lt;li&gt;Udskriver udvalgte oplysninger.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;code&gt;DictReader&lt;/code&gt; gør det muligt at bruge kolonnenavne i stedet for kolonnenumre.&lt;/p&gt;

&lt;p&gt;Det gør koden mere læsbar.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;utf-8-sig&lt;/code&gt; hjælper også med at håndtere filer, der er gemt med UTF-8 BOM fra visse regnearksprogrammer.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Find opgaver, der mangler vurdering
&lt;/h2&gt;

&lt;p&gt;Det næste skridt er at få overblik over, hvilke henvendelser der endnu ikke er gennemgået.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;mangler_vurdering&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="n"&gt;opgave&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;opgave&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;opgaver&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;opgave&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"vurdering"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"Mangler"&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Opgaver uden afsluttet vurdering:"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;opgave&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;mangler_vurdering&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;"#{opgave['opgave_id']} - "&lt;/span&gt;
        &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;"{opgave['ydelse']} i "&lt;/span&gt;
        &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;"{opgave['omraade']}"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Med vores eksempeldata får vi:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Opgaver uden afsluttet vurdering:
#101 - Træfældning i Kolding
#103 - Stubfræsning i Kolding
#105 - Træpleje i Kolding
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nu kan den administrative medarbejder se, hvilke opgaver der mangler opfølgning.&lt;/p&gt;

&lt;p&gt;Bemærk, at &lt;code&gt;vurdering&lt;/code&gt; kun beskriver den administrative registrering.&lt;/p&gt;

&lt;p&gt;Programmet afgør ikke, om en opgave er sikker eller teknisk forsvarlig.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Skab overblik over opgavetyper
&lt;/h2&gt;

&lt;p&gt;Hvis vi vil vide, hvilke typer henvendelser der findes i datasættet, kan vi bruge &lt;code&gt;Counter&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;collections&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nc"&gt;Counter&lt;/span&gt;

&lt;span class="n"&gt;fordeling&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Counter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;opgave&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"ydelse"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;opgave&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;opgaver&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;ydelse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;antal&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fordeling&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;"{ydelse}: {antal}"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Resultatet bliver:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Beskæring: 1
Stubfræsning: 1
Træfældning: 2
Træpleje: 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Denne type optælling kan bruges til at forstå fordelingen af registrerede henvendelser.&lt;/p&gt;

&lt;p&gt;Det er dog vigtigt at læse tallene rigtigt.&lt;/p&gt;

&lt;p&gt;Fem registrerede opgaver siger ikke noget sikkert om efterspørgslen i hele Kolding.&lt;/p&gt;

&lt;p&gt;Et lille datasæt kan give et internt overblik, men det er ikke en markedsundersøgelse.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Kontrollér data før analyse
&lt;/h2&gt;

&lt;p&gt;Et problem med CSV-filer er, at brugere kan skrive den samme opgavetype på forskellige måder.&lt;/p&gt;

&lt;p&gt;Eksempelvis:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Træfældning&lt;/li&gt;
&lt;li&gt;træfældning&lt;/li&gt;
&lt;li&gt;Traefaeldning&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For et menneske er betydningen tydelig.&lt;/p&gt;

&lt;p&gt;For et program er det forskellige tekstværdier.&lt;/p&gt;

&lt;p&gt;Derfor kan vi definere tilladte værdier.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;gyldige_ydelser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"Træfældning"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"Beskæring"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"Stubfræsning"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"Træpleje"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;opgave&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;opgaver&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;opgave&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"ydelse"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;gyldige_ydelser&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s2"&gt;"Ukendt ydelse:"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;opgave&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"opgave_id"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;opgave&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"ydelse"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Denne kontrol hjælper med at opdage tastefejl og uventede værdier.&lt;/p&gt;

&lt;p&gt;Et større system kunne vise en dropdown-menu, så medarbejderen vælger en ydelse fra en fast liste.&lt;/p&gt;

&lt;p&gt;Det kan mindske behovet for manuel oprydning i data.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Lav en enkel Markdown-rapport
&lt;/h2&gt;

&lt;p&gt;Når oplysningerne er læst og kontrolleret, kan vi lave en lille rapport.&lt;/p&gt;

&lt;p&gt;Markdown er praktisk, fordi formatet kan læses direkte som tekst og vises pænt på platforme som GitHub.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;collections&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nc"&gt;Counter&lt;/span&gt;

&lt;span class="n"&gt;fordeling&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Counter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;opgave&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"ydelse"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;opgave&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;opgaver&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;linjer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s2"&gt;"# Oversigt over træopgaver"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;"Antal registrerede opgaver: {len(opgaver)}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"## Fordeling efter ydelse"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;""&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;ydelse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;antal&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fordeling&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;linjer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;"- {ydelse}: {antal}"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;rapport&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"\n"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;linjer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s2"&gt;"\n"&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;"rapport.md"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"w"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"utf-8"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;fil&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;fil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rapport&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Rapporten er gemt."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Resultatet bliver en fil med denne struktur:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;# Oversigt over træopgaver
&lt;/span&gt;
Antal registrerede opgaver: 5

&lt;span class="gu"&gt;## Fordeling efter ydelse
&lt;/span&gt;
&lt;span class="k"&gt;- &lt;/span&gt;Beskæring: 1
&lt;span class="k"&gt;- &lt;/span&gt;Stubfræsning: 1
&lt;span class="k"&gt;- &lt;/span&gt;Træfældning: 2
&lt;span class="k"&gt;- &lt;/span&gt;Træpleje: 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Det er et lille eksempel, men det viser en nyttig arbejdsgang:&lt;/p&gt;

&lt;p&gt;CSV-data bliver til en læsbar oversigt uden manuel optælling.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Hvorfor bør man ikke automatisere selve sikkerhedsvurderingen?
&lt;/h2&gt;

&lt;p&gt;Her kommer en vigtig forskel mellem almindelig datahåndtering og fagligt træarbejde.&lt;/p&gt;

&lt;p&gt;Et program kan godt registrere oplysninger som:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Træfældning ønskes&lt;/li&gt;
&lt;li&gt;Adgangsforhold er begrænsede&lt;/li&gt;
&lt;li&gt;Kunden har meldt om en knækket gren&lt;/li&gt;
&lt;li&gt;Faglig vurdering mangler&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Men programmet kan ikke alene afgøre, om træet er stabilt, eller hvilken fældningsmetode der er sikker.&lt;/p&gt;

&lt;p&gt;Det kræver en vurdering af blandt andet træets tilstand, omgivelserne og de konkrete arbejdsforhold.&lt;/p&gt;

&lt;p&gt;Et stort træ tæt på et hus kan eksempelvis kræve sektionsfældning, klatring eller andet specialudstyr.&lt;/p&gt;

&lt;p&gt;De metoder skal vælges af kvalificerede fagfolk.&lt;/p&gt;

&lt;p&gt;Derfor er det en god idé at holde administrative data adskilt fra faglige beslutninger.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Et digitalt system bør støtte medarbejdernes arbejde, ikke erstatte deres professionelle vurdering.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Hvad med persondata?
&lt;/h2&gt;

&lt;p&gt;Eksemplet bruger kun fiktive opgave-ID'er og generelle områdenavne.&lt;/p&gt;

&lt;p&gt;Virkelige kundeoplysninger kræver større omtanke.&lt;/p&gt;

&lt;p&gt;Navne, telefonnumre, adresser og billeder af private ejendomme kan være personoplysninger.&lt;/p&gt;

&lt;p&gt;Hvis oplysningerne kan knyttes til en identificerbar person, skal de håndteres efter relevante databeskyttelsesregler.&lt;/p&gt;

&lt;p&gt;Man bør blandt andet overveje:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hvilke oplysninger der faktisk er nødvendige&lt;/li&gt;
&lt;li&gt;Hvem der må læse dem&lt;/li&gt;
&lt;li&gt;Hvordan de opbevares sikkert&lt;/li&gt;
&lt;li&gt;Hvornår de skal slettes&lt;/li&gt;
&lt;li&gt;Om der er et lovligt grundlag for behandlingen&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Undgå at lægge virkelige kundedata i offentlige GitHub-repositories.&lt;/p&gt;

&lt;p&gt;Hvis du vil dele et kodeeksempel, bør du bruge opdigtede eller korrekt anonymiserede data.&lt;/p&gt;

&lt;h2&gt;
  
  
  10. Et praktisk eksempel fra træservicebranchen
&lt;/h2&gt;

&lt;p&gt;Skovhugger Lund er en træservicevirksomhed med base i Kolding.&lt;/p&gt;

&lt;p&gt;Virksomheden arbejder blandt andet med træfældning, beskæring, stubfræsning og skovpleje.&lt;/p&gt;

&lt;p&gt;Opgaverne kan variere fra almindelige havetræer til træer, som står tæt på bygninger eller har vanskelige adgangsforhold.&lt;/p&gt;

&lt;p&gt;På virksomhedens side om &lt;a href="https://www.skovhuggerlund.dk/traefaeldning/" rel="noopener noreferrer"&gt;professionel træfældning i Kolding&lt;/a&gt; kan man læse mere om de forskellige arbejdsmetoder og forhold, som kræver faglig vurdering.&lt;/p&gt;

&lt;p&gt;Det er netop denne forskel mellem administrative oplysninger og praktisk arbejde, der gør et enkelt digitalt system interessant.&lt;/p&gt;

&lt;p&gt;Et system kan organisere henvendelser, dokumentere status og hjælpe med opfølgning.&lt;/p&gt;

&lt;p&gt;Men det skal stadig være medarbejderne, der vurderer den konkrete opgave.&lt;/p&gt;

&lt;p&gt;Dette tutorial er et generelt softwareeksempel. Det beskriver ikke et internt system, som Skovhugger Lund anvender.&lt;/p&gt;

&lt;h2&gt;
  
  
  11. Mulige forbedringer
&lt;/h2&gt;

&lt;p&gt;Når den enkle løsning fungerer, kan man bygge videre på den.&lt;/p&gt;

&lt;p&gt;Et naturligt næste skridt kunne være at tilføje:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Validering af obligatoriske felter&lt;/li&gt;
&lt;li&gt;Kontrol af dublerede opgave-ID'er&lt;/li&gt;
&lt;li&gt;Filtrering efter område&lt;/li&gt;
&lt;li&gt;En intern oversigt over administrative opgavestatusser&lt;/li&gt;
&lt;li&gt;Eksport til et regneark&lt;/li&gt;
&lt;li&gt;Adgangskontrol, hvis rigtige kundeoplysninger indgår&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For større datasæt kan en database være mere passende end CSV.&lt;/p&gt;

&lt;p&gt;Men til at lære grundlæggende datahåndtering er CSV et godt sted at begynde.&lt;/p&gt;

&lt;p&gt;Det kræver få værktøjer og gør det let at forstå, hvordan oplysninger bevæger sig fra en tekstfil til en rapport.&lt;/p&gt;

&lt;h2&gt;
  
  
  Afsluttende tanke
&lt;/h2&gt;

&lt;p&gt;Digitalisering behøver ikke begynde med en stor platform.&lt;/p&gt;

&lt;p&gt;Nogle gange er en tydelig datastruktur og et kort Python-script nok til at skabe et bedre administrativt overblik.&lt;/p&gt;

&lt;p&gt;Det gælder også i praktiske brancher som træpleje og skovarbejde.&lt;/p&gt;

&lt;p&gt;Teknologien kan hjælpe med at holde styr på oplysningerne.&lt;/p&gt;

&lt;p&gt;Fagligheden afgør stadig, hvordan arbejdet skal udføres.&lt;/p&gt;

</description>
      <category>python</category>
      <category>tutorial</category>
      <category>data</category>
      <category>beginners</category>
    </item>
  </channel>
</rss>
