Skip to content
Asko Kauppi edited this page Apr 29, 2021 · 6 revisions

Hacklab Helsinki #web-kehitys kurssi 21.4.2021 -

2. Sovellus (front-end)

Kuva: Wikimedia Commons, CC BY-AS 4.0

Tällä kurssikerralla:

  • pyöritään hakemistossa packages/app
  • tutustutaan Vue.js 3 -alustaan
  • tutustutaan selainpuolen routtaukseen ("client-side routing") ja sen toteutukseen Vue Router -kirjastolla

Jos aika riittää:

  • käynnistetään Cypress ja ajetaan sen testit

Aloitus ja ihmettely

Ensin juuritasolla:

$ git pull
$ npm install

Sitten frontin hakemistossa:

$ cd packages/app
$ npm install

Katsotaan hakemiston sisältöä hetki IDE:ssä:

Käydään läpi tämän tason tiedostot / hakemistot

package.json

Aina node-moduuleilla on parasta vilkaista package.json ensin. Mitä riippuvuuksia, mitä komentoja, files ja exports -kentät.

App

sisältää päätason Vue-komponentin ja sovelluksessa kaikkialla näkyvät osaset (esim. login)

pages

sisältää eri tilanteissa näkyvän sisällön. Vue Routing määrittää, mikä näistä on näytöllä.

data

sisältää Firestore -tietokannan sitomisen Vue:n (tai kulloisenkin UI-alustan) reaktiivisiksi muuttujiksi.

firebase, tools

tarpeellisia työkaluja, jotta kokonaisuus pysyisi pystyssä.

app.js etc.

Juuritasolla on aplikaation käyntiin laittamiseksi tarvittavia tiedostoja: app.js, router.js, user.js. Ne eivät sisällä kovin sovelluskeskeistä asiaa, vaan se on Vue-komponenteissa.

Vue-komponentit

Hakemistossa App (päätaso) ja pages (sovelluksen sisältö) on Vue-komponentit, jotka määräävät mitä sovellus tekee ja miltä se näyttää.

Pidetään UI ja IDE rinnakkain auki.

*Katsotaan esim. ylävasemman ("YOUR LOGO HERE!") elementin sisältöä selaimessa (oikea klikki > Tarkista).

Etsitään tuo lähdekoodista

Käydään komponentti läpi

  • Vue-komponentit käsittävät HTML:n (template), CSS:n style ja ohjelmapuolen script.

    • style:t ovat yleensä scoped, eli eivät vuoda toisiin komponentteihin. Tämä lyhentää myös niiden määrittelyä.
    • osiot voivat olla missä vain järjestyksessä mutta template-style-script on usein käytetty
    • komponentin skriptin lopussa on export default { ... }, jonka kentät ovat määrämuotoisia

Tämä on yksinkertainen komponentti.

Katsotaan toista.

Valitse yksi kerrallaan ja käydään läpi:

  • yläoikean käyttäjän nimen näyttävä komponentti (jos olet kirjautuneena)
  • pages/Home/index.vue eli alkunäyttö

Näillä kierroksilla luodaan huomiota esim. v-if ja v-for -rakenteisiin, eli siihen, miten Vue näkyy lähdekoodissa.

Reaktiivisuus

Reaktiivinen ohjelmointi (Wikipedia)

Web-sovelluksen kohdalla reaktiivisuus tarkoittaa esim. sitä, että tietokannassa tapahtuvat muutokset reititetään sovelluksen läpi ruudulla ilman varsinaista aktiivista ohjelmointia. Reitti laitetaan pystyyn ja sen jälkeen UI vain maagisesti päivittyy, jos taustajärjestelmän tiedot muuttuvat.

Jokainen moderni web-kehitysalusta tukee reaktiivisuutta, tosin kukin omalla tavallaan.

Nämä eroavat toistaan siten, että:

  • Ref

    • voi käyttää kaikille tietotyypeille
    • varsinainen arvo luetaan/asetetaan .value -syntaksilla
    • template-osassa (HTML) .value:n voi jättää pois; JavaScriptissä se tarvitaan aina
  • Reactive

    • voi käyttää vain objekteille: reactive({ count: 0 })
    • kenttiä voi käyttää suoraan (.count), myös JavaScript-puolella

Reactive ja Ref ovat kuin kaksi eri ohjelmointirajapintaa samaan asiaan, ja on kurssin pitäjän mielestä vähän vaivauttavaa, että tarjolla on molemmat. Missään ei suoraan sanota, milloin toinen olisi toista parempi.

