Todo-applikasjon

Start med å gå til codepen.io/pen/, og denne gangen skal vi ikke begynne med javascript, men med HTML. Siden denne teksten ikke er for å lære HTML, så kan du bare kopiere dette:

<h1>To-Do App</h1>

<form>
  <input type="text" autocomplete="off">
  <button>Create item</button>
</form>

<h3>Need To Do</h3>
<ul>
  <!-- Eksempeldata -->
  <li>Buy carrots <button>Delete</button></li>
  <li>Buy apples <button>Delete</button></li>
  <li>Buy lettuce <button>Delete</button></li>
</ul>

Etter å ha skrevet av kan du sjekke at du får opp det du forventer å se i forhåndsvisningsvinduet i codepen. La oss fortsette litt til i HTML-fanen ved å legge til en id på form-tag’en:

<form id="ourForm">

Dette gjør at vi nå kan søke etter denne id’en med javascript.

Nå kan vi gå til javascript-fanen. Tidligere har vi brukt document for å legge til en eventlistener. Vi skal bruke document nå, men ikke til addEventListener(). Vi skal prøve å få tak i form’en vår:

document.getElementById("ourForm")

Dette returnerer et objekt som tilsvarer form’en som vi satte opp i HTML. I teorien kan vi legge til .addEventListener() på slutten av den linja, men det er oftest ryddigere å gi elementet et variabelnavn, og heller legge addEventListener til på variabelen, som vist under:

let ourForm = document.getElementById("ourForm");
// som eksempel bruker vi en click-lytter som viser en alert-box
form.addEventListener('click', () => alert('Thanks for clicking'))

Vanligvis når vi har et form-element er det ikke bare et tilfeldig klikk hvor som helst på form’en vi ønsker å lytte til, men heller når form’en blir sendt, eller submitted. Så la oss bytte ‘click’ med ‘submit’. En ting som er verdt å merke er at når man submit’er et skjema (form), så vil browseren som utgangspunkt alltid laste inn siden på nytt. For å unngå dette bruker vi en metode som heter preventDefault:

form.addEventListener('submit', (event) => {
  event.preventDefault()
  alert('Thanks for submitting')
})

Hvis du nå går ned til forhåndsvisningen igjen, så kan du trykke på skjemaet, men det vil ikke skje noe. Hvis du derimot fyller inn litt tekst og trykker på knappen “create item”, så vil du få opp alert-boksen igjen.

Men det vi egentlig vi vil gjøre er jo ikke å vise frem en alert-boks. Vi ønsker å hente verdien fra input-feltet, og legge det til som et nytt element i lista under.

La oss hoppe tilbake til HTML-koden, og legge til en ID på input-feltet:

<input type="text" id="ourField" autocomplete="off">

Og så, tilbake i JavaScript-koden vil vi først hente ut input-elementet:

let ourField = document.getElementById("ourField")

Legg denne linja rett under den første linja i koden, slik at variablene blir samlet i toppen.

Nå kan vi bytte ut alert-linja med følgende:

console.log(ourField.value)

Som bør gi oss tilbake verdien av det som er i input-feltet, når vi trykker “create item”-knappen. Prøv å fylle inn noe tekst, trykk “Create Item”, og sjekk konsollet.

Med det vet vi at vi får tak i teksten i feltet, men vi ønsker jo å fylle ut en liste, ikke bare logge teksten til konsollet. La oss hoppe tilbake til HTML’en, og legge til en ID på ul-elementet:

<ul id="ourList">

Tilbake i javascript, kan vi først hente ut elementet, i toppen av dokumentet:

let ourList = document.getElementById("ourList")

Så kan vi gå til bunn av dokumentet, og lage en funksjon:

// navnet på funksjonen er valgfritt
function createItem() {}

Nå har vi foreløbig bare en tom funksjon som vi skal fylle ut snart. Men la oss nå bytte ut console.log linja i addEventListener funksjonen med:

createItem(ourField.value)

Siden vi ønsker å sende inn verdien som et argument til funksjonen createItem, må funksjonen kunne ta inn argumenter:

