Selaimessa toimiva sovellus
Learning objectives
- Tutustut selaimessa toimivan sovelluksen luomiseen askeleittaisia ohjeita noudattaen.
Olemme tähän mennessä tutustuneet selaimessa toimivien ohjelmistojen peruskäsitteistöön. Olemme käsitelleet sivun rakennetta kuvaavaa HTML-kieltä, dynaamisen toiminnallisuuden lisäämiseen käytettävää Dart-kieltä, sekä tyylien määrittelyyn käytettävää CSS-kieltä. Olemme lisäksi käsitelleet myös tietokannan käytt öä osana sovelluksiamme. Nivotaan seuraavaksi yhteen harjoittelemamme osa-alueet ja luodaan selaimessa toimiva sovellus.
Lähtökohtana sovellukselle on seuraava kuvaus: Kertaan oppimiani asioita keksimällä kysymyksiä ja vastauksia sekä kysymällä näitä kysymyksiä itseltäni. Olen kirjoitellut kysymyksiä koneelle ja jakanut niitä myös kavereilleni. Kaverini ehdotti, että näitä kysymyksiä varten pitäisi tehdä jonkinlainen verkkopalvelu, joka mahdollistaisi kysymysten jakamisen kenelle tahansa. Haluaisin tällaisen sovelluksen -- lähtökohtaisesti sovelluksessa pitäisi olla mahdollisuus kysymysten kysymiseen. Jokaisella kysymyksellä on kysymysteksti ja vastausvaihtoehtoja, joista täsmälleen yksi on oikein.
Kehitetään seuraavaksi askeleittain selaimessa toimiva sovellus, joka mahdollistaa yllä kuvattujen kysymysten jakamisen.
Toteutetaan sovellus "ylhäältä alaspäin" siten, että toteutamme ensin HTML-sivun sekä sivuun liittyvät tyylit. Tämän jälkeen luomme sivulle kyselytoiminnallisuuden. Lopulta toteutetaan mahdollisuus kysymysten hakemiseen annetusta osoitteesta JSON-muodossa.
Sivun runko
Luodaan ensin sovellusta varten HTML-dokumentti, joka sisältää kysymyksen ja vastausvaihtoehtoja. Myöhemmin toteutettava dynaamista toiminnallisuutta sisältävä versio tulee mahdollistamaan vastausvaihtoehtojen klikkaamisen.
HTML-dokumenttiin luodaan erilliset alueet kysymykselle ja vastauksille. Alueet luodaan div
-tyyppisinä elementteinä. Vastaukset sisältävä elementti sisältää alla kolme vastausvaihtoehtoa.
<div id='kysymys'>Tähän tulee kysymys.</div>
<div id='vastaukset'>
<div class='vaihtoehto'>
Vastaus 1
</div>
<div class='vaihtoehto'>
Vastaus 2
</div>
<div class='vaihtoehto'>
Vastaus 3
</div>
</div>
Selaimessa sivu näyttää seuraavanlaiselta.
Sivun tyylittely
Lisätään sivulle seuraavaksi tyylit. Määritellään ensin sivun taustaväriksi lumenvalkoinen (snow
) ja lisätään reunoille hieman tilaa (padding: 1rem
).
body {
background-color: snow;
padding: 1rem;
}
Muokataan tämän jälkeen kysymyksen näyttämiseen tarkoitettua aluetta. Asetetaan kysymyksen taustaväriksi vaaleanvihreä (background-color: lightgreen
), määritellään kysymyksen sisältävälle alueelle pyöreät reunat (border-radius: 15px
), ja asetetaan kysymysteksti alueen keskelle (text-align: center
). HTML-dokumenttiin on määritelty kysymyksen sisältävälle alueelle id-attribuutti kysymys
. Tämä alue voidaan valita CSS-tyylien määrittelyä varten muodossa #kysymys
.
#kysymys {
background-color: lightgreen;
border-radius: 15px;
text-align: center;
}
Yhdistettynä aiemmin määriteltyyn body
-elementin tyyliin, sivu näyttää nyt seuraavalta.
Kysymyksen sisältävä elementti näyttää melko tiiviiltä. Lisätään elementille ilmavuutta padding
-tyylillä, jonka arvoksi asetetaan 1rem
. Sivu näyttää tämän jälkeen seuraavalta.
Muokataan seuraavaksi vastausvaihtoehtoja. Määritellään kunkin vastausvaihtoehdon taustaväriksi vaaleansininen (background-color: lightblue
), asetetaan kysymystä mukaillen vastausvaihtoehdoille pyöreät reunat (border-radius: 15px
), ja asetetaan vastausteksti elementin keskelle (text-align: center
). Lisätään elementeille myös ilmavuutta padding-tyylillä (padding: 1rem
).
Toisin kuin kysymyselementti joita on vain yksi, vastausvaihtoehtoja on useita. Jotta jokainen vastausvaihtoehto voi käyttää samaa tyyliä, vastausvaihtoehdolle määritelty tyyliluokka (attribuutti class
). Tyyliluokan arvoksi on asetettu vaihtoehto
. Elementit, joiden tyyliluokan arvo on vaihtoehto
, voidaan valita CSS-tyylien määrittelyä varten muodossa .vaihtoehto
.
.vaihtoehto {
background-color: lightblue;
border-radius: 15px;
text-align: center;
padding: 1rem;
}
Sivu näyttää nyt seuraavanlaiselta.
Kuten yllä huomaamme, kysymystekstin laatikko ja vastausvaihtoehtojen laatikot ovat kiinni toisissaan. Lisätään laatikoiden ympärille tilaa margin
-tyylillä. Asetetaan sekä kysymykselle että vastausvaihtoehdoille margin
-tyylin arvoksi 1rem
.
#kysymys {
background-color: lightgreen;
border-radius: 15px;
text-align: center;
padding: 1rem;
margin: 1rem;
}
.vaihtoehto {
background-color: lightblue;
border-radius: 15px;
text-align: center;
padding: 1rem;
margin: 1rem;
}
Nyt sivu näyttää seuraavanlaiselta.
CSS tarjoaa tuen valitsimiin lisättäville määreille. Määreellä hover
määritellä miten tyyli muuttuu mikäli hiiren kursori viedään elementin päälle. Määreet kirjoitetaan valitsimen perään kaksoispisteellä erotettuna. Alla oleva määre vaihtaa vaihtoehto
-luokalla määritellyn elementin taustavärin lightskyblue
:ksi kun kursori on elementin päällä.
.vaihtoehto:hover {
background-color: lightskyblue;
}
Voit kokeilla alla olevassa esimerkissä miten elementin tyyli muuttuu, kun viet hiiren sen päälle.
Kysymyksen ja vastausvaihtoehtojen näyttäminen
Toteutetaan seuraavaksi toiminnallisuus kysymystekstin ja vastausvaihtoehtojen asettamiseen sekä vastausvaihtoehtojen valinnan käsittelyyn. Tässä kohtaa toteutamme yksittäisen kysymyksen ja siihen liittyvien vaihtoehtojen asettamisen. Useamman kysymyksen käsittelyä kannattaa lähteä toteuttamaan vasta kun yksittäisen kysymyksen käsittely toimii.
Toteutetaan sekä kysymystekstin että vastausvaihtoehtojen asettamista varten erilliset funktiot. Kysymystekstin asettaminen on suoraviivaista: haetaan kysymyksen sisältävä elementti ja asetetaan sen teksti.
asetaKysymysteksti(teksti) {
querySelector('#kysymys').text = teksti;
}
Määritellään vastausvaihtoehdot siten, että jokainen vastausvaihtoehto on sanakirja, joka sisältää vastausvaihtoehdon tekstin sekä tiedon vastausvaihtoehdon oikeellisuudesta. Vastausvaihtoehdot säilytetään listalla.
var vaihtoehdot = [];
vaihtoehdot.add({'teksti': 'eka', 'oikein': true});
vaihtoehdot.add({'teksti': 'toka', 'oikein': false});
Vastausvaihtoehtojen asettamiseen tarkoitettu funktio saa parametrinaan listan vastausvaihtoehtoja. Funktio poistaa aiemmat vastausvaihtoehdot elementistä, joka tunnistetaan id:llä vastaukset
. Tämän jälkeen parametrina saadulla listalla olevat vaihtoehdot lisätään vastauksille tarkoitettuun elementtiin yksi kerrallaan.
asetaVastausvaihtoehdot(vaihtoehdot) {
querySelector('#vastaukset').children.clear();
for (var i = 0; i < vaihtoehdot.length; i++) {
lisaaVastausvaihtoehto(vaihtoehdot[i]);
}
}
lisaaVastausvaihtoehto(vaihtoehto) {
}
Yllä yksittäisen vastauksen lisäämiseen tarkoitettu funktio on jätetty vielä tyhjäksi. Funktion tulee (1) luoda div
-elementti, jolla on tyyliluokka vaihtoehto
, (2) asettaa elementille vastausvaihtoehdon teksti, ja (3) lisätä vastausvaihtoehto vastaukset sisältävään elementtiin. Tämä tapahtuu seuraavalla tavalla.
lisaaVastausvaihtoehto(vaihtoehto) {
var elementti = Element.div();
elementti.className = 'vaihtoehto';
elementti.text = vaihtoehto['teksti'];
querySelector('#vastaukset').children.add(elementti);
}
Tehdään vielä funktioita asetaKysymysteksti
ja asetaVastausvaihtoehdot
käyttävä funktio asetaKysymys
. Funktiolle annnetaan parametrina kysymystekstin ja vastausvaihtoehdot sisältävä sanakirja.
asetaKysymys(kysymys) {
asetaKysymysteksti(kysymys['teksti']);
asetaVastausvaihtoehdot(kysymys['vaihtoehdot']);
}
Nyt yksittäisen kysymyksen asettaminen onnistuu funktiota asetaKysymys
kutsumalla. Alla olevassa esimerkissä näytetään, miten funktiolle asetaKysymys
annettava sanakirja muodostetaan.
main() {
var vaihtoehdot = [];
vaihtoehdot.add({'teksti': '1+3', 'oikein': true});
vaihtoehdot.add({'teksti': '3--2', 'oikein': false});
vaihtoehdot.add({'teksti': '3', 'oikein': false});
var kysymys = {};
kysymys['teksti'] = 'Paljonko on 2+2?';
kysymys['vaihtoehdot'] = vaihtoehdot;
asetaKysymys(kysymys);
}
Valinnan oikeellisuudesta kertominen
Lisätään seuraavaksi toiminnallisuus oikean (tai väärän) vastauksen kertomiseen. Toteutetaan tämä siten, että käyttäjälle näytetään vastausvaihtoehdon klikkaamisen jälkeen vastaustekstin paikalla tieto vastauksen oikeellisuudesta.
Lisätään funktioon lisaaVastausvaihtoehto
tapahtumankäsittelijä, joka käsittelee elementin klikkauksen. Mikäli valittu vastausvaihtoehto on oikea, asetetaan elementin tekstiksi "oikein!", muulloin elementin tekstiksi asetetaan "väärin!". Tässä oletetaan, että vastausvaihtoehdon oikeellisuus on vastausvaihtoehtoa kuvaavan sanakirjan muuttujassa oikein
.
lisaaVastausvaihtoehto(vaihtoehto) {
var elementti = Element.div();
elementti.className = 'vaihtoehto';
elementti.text = vaihtoehto['teksti'];
elementti.onClick.listen((e) {
if (vaihtoehto['oikein']) {
elementti.text = 'oikein!';
} else {
elementti.text = 'väärin!';
}
});
querySelector('#vastaukset').children.add(elementti);
}
Nyt, kun käyttäjä klikkaa vastausvaihtoehdon sisältävää elementtiä, elementtiin tulee tieto valinnan oikeellisuudesta.
Useamman kysymyksen näyttäminen
Toteutetaan seuraavaksi toiminnallisuus useamman kysymyksen näyttämiseen. Toteutetaan tämä siten, että sivulla on nappi, jota painamalla kysymystä voi vaihtaa. Lisätään HTML-dokumenttiin ensin nappi.
<div id='kysymys'>Tähän tulee kysymys.</div>
<div id='vastaukset'>
<div class='vaihtoehto'>
Vastaus 1
</div>
<div class='vaihtoehto'>
Vastaus 2
</div>
<div class='vaihtoehto'>
Vastaus 3
</div>
</div>
<button id="seuraava">Seuraava kysymys</button>
Tyylitellään nappia hieman. Lisätään napille tilaa padding
ja margin
-tyyleillä.
#seuraava {
padding: 1rem;
margin: 1rem;
}
Sivu näyttää tyylitellyn napin kanssa seuraavalta.
Nappia ei ole keskitetty, joten se näyttää olevan hieman oudossa paikassa. Keskitetään nappi. Lisätään napin ympärille div
-elementti, jolle määritellään sisällön keskittäminen tyylillä text-align: center
.
<div id="nappi">
<button id="seuraava">Seuraava kysymys</button>
</div>
#nappi {
text-align: center;
}
Nyt sivu näyttää seuraavalta.
Lisätään seuraavaksi mahdollisuus kysymyksen vaihtamiseen. Toteutetaan kysymyksen vaihtaminen siten, että napin painaminen asettaa sivulle uuden kysymyksen.
Miten tämä oikein kannattaisi toteuttaa? Eräs mahdollisuus on toteuttaa toiminnallisuus siten, että ohjelman main
-funktiossa luodaan lista, joka sisältää kysyksiä. Tämän jälkeen napille lisätään tapahtumankäsittelijä. Kun käyttäjä klikkaa nappia, ohjelma valitsee listalta yhden kysymyksen ja asettaa sen näkyville.
Alla olevassa esimerkissä luodaan kaksi kysymystä, jotka on asetettuna listalle, sekä kysymyksen satunnaisesti valitseva tapahtumankuuntelija.
import 'dart:html';
main() {
var kysymykset = [];
var vaihtoehdot = [];
vaihtoehdot.add({'teksti': 'Kyllä', 'oikein': true});
vaihtoehdot.add({'teksti': 'Ei', 'oikein': false});
kysymykset.add({
'teksti': 'Siistiä?',
'vaihtoehdot': vaihtoehdot
});
kysymykset.add({
'teksti': 'Toimiiko?',
'vaihtoehdot': vaihtoehdot
});
querySelector('#seuraava').onClick.listen((e) {
kysymykset.shuffle();
asetaKysymys(kysymykset[0]);
});
}
Tässä kohtaa toteutettu ohjelman versio näyttää käyttäjälle uuden kysymyksen vasta kun käyttäjä klikkaa nappia. Ennen kuin nappia klikataan, sivulla näkyy HTML-dokumentissa oleva teksti. Muokataan sivua siten, että ohjelman käynnistyessä käyttäjälle näkyy ensin teksti, joka pyytää käyttäjää valitsemaan seuraavan kysymyksen.
<div id='kysymys'>Klikkaa nappia aloittaaksesi.</div>
<div id='vastaukset'>
</div>
<div id='nappi'>
<button id='seuraava'>Seuraava kysymys</button>
</div>
Kysymysten lataaminen verkosta
Kysymysten kirjoittaminen osaksi ohjelmaa ei ole kovin mielekästä. Muokataan seuraavaksi ohjelmaa siten, että se hyödyntää verkossa valmiina olevaa JSON-muotoista dokumenttia, joka sisältää kysymykset ja vastaukset. Ohjelmaa varten on luotu kysymyksiä sisältävä tiedosto, joka on asetettu tälle sivustolle osoitteeseen /json-data/kysymykset.json.
Oleelliset muutokset ohjelmassa sisältävät tiedoston hakemisen, tiedoston muuntamisen sanakirjaksi, sekä kysymysten hakeminen tiedostosta. Tiedostossa käytetyn tiedon esitysmuotoa ei tarvitse muuttaa, sillä se noudattaa valmiiksi kysymystemme muotoa.
import 'dart:html';
import 'dart:convert';
main() async {
var osoite = 'https://csfoundations.cs.aalto.fi/json-data/kysymykset.json';
var sisalto = await HttpRequest.getString(osoite);
var sanakirja = jsonDecode(sisalto);
var kysymykset = sanakirja['kysymykset'];
querySelector('#seuraava').onClick.listen((e) {
kysymykset.shuffle();
asetaKysymys(kysymykset[0]);
});
}
Voit tarkastella kehitettyä ohjelmaa kokonaisuudessaan alla.
Kehityspolku ja kehitysehdotuksia
Tässä materiaalin osassa esitettiin kyselyiden näyttämiseen tarkoitetun ohjelman kehitysaskeleet tyhjästä lähtien. Tässä tapauksessa ohjelman toteutus aloitettiin HTML-dokumentin ja tyylien määrittelystä, jonka jälkeen siirryttiin dynaamisen toiminnallisuuden kehittämiseen. Tässä lähestymistavassa on hyvänä puolena se, että toteutettava toiminnallisuus on jatkuvasti nähtävillä. Mikäli toteutus olisi lähtenyt dynaamisen toiminnallisuuden kehittämisestä, olisi ohjelmassa pitänyt vähintäänkin tehdä HTML-dokumentti ennen kuin toiminnallisuutta voi kokeilla.
Ohjelman kehitystä voisi jatkaa useilla tavoilla, joista tässä on listattuna muutama:
- Ohjelmaan voisi lisätä pistekirjanpidon, joka pitää kirjaa oikeiden vastausten lukumäärästä.
- Ohjelmaan voisi lisätä tyylejä: esimerkiksi väärin menneen vastauksen taustan voisi vaihtaa punaiseksi.
- Ohjelmaan voisi lisätä tietokannan pistekirjanpidolle. Tätä tietokantaa voisi käyttää myös sellaisten kysymysten valintaan, joihin käyttäjä ei ole vielä vastannut.
Hi! Please help us improve the course!
Please consider the following statements and questions regarding this part of the course. We use your answers for improving the course.
I can see how the assignments and materials fit in with what I am supposed to learn.
I find most of what I learned so far interesting.
I am certain that I can learn the taught skills and knowledge.
I find that I would benefit from having explicit deadlines.
I feel overwhelmed by the amount of work.
I try out the examples outlined in the materials.
I feel that the assignments are too difficult.
I feel that I've been systematic and organized in my studying.
How many hours (estimated with a precision of half an hour) did you spend reading the material and completing the assignments for this part? (use a dot as the decimal separator, e.g 8.5)
How would you improve the material or assignments?