# Web-ohjelmointi, syksy 2023
## **KEHITYSBLOGI**
---
### Tekijä: Paula-Riitta Huttunen
---
[ToC]
## Viikko 1
### Mitä opin tällä viikolla?
Aloitusviikolla sain palauteltua mieleen, kuinka Visual Studio Code toimii ja liityin kurssin Slack-ryhmään. Päivittelin ohjelmistoja koneeseeni ja latasin Node.js:n uusiksi. En oikeastaan oppinut vielä mitään uutta, lähinnä tuli vanhan kertausta, mikä oli toki ihan tarpeellista.
### Mitä harjoituksia tein?
En vielä mitään (eipä tainnut edes olla mitään harjoituksia, vain sinne Slackiin rekisteröityminen), aika meni ihan puhtaasti luentojen kuunteluun ja perehtyen kurssimateriaaleihin sekä palauttaen mieleen kuinka VS Code ynnä sen yhteydessä tarvittavat lisäosat toimivat. Koitin myös hiukan palautella mieleen HMTL:n perusteita.
---
## Viikko 2
### Mitä opin tällä viikolla?
Tällä viikolla latasin ja konfiguroin Git:in ja perehdyin GitHubiin. Lisäksi palauttelin mieleen HTML:n perusteita. Git:iin uudelleen perehtymiseen ja gitin käyttövalmiiksi saattamiseen meni jonkun verran aikaa. Vaikka olin edellisessä työpaikassani käyttänyt gittiä, en kuitenkaan koskaan ollut aivan ymmärtänyt sen toimintaperiaatetta. Töissä ollessani oli Git käytännössä asennettu koneelleni puolestani ja minun tarvitsi vain osata käyttää graafistakäyttöliittymää (Git Extensions), jonka avulla tiedostoja "pullattiin", "pushattiin" ja "commitettiin", eikä minun tarvinnut ymmärtää mitä taustalla tapahtui. Nyt kun parin vuoden tauon jälkeen piti yrittää muistaa, mikä oli Git:in toimintalogiikka ja kuinka sillä operoitiin niin meni kyllä sormi suuhun.
Git:iä asentaessa en meinannut ymmärtää kuinka luon SSH-avaimen ja yhdistän sen GitLabiin niin uppouduin sitten lukemaan Git:in opaskirjaa ja GitLabin ohjeita. -Osaan nyt luoda yksityisen ja julkisen SSH-avaimen (sekä komentokehotteen kautta OpenSSH:ta käyttäen että PuttyGen-ohjelman avulla) ja tiedän, että RSA:n sijaan kannattaa käyttää ed25519-salausta, ja että jos OpenSSH:sta on vanha versio niin tulee avainta luodessa käyttää "-o" -komentoa (selvisi, että minulla sittenkin oli OpenSSH, jota Windowseissa ei kuulema ole automaattisesti ja versioni oli riittävän tuore jottei tarvinnutkaan kikkailla). Opettajan ohjeisiin laittama linkki GitHubin sivuille ei toiminut niin en vielä saanut laitettua luomaani avainta sinne, mutta toivon osaavani laittaa sen sinne kunhan linkki päivitetään.
Tällä viikolla opin myös paljon erilaisia käskyjä, joilla voi operoida Git Bash:issa ja luulen nyt ymmärtäväni aiempaa perusteellisemmin gitin toimintaperiaatetta. En usko enää palaavani käyttämään sitä graafistakäyttöliittymää, jota käytin töissä, koska sain nyt tietää sen olevan vajavaisempi toimintaominaisuuksiltansa verrattuna siihen, että syöttää käskyt suoraan komentokehotteeseen.
### Mitä harjoituksia tein?
Tällä viikolla leijonan osan ajasta vei TRA1-kurssin tehtävät ja Git:in kanssa säätäminen, että olin siksi jäljessä luentojen kuuntelusta ja jälkijunassa tullessani missasin ensimmäisen harjoituksen palautuksen :( Haluan kuitenkin vielä tehdä sen, koska tarvitsen kertausta HTML:ään liittyvissä asioissa. Toivottavasti tehtävän saa vielä palauttaa myöhässäkin, vaikka en oleta saavani siitä enää mitään pisteitä.
#### Harjoitus 1
Tämä oli HTML:ään liittyvä tehtävä, kopion opettajan pohjan ja tein siihen päälle omat muokkaukseni. Lopuksi ajoin HTML ja CSS-koodit validaattorissa. En löytänyt kuin yhden virheen, joka oli helppo korjata. Tehtävä näytti aluksi vaikeammalta kuin mitä se loppupelissä oli.
---
## Viikko 3
### Mitä opin tällä viikolla?
Ensimmäisenä opiskeluvuotenani aloitimme ohjelmoinnin perusteet TypeScriptillä, joten JavaScript oli toimintaperiaatteeltansa tuttu kieli, joskin pitkän tauon jälkeen tuntuu, että pitää jälleen aloittaa melkeinpä ihan perusteista. Viimeaikainen Javan kanssa operoiminen on onneksi helpottanut tiedon nopeaa omaksumista ja nyt hahmotan olioiden mekaniikkaa paremmin kuin aiemmin.
### Mitä harjoituksia tein?
Tällä viikolla perehdyin vielä lisää JavaScriptiin ja tein kummatkin JS-aiheiset ohjelmointitehtävät; "*Javascript -perusteet, teht 1*" & "*Javascript -perusteet, teht 2*". Ne sujuivat odotettua mukavammin, mutta kyllä niissä silti riitti haasteita. Olin tyytyväinen, että sain kummatkin algoritmit lopulta toimimaan :)
#### Harjoitus 2
##### Javascript -perusteet, teht 1
Aloitin palindromifunktio-tehtävästä, joka sujuikin yllättävän hyvin, kun lopulta keksin miten vertaan taulukoiden alkioita keskenään. Jonkun verran meni aikaa palautellessa mieleen ihan ohjelmoinnin perustaitoja, kuten ehtolauseita sekä luuppien toimintaa. Onneksi meneillään olevan TRA1-kurssin Java-taitojen opettelun ohella on tullut kerrattua ihan ohjelmoinnin perusteitakin niin ei aivan nollasta ole tarvinnut aloittaa JS:n kanssa, ja asioiden omaksuminen on nopeampaa, kun on joku vertailukohta.
Tässä tehtävässä palautui hyvin mieleen, kuinka sijoitetaan dataa listoihin ja verrataan keskenään listojen alkioita toisiinsa.
```javascript=16
function palindromi() {
// kysytään syöte
var input = require("readline-sync");
var nimi = input.question("Anna sana: ");
// taulukoiden luonti
const talsi = [];
const kaannos = [];
// muuttujien alustus
let i = 0;
let vastaus;
// merkit syötetystä sanasta talteen
for (i = 0; i < nimi.length; i++) {
talsi.push(nimi[i]);
// testitulostus
//console.log("talsin testitulostus: " + talsi);
}
// sijoitetaan talsi-taulukon merkit lopusta alkuun kaannos-taulukkoon
for (i = 0; i < talsi.length; i++) {
kaannos[i] = talsi[talsi.length - 1 - i];
// testitulostus
//console.log("käännoksen testitulostus: " + kaannos);
}
// taulukoiden vertaus loopin avulla
for (i = 0; i < talsi.length; i++) {
if (kaannos[i] == talsi[i]) {
vastaus = true;
}
if (kaannos[i] != talsi[i]) {
vastaus = false;
}
}
return vastaus;
}
console.log("Annettu sana on palindromi: " + palindromi());
```
---
#### Harjoitus 3
##### Javascript -perusteet, teht 2
Harjoitus 3:ssa opettelin luomaan oliota sekä sijoittamaan luodut oliot taulukkoon. Aluksi oli hieman vaikeuksia saada luotua uusia olioita, mutta lopulta muistin miten JavaScriptissä käytetään luokkaa ja sen konstruktoria.
Toisessa osassa tehtävää oli luotava funktio, joka etsii käyttäjän aiemmassa vaiheessa luotujen henkilöiden syötettyjä puhelinnumeroita aiemmin luodusta puhelinluettelosta. Toteutin tämän niin, että käyttäjältä jatkuvasti kysytään minkä toiminnon tämä haluaa suorittaa ja sitten toimintavaihtoehdot on listattu switch-rakenteeseen, jossa kutsutaan funktiota, joka joko suorittaa lisäyksen tai hakemisen tai sitten lopettaa ohjelman suorittamisen.
```javascript=16
/* Tee puhelinluettelo.
Puhelinluetteloon lisäät taulukkoon objekteja (eli henkilöitä joilla nimi
ja puhelinnumero).
Käyttäjältä kysytään henkilön nimi ja puhelinnumero.
Henkilön poisto -toimintoa ei tarvitse tässä versiossa olla.
Tee hakutoiminto jossa haet nimen perusteella puhelinnumeron.
Tee puhelinnumeron haku funktioksi. Funktion parametrina on taulukko josta
haetaan henkilön nimi. Funktio palauttaa puhelinnumeron.
Kutsu funktiota.
Käyttöliittymän voit tehdä millaiseksi haluat
(komentokehoite -pohjainen kuitenkin) */
// lukijan määrittäminen
const lukija = require("readline-sync");
// muuttujan alustus
var jatka = true;
// luodaan puhelinluettelo-taulukko
let puhelinluettelo = [];
// Henkilo-luokan kontruktori
class Henkilo {
constructor(nimi, numero) {
this.nimi = nimi; // uudelle oliolle olion parametrina nimi
this.numero = numero;
}
}
// funktio, joka kysyy käyttäjän tiedot ja luo niiden pohjalta henkilön, joka lisätään puhelinluetteloon
function lisaaHenkilo() {
let nimi = lukija.question("Anna nimi: ");
let numero = lukija.question("Anna puhelinnumero: ");
let henkilo = new Henkilo(nimi, numero); // luodaan uusi Henkilo-olio
puhelinluettelo.push(henkilo); // lisätään luotu olio puhelinluetteloon
}
// funktio, jolla etsitään numero nimen perusteella
function haeNumero() {
let haettavaNimi = lukija.question("Anna haettavan nimi: ");
for (let i = 0; i < puhelinluettelo.length; i++) {
// käydään läpi puhelinluettelon oliot
let henkilo = puhelinluettelo[i];
// jos arvot ja tyypit ovat samoja
if (henkilo.nimi === haettavaNimi) {
return henkilo.numero; // mikäli vastaavuus löytyi, palautetaan haettavan numero
}
}
// muussa tapauksessa
return "Etsittyä henkilöä ei löytynyt";
}
// kysytään jatkuvasti mitä käyttäjä haluaa tehdä
while (jatka) {
let toiminto = lukija.question("Valitse toiminto (lisaa/hae/lopeta): ");
// switch-rakenteella jatkotoimien selvittäminen
switch (toiminto) {
case "lisaa":
lisaaHenkilo(); // lisätään Henkilo-olio puhelinluettelo-taulukkoon
break;
case "hae":
console.log(haeNumero()); // haetaan numeroa nimen perusteella
break;
case "lopeta":
jatka = false; // lopettaa ohjelman suorituksen
break;
default:
console.log("Tuntematon toiminto.");
}
}
```
---
## Viikko 4
### Mitä opin tällä viikolla?
Luokkien kanssa operointia; olioiden luomista luokasta ja luokan periytymistä. Luokat ja periytyminen on jo Java:sta tuttua, mutta se vaati vähän mieleen palauttelua. Luulin jo, että gitin kanssa operoiminen sujuisi kun olen sitä aiemminkin käyttänyt, mutta edellisestä kerrasta olikin sen verran aikaa, että sen kanssa vänkäämiseen kuluikin aika paljon aikaa. Toivon, että palautukseni onnistui ja palauttamani osoite päästää opettajan katsomaan ohjelmakoodiani.
### Mitä harjoituksia tein?
#### Harjoitus 3
Suurimmat vaikeudet ovat tosiaan olleet git:in kanssa, en jostain syystä millään meinannut keksiä syytä siihen, miksi en saanut pushattua tiedostoa GitHubin repositoryyn. Terminaaliin tuli jatkuvasti jonkinlainen virheilmoitus, ilmeisesti liittyen johonkin merge-ongelmaan. En kyllä ymmärrä miten se oli mahdollista, kun repositorio oli olevinaan tyhjä, kun sinne yritin lisätä. No, oli siellä se README-tiedosto sekä lisenssi, olisivatko ne jotenkin voineet sekoittaa hommia...? Lopulta kuitenkin sain ongelman ratkaistuksi, en oikeastaan ole edes varma miksi onnistuin loppujen lopuksi. Pääasia on, että Henkilo.js -tiedoto on nyt Githubissa kansiossa Teht3. Toivon, että jatkossa gitin käyttö ei ole yhtä sekavaa ja hommat luistaa huomattavasti paremmin.
Itse ohjelman kanssa ei ollut juuri ongelmia. Hieman päänvaivaa aiheutti Date()-metodin ymmärtäminen, jouduin siitä lukemaan jonkun verran ennen kuin ymmärsin sen toimintalogiikan.
Huom! Harjoitus oli alunperin palautettu GitHubiin erilliseen repositoryyn (Henkilo.js, jossa oli kansio nimeltä Teht3, jossa oli tiedosto Henkilo.js), mutta kopioin kansion ja nimesin sen uudestaan tehtävänannon mukaisesti "Urheilija1" ja siirsin sen REST1-repostoryyn, jonne olen jakanut muutkin harjoitukset, jotka piti jakaa GitHubissa.
```javascript=16
/* Kehitystehtävänä on määritellä olio-ohjelmointikielille ominainen luokkamäärittely
ja periytyminen JavaScript-kielellä.
Määrittele yliluokka Henkilo, joka sisältää ihmisen henkilötietoja: etunimet, sukunimi,
kutsumanimi, syntymävuosi.
Määrittele luokka Urheilija, joka perii Henkilo-luokan ja toteuttaa lisäksi
saantifunktiot (get- ja set-) Urheilija-luokalle merkityksellisiin attribuutteihin.
Lisää Urheilija luokkaan seuraavat ominaisuudet: linkki kuvaan, omapaino, laji,
saavutukset. Kirjoita nämä vaatimukset toteuttava koodi joka toimii node.js-tulkissa.
Toteuta koodi. Lisää koodiin esimerkkejä Urheilija –olioista.
Talleta toteutuksesi gitlab (tai github) -ympäristöön omaan projektiisi hakemistoon:
Urheilu1
Kirjoita tehtävästä kommentointi ja huomioita kehittäjän blogiin (hackmd.io). */
class Henkilo {
// alustetaan Henkilo-luokan omat yksityiset muuttujat
#etunimi;
#sukunimi;
#kutsumanimi;
#syntymaVuosi;
// konstruktori Henkilo-luokalle
constructor(etunimi, sukunimi, kutsumanimi, syntymaVuosi) {
this.#etunimi = etunimi;
this.#sukunimi = sukunimi;
this.#kutsumanimi = kutsumanimi;
this.#syntymaVuosi = new Date(syntymaVuosi, 0, 1); // asettaa päivämääräksi 1. tammikuuta parametrsissa saatuna vuonna
}
// **** getterit ****
// getteri joka palauttaa syntymävuoden Date-objektin getFullYear()-metodilla
get syntymaVuosi() {
return this.#syntymaVuosi.getFullYear();
}
get etunimi() {
return this.#etunimi;
}
get sukunimi() {
return this.#sukunimi;
}
get kutsumanimi() {
return this.#kutsumanimi;
}
get syntymaVuosi() {
return this.#syntymaVuosi.getFullYear();
}
// *** setterit ***
set etunimi(etunimi) {
this.#etunimi = etunimi;
}
set sukunimi(sukunimi) {
this.#sukunimi = sukunimi;
}
set kutsumanimi(kutsumanimi) {
this.#kutsumanimi = kutsumanimi;
}
set syntymaVuosi(syntymaVuosi) {
this.#syntymaVuosi = new Date(syntymaVuosi, 0, 1);
}
}
// Urheilija-luokka laajentaa Henkilo-luokkaa (eli perii Henkilon ominaisuudet)
class Urheilija extends Henkilo {
// alustetaan Urheilija-olion omat yksityiset muuttujat
#linkki_kuvaan;
#omapaino;
#laji;
#saavutukset;
// Urheilija-luokan konstruktori olion luomiseksi
constructor(
etunimi,
sukunimi,
kutsumanimi,
syntymaVuosi,
linkki_kuvaan,
omapaino,
laji,
saavutukset
) {
super(etunimi, sukunimi, kutsumanimi, syntymaVuosi);
this.#linkki_kuvaan = linkki_kuvaan;
this.#omapaino = omapaino;
this.#laji = laji;
this.#saavutukset = saavutukset;
}
// **** getterit ****
get linkki_kuvaan() {
return this.#linkki_kuvaan;
}
get omapaino() {
return this.#omapaino;
}
get laji() {
return this.#laji;
}
get saavutukset() {
return this.#saavutukset;
}
// **** setterit ****
set linkki_kuvaan(linkki_kuvaan) {
this.#linkki_kuvaan = linkki_kuvaan;
}
set omapaino(omapaino) {
this.#omapaino = omapaino;
}
set laji(laji) {
this.#laji = laji;
}
set saavutukset(saavutukset) {
this.#saavutukset = saavutukset;
}
}
// Luodaan uusia Urheilija-olioita
const Maija = new Urheilija(
"Maija",
"Meikäläinen",
"Maija",
1990,
"link_to_image",
60,
"Jalkapallo",
"SM-mitali 2019"
);
const Matti = new Urheilija(
"Matti",
"Meikäläinen",
"Matti",
1992,
"link_to_image",
80,
"Jääkiekko",
"SM-mitali 2020"
);
const Megan = new Urheilija(
"Megan",
"Rapinoe",
"Megan",
1985,
"https://upload.wikimedia.org/wikipedia/commons/thumb/3/37/Megancoupe2019.jpg/640px-Megancoupe2019.jpg",
60,
"Jalkapallo",
"Voittava maali vuonna 2008 sekä vuonna 2012 olympialaisten kultamitaliotteluissa"
);
const Alexia = new Urheilija(
"Alexia",
"Putellas",
"Alexia",
1994,
"https://en.wikipedia.org/wiki/Alexia_Putellas#/media/File:Barcelona_femen%C3%AD_Medalla_d'Honor_del_Parlament_de_Catalunya_02_(Putellas_1).jpg",
56,
"Jalkapallo",
"FIFA Best Women’s Player -palkinnon voittaja vuonna 2022"
);
const Mia = new Urheilija(
"Mia",
"Hamm",
"Mia",
1972,
"https://upload.wikimedia.org/wikipedia/commons/3/3e/Mia_Hamm_corner_%28cropped%29.jpg",
58,
"Jalkapallo",
"Kaksinkertainen olympiakultamitalisti, kaksinkertainen FIFA Naisten maailmancupin mestari"
);
// Tulostetaan olioiden tiedot
console.log(
"Henkilön tiedot: " +
Megan.etunimi +
" " +
Megan.sukunimi +
" " +
Megan.omapaino +
" , " +
"Saavutukset: " +
Megan.saavutukset +
" " +
"Linkki kuvaan: " +
" " +
Megan.linkki_kuvaan
);
console.log(" ");
console.log("***********************");
console.log(
"Henkilön tiedot: " +
Alexia.etunimi +
" " +
Alexia.sukunimi +
" " +
Alexia.omapaino +
" , " +
"Saavutukset: " +
Alexia.saavutukset +
" " +
"Linkki kuvaan: " +
" " +
Alexia.linkki_kuvaan
);
console.log(" ");
console.log("***********************");
console.log(
"Henkilön tiedot: " +
Mia.etunimi +
" " +
Mia.sukunimi +
" " +
Mia.omapaino +
" , " +
"Saavutukset: " +
Mia.saavutukset +
" " +
"Linkki kuvaan: " +
" " +
Mia.linkki_kuvaan
);
console.log(" ");
console.log("***********************");
console.log("");
```
---
## Viikko 5 ja 6
### Mitä opin näillä viikoilla?
Viikkojen 5 ja 6 aikana perehdyttiin REST:iin ja RESTful API:ien luomiseen. Opettelimme myös AJAX:in, JQueryn ja Expressin käyttöä ja asensimme CORS-selainlaajennuksen sekä Postman-ohjelman, jonka avulla testasimme API-pyyntöjä. Käytin aika paljon aikaa näinä kahtena viikkona ymmärtääkseni mikä oikeastaan on API ja REST. Kävin lukuisia keskusteluja ChatGPT:n kanssa näiden kahden viikkojen aiheista, koska tunneilla tuli niin paljon asiaa niin lyhyen ajan sisällä, ettei sitä informaatiomäärää oikein pystynyt mitenkään käsittelemään ja tuntui, että kaikki käsitteet menivät sekaisin. Nyt koen, että ymmärrän paremmin, mutta luulen, että pitää vieläkin selkeyttää AJAX:in ja JQueryn toimintaperiaatteita, vaikka harjoituksissa 4a ja 4b harjoittelinkin niiden käyttöä.
### Mitä harjoituksia tein?
Tein kummatkin harjoitustehtävät, jotka oli ajoitettu näille kahdelle viikolle. Harjoituksessa 4a oli tarkoitus luoda REST API sanakirjaa varten ja harjoituksessa 4b oli tarkoitus luoda tuohon luotuun sanakirja REST API:in käyttöliittymä. Eniten aikaa kului jälkimmäiseen tehtävään, yritin pitkään saada haluamani toiminnalisuudet toimimaan yhden index.html-dokumentin avulla, mutta lopulta päädyin jakamaan eri toiminnallisuudet omiksi .html-dokumenteiksi.
#### Harjoitus 4a
##### REST API sanakirjaa varten
Tässä harjoituksessa ei ollut juuri vaikeuksia, kun lopulta ymmärsin miten se REST-puoli tähän sanakirja API:iin toteutetaan. Toteutukseni on synkroninen, koska koin sen olevan helpompi toteuttaa (en tiedä olisiko se asynkroninen toteutus ollut juuri vaikeampi, mutta koin tämän tavan suoraviivaisemmaksi). Päätin tehdä sanankirjan luomiselle oman funktion, jota kutsutaan suorituksen alussa ja sitten siihen vain viitataan API-pyynnöissä.
```javascript=16
/*
=> Luo REST API sanakirjaa varten.
Sanakirjassa (tieto tiedostossa - esim. sanakirja.txt) on taulukossa eri riveillä sana suomeksi
ja englanniksi; esim. auto car talo house. Tarvitset API:a varten luoda hakua varten metodin
(toteuta GET metodi) sekä sanojen lisäystä varten metodin (toteuta POST metodi). Tiedonhaussa
parametrina on suomenkielinen sana. Haku palauttaa englannin kielisen sanan vastineen.
Tiedonlisäyksessä tallennetaan suomenkielinen sana ja vastaava englanninkielinen sana uudelle
riville (tekstitiedostoa kasvatetaan uudella rivillä). Tiedoston käsittelyä varten tarvitset
fs moduulin ( require("fs")). Em. moduulista löytyy esim. metodi writeFileSync kirjoitusta
varten ja readFileSync lukemista varten. (Tiedoston käsittely voi olla synkroninen tai
asynkroninen - saa valita).
=> Talleta toteutuksesi gitlab (tai github) -ympäristöön omaan projektiisi hakemistoon: "REST1"
=> Kirjoita tehtävästä kommentointi ja huomioita kehittäjän blogiin (hackmd.io).
*/
const fs = require("fs");
const express = require("express");
var app = express(); // tehdään sovellus-olio express-kirjaston kautta
app.use(express.json()); // käytetään json-muotoista dataa
app.use(express.urlencoded({ extended: true })); // käytetään tiedonsiirrossa laajennettua muotoa, UTF8-merkistö
// ***** (MITÄ SALLITAAN RESTapissa käytettävän) ******
// esiasetusten määrittely, CORS-määrittely
// Add headers
app.use(function (req, res, next) {
// Website you wish to allow to connect
res.setHeader("Access-Control-Allow-Origin", "*");
// Request methods you wish to allow
res.setHeader(
"Access-Control-Allow-Methods",
"GET, POST, OPTIONS, PUT, PATCH, DELETE"
);
// Request headers you wish to allow
res.setHeader(
"Access-Control-Allow-Headers",
"Origin, Accept, Content-Type, X-Requested-With, X-CSRF-Token"
);
// Set to true if you need the website to include cookies in the requests sent
// to the API (e.g. in case you use sessions)
res.setHeader("Access-Control-Allow-Credentials", true);
res.setHeader("Content-type", "application/json");
// Pass to next layer of middleware
next();
});
// *****************************************
/* luodaan funktio sanakirjan luomiseksi, jolla sanakirja
voidaan ladata ilman, että täytyy jokaisessa vaiheessa
luoda sanakirja uudestaan */
function lataaSanakirja() {
const data = fs.readFileSync("./sanakirja.txt", {
// luetaan data tekstitiedostosta
encoding: "utf8",
flag: "r", // määritellään, että tiedosto avataan lukemista varten
});
const splitLines = data.split(/\r?\n/); // pilkkoo datan rivinvaihtomerkkien perusteella eri riveihin
const sanakirja = []; // luodaan sanakirja-taulukko
splitLines.forEach((line) => {
// jokaisella rivillä...
const sanat = line.split(" "); // split-metodin avulla annetaan sanat-muuttujalle arvoksi taulukko, jonka alkioiksi laitetaan jaettu rivi
const sanapari = { fin: sanat[0], eng: sanat[1] }; // luodaan sanapari-objekti, jonka avain-arvo pareina on suomenkielinen sana (avain) ja englanninkielinen sana (arvo)
sanakirja.push(sanapari); // sijoitetaan sanapari-objekti sanakirja-taulukkoon
});
return sanakirja;
}
// ladataan sanakirja muistiin sovelluksen käynnistyessä
let sanakirja = lataaSanakirja();
// *********************************************************************
// GET
// näyttää sanakirjan
app.get("/sanakirja", (req, res) => {
res.json(sanakirja); // sanakirjan palautus
});
// *********************************************************
// GET
// sanojen etsiminen
app.get("/searchword/:fin", (req, res) => {
const fin = req.params.fin; // haetaan 'fin' parametrin arvo URL-osoitteesta
const eng = sanakirja.find((sanapari) => sanapari.fin === fin); // etsii sanakirjasta syötettyä sanaa ja palauttaa englanninkielisen version
res.json(eng ? { eng: eng.eng } : { message: "ei löydy!" }); // tämä on ehtolause ("ternary operator") lähettämään JSON-muotoinen vastaus HTTP-pyyntöön, mikäli englannin kielistä sanaa ei löydy
});
// *************************************************
// POST
// sanaparin lisääminen sanakirjaan
app.post("/addword", (req, res) => {
if (!req.body.fin || !req.body.eng) {
// tarkistetaan, etteivät kentät ole tyhjiä ja että ne ovat ylippätänsä mukana
res.status(400).json({ message: "Virheellinen pyyntö" }); // jos kentät ovat tyhjiä, nulleja tai määrittelemättömiä lähetetään virheilmoitus
return;
}
const sanapari = req.body; // otetaan rungosta fin- ja eng-kentät ja sijoitetaan ne sanapari-objektiin
sanakirja.push(sanapari); // lisätään sanapari sanakirja-taulukkoon
fs.writeFileSync(
"./sanakirja.txt",
sanakirja.map((sanapari) => `${sanapari.fin} ${sanapari.eng}`).join("\n"), // lisätään sanapari halutussa muodossa tekstitiedostoon
{ encoding: "utf8", flag: "w" } // tiedostoon kirjoittamisen asetusten määrittäminen, w = write
);
res.json(sanakirja); // vastauksena sanakirja JSON-muodossa HTTP-pyyntöön
});
// ***************************************************
// käynnistetään express-palvelin ja kuunnellaan porttia 3000
app.listen(3000, () => {
console.log(`Serveri kuuntelee portissa 3000`);
});
```
### Harjoitus 4b
##### REST APIiin käyttöliittymä, jonka avulla voi lisätä ja hakea sanoja
Tässä harjoituksessa toteutin käyttöliittymän sanakirjalle. Tein html-pohjaisen käyttöliittymän, jossa jokainen toiminnallisuus (sanakirjan katsominen, sanaparin lisääminen ja sanan hakeminen) on toteutettu omana sivuna (omissa .html-tiedostoissa). Gittiin palautin myös alkuperäisen suunnitelmani käyttöliittymästä (sanakirjaUI.html), jota en kuitenkaan saanut toimimaan, ja tästä syystä päädyin tekemään omat sivut jokaiselle eri toiminnolle.
Varsinaiseen ratkaisuuni kuuluvat siis seuraavat tiedostot:
1) index.html
2) view.html
3) add.html
4) search.html
5) sanakirja.txt
Laitan tähän esimerkiksi vain index.html-tiedoston:
```javascript=16
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Sanakirjan pääsivu</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script>
$(document).ready(function () {
// Siirry "Näytä sanakirja" -sivulle
$("#viewButton").click(function () {
window.location.href = "view.html";
});
// Siirry "Lisää sana" -sivulle
$("#addButton").click(function () {
window.location.href = "add.html";
});
// Siirry "Hae sanaa" -sivulle
$("#searchButton").click(function () {
window.location.href = "search.html";
});
});
</script>
<style>
body {
font-family: Arial, sans-serif;
text-align: center;
margin-top: 50px;
background-color: #daa982;
}
button {
padding: 10px 20px;
margin: 10px;
font-size: 16px;
cursor: pointer;
}
</style>
</head>
<body>
<h1>Sanakirja</h1>
<button id="viewButton">Näytä sanakirja</button>
<button id="addButton">Lisää sana</button>
<button id="searchButton">Hae sanaa</button>
</body>
</html>
```
Tässä linkki GitHubiin, jonne tehtävä on palautettu: https://github.com/PaulaRii/REST1.git
---
## Viikko 7
### Mitä opin tällä viikoilla?
Viikon 7 aikana mm. perehdyimme tietokantojen toimintaan, Javascriptiin ja sen synkroniseen ja asynkroniseen suoritukseen, AJAX:iin sekä MVC:hen. Rinnakkaisuus sekä lupaukset käsitteenä olivat jo tuttuja Hajautetut järjestelmät -kurssilta, joskin niiden käyttäminen ei ollut itselle kovin selkeää, joten oli hyvä että niitä opeteltiin vielä tälläkin kurssilla. Generators, jolla kutsutaan useita alkioita kerralla ei sen sijaan ollut tuttu käsite. AJAX:iin perehtyminen vaati vielä omaa perehtymistä, koska se ei oikein kunnolla auennut luentojen aikana.
### Mitä harjoituksia tein?
Tällä viikolla ei ollut palautettavaa harjoitusta.
---
## Viikko 8 ja 9
### Mitä opin näillä viikoilla?
Viikon 8 (luento 2.11.) aikana opettelimme tietokantasovelluksen tekoa. Teimme tunnilla yhdessä vanhan harjoitustehtävän, jossa luotiin tietokantasovellus. Asensimme MariaDB-ohjelman (minulla oli se jo valmiina, mutta olin unohtanut salasanani niin jouduin aika pitkään säätämään, ennen kuin sain HeidiSQL:ssä tietokannan auki. Onnistuin lopulta komentokehotteen kautta vaihtamaan uuden salasanan, kiitos ChatGPT:n ohjeiden!). Palauttelin mieleen miten SQL:n kanssa operoitiin ja miten HeidiSQL toimii. Viikolla 9 (luento 9.11.) käsittelimme REACT:ia, funktionaalisia komponentteja sekä tapahtumankäsitteliöitä (hooks). Opin ymmärtämään paremmin mitä tarkoittaa REACT ja miten tapahtumankäsittelijöillä operoidaan.
### Mitä harjoituksia tein?
Tein harjoituksen 5, palautin sen paljon yli deadlinen (jäin useamman viikon jälkeen sairastelun takia), mutta sain tehtävän lopulta tehtyä ja palautin sen GitHubiin REST1-repositoryyn, johon olen jakanut oikeudet opettajalle. Yllättävän paljon aikaa meni pelkästään pohjakoodin luomiseen, vaikka seurasin huolellisesti opetusvideon ohjeistusta. Kuitenkin ilmeni erilaisia ongelmia, joiden ratkaisemiseen meni aika paljon aikaa. Lopulta kun olin saanut pohjan valmiiksi niin varsinaisen tehtävän tekemiseen (metodien lisäämisen REST APIin) ei kulunut kovin paljon aikaa. Testasin Postmanilla, että ratkaisuni varmasti toimii ja hyvinhän se toimi :)
#### Harjoitus 5
En lisää tähän kaikkia tehtävään toteuttamiseen vaadittavaia koodejani, koska pitäisi lisätä useita tiedostoja, mutta listaan mihin kaikkiin tiedostoihin tein muutoksia/lisäyksiä, jotta sain PUT- ja DELETE-operaatiot toimimaan, ja laitan esimerkiksi pyyntöjen käsittelyyn tekemäni koodin tähän alle.
Seuraavissa tiedostoissa on merkitty kommenteilla ne kohdat, joissa on omat lisäykseni:
1) controllers/postControllers.js
2) models/Post.js
3) routes/postRoutes.js:
```javascript=16
const express = require("express");
const postControllers = require("../controllers/postControllers");
const router = express.Router();
// @route GET && POST - /posts/
router
.route("/")
.get(postControllers.getAllPosts)
.post(postControllers.createNewPost);
// ************************** OMA OSUUS ********************************
// käsitellään GET, PUT ja DELETE -pyynnöt polussa /posts/:id
router
.route("/:id")
.get(postControllers.getPostById)
.put(postControllers.updateById)
.delete(postControllers.deleteById);
// ************************** OMA OSUUS LOPPUU ***************************
module.exports = router;
```
Tässä vielä linkki GitHubiin, jonne tehtävä on palautettu: https://github.com/PaulaRii/REST1.git
---
## Viikko 10
### Mitä opin tällä viikoilla?
Viikon 10 aikana (luento 16.11.) opettelimme REACT:in käyttöä. Tällä viikolla oli suuria vaikuksia pysyä luennosta kärryillä ja palasin tallenteessa takaisin monia kertoja, kun en saanut toistettua omalla koneella stä mitä luennoitsija teki luennon aikana. Opin tällä viikolla lisää JSX:stä, webhookeista ja stateista.
### Mitä harjoituksia tein?
Kävimme tunnilla läpi edellisen harjoitustehtävän, tällä viikolla ei ollut vielä uutta harjoitusta.
---
## Viikko 11
### Mitä opin tällä viikoilla?
Viikon 11 aikana (luento 23.11.) opeteltiin lisää hookkien käyttöä. Teimme tunnilla aiemmin tehdylle puhelinluettelolle Reactilla käyttöliittymän ja perehdyimme vähän myös Boostrappiin, jolla saa valmiita layoutteja. Vaikka koodin sai valmiiksi niin oli suuria ongelmia saada käyttöliittymä toimimaan, koska valmiissa pohjassa käytettiin vanhempaa react-route-versiota, minkä seurauksena jouduin aika paljon päivittämään pohjakoodia, jotta sain sen edes jotenkuten toimivaksi. Tuli jälleen kerrattua tietokannan luontia ja täytyi myös perehtyä reitittämiseen, joka yhä edelleen tuntuu aika monimutkaiselta aihekokonaisuudelta. Luennon lopuksi esiteltiin vähän Context API:a, jota on tarkoitus hyödyntää harjoitus 6:ssa, mutta minulle ei kyllä vielä yhtään auennut mikä se edes on. Täytynee pyytää ChatGPT:tä selittämään vähän tarkemmin, kun tunnilla konseptia ei juuri avattu. Toinen asia, johon olisin toivonut lisäopastusta tunneilla on Axios. Se mainittiin muutaman kerran, mutta sen toimintaa tai tarpeellisuutta ei oikeastaan ollenkaan selitetty.
### Mitä harjoituksia tein?
Tälle viikolle ei ollut uutta harjoitusta, mutta meni aika paljon aikaa toteuttaamaan tunnilla yhdessä tehtyjä harjoituksia. Myös perehdyimme seuraaviin harjoitustehtäviin luomalla puhelinluettelo API:lle käyttölittymän Reactilla hyödyntäen Context API:a ja Bootstrappiä.
---
## Viikko 12
### Mitä opin tällä viikoilla?
Teimme luennolla yhdessä harjoitus 6:sta, jäin ihmettelemään miksi opettajalla ohjelma automaattisesti ehdottaa toista porttia, jos ehdotettu on toisessa käytössä, koska minulla se se ei ehdottanut. Jotta sain ohjelman toimimaan piti minun manuaalisesti muokata koodiini toinen portti (3001). Tällä viikolla en varsinaisesti oppinut mitään uutta, mutta syvennyin aiemmin opeteltuihin asioihin, kuten tietokantayhteyden toteuttamiseen, REST API:n luomiseen, Reactiin ja Context API:iin, joita nyt ymmärrän paremmin. Meni hyvin monta tuntia perehtyessä siihen miten toteutan harjoitus 6:sen.
### Mitä harjoituksia tein?
Tein harjoituksen 6, ja käytin siihen varmasti enemmän aikaa kuin sen tekemiseen oli tarkoitettu käytettävän aikaa. Oli suuria ongelmia ymmärtää Context API:n käyttöä enkä meinannut saada Lisää, Muokkaa ja Poista -toiminnallisuuksia toimimaan, vaikka palvelimen puolella ne toimivat. Lopulta keksin, että minulla oli turhaan GlobalState.js-tiedosto sekoittamassa kuvioita ja kun poistin sen ja siihen kuuluvat context-tiedostot ja jätin vain PelaajatContext.js-tiedoston niin hommat ryhtyivät toimimaan paremmin. Ongelmia oli yhä poiston ja lisäyksen kanssa. Useiden tuntien ihmettelyn ja tuskailun jälkeen löysin virheen; olin palvelimen puolelle merkannut osoitteeksi "...urheilija/:id" ja käyttöliittymän puolelle "...urheilijat/:id". Useamman illan tuskailu ratkesi siis yhden kirjaimen poistamisella... Lopulta siis kaikki vaadittavat toiminnallisuudet toimivat. Kuvia en yrityksestäni huolimatta valitettavasti saanut näkymään urheilija-näkymässä.
Käyttöliittymäni idea on siis siinä, että taulukkoon voi lisätä urheilijoita painamalla etusivulla olevaa "Lisää urheilija"-painiketta ja urheilijoiden tietoja pääsee katsomaan ja muokkaamaan klikkaamalla urheilijan sukunimeä. Tällöin aukeaa näkymä sivun alareunaan. Näkymässä voi myös poistaa urheilijan. Muokkaus-painikkeella pääsee samanlaiseen ikkunaan kuin lisäys-painikkeella. Taulukon otsikko päivittyy sitä mukaan kun tietuemäärä taulussa muuttuu.
Käytin ratkaisussani sekä Context API:a komponenttien kanssa että Bootstrappia muotoiluun (taulukko, painikkeet, näkymä ja lisäys- sekä muokkaussivu).
#### Harjoitus
Tässä linkki GitHubiin, jonne tehtävä on palautettu: https://github.com/PaulaRii/REST1.git
**Huom!** Tietokannan luontiskripti on Urheilija2/serveri-kansion sisällä tekstitiedostona.
---
## Viikko 13
### Mitä opin tällä viikoilla?
Tällä viikolla perehdyimme automatisoituun testaamiseen. Mieleeni jäi erityisesti yksikkötestaaminen.
### Mitä harjoituksia tein?
Tein viimeisen harjoituksen (harjoitus 7) kevään aikana. Sen kanssa ei ollut suuresti ongelmia, sen tekeminen vain vei paljon aikaa. Yksi ärsyttävä ongelma oli kylläkin käytttämässäni valmiissa bootstrap-pohjassa; siinä oli jokin .pug-tiedosto, jollaisesta ei oltu mielestäni puhuttu kurssin aikana. Se aiheutti ongelmia, koska index.html-tiedostoon koostamani sivunrakenne piti muuttaa tuohon pug-tiedoston muotoon tai muuten muutokset eivät tallentuneet jostain käsittämättömästä syystä. Kerran jo menetin tekemäni koodin ja minun piti aloittaa alusta. Jostain syystä kun projektin avaa uudestaan sen kerran suljettua niin index.html muuttuu index.pug-tiedoston mukaiseksi. Onneksi ChatGPT osasi auttaa sen index.pug-tiedoston luomisessa, koska sen käytön opetteluun ei ollut enää aikaa. Nyt jälkiviisaana tuli mieleen, että sen index.pug-tiedoston olisi varmaan vaan voinut poistaa koko projektista...?
#### Harjoitus
```javascript=16
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no"
/>
<meta name="description" content="" />
<meta name="author" content="" />
<title>Portfolio - Harjoitus 7</title>
<link
rel="icon"
type="image/x-icon"
href="assets/img/profiilikuva_uusi2.jpg"
/>
<!-- Font Awesome icons (free version)-->
<script
src="https://use.fontawesome.com/releases/v6.3.0/js/all.js"
crossorigin="anonymous"
></script>
<!-- Google fonts-->
<link
href="https://fonts.googleapis.com/css?family=Saira+Extra+Condensed:500,700"
rel="stylesheet"
type="text/css"
/>
<link
href="https://fonts.googleapis.com/css?family=Muli:400,400i,800,800i"
rel="stylesheet"
type="text/css"
/>
<!-- Core theme CSS (includes Bootstrap)-->
<link href="css/styles.css" rel="stylesheet" />
</head>
<body id="page-top">
<!-- Navigation-->
<nav
class="navbar navbar-expand-lg navbar-dark bg-primary fixed-top"
id="sideNav"
>
<a class="navbar-brand js-scroll-trigger" href="#page-top">
<span class="d-block d-lg-none">Paula-Riitta Huttunen</span>
<span class="d-none d-lg-block">
<img
class="img-fluid img-profile rounded-circle mx-auto mb-2"
src="assets/img/profiilikuva_uusi2.jpg"
alt="Profiilikuva"
/>
</span>
</a>
<button
class="navbar-toggler"
type="button"
data-bs-toggle="collapse"
data-bs-target="#navbarResponsive"
aria-controls="navbarResponsive"
aria-expanded="false"
aria-label="Toggle navigation"
>
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarResponsive">
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link js-scroll-trigger" href="#about"
>Home</a
>
</li>
<li class="nav-item">
<a class="nav-link js-scroll-trigger" href="#experience"
>Harjoitukset</a
>
<ul>
<li>
<a
class="nav-link js-scroll-trigger"
href="#harjoitus-0"
>Harjoitus 0</a
>
</li>
<li>
<a
class="nav-link js-scroll-trigger"
href="#harjoitus-1"
>Harjoitus 1</a
>
</li>
<li>
<a
class="nav-link js-scroll-trigger"
href="#harjoitus-2"
>Harjoitus 2</a
>
</li>
<li>
<a
class="nav-link js-scroll-trigger"
href="#harjoitus-3"
>Harjoitus 3</a
>
</li>
<li>
<a
class="nav-link js-scroll-trigger"
href="#harjoitus-4a"
>Harjoitus 4a</a
>
</li>
<li>
<a
class="nav-link js-scroll-trigger"
href="#harjoitus-4b"
>Harjoitus 4b</a
>
</li>
<li>
<a
class="nav-link js-scroll-trigger"
href="#harjoitus-5"
>Harjoitus 5</a
>
</li>
<li>
<a
class="nav-link js-scroll-trigger"
href="#harjoitus-6"
>Harjoitus 6</a
>
</li>
<li>
<a
class="nav-link js-scroll-trigger"
href="#harjoitus-7"
>Harjoitus 7</a
>
</li>
</ul>
</li>
<li class="nav-item">
<a class="nav-link js-scroll-trigger" href="#education"
>Palauteosio</a
>
</li>
</ul>
</div>
</nav>
<!-- Page Content-->
<div class="container-fluid p-0">
<!-- About-->
<section class="resume-section" id="about">
<div class="resume-section-content">
<h1 class="mb-0">
Paula-Riitta
<span class="text-primary">Huttunen</span>
</h1>
<div class="subheading mb-5">
Itä-Suomen Yliopisto · tietojenkäsittelytiede ·
<a href="mailto:paularii@uef.fi">paularii@uef.fi</a>
</div>
<p class="lead mb-5">
Tämä on Web-ohjelmoinnin kurssia varten tehty portfolio,
johon koostettu kuvaukset kurssilla tekemistäni
harjoitustöistä ja lisäksi siihen on sisällytetty
palauteosio.
</p>
<div class="social-icons">
<a
class="social-icon"
href="https://www.linkedin.com/in/paula-riitta-huttunen/"
>
<i class="fab fa-linkedin-in"></i>
</a>
<a
class="social-icon"
href="https://github.com/PaulaRii"
>
<i class="fab fa-github"></i>
</a>
<a
class="social-icon"
href="https://twitter.com/Lydiitti"
>
<i class="fab fa-twitter"></i>
</a>
</div>
</div>
</section>
<hr class="m-0" />
<!-- Experience-->
<section class="resume-section" id="experience">
<div class="resume-section-content">
<h2 class="mb-5">Harjoitukset</h2>
<div
class="d-flex flex-column flex-md-row justify-content-between mb-5"
id="harjoitus-0"
>
<div class="flex-grow-1">
<h3 class="mb-0">Harjoitus 0</h3>
<div class="subheading mb-3">
HTML-koodin korjaaminen
</div>
<p>
Tämä oli HTML:ään liittyvä tehtävä, jossa kopion
opettajan pohjan ja tein siihen päälle omat
muokkaukseni. Lopuksi ajoin HTML ja CSS-koodit
validaattorissa. En löytänyt kuin yhden virheen,
joka oli helppo korjata. Tehtävä näytti aluksi
vaikeammalta kuin mitä se loppupelissä oli.
</p>
</div>
<div class="flex-shrink-0">
<span class="text-primary">Viikko 2</span>
</div>
</div>
<div
class="d-flex flex-column flex-md-row justify-content-between mb-5"
id="harjoitus-1"
>
<div class="flex-grow-1">
<h3 class="mb-0">Harjoitus 1</h3>
<div class="subheading mb-3">
Javascript -perusteet 1
</div>
<p>
Harjoituksessa 1 toteutin palindromifunktion,
joka tarkistaa, onko annettu merkkijono
palindromi. Käytin JavaScriptin listoja ja
luuppeja sekä ehtolauseita vertaillakseni
merkkijonon alkioita toisiinsa. Koodi koostui
seuraavista vaiheista:
</p>
<p>
1. Syötteen kysyminen käyttäjältä readline-sync
-kirjastolla.<br />
2. Merkkien tallentaminen syötetystä sanasta
taulukkoon.<br />
3. Taulukon kääntäminen sijoittamalla
alkuperäisen taulukon merkit lopusta alkuun
uuteen taulukkoon.<br />
4. Alkuperäisen ja käännetyn taulukon merkkien
vertailu toisiinsa luupin avulla.<br />
5. Vertailun tuloksen palauttaminen
totuusarvona.
</p>
<p>
Harjoituksen tavoitteena oli harjoitella
ohjelmoinnin perusteita, erityisesti
ehtolauseiden ja luuppien käyttöä, sekä
vahvistaa JavaScript-osaamistani. Tämä harjoitus
oli hyvä kertausharjoitus, joka auttoi
palauttamaan mieleen ohjelmoinnin perustaitoja.
</p>
<p>
Harjoituksen aikana palautui hyvin mieleen,
kuinka sijoitetaan dataa listoihin ja verrataan
keskenään listojen alkioita toisiinsa. Opin myös
paremmin käsittelemään merkkijonoja sekä listoja
JavaScriptissä. Vaikka tämä harjoitus auttoikin
kertaamaan perusasioita, koen, että
lisäharjoitus olisi ehkä tarpeen, erityisesti
monimutkaisempien algoritmien ja
tietorakenteiden osalta.
</p>
<p>
Mielestäni onnistuin harjoituksessa ihan hyvin,
sillä sain palindromifunktion toimimaan. Jos
tekisin tehtävän uudelleen, kiinnittäisin
enemmän huomiota tarkemmin tehtävänantoon (jotta
teen juuri sitä mitä pyydettiin) sekä koodin
optimointiin ja tehokkuuteen, esimerkiksi
käyttämällä jotain tehokkaampia vertailu- ja
käsittelytapoja.
</p>
<p>Linkki vastaukseen HackMD:ssä:</p>
<a href="https://hackmd.io/@PaulaH/SsH20JNSf411"
>https://hackmd.io/@PaulaH/SsH20JNSf411</a
>
</div>
<div class="flex-shrink-0">
<span class="text-primary">Viikko 3</span>
</div>
</div>
<div
class="d-flex flex-column flex-md-row justify-content-between mb-5"
id="harjoitus-2"
>
<div class="flex-grow-1">
<h3 class="mb-0">Harjoitus 2</h3>
<div class="subheading mb-3">
Javascript -perusteet 2
</div>
<p>
Harjoituksessa 2 opettelin luomaan oliota sekä
sijoittamaan luodut oliot taulukkoon. Aluksi oli
hieman vaikeuksia saada luotua uusia olioita,
mutta lopulta muistin miten JavaScriptissä
käytetään luokkaa ja sen konstruktoria.
</p>
<p>
Toisessa osassa harjoitusta oli luotava funktio,
joka etsii käyttäjän aiemmassa vaiheessa
luotujen henkilöiden syötettyjä puhelinnumeroita
aiemmin luodusta puhelinluettelosta. Toteutin
tämän niin, että käyttäjältä jatkuvasti kysytään
minkä toiminnon tämä haluaa suorittaa ja sitten
toimintavaihtoehdot on listattu
switch-rakenteeseen, jossa kutsutaan funktiota,
joka joko suorittaa lisäyksen tai hakemisen tai
sitten lopettaa ohjelman suorittamisen.
</p>
<p>
Harjoituksessa 2 toteutin
puhelinluettelo-ohjelman, jossa käyttäjä voi
lisätä henkilöitä nimellä ja puhelinnumerolla,
sekä hakea henkilöiden puhelinnumeroita nimellä.
Koodi koostui seuraavista vaiheista:
</p>
<p>
1. Syötteen kysyminen käyttäjältä readline-sync
-kirjastolla.<br />
2. Henkilön tietojen tallentaminen oliona
puhelinluettelo-taulukkoon.<br />
3. Olion luominen Henkilo-luokasta käyttäen sen
konstruktoria.<br />
4. Olioiden lisääminen
puhelinluettelo-taulukkoon.<br />
5. Funktio, joka hakee puhelinnumeron syötetyn
nimen perusteella.<br />
6. Käyttäjän jatkuva syötteen kysyminen ja
toiminnan suorittaminen switch-rakenteen avulla.
</p>
<p>
Harjoitusen tavoitteena oli harjoitella
oliopohjaista ohjelmointia JavaScriptissä,
erityisesti luokkien ja konstruktorien käyttöä,
sekä vahvistaa taitoja käsitellä taulukoita ja
funktioita. Tämä harjoitus auttoi ymmärtämään
ohjelmoinnin peruskonsepteja ja oli hyvää
kertausta olio-ohjelmoinnin perusteista.
</p>
<p>
Harjoituksen aikana kertasin kuinka luodaan ja
käytetään olioita sekä operoidaan taulukoiden ja
merkkijonojen kanssa JavaScriptissä. Harjoitus
auttoi myös ymmärtämään paremmin funktioiden
käyttöä ja niiden parametrisointia.
</p>
<p>
Mielestäni onnistuin harjoituksessa hyvin, sillä
sain puhelinluettelo-ohjelman toimimaan oikein
ja kaikki vaaditut toiminnallisuudet
toteutettua. Ei tule mieleen mitä olisi
kannattanut tehdä toisin.
</p>
<p>Linkki vastaukseen HackMD:ssä:</p>
<a href="https://hackmd.io/@PaulaH/SsH20JNSf411"
>https://hackmd.io/@PaulaH/SsH20JNSf411</a
>
</div>
<div class="flex-shrink-0">
<span class="text-primary">Viikko 3</span>
</div>
</div>
<div
class="d-flex flex-column flex-md-row justify-content-between mb-5"
id="harjoitus-3"
>
<div class="flex-grow-1">
<h3 class="mb-0">Harjoitus 3</h3>
<div class="subheading mb-3">
JavaScript -perusteet 3: Luokat ja periytyminen
</div>
<p>
Harjoituksessa 3 opettelin luokkien määrittelyä
ja periytymistä JavaScriptissä. Vaikka konsepti
oli ennestään tuttu Java-kielestä, tarvitsin
hieman kertausta.
</p>
<p>
Suurin haaste oli Git-versionhallinnan kanssa.
Yritin pushata tiedostoa GitHub-repositorioon,
mutta kohtasin jatkuvasti virheilmoituksia,
jotka liittyivät mahdollisesti merge-ongelmiin.
Lopulta sain ongelman ratkaistua, vaikka en
olekaan ihan varma, mikä lopulta auttoi
ratkaisemaan ongelman.
</p>
<p>
Itse ohjelmointitehtävässä ei ollut suurempia
ongelmia, mutta Date-metodin toimintalogiikan
ymmärtäminen vaati hieman lisäopiskelua.
</p>
<p>
Harjoituksessa 3 toteutin Henkilo-luokan ja
siitä periytyvän Urheilija-luokan.
Urheilija-luokassa määriteltiin get- ja
set-funktiot erityisille attribuuteille. Koodi
koostui seuraavista vaiheista:
</p>
<p>
1. Henkilo-luokan määrittely ja konstruktorin
luominen.<br />
2. Henkilo-luokan getter- ja setter-metodien
määrittely.<br />
3. Urheilija-luokan määrittely, joka perii
Henkilo-luokan.<br />
4. Urheilija-luokan omien ominaisuuksien ja
metodien määrittely.<br />
5. Urheilija-olioiden luominen ja niiden
tietojen tulostaminen.
</p>
<p>
Harjoitusta tehdessä syvensin ymmärrystäni
olio-ohjelmoinnista, erityisesti luokkien ja
periytymisen osalta. Tämä harjoitus vahvisti
lisää JavaScript-taitojani ja oli hyvää
kertausta olio-ohjelmoinnin perusteista.
</p>
<p>
Harjoituksen aikana opin lisää luokkien
määrittelystä ja periytymisestä JavaScriptissä.
Tämä harjoitus auttoi myös ymmärtämään paremmin
getter- ja setter-metodien käyttöä. Ei tule
mieleen mitä jäi oppimatta.
</p>
<p>
Mielestäni onnistuin tehtävässä ihan hyvin,
sillä sain toteutettua kaikki vaaditut
toiminnallisuudet ja koodini toimii oikein. Jos
tekisin tehtävän uudelleen, kiinnittäisin
enemmän huomiota Git-versionhallinnan käyttöön,
ettei olisi niin paljon ongelmia sen kanssa.
</p>
<p>
<strong>Huom! </strong>Harjoitus oli alunperin
palautettu GitHubiin erilliseen repositoryyn
(Henkilo.js, jossa oli kansio nimeltä Teht3,
jossa oli tiedosto Henkilo.js), mutta kopioin
kansion ja nimesin sen uudestaan tehtävänannon
mukaisesti "Urheilija1" ja siirsin sen
REST1-repostoryyn, jonne olen jakanut muutkin
harjoitukset, jotka piti jakaa GitHubissa. Tässä
linkki päivitettyyn sijaintiin
REST1-repositoryyn:
</p>
<a href="https://github.com/PaulaRii/REST1.git"
>https://github.com/PaulaRii/REST1.git</a
>
</div>
<div class="flex-shrink-0">
<span class="text-primary">Viikko 4</span>
</div>
</div>
<div
class="d-flex flex-column flex-md-row justify-content-between mb-5"
id="harjoitus-4a"
>
<div class="flex-grow-1">
<h3 class="mb-0">Harjoitus 4a</h3>
<div class="subheading mb-3">
Sanakirja REST API
</div>
<p>
Harjoituksessa 4a luotiin REST API sanakirjaa
varten. Sanakirjassa jokainen rivi sisältää
suomenkielisen sanan ja sen englanninkielisen
vastineen. Toteutin tehtävän synkronisena, koska
koin sen olevan helpompi toteuttaa.
</p>
<p>
REST API:n toteutus koostui seuraavista
vaiheista:
</p>
<p>
1. Express-sovellusolion luominen.<br />
2. CORS- ja esiasetusten määrittely
API-pyynnöille.<br />
3. Sanakirjan lataaminen tekstitiedostosta
sovelluksen käynnistyessä.<br />
4. GET-metodin toteutus koko sanakirjan
palauttamiseksi.<br />
5. GET-metodin toteutus sanojen etsimiseksi
suomenkielisen sanan perusteella.<br />
6. POST-metodin toteutus uusien sanaparien
lisäämiseksi sanakirjaan ja tekstitiedostoon.
</p>
<p>
Harjoituksen tavoitteena oli syventää ymmärrystä
REST API:n toiminnasta ja Expressin käytöstä.
Tämä harjoitus auttoi hahmottamaan paremmin,
miten API-pyyntöjä käsitellään ja miten
tiedostoja käsitellään Node.js:ssä.
</p>
<p>
Tehtävän aikana opin enemmän REST API:sta ja
Expressin käytöstä, sekä tiedostojen
käsittelystä Node.js:ssä. Tämä harjoitus oli
hyödyllinen johdatus API-kehitykseen ja auttoi
ymmärtämään REST-arkkitehtuurin periaatteita.
</p>
<p>
Harjoitus onnistui hyvin, sillä sain API:n
toimimaan oikein ja kaikki vaaditut
toiminnallisuudet toteutettua. Jos tekisin
tehtävän uudelleen, harkitsisin asynkronisen
toteutuksen käyttämistä parantaakseni
sovelluksen suorituskykyä.
</p>
<p>Linkki vastaukseen GitHubissa:</p>
<a href="https://github.com/PaulaRii/REST1"
>https://github.com/PaulaRii/REST1</a
>
</div>
<div class="flex-shrink-0">
<span class="text-primary">Viikko 5</span>
</div>
</div>
<div
class="d-flex flex-column flex-md-row justify-content-between mb-5"
id="harjoitus-4b"
>
<div class="flex-grow-1">
<h3 class="mb-0">Harjoitus 4b</h3>
<div class="subheading mb-3">
Sanakirja REST API -käyttöliittymä
</div>
<p>
Harjoituksessa 4b luotiin käyttöliittymä aiemmin
toteutettuun sanakirja REST APIin.
Käyttöliittymän avulla voi lisätä ja hakea
sanoja. Toteutin käyttöliittymän HTML:n ja
jQueryn avulla, ja jaoin toiminnallisuudet eri
sivuille.
</p>
<p>
Käyttöliittymän toteutus koostui seuraavista
vaiheista:
</p>
<p>
1. HTML-sivujen luominen eri
toiminnallisuuksille:
<strong
>index.html, view.html, add.html,
search.html</strong
>.<br />
2. jQueryn käyttö lomakkeiden käsittelyyn ja
API-pyyntöjen lähettämiseen.<br />
3. CORS-otsikoiden lisääminen REST APIin, jotta
käyttöliittymä voi tehdä pyyntöjä
palvelimelle.<br />
4. Sanakirjan tietojen hakeminen ja näyttäminen
<strong>view.html</strong>-sivulla.<br />
5. Uuden sanaparin lisääminen
<strong>add.html</strong>-sivun lomakkeella.<br />
6. Suomenkielisen sanan hakeminen ja sen
englanninkielisen vastineen näyttäminen
<strong>search.html</strong>-sivulla.
</p>
<p>
Harjoituksen tavoitteena oli syventää ymmärrystä
käyttöliittymien luomisesta REST API:a
hyödyntäen. Tämä harjoitus auttoi ymmärtämään
paremmin jQueryn käyttöä ja API-pyyntöjen
käsittelyä.
</p>
<p>
Harjoitusta tehdessä opin jQueryn käyttöä ja
ymmärsin paremmin REST API -käyttöliittymien
toteuttamista. Tämä harjoitus oli hyödyllinen
johdatus API-kehitykseen ja auttoi ymmärtämään,
miten käyttöliittymä ja API kommunikoivat
keskenään.
</p>
<p>
Harjoitus onnistui ihan hyvin, sain
käyttöliittymän toimimaan ja kaikki vaaditut
toiminnallisuudet toteutettua. Jos tekisin
tehtävän uudelleen, toteuttaisin ehkä
käyttöliittymän yksinkertaisemmin yhdelle
sivulle, jotta käytettävyys olisi selkempää.
</p>
<p>Linkki vastaukseen GitHubissa:</p>
<a href="https://github.com/PaulaRii/REST1"
>https://github.com/PaulaRii/REST1</a
>
</div>
<div class="flex-shrink-0">
<span class="text-primary">Viikko 5 ja 6</span>
</div>
</div>
<div
class="d-flex flex-column flex-md-row justify-content-between mb-5"
id="harjoitus-5"
>
<div class="flex-grow-1">
<h3 class="mb-0">Harjoitus 5</h3>
<div class="subheading mb-3">
Node-Express-starter
</div>
<p>
Harjoituksessa 5 luotiin Node.js REST API, joka
käyttää Expressiä ja MariaDB:tä. Tavoitteena oli
lisätä pohjakoodiin REST-rajapinnan mukaiset
metodit PUT ja DELETE tietyn id:n perusteella
kenttien "title" ja "body" päivittämiseksi ja
tietueen poistamiseksi.
</p>
<p>
REST API:n toteutus koostui seuraavista
vaiheista:
</p>
<p>
1. MariaDB:n kanssa taistelu ja tietokannan
luominen.<br />
2. Pohjakoodin kopioiminen.<br />
3. PUT- ja DELETE-metodien lisääminen REST
API:iin pohjakoodin mukaisesti.<br />
4. API:n testaaminen Postmanilla kaikkien
operaatioiden varmistamiseksi (GET, POST, PUT,
DELETE).
</p>
<p>
Seuraavissa tiedostoissa tein muutoksia PUT- ja
DELETE-metodien lisäämiseksi:
</p>
<ul>
<li>controllers/postControllers.js</li>
<li>models/Post.js</li>
<li>routes/postRoutes.js</li>
</ul>
<p>
Tämä harjoitus auttoi ymmärtämään paremmin
Node.js:n ja MariaDB:n käyttöä REST API:n
kanssa. Opin erityisesti, miten tietokannan
operaatioita (kuten haku, lisäys, päivitys ja
poisto) voidaan toteuttaa API:n kautta ja miten
testata niitä Postmanilla.
</p>
<p>
Alkuvaikeuksista huolimatta harjoitus onnistui
lopulta hyvin, sillä sain kaikki vaaditut
toiminnallisuudet toimimaan. Varmasti tämän
olisi voinut tehdä jotenkin paremminkin, mutta
en tiedä mitä tekisin toisin, jos tekisin
harjoituksen uudestaan.
</p>
<p>Linkki vastaukseen GitHubissa:</p>
<a href="https://github.com/PaulaRii/REST1"
>https://github.com/PaulaRii/REST1</a
>
</div>
<div class="flex-shrink-0">
<span class="text-primary">Joulukuu</span>
</div>
</div>
<div
class="d-flex flex-column flex-md-row justify-content-between mb-5"
id="harjoitus-6"
>
<div class="flex-grow-1">
<h3 class="mb-0">Harjoitus 6</h3>
<div class="subheading mb-3">
Urheilija II (REST API & React-sovellus)
</div>
<p>
Harjoituksessa 6 toteutettiin
JavaScript-palvelinsovellus, joka tarjoaa
REST-rajapinnan kautta tiedon urheilijoista.
Tavoitteena oli toteuttaa datan haku, lisäys,
poisto ja päivitys MariaDB-tietokantaan sekä
luoda React-käyttöliittymä, jonka avulla näitä
operaatioita voi suorittaa.
</p>
<p>
REST API:n ja React-käyttöliittymän toteutus
koostui seuraavista vaiheista:
</p>
<p>
1. Tietokannan luominen MariaDB:llä.<br />
2. REST-rajapinnan toteuttaminen Expressin
avulla.<br />
3. CRUD (Create, Read, Update, Delete)
-operaatioiden toteutus tietokantaan.<br />
4. React-sovelluksen luominen ja Context API:n
hyödyntäminen datan hallinnassa.<br />
5. Bootstrapin käyttö käyttöliittymän
muotoilussa.
</p>
<p>
Palvelinpuolen koodissa hyödynsin seuraavia
tiedostoja:
</p>
<ul>
<li>index.js</li>
</ul>
<p>React-käyttöliittymän tärkeimmät komponentit:</p>
<ul>
<li>App.js</li>
<li>PelaajaContext.js</li>
<li>PelaajaLista.js</li>
<li>LisaaPelaaja.js</li>
<li>MuokkaaPelaaja.js</li>
<li>PelaajaTiedotNakyma.js</li>
<li>PoistaPelaaja.js</li>
</ul>
<p>
Tämä harjoitus syvensi ymmärrystäni REST API:n
luomisesta ja Reactin käytöstä
tietokantasovelluksen rakentamisessa.
Pitkällisen selvittelyn jälkeen koin myös
oppivani Context API:n hyödyntämistä datan
hallinnassa.
</p>
<p>
Harjoitus onnistui lopulta melko hyvin, vaikka
kohtasin aluksi paljon haasteita, erityisesti
Context API:n käytössä ja React-komponenttien
hallinnassa. Lopputuloksena oli kuitenkin
toimiva sovellus, joka mahdollistaa
urheilijoiden tietojen hallinnan (lisäys,
tarkastelu, muookaus ja poisto). Jostain syystä
en saanut kuvia näkymään ja pitkän ja
piinallisen yrittämisen jälkeen luovuin kuvien
lisäämisestä. Jos tekisin harjoituksen
uudelleen, selvittäisin miten kuvat saa
näkymään. Perehtyisin myös vielä lisää siihen
Context API:n toimintaan.
</p>
<p>Linkki vastaukseen GitHubissa:</p>
<a href="https://github.com/PaulaRii/REST1"
>https://github.com/PaulaRii/REST1</a
>
<p>
<strong>Huom!</strong> Tietokannan luontiskripti
on Urheilija2/serveri-kansion sisällä
tekstitiedostona.
</p>
</div>
<div class="flex-shrink-0">
<span class="text-primary">Joulukuu</span>
</div>
</div>
<div
class="d-flex flex-column flex-md-row justify-content-between mb-5"
id="harjoitus-7"
>
<div class="flex-grow-1">
<h3 class="mb-0">Harjoitus 7</h3>
<div class="subheading mb-3">e-Portfolio</div>
<p>
Viimeisessä harjoituksessa koostin e-portfolion,
jossa on mahdollista navigoida aiemmin
toteuttamieni tehtävien sisällä. Käytin
erityisesti Bootstrap-komponentteja
hyödyntämällä valmista Bootstrap-pohjaa, mikä
helpotti rakenteen luomista ja sisällön
lisäämistä.
</p>
<p>
Vaikka harjoitus ei ollut erityisen vaikea, se
vei aikaa. Ainoa varsinainen haaste oli
valmiissa Bootstrap-pohjassa ollut
.pug-tiedosto, joka aiheutti harmaita hiuksia,
koska muutokset index.html-tiedostoon eivät
jostain syystä tallentuneet oikein ilman sen
muokkaamista. Onneksi ChatGPT auttoi
.pug-tiedoston luomisessa, vaikka näin
jälkikäteen ajateltuna sen poistaminen olisi
voinut olla helpompi ratkaisu.
</p>
<p>
Harjoituksen tavoitteena oli koota aiemmat
tehtävät selkeästi navigoitavaksi portfolioksi
ja harjoitella Bootstrapin käyttöä web-sivuston
rakentamisessa. Tämä auttoi ymmärtämään paremmin
Bootstrap-komponenttien hyödyntämistä ja
sivuston rakenteen suunnittelua.
</p>
<p>
Opin paljon Bootstrapin käytöstä ja sen
tarjoamista valmiista komponenteista, jotka
helpottivat sivuston rakenteen luomista. Sain
myös kokemusta siitä, kuinka valmiita pohjia
voidaan mukauttaa omiin tarpeisiin sopiviksi.
Vaikka .pug-tiedoston käsittely aiheutti
haasteita, onnistuin lopulta luomaan toimivan
portfolion.
</p>
<p>
Mielestäni onnistuin e-portfolion
toteuttamisessa hyvin, sillä lopullinen
portfolio on selkeä ja toimiva. Jos tekisin
harjoituksen uudelleen, poistaisin
.pug-tiedoston kokonaan välttääkseni sen
aiheuttamat ongelmat ja keskittyisin pelkästään
index.html-tiedoston muokkaamiseen.
</p>
<p>Linkki harjoitukseen GitHubissa:</p>
<a href="https://github.com/PaulaRii/REST1"
>https://github.com/PaulaRii/REST1</a
>
</div>
<div class="flex-shrink-0">
<span class="text-primary">Toukokuu</span>
</div>
</div>
</div>
</section>
<hr class="m-0" />
<!-- Education-->
<section class="resume-section" id="education">
<div class="resume-section-content">
<h2 class="mb-5">Palauteosio</h2>
<div class="mb-4">
<strong>Alkutilanne:</strong>
<p>
Ennen kurssia tietoni ja taitoni web-ohjelmoinnista
olivat lähes alkeelliset.
</p>
</div>
<div class="mb-4">
<strong>Nykytilanne:</strong>
<p>
Koen nyt osaavani käyttää Bootstrappiä, ja luoda
ainakin jokseenkin toimivia web-sivuja sekä uskon
ymmärtäväni pääperiaatteet REST API:sta ja Reactin
käytöstä. Lisäksi hahmotan nyt paremmin
tietokantaohjelmointia ja siten miten websivuihin
saa tehtyä AJAX:illa asynkronisia kutsuja. Myös tuli
palauteltua mieleen Javascriptin sekä Git:in
käyttöä.
</p>
</div>
<div class="mb-4">
<strong>Ajankäyttö:</strong>
<p>
En tajunnut kurssilla ottaa käyttöön Clockifyitä
seuraamaan ajankäyttöäni (tähän olisi hyvä
muistuttaa opiskelijoita), joten en ole aivan
kärryillä siitä kuinka monta tuntia olen kurssin
suorittamiseen käyttänyt. Kuitenkin tuntuu, että
lähes jokaiseen tehtävään (ensimmäisiä tehtäviä
lukuunottamatta) tarvitsin enemmän aikaa kuin ehkä
oli tarkoitettu niihin käytettäväksi. Uskon, että
käytin lopulta aikaa huomattavasti enemmän kuin mitä
oli laskettu kurssin suorittamiseen.
</p>
</div>
<div class="mb-4">
<strong>Tukevat tekijät:</strong>
<p>
Luennoilla sai mukavasti tukea tehtävien tekemiseen.
Oli hyvä, että niiden alkuun saattamiseen käytettiin
aikaa luennolla. ChatGPT:n kanssa keskustelu kurssin
teemoista on ollut myös erittäin hyvä tuki, niin
virheiden löytämiseen koodista kuin myös avaamaan
hankalia käsitteitä, joihin ei mielestäni tunnilla
käytetty tarpeeksi aikaa, ja joiden opiskelu
jätettiinkin enemmän opiskelijan oman aktiivisuuden
varaan. Muistan keskustelleeni erityisen paljon REST
API:in liittyvistä asioista ChatGPT:n kanssa.
Luennoilla tuli aina hirmuisen paljon informaatiota
lyhyessä ajassa, ja sitä informaatiotulvaa on ollut
hyvä avata tekoälyn kanssa.
</p>
</div>
<div class="mb-4">
<strong>Häiritsevät tekijät:</strong>
<p>
Mielestäni olisi ollut parempi jakaa luennot kahteen
kertaan viikossa. Luennot olivat hyvin raskaita, ja
kun koko viikon käsiteltävät asiat ahdetaan yhteen
sessioon niin ei siinä oikein pysty sulattelemaan
kaikkea kuulemaansa ja tulee sellainen
informaatioähky ja tuntuu, ettei tunnilla käsitellyt
asiat oikein tahdo jäädä päähän. Positiivista oli
se, että oli kaksi viikkoa aikaa sulatella saatua
informaatiota ja paneutua harjoitustehtävien
tekemiseen.
</p>
</div>
<div class="mb-4">
<strong>Ohjaus ja apu:</strong>
<p>
Luennoilla sai hyvin ohjausta, siitä kiitos
luennoitsijalle. Arvostan sitä, että aina
varmistettiin, että ovathan kaikki yhä kelkassa
mukana (vaikka lopulta itse putosinkin kelkasta ja
kurssin suorittaminen pääsi venymään...).
</p>
</div>
<div class="mb-4">
<strong>Kohokohdat ja toiveet:</strong>
<p>
Oli hieno fiilis, kun pitkän taistelun jälkeen sai
tehtävän toimimaan. Erityisen suuria haasteita oli
harjoitus 6:sen kanssa, mutta kun sain sen lopulta
valmiiksi, oli aika voittajafiilis. Toiveena olisi,
että opitut asiat säilyvät muistissa ja niitä
kykenesi jatkossa hyödyntämään.
</p>
</div>
<div class="mb-4">
<strong>Tulevaisuuden suunnitelmat:</strong>
<p>
Haluan suunnitella omat nettisivut ja sitä varten
tulen varmaankin hyödyntämään kurssilla opittuja
asioita. Muutoin web-ohjelmointi ei ole ollut
itselläni suuren kiinnostuksen kohteena, joskin
kuitenkin päädyin työskentelemään GenAI-hankkeessa
web-selaimessa toimivan platformer-pelin kanssa,
joten jonkun verran näitä asioita on tullut
kuitenkin vastaan tässä kevään aikana jo.
</p>
</div>
</div>
</section>
<hr class="m-0" />
</div>
<!-- Bootstrap core JS-->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js"></script>
<!-- Core theme JS-->
<script src="js/scripts.js"></script>
</body>
</html>
```