Forbedre Create Item

Til nå har create-item funksjonaliteten vår fungert på samme måte som standard skjema-sending fungerer. Man fyller ut, og sender inn data, og nettleseren laster opp sida på nytt. Men, siden vi nå har asynkron funksjonalitet på update og delete, så er det på tide å gjøre det samme for create item.

Vi ønsker at når brukeren legger til en ny to-do, så legges den til i lista umiddelbart, uten behov for å oppdatere nettsida.

Gå til linje 1 i browser.js, og lag noen nye linje skift, så du har litt plass. På linje 1 kan du skrive:

// Create Feature

På linja under skal vi prøve å få tak i form-tag’en som ligger i HTML’en vår. For å gjøre det enklest mulig kan vi hoppe inn i server.js, og scrolle ned til vi finner form-tag’en. Når du finner den kan du legge til et id-attributt, som kan få verdien create-form. Verdien spiller som vanlig ikke egentlig noen rolle, men det er greit at det er en id som gir mening i sammenhengen.

<form id="create-form" action="/create-item" method="POST">

Siden vi først er i denne fila kan vi også lage et id-attributt til input-tag’en som er et par linjer under form-tag’en. id-attributtet kan ha verdien create-field:

<input id="create-field" name="item" autofocus autocomplete="off" class="form-control mr-3">

Scroll ned enda litt, til du finner ul-tag’en, og la oss gi den også et id-attributt, med verdi item-list:

<ul id="item-list" class="list-group pb-5">

Nå som disse elementene har id’er er det enkelt å få hentet disse elementene i browser-javascript-koden vår. Så, tilbake i browser.js, gå til linje 2:

document

Vi ønsker å se på HTML-documentet vårt, og deretter få tak i et element, ved å bruke id’en til elementet. Id’en vi vil ha tak i er create-form:

document.getElementById(‘create-form’)

Og til slutt ønsker vi å legge til en eventListener, som lytter etter når skjemaet sendes (submittes).