Esimerkkisovelluksessa päädyttiin käyttämään vain Ref:jä.

Tarkemmin sanottuna shallowRef, jolloin objektimuotoisen arvon alakentät eivät ole reaktiivisia. Ero on pieni - käytännössä shallowRef on kevyempi.


Tarkastellaan, miten reaktiivisuus näkyy esimerkkiohjelmassa.

Sukelletaan koodiin

pages/Home/index.vue sisältää rivin:

const [projectsRef, unsub] = activeProjects(uid);   // start following this user's projects

activeProjects löytyy data/activeProjects.js:tä

`activeProjects` -määrittely
function activeProjects(uid) {    // (string) => Ref of Map of  -> { ..projectsC doc }
 // Firestore does not allow '.where()' on missing fields (Mar 2021); we want projects without '.removed'.
 // Let them come and skip the ones not wanted.
 //
 const [mapRef,unsub] = collRef( collection(db, 'projects/'), where('members', 'array-contains', uid), {
   conv: (v) => v.removed ? null : v    // filter out removed already at the porch
 });
 return [mapRef,unsub];
}

collection ja where määrittelevät, mitä Firestore:n osaa seurataan. collRef on apukoodia, joka kuuntelee Firestorea ja muuttaa kuulemansa Vue.js:n Ref:ksi.

unsub on funktio, jota pitää kutsua, jos sovellus ei enää ole kiinnostunut seuraamaan tietokantaa (esim. sovelluksen sisäistä näkymää vaihdettaessa).

Sorkitaan dataa!

Nyt, kun tiedämme mistä Ref saa tietonsa, voidaan kokeilla:

  • avata emulaattorin UI:n (localhost:4000) ja muuttaa siellä tietoja Voit riskittä lisätä, poistaa, vaihtaa tietoja. Päivittyykö sovelluksen UI vastaavasti?

Tekemäsi muutokset ovat käytössä niin kauan, kunnes käynnissä oleva emulaattori (npm run dev) suljetaan.

Uudella käynnistyksellä alkutiedot luetaan uudestaan (ks. local/docs.js).


Olemme nyt tutustuneet Vue.js -komponentteihin käytännössä ja reaktiiviseen tiedon kulkuun tietokannasta selaimeen.

Seuraavaksi katsotaan, miten selain ylipäänsä päätyy käyttämään ensimmäistä komponenttiaan.

app.js, router.js

src-hakemiston juureen on jätetty joitain ohjelmatiedostoja.

Nämä ovat pääosin sovelluksen logiikasta (joka elää Vue-komponenteissa) riippumattomia, mutta tarpeellisia, jotta kaikki toimii.

app.js

Tämä sisältää sovelluksen "entry point"in. init-funktiota kutsutaan, kun Firebase on saatu käyntiin, joten sovelluksella on jo tietty toimintaympäristö valmiina käynnistyessään (tämä yksinkertaistaa sen koodia).

Lisäksi tämä taso lataa Vue.js:n komponenttipuun (createApp(App)) ja käynnistää routtauksen hallinnan (app.use(router)).

router.js

Tämä on kenties lähdekooditiedostoista maagisin. Lukemalla Vue Router Guide:n kahteen tai viiteen kertaan voi ehkä saada käsitystä yksityiskohdista (kurssin pitäjä on vähän tuskastunut tämän kulman kanssa..).

Olannaisinta on ymmärtää selainpuolisen routtauksen konsepti (seuraavassa kappaleessa).

Käytännössä tämä osa tiedostosta on se sovelluksen kannalta olennaisin (kommentit poistettu):

const routes = [
  rLocked('/', Home ),
  rOpen('/', HomeGuest, { name: 'Home.guest' }),
  rLocked('/projects/:id', Project),   // '/projects/<project-id>[&user=...]'
  { path: '/index.html', redirect: '/' },
  rOpen('/:pathMatch(.*)', NotFound )
];

Tämä määrittelee seuraavat reitit:

  • / päänäyttö
  • / vierailijanäyttö (kun käyttäjä ei ole kirjautunut)
  • /projects/:id tietyn projektin sisältö

Lisäksi index.html ohjautuu päänäyttöön ja muille reiteille näytetään 404-ruutu.

Selainpään routtaus

Aloitetaanpa tarinalla.


Paljon aikaisemmin, kun serverit lähettivät selaimelle sivuja, ne olivat vastuussa myös noiden sivujen osoitteista. Olet nähnyt näitä osoitteita selaimen osoitepalkissa. Joka sivu ladattiin uudelleen, mutta koska ne olivat kevyitä ja Internet ei ollut vielä kovin vanha, tämä kaikki oli mahdollista.