function createItem(item) {

Og nå, i kroppen av funksjonen kan vi generere HTML, med javascript:

function createItem(item) {
  // ourList er ul-elementet vårt
  ourList.insertAdjecentHTML("beforeend", item)
}

I forhåndsvisningen, fyll ut tekst som f.eks “wash my car”, og trykk “create item”, så vil teksten legges til nederst. Men ting ser ikke helt riktig ut. Og det er fordi vi setter bare inn teksten, men ikke li-tag’ene som våre eksempeldata har, og vi mangler slette-knappen. La oss hoppe tilbake til HTML-koden. Kopier en av linjene som begynner med <li>, og slett deretter alle linjene som har eksempeldata, altså alle linjene som begynner med <li>.

Tilbake i createItem-funksjonen vår lager vi oss en variabel som holder på HTML’en vi har kopiert:

function createItem(item) {
  // Lim inn linja som vi kopierte, og bytt ut teksten med item-parameteren
  let ourHTML = ´<li>${item}<button>Create Item</button></li>´
  // vi bytter ut det andre argumentet med ourHTML
  ourList.insertAdjecentHTML("beforeend", ourHTML)
}

La oss gjøre at input-feltet tømmer seg når man trykker Create Item:

Legg til en ny linje i createItem-funksjonen:

// for å tømme feltet når knappen blir klikket på:
ourField.value = ""

Vi kan også gjøre at feltet blir autofokusert etter at man har klikket, slik at man ikke trenger å klikke inni tekstfeltet for å begynne å skrive:

ourField.focus()

Nå kan du i forhåndsvisningen prøve å legge til flere elementer ved å skrive inn tekst, og trykke enter.

Vi skal nå ordne slette-knappen. Siden disse genereres av oss med javascript, så kan vi ikke bruke addEventListener, ettersom elementene ikke finnes når nettsiden lastes.

La oss lage en flunkende ny funksjon, helt nederst i dokumentet:

function deleteItem() {
  alert("Delete requested")
}

Spørsmålet er, hvordan får vi til å kalle på denne funksjonen?

Vi kan gå tilbake til createItem funksjonen vår, og endre button tag-en som genereres i ourHTML til å ta med et onclick-attributt, som kan spørre etter en funksjon:

// linjeskiftene er ikke nødvendig
let ourHTML = ´
  <li>${item}<button onclick="deleteItem()">Create Item</button></li>
´

Hvis vi nå går ned igjen i forhåndsvisningen, skriver inn Buy Carrots, trykker create item, og deretter trykker Delete – så vil vi se alert-boksen. Som vanlig er det ikke en alert-boks vi vil ha, men heller faktisk slette elementet. Men når vi får en lang liste med elementer, så må vi vite at vi sletter riktig element. Hvordan kan vi gjøre det? Vi må vite hvilket element som ble trykket på.

I ourHTML-variabelen kan du finne igjen onclick-attributtet, og legge til this som argument i deleteItem-kallet.

let ourHTML = ´
  <li>${item}<button onclick="deleteItem(this)">Create Item</button></li>
´

Browseren vil da tolke this som elementet som ble klikket på.

Tilbake i deleteItem funksjonen vår må vi da kunne ta imot parameter:

// navnet på parameteret spiller ingen rolle, men det bør gi mening
function deleteItem(elementToDelete) {
  elementToDelete.parentElement.remove()
}

Nå kan du opprette noen elementer, og prøve å slette de igjen, og da skal alt fungere fint!

Veien videre

Det er et stort problem med applikasjonen vår. Hver gang man laster inn siden på nytt, så vil alt slettes. Selv om du har lagt inn mange punkter i to-do lista, så vil alt forsvinne. Vi sier da at vi ikke har “persistente” (vedvarende) data. Det vi må gjøre er å lagre dataene et sted, slik at brukeren kan hente de frem på et senere tidspunkt. Vi kunne teoretisk sett lagret dataene på brukerens fysiske enhet, enten det er en laptop, mobil, eller hva som helst. Problemet da er at brukeren ikke ville kunne se de samme dataene på tvers av alle enhetene sine. Den eneste praktiske løsningen vi har for å kunne ha persistente data på tvers av flere data er å ha en server (tjener), og da gjerne en databaseserver. En databaseserver forstår ikke HTML-elementer, slik vi har i koden nå. De foretrekker såkalte primitive data, slik som tall, ren tekst, booleans, eller lignende. 

Det å både presentere data som skal se fin ut for brukeren, men også ha data som gir mening for en database kan bli forvirrende og komplisert, og er grunnen til at en del javascript-rammeverk har blitt laget, slik som Angular, Vue, React. Rammeverkene har som hensikt å forenkle prosessen. I virkeligheten er det best å holde seg til et slikt rammeverk, men for å forstå hensikten med dem er det viktig å vite hva slike rammeverk egentlig gjør. Veldig mange begynnerutviklere prøver å lære alt på en gang, og alle rammeverkene samtidig. Det har liten hensikt. Har man først lært alt det underliggende som et rammeverk gjør er det en enkel sak å lære selve rammeverket. Det anbefales sterkt å styre unna rammeverkene, inntil videre.