document.getElementById('create-form')  .addEventListener('submit', function() {

}

Vi vil også sende inn eventet som oppstår som et argument i den anonyme funksjonen. Skriv derfor event (uten anførselstegn) mellom parantesene.

Nå vil vi skru av den standard oppførselen til skjemaet, som er å refreshe nettsida når skjemaet blir sendt, og det løser vi enkelt! Som første linje i den anonyme funksjonen skriver vi:

event.preventDefault()

Du kan nå lagre fila (ctrl+s) og gå til nettleseren og oppdater sida (ctrl+r eller F5). Hvis du nå prøver å lage en ny todo, så kan du se at det ikke skjer noen ting – og det er nettopp det vi ønsker, akkurat nå! Så, på neste linje i koden trenger vi en ny fetch. Kopier fetch-metoden fra delete-blocka vår, og lim den inn på linja under event.preventDefault(). Nå kan vi endre det vi trenger. Begynn med å bytte ut endepunktet som vi sender en request til, fra delete-item til create-item. I body’en av requesten vår kan du bytte ut “id” med “text”, og sette verdien til x, som en foreløbig placeholder. 

På linje 2, lag deg et par linjeskift. På linje to lager vi en variabel som heter createField, hvor vi skal ha tak i teksten fra input-feltet.

let createField = document.getElementById('create-field')

Nå som vi har tak i elementet kan vi bytte ut x-placeholderen vår i request-body’en med createField.value, for å hente ut verdien.

Nå må vi hoppe inn i then-metoden, for å endre hva som skjer når vi får tilbake en respons fra serveren. La oss slette linja som ligger der, og skriv heller en kommentar, og et alert-kall::

// Create the HTML for a new item
alert("You created a new item")

På et senere tidspunkt kommer vi tilbake hit for å generere HTML som gjør at todo’en som blir laget, blir lagt til i lista med de andre.

La oss nå lagre fila, og så hoppe tilbake til server.js for å endre hva som skjer på server-sida når den får en forespørsel til create-item. Finn igjen app.post(‘/create-item’). Finn insertOne()-metoden vår, og i objektet som sendes inn, endre verdien fra req.body.item til req.body.text. Så kan du endre fra res.redirect(‘/’), til res.send(“success!”). 

Lagre fila, og test at det fungerer i browseren. Prøv å legge til et nytt item. Du vil få opp en pop-up, men den nye todo’en ligger ikke i lista. Gå til mongoDB-compass, og sjekk at todo’en ligger der. Dersom den gjør det, så har du gjort alt riktig og vi kan nå gjøre det som trengs for å vise frem den nye todoen i lista.

Tilbake i browser.js, kan gå tilbake til create feature, finne fetch-metoden, og til slutt finne then-metoden hvor vi har alert-kallet. Fjern linja, og la oss heller få tak i lista med todos:

document.getElementById('item-list')

Når vi har fått tak i det elementet, så vil vi kjøre en metode som heter 

insertAdjecentHTML(a, b)
  1. tar inn spesifikke tekststrenger om hvor i elementet vi skal legge til mer HTML – for vår del skriver vi “beforeend”, altså før slutten av elementet.
  2. tar inn hva som skal legges til, la oss teste med “hello”.
document.getElementById('item-list').insertAdjecentHTML('beforeend', 'hello')

Lagre fila (ctrl+s) og last inn sida på nytt i nettleseren (ctrl+r eller F5). Hvis du nå legger til et nytt element i lista, vil du se “hello” dukke opp nederst i lista, helt dynamisk.

Så nå, istedenfor ‘hello’, kan vi sette inn den html’en som lager et liste-element. Bytt ut argumentet ‘hello’, med en funksjon som vi kaller itemTemplate() – dette er en funksjon vi selv skal definere. Gå til linje 1, der vi har kommentaren Create Feature, og lag noen linjeskift. Gå til toppen igjen, så vi kan lage funksjonen:

function itemTemplate() {
  return `Hello, from a function`
}

Lagre fila (ctrl+s), last inn sida på nytt (ctrl+r eller F5), og legg til en ny todo i lista. Du bør da få opp Hello from a function i bunn av lista. Nå ønsker vi å bytte ut denne teksten, med den faktiske HTML-koden for å generere et list item. Denne HTML’en har vi jo allerede, i server.js, så la oss oppe inn i den. Let i HTML’en til du finner ul-tag’en med id item-list. Under denne har vi en return med en template-literal. Kopier fra den første backtick’en tilogmed den siste backtick’en. Når du har det, kan vi kopiere det inn i itemTemplate-funksjonen vår browser.js:

function itemTemplate() {
return `
      <li class="list-group-item list-group-item-action d-flex                   align-items-center justify-content-between">
        <span class="item-text">${item.text}</span>
        <div>
          <button class="edit-me btn btn-secondary btn-sm mr-1"                  data-id="${item._id}">Edit</button>
          <button class="delete-me btn btn-danger btn-sm"                  data-id="${item._id}">Delete</button>
        </div>
      </li>
      `
}

Hvis du nå tenker at å ha duplisert kode i flere filer virker redundant og overflødig, så har du helt rett. Vi skal fikse det senere, men for nå trenger vi ikke bekymre oss for det. Det vi kan bekymre oss for er: hvordan skal vi få tak i riktig data-id’er fra databasen for elementer vi nettopp har opprettet? Per nå sender vi inn ${item._id}, som ga mening i sammenheng med server.js fila, men det gir ikke mening her i browser.js. La oss hoppe inn i server.js, og scroll ned til du finner

app.post('/create-item', ...)

I dette POST-endepunktet, finn der vi sender tilbake en respons med success:

res.send('Success')

La oss bytte denne linja med:

res.json()

Det vil si at vi sender tilbake et javascript object tilbake som respons. Det vi skal bruke som argument er data som vi får tilbake fra databasen. Derfor, i den anonyme funksjonen rett før, la oss legge inn et par parameter:

function(err, info)

Som argument i res.json() kan vi bruke info. Den inneholder et array som heter ops. Det første elementet i ops-arrayet inneholder et javascript-objekt med informasjon om dokumentet vi nettopp har opprettet:

res.json(info.ops[0])

La oss lagre fila, og hoppe tilbake til browser.js

Finn fetch-metoden til create-feature’n vår, og gå til then-metoden. Der kalles en anonym funksjon. Siden vi vet at vi nå får data fra node-serveren vår kan vi legge inn et parameter i denne funksjonen, som gjør at vi får tak i json-objektet. La oss lage en parameter ved navn response:

function(response) { ...

Helt i slutten av kroppen til den anonyme funksjonen, har vi insertAdjecentHTML-metoden hvor vi kaller itemTemplate-funksjonen. La oss legge inn response.data som et argument i itemTemplate.

itemTemplate(response.data)

Responsen vi får tilbake har altså et javascript-objekt som heter data, som inneholder informasjon om det nyopprettede mongodb-dokumentet. Til slutt må vi gå til toppen, til itemTemplate-funksjonen vår, og gjøre så den tar inn en parameter som heter item. Med det gjort, vil referansene til item._id og item.text gi mening.

La oss lagre fila (ctrl+s) og laste inn sida på nytt i nettleseren (ctrl+r eller F5)

Legg til en ny todo i lista, og se at det automatisk blir lagt til i lista. Du kan også trykke edit, for å endre teksten. Og det vil si at knappene har riktige id’er. Du kan også bekrefte dette ved å sjekke i mongodb-compass.

En mini-ting vi kan fikse, er å tømme input-feltet når brukeren har lagt til en ny todo. I browser.js, finn then-metoden under Create Feature-kommentaren. Lag en ny linje under document.getElementById, hvor vi setter createField til en tom streng

createField = ''

Og på linja under der kan vi forsikre at tekstpekeren står i input-feltet igjen

createField.focus()


Lagre fila, gå til nettleseren og last inn sida på nytt. Hvis du nå skriver inn tekst for en ny todo, trykker enter, så tømmes feltet, og du kan fortsette å skrive inn flere todos, helt uten å flytte henda fra tastaturet.

Det neste vi kan se på er å fjerne den duplikate-koden vi har i både browser.js og server.js.

Vår liste over todos vises først fram med server.js, og vi legger til nye todos med browser.js.

Gå til bunn av HTML-koden vår, og finn våre to script-tag’er. Lag en ny script-tag rett over dem. 

<script>
let items = ${JSON.stringify(items)}
</script>

Siden vi er i en template literal må vi bruke ${}-syntaks for å gjøre noe dynamisk.

JSON er som tidligere nevnt JavaScript Object Notation, og er bare en typisk måte å sende data på. Vi ønsker å gjøre om items til en JSON-string, så dét blir argumentet til stringify-metoden. 

La oss nå være litt crazy og slette alt som ligger mellom ul-tag’ene, litt lenger opp i koden. Der skal du da kun stå igjen med

<ul id="item-list" class="list-group pb-5"></ul>

Hvis du lagrer nå, og laster inn sida på nytt, så vil naturligvis lista være tom. Men nå kan vi bruker browser-javascript for å generere den samme lista.

Tilbake i browser.js, under itemTemplate-funksjonen kan du lage noen linjeskift, så du får litt plass. Her kan vi skrive en kommentar til oss selv, bare for å holde oss organisert:

// Initial Page Load Render

Så ønsker vi å få tak i ul-elementet som vises på siden. Siden den har en id, er det såre enkelt:

document.getElementById('item-list')

På dette elementet ønsker vi å legge til HTML, så vi kan bruke insertAdjecentHTML()-metoden:

document.getElementById('item-list').insertAdjecentHTML('beforeend', ourHTML)

På linja over denne kan vi lage en variabel som heter ourHTML:

let ourHTML = items.map(function(item) {

})

Vi tar tak i items, som er et array, og derfor har tilgang på map-metoden. Map tar inn en funksjon som argument. I den anonyme funksjonen setter vi item som argument.

Nå er alt vi trenger å gjøre å fylle inn body’en:

return itemTemplate(item)

Rett bak den lukkende parantesen til variabeldeklarasjonen av ourHTML legger vi til join-metoden med en tom streng som argument, ettersom map returnerer et array. Vi ønsker bare å få tilbake en string med tekst.

Tilsammen får vi da:

let ourHTML = items.map(function(item) {
  return itemTemplate(item)
}).join('')

Hvis vi nå lagrer fila og oppdaterer browseren vil du se alle elementene som ligger i databasen igjen. Neat!