Om koden
I skjema malen er det hovedsaklig vĂŠrt fokus pĂ„ Ă„ vise hvordan felter kan settes opp. Det er kun tilstandshĂ„ndtering og validering pĂ„ et par av feltene for Ă„ vise et eksempel pĂ„ hvordan man kan gjĂžre dette. Dette gjelder âadresseâ, âpostnrâ, âpoststedâ og âhvordan skal du reise?â - spĂžrsmĂ„let om transport. Se koden for sidemal helt nederst.
Kodeeksempel
PÄmelding til fjelltur
Meld deg pÄ bedriftsidrettslagets Ärlige fjelltur til Rondane. PÄmeldingsskjemaet har 5 steg, og det tar 3-4 minutter Ä fylle det ut.
Informasjon om deg
Postnummeret er ikke gyldig. Du mÄ skrive et postnummer med 4 tegn.
Poststedet er ikke gyldig. Du mÄ skrive et poststed med minst 2 tegn.
Om turen
Har du noen allergier eller matĂžnsker?
NÄr du krysser av samtykker du til at Oslo kommune behandler personopplysninger elektronisk og formidler opplysninger til de ansatte som arrangerer turen.
- Adressen du har skrevet er ikke gyldig. Skriv adressen inn pÄ nytt
- Postnummeret er ikke gyldig. Du mÄ skrive et postnummer med 4 tegn.
- Poststedet er ikke gyldig. Du mÄ skrive et poststed med minst 2 tegn
- Du mÄ velge en reisemetode
Under er et forslag pÄ hvordan man kan implementere tilstandshÄndtering og validering i skjemaet i Vue og React.
Se kodeeksempel i fanene for Vue og React
<template>
<page-main class="pkt-container pkt-container--laptop">
<h1>Mal for skjema</h1>
<p class="pkt-txt-22-light">
Dette er en mal for et skjema. Kun adresse, postnr, poststed og spÞrsmÄlet om transportmiddel har validering.
Dette er kun for Ă„ vise et eksempel pĂ„ hvordan de kan valideres. âš
</p>
<div :class="[responsivePhabletGrid, 'pkt-grid--gap-size-32', 'pkt-grid--gap-size-48-phablet-up']">
<div :class="cellClass(12)">
<h1 class="pkt-txt-28 pkt-txt-54--phablet-up mb-size-24">PÄmelding til fjelltur</h1>
<p class="pkt-txt-20-light pkt-txt-24-light--phablet-up mb-size-0">
Meld deg pÄ bedriftsidrettslagets Ärlige fjelltur til Rondane. PÄmeldingsskjemaet har 5 steg, og det tar 3-4
minutter Ă„ fylle det ut.
</p>
</div>
<PktMessagebox :class="cellClass(12)" skin="blue" title="FĂžr du fyller ut">
Du kan melde deg pÄ turen dersom du
<ul>
<li>har vÊrt fast ansatt i Oslo kommune i mer enn 1 Är</li>
<li>ikke er redd for hĂžyder</li>
<li>har godt humĂžr</li>
</ul>
For Ă„ fylle ut skjemaet trenger du dokumentasjon som viser hvor lenge du har vĂŠrt ansatt i Oslo kommune
</PktMessagebox>
<section :class="[cellClass(10), responsivePhabletGrid, responsiveSectionGap]">
<h2 :class="[cellClass(12), 'pkt-txt-22', 'pkt-txt-24--phablet-up', 'mb-size-0']">Informasjon om deg</h2>
<PktTextinput id="firstName" label="Fornavn" :class="cellClass(12)" />
<PktTextinput id="lastName" label="Etternavn" :class="cellClass(12)" />
<PktTextinput id="birthdate" label="FÞdselsdato (dd.mm.ÄÄÄÄ)" :class="cellClass(12)" placeholder="datepicker" />
<PktTextinput
id="address"
label="Adresse"
:class="cellClass(12)"
helptext="Adressen trenger vi for Ă„ sende deg informasjon om turen."
@input="(event) => setAddress(event.target.value)"
@blur="(event) => setAddressError(!isAddressValid(event.target.value))"
v-model="address"
errorMessage="Adressen du har skrevet er ikke gyldig. Skriv adressen pÄ nytt."
:hasError="hasAddressError"
/>
<div :class="[cellClass(12), responsivePhabletGrid, 'pkt-grid--gap-size-8']">
<PktTextinput
id="postalCode"
label="Postnr."
class="pkt-cell pkt-cell--span4 pkt-cell--span3-tablet-up"
:hasError="hasPostalCodeError"
@input="(event) => setPostalCode(event.target.value)"
@blur="(event) => setPostalCodeError(!isPostalCodeValid(event.target.value))"
v-model="postalCode"
/>
<PktTextinput
id="postalArea"
label="Poststed"
class="pkt-cell pkt-cell--span8 pkt-cell--span9-tablet-up"
:hasError="hasPostalAreaError"
@input="(event) => setPostalArea(event.target.value)"
@blur="(event) => setPostalAreaError(!isPostalAreaValid(event.target.value))"
v-model="postalArea"
/>
<PktAlert
v-if="hasPostalCodeError"
:class="cellClass(12)"
skin="error"
aria-live="assertive"
id="postalCode-error"
compact
>
Postnummeret er ikke gyldig. Du mÄ skrive et postnummer med 4 tegn.
</PktAlert>
<PktAlert
v-if="hasPostalAreaError"
:class="cellClass(12)"
skin="error"
aria-live="assertive"
id="postalArea-error"
compact
>
Poststedet er ikke gyldig. Du mÄ skrive et poststed med minst 2 tegn.
</PktAlert>
</div>
<PktSelect
:class="cellClass(12)"
label="I hvilken virksomhet jobber du?"
helptext="Her velger du hvilken virksomhet du jobber i. For eksempel tilhÞrer alle som jobber pÄ en skole Utdanningsetaten."
id="business"
v-model="business"
@change="(event) => setBusiness(event.target.value)"
>
<option selected>Velg virksomhet</option>
<option value="1">Origo</option>
<option value="2">BymiljĂžetaten</option>
<option value="3">Finn</option>
<option value="4">Handelsbanken</option>
</PktSelect>
</section>
<hr :class="[cellClass(12), 'pkt-hr']" />
<section :class="[cellClass(10), responsivePhabletGrid, responsiveSectionGap]">
<h2 :class="[cellClass(12), 'pkt-txt-22', 'pkt-txt-24--phablet-up', 'mb-size-0']">Om turen</h2>
<PktInputWrapper
:class="cellClass(12)"
label="Hvordan skal du reise?"
helptext="Velg alternativet som passer best"
forId="transportRadioGroup"
hasFieldset
:hasError="hasTransportError"
errorMessage="Du mÄ velge en reisemetode"
>
<PktRadiobutton
v-for="option in transportOptions"
:key="option.id"
:id="option.id"
:label="option.value"
v-model="transport"
name="transportMethod"
:hasError="hasTransportError"
/>
</PktInputWrapper>
<PktInputWrapper
:class="cellClass(12)"
label="Hva slags rom Ăžnsker du?"
helptext="Velg romtypen som passer deg best. Du kan velge flere alternativer."
optionalTag
optionalText="Valgfritt"
forId="roomCheckboxGroup"
hasFieldset
>
<PktCheckbox id="single-room" label="Enkeltrom (koster 200 kr ekstra)" />
<PktCheckbox id="double-room" label="Dobbeltrom" />
<PktCheckbox id="three-person-room" label="Tremannsrom" />
<PktCheckbox id="multiple-people" label="Soverom (12 sengeplasser)" />
</PktInputWrapper>
<PktTextarea
:class="cellClass(12)"
id="foodPreferences"
label="Har du noen allergier eller matĂžnsker?"
placeholder="Gluten, laktose, spiser ikke kjĂžtt etc."
helptext="Hvorfor spĂžr vi om dette?"
helptextDropdown="Vi vil sÞrge for at alle fÄr et godt mÄltid. Dersom du har allergier eller matÞnsker, kan du skrive det her. "
/>
<PktTextinput
id="uploadFiles"
label="Last opp dokumentasjon"
:class="cellClass(12)"
helptext="Her laster du opp dokumentasjon som viser hvor lenge du har jobbet i kommunen "
placeholder="Kun et tekstfelt, ingen filopplasting enda. "
/>
</section>
<hr :class="[cellClass(12), 'pkt-hr']" />
<section :class="[cellClass(12), responsivePhabletGrid]">
<p :class="[cellClass(12), 'pkt-txt-16-light', 'm-size-0']">
NÄr du krysser av samtykker du til at Oslo kommune behandler personopplysninger elektronisk og formidler
opplysninger til de ansatte som arrangerer turen.
</p>
<PktCheckbox
:class="cellClass(12)"
id="checboxDataAgreement"
label="Jeg samtykker til at Oslo kommune innhenter mine opplysninger"
/>
</section>
<PktAlert
:class="cellClass(12)"
v-if="hasAnyError"
skin="error"
aria-live="assertive"
id="summary-error"
title="Vi mangler informasjon fra deg"
>
<ul>
<li v-if="hasAddressError">Adressen du har skrevet er ikke gyldig. Skriv adressen inn pÄ nytt</li>
<li v-if="hasPostalAreaError">Postnummeret er ikke gyldig. Du mÄ skrive et postnummer med 4 tegn.</li>
<li v-if="hasPostalCodeError">Poststedet er ikke gyldig. Du mÄ skrive et poststed med minst 2 tegn</li>
<li v-if="hasTransportError">Du mÄ velge en reisemetode</li>
</ul>
</PktAlert>
<PktButton
class="pkt-cell pkt-cell--span6-phablet-up pkt-cell--span12"
skin="primary"
variant="label-only"
type="submit"
:style="{ width: 'fit-content' }"
>
Send inn pÄmelding
</PktButton>
</div>
</page-main>
</template>
<script>
import PageMain from '@/dev-components/PageMain.vue'
import { PktAlert } from '@/components/alert'
import { PktButton } from '@/components/button'
import { PktCheckbox } from '@/components/checkbox'
import { PktInputWrapper } from '@/components/inputwrapper'
import { PktMessagebox } from '@/components/messagebox'
import { PktRadiobutton } from '@/components/radiobutton'
import { PktSelect } from '@/components/select'
import { PktTextarea } from '@/components/textarea'
import { PktTextinput } from '@/components/textinput'
export default {
name: 'TemplateForm',
components: {
PageMain,
PktAlert,
PktButton,
PktCheckbox,
PktInputWrapper,
PktMessagebox,
PktRadiobutton,
PktSelect,
PktTextarea,
PktTextinput,
},
data: () => {
return {
responsivePhabletGrid: 'pkt-grid pkt-grid--phablet',
responsiveSectionGap: 'pkt-grid--gap-size-24 pkt-grid--gap-size-32-phablet-up',
address: '',
business: '',
hasAddressError: false,
hasPostalAreaError: false,
hasPostalCodeError: false,
hasTransportError: false,
postalArea: '',
postalCode: '',
transport: 'Buss',
transportOptions: [
{ id: 'bus', value: 'Buss' },
{ id: 'car', value: 'Egen bil' },
{ id: 'train', value: 'Tog' },
],
}
},
computed: {
hasAnyError() {
return this.hasAddressError || this.hasPostalAreaError || this.hasPostalCodeError || this.hasTransportError
},
},
methods: {
// Styling
cellClass(spanNo) {
return `pkt-cell pkt-cell--span12 pkt-cell--span${spanNo}-phablet-up`
},
// Validators
isAddressValid(address) {
// Enkel regex validering. Kun tall, bokstaver, mellomrom og ".,-" er tillatt.
const regex = /^[a-zA-Z0-9\s.,-]*$/
console.log(address, regex.test(address))
return regex.test(address)
},
isPostalAreaValid(postalArea) {
// Tillater kun tall og mÄ vÊre 4 tegn langt.
const regex = /^[a-zA-ZÊÞÄĂĂĂ
\s]{2,}$/
console.log(postalArea, regex.test(postalArea))
return regex.test(postalArea)
},
isPostalCodeValid(postalCode) {
// Tillater alle bokstaver og mellomrom, samt ÊÞÄ og ĂĂĂ
. Strengen mÄ ogsÄ vÊre minst 2 tegn lang.
const regex = /^[0-9]{4}$/
console.log(postalCode, regex.test(postalCode))
return regex.test(postalCode)
},
// State handling
setAddress(address) {
this.address = address
},
setPostalCode(postalCode) {
this.postalCode = postalCode
},
setPostalArea(postalArea) {
this.postalArea = postalArea
},
setBusiness(business) {
this.business = business
},
// Error handling
setAddressError(isError) {
this.hasAddressError = isError
},
setPostalAreaError(isError) {
this.hasPostalAreaError = isError
},
setPostalCodeError(isError) {
this.hasPostalCodeError = isError
},
},
}
</script>
<style lang="scss"></style>
import React, { useEffect } from 'react'
import {
PktAlert,
PktButton,
PktCheckbox,
PktInputWrapper,
PktMessagebox,
PktRadioButton,
PktSelect,
PktTextarea,
PktTextinput,
} from '..'
/**
* TemplateSchema
*
* @description
* Dette er en mal for et skjema. Skjemaet er delt inn i flere seksjoner, og inneholder ulike typer input-felter.
* I en reell applikasjon hadde det vĂŠrt ideelt og mer leselig Ă„ dele opp skjemaet i flere komponenter.
* Man kunne for eksempel hatt en komponent for hver seksjon og kalt de "About You", "About the trip" og "Data agreement".
*
* For Ä vise et eksempel pÄ hvordan validering kan gjÞres i et skjema, har vi lagt til en enkel validering av adressen.
* Ellers er skjemaet uten tilstandshÄndtering og validering.
*/
const isAddressValid = (address: string): boolean => {
// Enkel regex validering. Kun tall, bokstaver, mellomrom og ".,-" er tillatt.
const regex = /^[a-zA-Z0-9\s.,-]*$/
return regex.test(address)
}
const isPostalCodeValid = (postalCode: string): boolean => {
// Tillater kun tall og mÄ vÊre 4 tegn langt.
const regex = /^[0-9]{4}$/
return regex.test(postalCode)
}
const isPostalAreaValid = (postalArea: string): boolean => {
// Tillater alle bokstaver og mellomrom, samt ÊÞÄ og ĂĂĂ
. Strengen mÄ ogsÄ vÊre minst 2 tegn lang.
const regex = /^[a-zA-ZÊÞÄĂĂĂ
\s]{2,}$/
return regex.test(postalArea)
}
export default function TemplateForm() {
// State
const [business, setBusiness] = React.useState<string>('')
const [address, setAddress] = React.useState<string>('')
const [postalCode, setPostalCode] = React.useState<string>('')
const [postalArea, setPostalArea] = React.useState<string>('')
const [hasAddressError, setAddressError] = React.useState<boolean>(false)
const [hasPostalCodeError, setPostalCodeError] = React.useState<boolean>(false)
const [hasPostalAreaError, setPostalAreaError] = React.useState<boolean>(false)
/**
* Radio knapp grupper bĂžr alltid ha en default verdi. Dermed
* Dermed vil det ikke vĂŠre noe behov for Ă„ validere om en verdi er valgt.
* For Ä vise et eksempel pÄ hvordan radioknapper kan valideres,
* har vi satt default verdi til 'hasTransportError' til true. I en reell applikasjon,
* ville dette vĂŠrt satt til false.
* */
const [transport, setTransport] = React.useState<string>('bus')
const [hasTransportError, setTransportError] = React.useState<boolean>(true)
// Styles
const cellClass = (spanNo: number) =>
`pkt-cell pkt-cell--span12 pkt-cell--span${spanNo}-phablet-up`
const responsivePhabletGrid = 'pkt-grid pkt-grid--phablet'
const responsiveSectionGap = 'pkt-grid--gap-size-24 pkt-grid--gap-size-32-phablet-up'
const SectionAboutYou = (
<section className={`${cellClass(10)} ${responsivePhabletGrid} ${responsiveSectionGap}`}>
<h2 className={`${cellClass(12)} pkt-txt-22 pkt-txt-24--phablet-up mb-size-0`}>
Informasjon om deg
</h2>
<PktTextinput id="firstName" label="Fornavn" className={cellClass(12)} />
<PktTextinput id="lastName" label="Etternavn" className={cellClass(12)} />
<PktTextinput
id="birthdate"
label="FÞdselsdato (dd.mm.ÄÄÄÄ)"
className={cellClass(12)}
placeholder="datepicker"
/>
<PktTextinput
id="address"
label="Adresse"
className={cellClass(12)}
helptext="Adressen trenger vi for Ă„ sende deg informasjon om turen."
onChange={(event: React.FocusEvent<HTMLInputElement>) => setAddress(event.target.value)}
onBlur={(event: React.FocusEvent<HTMLInputElement>) =>
setAddressError(!isAddressValid(event.target.value))
}
value={address}
errorMessage="Adressen du har skrevet er ikke gyldig. Skriv adressen pÄ nytt."
hasError={hasAddressError}
/>
<div className={`${cellClass(12)} ${responsivePhabletGrid} pkt-grid--gap-size-8`}>
<PktTextinput
id="postalCode"
label="Postnr."
className="pkt-cell pkt-cell--span4 pkt-cell--span3-tablet-up"
hasError={hasPostalCodeError}
onChange={(event: React.FocusEvent<HTMLInputElement>) =>
setPostalCode(event.target.value)
}
onBlur={(event: React.FocusEvent<HTMLInputElement>) =>
setPostalCodeError(!isPostalCodeValid(event.target.value))
}
value={postalCode}
/>
<PktTextinput
id="postalArea"
label="Poststed"
className="pkt-cell pkt-cell--span8 pkt-cell--span9-tablet-up"
hasError={hasPostalAreaError}
onChange={(event: React.FocusEvent<HTMLInputElement>) =>
setPostalArea(event.target.value)
}
onBlur={(event: React.FocusEvent<HTMLInputElement>) =>
setPostalAreaError(!isPostalAreaValid(event.target.value))
}
value={postalArea}
/>
{hasPostalCodeError && (
<PktAlert
className={`${cellClass(12)}`}
skin="error"
aria-live="assertive"
id={`postalCode-error`}
compact
>
Postnummeret er ikke gyldig. Du mÄ skrive et postnummer med 4 tegn.
</PktAlert>
)}
{hasPostalAreaError && (
<PktAlert
className={`${cellClass(12)}`}
skin="error"
aria-live="assertive"
id={`postalArea-error`}
compact
>
Poststedet er ikke gyldig. Du mÄ skrive et poststed med minst 2 tegn.
</PktAlert>
)}
</div>
<PktSelect
className={cellClass(12)}
label="I hvilken virksomhet jobber du?"
helptext="Her velger du hvilken virksomhet du jobber i. For eksempel tilhÞrer alle som jobber pÄ en skole Utdanningsetaten. "
id="business"
value={business}
defaultValue={business}
onChange={(event: React.ChangeEvent<HTMLSelectElement>) => setBusiness(event.target.value)}
>
<option selected>Velg virksomhet</option>
<option value="1">Origo</option>
<option value="2">BymiljĂžetaten</option>
<option value="3">Finn</option>
<option value="4">Handelsbanken</option>
</PktSelect>
</section>
)
const transportOptions: { id: string; label: string }[] = [
{ id: 'bus', label: 'Buss' },
{ id: 'car', label: 'Egen bil' },
{ id: 'train', label: 'Tog' },
]
const SectionAboutTheTrip = (
<section className={`${cellClass(10)} ${responsivePhabletGrid} ${responsiveSectionGap}`}>
<h2 className={`${cellClass(12)} pkt-txt-22 pkt-txt-24--phablet-up mb-size-0`}>Om turen</h2>
<PktInputWrapper
className={cellClass(12)}
label="Hvordan skal du reise?"
helptext="Velg alternativet som passer best"
forId="TransportRadioGroup"
hasFieldset
hasError={hasTransportError}
errorMessage="Du mÄ velge en reisemetode"
>
{transportOptions.map((option) => (
<PktRadioButton
id={option.id}
label={option.label}
name="selectedTransport"
hasError={hasTransportError}
onChange={(event: React.ChangeEvent<HTMLInputElement>) => setTransport(event.target.id)}
checked={transport === option.id}
/>
))}
</PktInputWrapper>
<PktInputWrapper
className={cellClass(12)}
label="Hva slags rom Ăžnsker du?"
helptext="Velg romtypen som passer deg best. Du kan velge flere alternativer."
optionalTag
optionalText="Valgfritt"
forId="roomCheckboxGroup"
hasFieldset
>
<PktCheckbox id="single-room" label="Enkeltrom (koster 200 kr ekstra)" />
<PktCheckbox id="double-room" label="Dobbeltrom" />
<PktCheckbox id="three-person-room" label="Tremannsrom" />
<PktCheckbox id="multiple-people" label="Soverom (12 sengeplasser)" />
</PktInputWrapper>
<PktTextarea
className={cellClass(12)}
id="foodPreferences"
label="Har du noen allergier eller matĂžnsker?"
placeholder="Gluten, laktose, spiser ikke kjĂžtt etc."
helptext="Hvorfor spĂžr vi om dette?"
helptextDropdown="Vi vil sÞrge for at alle fÄr et godt mÄltid. Dersom du har allergier eller matÞnsker, kan du skrive det her. "
/>
<PktTextinput
id="uploadFiles"
label="Last opp dokumentasjon"
className={cellClass(12)}
helptext="Her laster du opp dokumentasjon som viser hvor lenge du har jobbet i kommunen "
placeholder="Kun et tekstfelt, ingen filopplasting enda. "
/>
</section>
)
return (
<>
<main className="page-main pkt-container">
<h1>Mal for skjema</h1>
<p className="pkt-txt-22-light">
Dette er en mal for et skjema. Kun adresse, postnr, poststed og spÞrsmÄlet om
transportmiddel har validering. Dette er kun for Ä vise et eksempel pÄ hvordan de kan
valideres. âš
</p>
<div
className={`${responsivePhabletGrid} pkt-grid--gap-size-32 pkt-grid--gap-size-48-phablet-up`}
>
<div className={cellClass(12)}>
<h1 className="pkt-txt-28 pkt-txt-54--phablet-up mb-size-24">PÄmelding til fjelltur</h1>
<p className="pkt-txt-20-light pkt-txt-24-light--phablet-up mb-size-0">
Meld deg pÄ bedriftsidrettslagets Ärlige fjelltur til Rondane. PÄmeldingsskjemaet har
5 steg, og det tar 3-4 minutter Ă„ fylle det ut.
</p>
</div>
<PktMessagebox className={cellClass(12)} skin="blue" title="FĂžr du fyller ut">
Du kan melde deg pÄ turen dersom du
<ul>
<li>har vÊrt fast ansatt i Oslo kommune i mer enn 1 Är</li>
<li>ikke er redd for hĂžyder</li>
<li>har godt humĂžr</li>
</ul>
For Ă„ fylle ut skjemaet trenger du dokumentasjon som viser hvor lenge du har vĂŠrt ansatt
i Oslo kommune
</PktMessagebox>
{SectionAboutYou}
<hr className={`pkt-hr ${cellClass(12)}`} />
{SectionAboutTheTrip}
<hr className={`${cellClass(12)} pkt-hr`} />
<section className={`${cellClass(12)} ${responsivePhabletGrid} `}>
<p className={`${cellClass(12)} pkt-txt-16-light m-size-0`}>
NÄr du krysser av samtykker du til at Oslo kommune behandler personopplysninger
elektronisk og formidler opplysninger til de ansatte som arrangerer turen.
</p>
<PktCheckbox
className={cellClass(12)}
id="checboxDataAgreement"
label="Jeg samtykker til at Oslo kommune innhenter mine opplysninger"
/>
</section>
<PktAlert
className={`${cellClass(12)}`}
skin="error"
aria-live="assertive"
id={`summary-error`}
title="Vi mangler informasjon fra deg"
>
<ul>
{hasAddressError && (
<li>Adressen du har skrevet er ikke gyldig. Skriv adressen inn pÄ nytt</li>
)}
{hasPostalAreaError && (
<li>Postnummeret er ikke gyldig. Du mÄ skrive et postnummer med 4 tegn.</li>
)}
{hasPostalCodeError && (
<li>Poststedet er ikke gyldig. Du mÄ skrive et poststed med minst 2 tegn</li>
)}
{hasTransportError && <li>Du mÄ velge en reisemetode</li>}
</ul>
</PktAlert>
<PktButton
className="pkt-cell pkt-cell--span6-phablet-up pkt-cell--span12"
skin="primary"
variant="label-only"
type="submit"
style={{ width: 'fit-content' }}
>
Send inn pÄmelding
</PktButton>
</div>
</main>
</>
)
}