Sittemmin Internet kasvoi, ja käyttäjät halusivat muutakin kuin taulukoita täytettäväkseen ja Submit-napin painettavakseen. Tieto alkoi kulkea Ajax:na serverin ja selaimen välillä silloinkin, kun käyttäjä ei tehnyt kerrassaan mitään.

Käyttäjät pitivät tästä. Lopputulemana se, mikä ennen oli "sivusto" olikin muuttunut "sovellukseksi". Sovellus osasi käynnistää itsensä nopeasti ja ladata lisää ominaisuuksia matkan varrella, jos niille oli tarvis. Serveri ei kuitenkaan enää määrännyt osoitepalkissa näkyvistä reiteistä. Selain teki nyt sen.


Selain on siis vastuussa siitä, mitä tapahtuu, kun palvelimelta pyydetään esim. sun.sovellus.com/projekti/5207.

Tällöin tapahtuu seuraavaa:

  • Firebase hosting (tai kuka domainia sun.sovellus.com hostaakin, lähettää selaimelle index.html:n.

    Miksi index.html?

    Koska selaimella ei ole vielä tietoa sovelluksesta. Voi olla, että käyttäjä sai linkin kaveriltaan tai netistä, eikä ole koskaan aiemmin käyttänyt sovellusta. Serveri siis "pelaa varman päälle" ja lähettää saman paketin riippumatta syvemmästä reitistä.

  • Selain saa index.html:n ja kerää siinä mainitut skriptit jne. ja käynnistää sovelluksen.

  • Sovelluksen sisäinen reititin (Vue Router) havaitsee pyydetyn polun (/projekti/5207) ja katsoo, löytyykö sitä vastaava Vue-komponentti.

    Tässä vaiheessa tulee mm. autentikaatio mukaan. Jos polkuun pääsy on suojattu (ks. yllä rLocked-funktio), ja kyseinen käyttäjä ei ole autentikoitunut(*), hänet ohjataankin ensin autentikoitumaan ja sen jälkeen pyytämälleen sivulle.

    (*) Tämä taas selviää esim. selaimen sisäisen, saittikohtaisen muistin perusteella. Firebase Auth tallettaa viimeaikaiset autentikoinnit IndexedDB -kantaan, mutta muitakin vaihtoehtoja on. (kurssin kannalta liian syvillä vesillä.. 🏊‍♂️🦈)

  • Jos Vue-komponentti löytyy, Vue Router päivittää selaimen router-view -tagin sisältämään sen. Tämä tag löytyy aina jostain Vue-ohjelmassa (esimerkissä App/index.vue).

Paina sovelluksen 'Jolly Jumper' -projektia

Mitä tapahtuu? Mitä tiedät edellä olevan suhteen tapahtuneen?

Voidaan myös lisätä debugger; -komento johonkin kohtaan reitityksen koodia ja katsoa, miten selaimen suoritus pysähtyy sinne.

Reittien merkitys

Reittien suunnittelulla on myös syvempi merkitys. Ne ovat osa sovelluksen käyttäjälle näkyvää rajapintaa.

Otetaan esimerkiksi /projekti/5207. Käyttäjä voi sen avulla:

  • kiinnittää suoran linkin selaimen kirjanmerkkeihin
  • jakaa linkin esim. sähköpostin tai pikaviestimen välityksellä muille

Sovellusta suunnitellessa kannattaakin jo alussa miettiä, miten se jakautuu eri osioihin ja mitkä niistä ansaitsevat ovan reittinsä.

Vinkki: Jos päädyt vaihtamaan reittejä matkan varrella, voit käyttää redirect -toimintaa niin, että vanhatkin reitit toimivat (jos mahdollista). Näin käyttäjien kirjanmerkit pysyvät toiminnassa pitempään.

Valmis!

Tämä riittää lähdekoodiin tutustumiselle.

Mikäli haluat kehittää omaa sovellustasi, voit aloittaa sen tältä pohjalta. Tee muutoksia lähdekoodiin, ja tutustu Vue.js:n dokumentointiin siinä sivussa.

Kurssin jatkokerrat käsittelevät sitä, miten sovellus pakataan tuotantokäyttöön, toimitetaan pilveen ja miten sitä seurataan (ja parannetaan) sen ollessa jo käyttäjien saatavilla.

Lisäaineistoa:


Seuraavaksi: 2.1 Cypress-käyttöliittymätestaus