Javascript ja DOM
- Alkuvalmistelut
- Tapahtumien käsittely
- Uusien elementtien luominen
- Tarkistukset
- Yhteenlasku
- Uudet jaot
- Pelin päättyminen
- Aikalaskuri
Demoissa rakennetaan Canastan pistelaskuri javascriptin ja Document Object Model (DOM) avulla.
HTML-palasen muuttaminen DOM-kutsuiksi onnistuu helposti DOMTool-työkalulla.
Alkuvalmistelut
- Käynnistä Firefox-selain. Seuraavat tehtävät toteutetaan Firefoxissa käyttäen W3C:n DOM-rajapintaa. Jos haluat tehdä selaimesta riippumattomia Javascript-ohjelmia niin rakenna ne jonkun valmiin kirjaston kuten Mochikit päälle. Standardia DOM-rajapintaa tullaan myöhemmin käyttämään myös PHP-kielellä.
- Tallenna käyttöösi valmis dokumenttipohja
- Avaa dokumenttipohja firefoxiin
- Avaa näkyville myös Firefoxin Javascript-konsoli (Tools|Javascript console)
johon tulevat mahdolliset virheilmoitukset. Lisäksi kannattaa avata ja asentaa
- JavaScript Debugger - voit rivi kerrallaan tarkastella javascriptin suoritusta ja tutkia muuttujien arvoja
- DOM Inspector - näyttää dokumentin puurakenteen. Tätä ei valitettavasti ole asennettu Firefoxiin mikroluokissa (valittava asennuksessa), mutta Mozilla Suitesta (Start | Programs | Mozilla | Mozilla) se löytyy valinnalla Tools | Web Development | DOM Inspector.
- Avaa avuksesi javascript-luentosivu, Gecko DOM Reference- ja A re-introduction to JavaScript -dokumentit.
- Luo haluamallasi editorilla canasta.js-tiedosto
Tapahtumien käsittely
- Ensimmäiseksi asetetaan javascript-ohjelmamme seuraamaan milloin "Aloita peli"-painiketta napautetaan. Joudumme käyttämään document.getElementById-funktiota jotta löydämme dokumentista oikean elementin jolle voimme määrittää tapahtumankäsittelijän
- Lisää canasta.js-ohjelmaan seuraavat rivit:
/* canasta.js */ /* haetaan painike, jonka id on "aloita". asetetaan painikkeen onclick-tapahtuman käsittelijäksi alku-niminen funktio */ window.onload = function() { var aloita = document.getElementById("aloita"); aloita.addEventListener("click", alku, false); } function alku() { alert("toimii"); }
- Päivitä firefoxissa oleva dokumenttipohja ja kokeile mitä tapahtuu kun napautata "Aloita peli"-painiketta. Jos kaikki meni oikein saat ilmoituksen "toimii".
- Jos olisi käsitelty lomakkeen submit-painikkeen onclick-tapahtuma
pitäisi mahdollisesti estää lomakkeen lähettäminen palvelimelle eli
estää painikkeen oletustoiminta. Tämä tapahtuisi seuraavasti:
function alku(e) { e.preventDefault(); // estää oletustoiminnan }
Uusien elementtien luominen
- Canastapelissä on useita jakoja ja jokaisen jaon jälkeen lasketaan kummallekkin puolueelle bonuspisteitä ja korttipisteitä. Luodaan yhtä jakoa varten syöttökentät eli kaksi tekstikenttää kummallekkin puolueelle
- Uusia elementtejä luodaan DOMin avulla:
- document.createElement-metodilla luodaan uusi elementti
- Luodut elementit täytyy liittää mukaan dokumenttipuuhun jonkun olemassaolevan elementin alaisuuteen esim. appendChild-metodilla
- Elementtien attribuutteja voi asettaa setAttribute-metodilla
- Jos elementti sisältää vain tekstiä voidaan sen tekstisisältö määrätä textContent-ominaisuudella
/* luodaan seuraavanlainen rakenne: <table> <caption>Jako 1</caption> <tr> <th></th> <th>Puolue 1</th> <th>Puolue 2</th> </tr> <tr> <th>Bonus</th> <td><input type="text" /></td> <td><input type="text" /></td> </tr> <tr> <th>Kortti</th> <td><input type="text" /></td> <td><input type="text" /></td> </tr> <tr> <th>Yhteensä</th> <td>0</td> <td>0</td> </tr> </table> */ function alku() { // etsitään lohko jonka sisälle luodaan uusia syöttökenttiä var peli = document.getElementById("peli"); /* luodaan taulukko, taulukon caption ja asetetaan captionin teksti */ var table = document.createElement("table"); var caption = document.createElement("caption"); caption.textContent = "Jako 1"; table.appendChild(caption); /* luodaan otsikkorivi ja lisätään se taulukkoon */ var tr = document.createElement("tr"); var th1 = document.createElement("th"); var th2 = document.createElement("th"); th2.textContent = "Puolue 1"; var th3 = document.createElement("th"); th3.textContent = "Puolue 2"; tr.appendChild(th1); tr.appendChild(th2); tr.appendChild(th3); table.appendChild(tr); /* bonuspisterivi */ // voidaan käyttää samoja muuttujia, koska elementit on jo lisätty table-elementin lapsiksi tr = document.createElement("tr"); th1 = document.createElement("th"); th1.textContent = "Bonus"; th2 = document.createElement("th"); var input1 = document.createElement("input"); input1.setAttribute("type", "text"); input1.setAttribute("size", "4"); var ekainput = input1; /* focus voidaan asettaa vasta kun elementti on näkyvillä dokumentissa */ th2.appendChild(input1); th3 = document.createElement("th"); var input2 = document.createElement("input"); input2.setAttribute("type", "text"); input2.setAttribute("size", "4"); th3.appendChild(input2); tr.appendChild(th1); tr.appendChild(th2); tr.appendChild(th3); table.appendChild(tr); /* korttipisterivi */ tr = document.createElement("tr"); th1 = document.createElement("th"); th1.textContent = "Kortti"; th2 = document.createElement("th"); input1 = document.createElement("input"); input1.setAttribute("type", "text"); input1.setAttribute("size", "4"); th2.appendChild(input1); th3 = document.createElement("th"); input2 = document.createElement("input"); input2.setAttribute("type", "text"); input2.setAttribute("size", "4"); th3.appendChild(input2); tr.appendChild(th1); tr.appendChild(th2); tr.appendChild(th3); table.appendChild(tr); /* Yhteensä */ tr = document.createElement("tr"); th1 = document.createElement("th"); th1.textContent = "Yhteensä"; th2 = document.createElement("th"); th3 = document.createElement("th"); tr.appendChild(th1); tr.appendChild(th2); tr.appendChild(th3); table.appendChild(tr); /* lisätään taulukko <div id="peli"></div> -lohkon sisälle */ peli.appendChild(table); ekainput.focus(); /* vasta täällä voidaan asettaa focus */ }
- Kokeile miten laskuri toimii nyt.
Tarkistukset
- Luodaan funktio jolla voidaan tarkistaa, että annetut pisteet ovat järkeviä:
function kelpaako(e) { var valid = "0123456789"; var syote = this.value; var err = 0; /* löytyykö muita kuin numeroita */ for(var i = 0; i < syote.length; i++ ) if ( valid.indexOf(syote.charAt(i)) == -1 ) err++; /* onko syöte kelvollisella välillä */ if ( this.value.length == 0 || err || syote < 0 || syote > 7000 ) { this.setAttribute("class", "virhe"); return false; } this.removeAttribute("class"); return true; }
- this viittaa tapahtuman aiheuttaneeseen elementtiin
- e on Event-tyyppinen olio, josta saisi tapahtumaan liittyviä tietoja. Niitä ei tässä kuitenkaan tarvita
- Asetetaan edellä luotu funktio käsittelijäksi syötekenttiin. Lisää
alku-funktioon kullekin input-kentälle käsittelijä seuraavaan tapaan:
input1.addEventListener("change",kelpaako,false);
- Kokeile toimiiko tarkistus. Virheellisen syötteen pitäisi muuttaa syöttökentän reunan punaiseksi.
Yhteenlasku
- Jaon pisteet pitää tietenkin laskea yhteen. Laskeminen voidaan toteuttaa joko tietyn painikkeen painamisen jälkeen tai automaattisesti kun tarpeelliset tiedot on syötetty.
- Luodaan uusi funktio, joka osaa laskea jaon pisteet.
function pisteet(e) { var kentta = this; /* haetaan taulukko jonka sisällä olevaa input-kenttää käsitellään */ var table = kentta.parentNode.parentNode.parentNode; /* joukkue 1 */ /* taulukon ensimmäinen lapsi on caption, toinen lapsi on otsikkorivi ja kolmas lapsi on bonusrivi => childNodes[2] bonusrivin ensimmäinen lapsi on otsikko ja toinen lapsi varsinainen syöttösolu => childNodes[1] syöttösolun ensimmäinen lapsi on tietysti input-kenttä => firstChild */ var bonus1 = table.childNodes[2].childNodes[1].firstChild; var kortti1 = table.childNodes[3].childNodes[1].firstChild; var yht = table.childNodes[4].childNodes[1]; /* arvot pitää pakottaa kokonaisluvuiksi koska muuten niitä käsiteltäisiin merkkijonoina */ yht.textContent = parseInt(bonus1.value) + parseInt(kortti1.value); /* joukkue 2 */ var bonus2 = table.childNodes[2].childNodes[2].firstChild; var kortti2 = table.childNodes[3].childNodes[2].firstChild; yht = table.childNodes[4].childNodes[2]; yht.textContent = parseInt(bonus2.value) + parseInt(kortti2.value); }
- Tutki DOM Inspectorilla mitä table-elementin sisällä on. Tämän avulla on helppo tarkastella rakennetta ja selvittää esim. monesko lapsielementti tietty elementti on.
- Lisää tämä uusi funktio jokaisen input-kentän käsittelijäksi samaan tapaan kuin lisäsit kelpaako-funktion. Samaa tapahtumaa voi siis käsitellä useampikin funktio.
- Kokeile miten ohjelma toimii. Mitä tapahtuu kun kaikissa yhteenlaskettavissa kentissä
ei ole oikeaa syötettä?
- Eräs tapa olisi katsoa tuleeko tulokseen arvo NaN funktiolla isNaN .
- Lisäksi voi kuitenkin olla muunkinlaisia virheitä. Parempi on siis tarkistaa onko
elementeille asetettu class-attribuutin arvo virhe.
Koska virhe on ainut vaihtoehto class-attribuutille, niin riittää tutkia onko class-ominaisuutta
ylipäätään asetettu
// onko jossain input-laatikossa virhe if (bonus1.className || bonus2.className || kortti1.className || kortti2.className ) { // tulostetaan tyhjä merkkijono, virhe-teksti tms. yhteensä-kohtiin } else { // lasketaan ja tulostetaan pisteet }
- Mitä mahdollisia ongelmia näet pisteet-funktion toteutustavassa? Mitä tapahtuu jos käytetyn taulukon rakennetta muutetaan?
Uudet jaot
- Nyt on saatu kasaan jo yhden jaon pisteiden jakaminen mutta peliinhän kuuluu
(yleensä) useita jakoja. Muuta pisteet-funktiota siten, että se lisää uuden jaon
automaattisesti heti kun kaikkiin pistekenttiin on lisätty pisteet:
/* tutkitaan viimeistä taulukkoa eli viimeisin jako */ var viimeinen = table.parentNode.lastChild; var bonus1 = viimeinen.childNodes[2].childNodes[1].firstChild.value; var bonus2 = viimeinen.childNodes[2].childNodes[2].firstChild.value; var kortti1 = viimeinen.childNodes[3].childNodes[1].firstChild.value; var kortti2 = viimeinen.childNodes[3].childNodes[2].firstChild.value; if (bonus1 != "" && bonus2 != "" && kortti1 != "" && kortti2 != "") alku();
-
Uuden jaonhan saa helpoiten lisättyä käyttämällä aiemmin luotua alku-funktiota.
Funktion voisi nyt halutessaan nimetä paremmin. Jakojen numerointi pitää myös saada
toimimaan. Korjataan alku-funktiota seuraavasti:
/* haetaan lista taulukoista (yksi taulukko == yksi jako) ja otetaan niiden lukumäärä */ var lkm = peli.getElementsByTagName("table").length; caption.textContent = "Jako " + ( lkm + 1 );
- Jakajan tehtävä vaihtuu jokaisessa jaossa. Ensimmäisen jaon jakajana toimii pelaaja 4 ja seuraavan jakajana pelaaja 1, sitten pelaaja 2 ja pelaaja 3 ja uudelleen samassa järjestyksessä. Lisää jaon nron perään tiedoksi myös jakajan nimi.
Pelin päättyminen
- Peli päättyy kun jompikumpi puolue saa yhteensä 5000 pistettä. Jos molemmat pääsevät samassa jaossa yli 5000 pisteen niin voittaja on puolue jolla on enemmän pisteitä.
- Pisteet lasketaan yhteensä jokaisesta jaosta. Muuta ensimmäiseksi pisteet-funktiota siten, että se laskee aina yhteensä-riville pisteet kaikista aiemmin suoritetuista jaoista eikä pelkästään viimeisimmästä. Käytä getElementsByTagName-metodia ja hae kaikki pelit (taulukot) ja laske niiden tiedoista summat kummallekkin joukkueelle.
- Kokeile laskennan toimintaa. Varmista, että jakokohtaiset välisummat ovat oikein vaikka jonkun jaon pisteitä jouduttaisiin korjaamaan myöhemmin.
- Lisää lopuksi tarkistus 5000 pisteen ylityksestä. Merkitse voittajajoukkueen pistemäärä esim. voittaja-tyylillä äläkä luo enää turhaan uutta jakoa.
Aikalaskuri
- Käytetty aika ei sinänsä vaikuta canastapelin loppupisteisiin mutta joskus on mielenkiintoista tietää kuinka kauan aikaa on käytetty kuhunkin jakoon ja koko peliin
-
Lisätään ohjelmaan laskuri, joka mittaa jokaiseen jakoon kuluneen ajan ja kertoo lopuksi myös
pelin kokonaisajan. Jos et jo aiemmin muuttanut niin muuta nyt alku-funktion nimeksi
jako (muista korjata myös alku-funktioon viittaavat kutsut) ja luo aivan uusi alku-funktio:
/* tarvitaan globaali muuttuja aikojen tallentamiseen */ var ajat = new Array(); function alku() { ajat[0] = new Date(); /* tyhjätään mahdollinen vanha peli */ var peli = document.getElementById("peli"); peli.removeAttribute("id"); var uusi = document.createElement("div"); uusi.setAttribute("id", "peli"); peli.parentNode.replaceChild(uusi, peli); jako(); }
- Muuta pisteet-funktiota siten, että jaon päättyessä lasketaan jakoon kulunut aika ja sijoitetaan aika näkyville "Yhteensä"-tekstin tilalle. Apua löytyy artikkelista How can I calculate the difference between two dates?
- Tulosta pelin loputtua koko peliin käytetty aika pistetaulukon perään.