Javascript ja DOM

Harjoitellaan Javascriptin ja DOM-rajapinnan perusteita.

Mallivastaus (mallivastaus.zip)

Alkuvalmistelut

Hello World

Tallenna käyttöösi valmis dokumenttipohja johonkin väliaikaishakemistoon.

Otsikoiden numerointi

Muutellaan elementtien sisältöä

  1. Lisää uusi funktio numeroi ja kutsu tätä window-objektin load-tapahtuman käsittelijäfunktiossa.
  2. Hae numeroi-funktiossa kaikki h2-elementit sopivan nimiseen muuttujaan. Tämä onnistuu document.getElementsByTagName-metodilla. getElementsByTagName-funktio palauttaa aina NodeList-tyyppisen listan riippumatta siitä montako alkiota löytyi.

    NodeList toimii käytännössä kuten taulukko, mutta on ns. live eli DOM-puuhun mahdollisesti tehdyt muutokset heijastuvat suoraan kyseisen listan elementteihin.

    document.getElementsByTagName etsii aina koko DOM-puusta. Elementti.getElementsByTagName etsii aina kyseisen elementin alla olevasta dokumenttihaarasta.
  3. Haetaan h2-elementtien tekstit. Yritä tulostaa kaikkien h2-elementtien tekstit konsoliin
    • Voit käydä NodeListin läpi for..of-silmukalla
    • Yksittäisen h2-elementin NodeListista saat item-metodilla tai []-operaattorilla aivan kuten taulukosta.
    • Kokeile miten h2-elementin textContent-ominaisuus toimii. Sillä saat suoraan jonkun elementin koko tekstisisällön eikä tarvitse välittää erillisistä TextNodeista
      Textnode?
      • Kunkin elementin sisällä oleva teksti muodostaa oikeasti oman TextNode-tyypin olionsa. TextNodeja voi olla myös useita
      • Textnoden saat tässä tapauksessa h2-elementin firstChild-attribuutista.
      • Varsinaisen tekstin (String-tyyppiä) saa nodeValue-attribuutista. Huomaa, että nodeValue antaa tekstisisällön vain textNode-tyypeiltä.
      • Yleensä riittää textContent-ominaisuuden käyttäminen eikä erillisistä textnodeista tarvi välittää
  4. textContent-attribuuttiin voi myös asettaa arvoja. Lisää sivulle näkyviin kunkin nykyisen otsikon eteen vielä otsikkonumerointi alkaen numerosta 1. Tarvitset tähän erillisen laskurimuuttujan.
  5. Miksi et voi kutsua numeroi()-funktiota muualta kuin load-tapahtuman sisältä? Kokeile laittaa kutsu load-tapahtuman ulkopuolelle. Mitä tapahtuu (tai ei tapahdu)? Miksi?

Elementtien lisäys

Lisätään elementtejä sivulle dynaamisesti DOM-rajapinnan avulla.

Linkit tekstiksi linkkilistaan

Linkit tekstiksi a-elementin jälkeen

  1. Luo uusi funktio linkit ja kutsu sitä windowin load-käsittelijäfunktiossa, jonka loit jo aiemmin.
  2. Merkitse id-ominaisuudella dokumentin lopussa oleva ul-elementti.
  3. Hae linkit-funktiossa id:llä varustettu ul johonkin muuttujaan.
  4. Pyydä kaikkia tämän elementin alla olevia a-elementtejä getElementsByTagName-metodilla.
  5. Tee silmukka ja hae kunkin a-elementin http-osoite href-ominaisuudesta ja tulosta se konsoliin. Tämä onnistuu suoraan href-ominaisuudesta (javascript-objektin property) tai getAttribute-metodilla, joka palauttaa HTML-elementin attribuutin arvon.

    Seuraavassa on demonstroitu miten käytetään javascript-objektin propertyja ja toisaalta html-elementin attribuutteja. Useimmilla html-attribuuteilla on olemassa vastaava javascript-objektin property, mutta ei kaikilla. Kannattaa aina tarkistaa MDN:n sivulta, että mitä propertyja on kullakin DOM-objektilla tarjolla. Omia propertyjahan saa myös vapaasti keksiä.

    document.forms[1][1];
    <input type="text" value="">
    document.forms[1][1].type;
    "text"
    document.forms[1][1].value;
    ""
    document.forms[1][1].getAttribute("text"); // olematon ominaisuus on null
    null
    document.forms[1][1].getAttribute("type");
    "text"
    document.forms[1][1].getAttribute("value");
    ""
    document.forms[1][1].type = "number";
    "number"
    document.forms[1][1].type;
    "number"
    document.forms[1][1].getAttribute("type");
    "number"
    document.forms[1][1].setAttribute("type","email");
    undefined
    document.forms[1][1].getAttribute("type");
    "email"
    document.forms[1][1].type;
    "email"
    document.forms[1][1].value = 1;
    1
    document.forms[1][1].value; // value on AINA merkkijono
    "1"
    document.forms[1][1].getAttribute("value"); // hupsista, tämä ei kuitenkaan muutu, koska alkuperäinen sisältö on kentän oletusarvo
    ""
    document.forms[1][1].setAttribute("value","-"); // asettaa oletusarvon eikä varsinaista arvoa
    undefined
    document.forms[1][1].value; // arvo on edelleen se mitä on value-propertyyn asetettu
    "1"
    document.forms[1][1].getAttribute("value"); // kentän oletusarvo on muuttunut
    "-"

    Huomaa kuinka input-elementin value-attribuutti toimii poikkeavasti.

    JavaScript: What's the difference between HTML attribute and DOM property?
  6. Luo osoitteesta uusi tekstisolmu createTextNode-metodilla li-elementin sisään (ei a-elementin). Miksi et voi tässä käyttää textContent-ominaisuutta? Voit kokeilla toteuttaa tätä textContentin avulla, niin huomaat miksi se ei onnistu.
  7. Lisää luomasi textnode kyseisen a-elementin sisältävän li-elementin loppuun. Tarvitset parentNode-attribuuttia ja appendChild-metodia. Sinun pitää myös keksiä miten saat a-elementin kautta käsiisi li-elementin. Esim. parentNode.
  8. Kokeile toimiiko skripti. Ulkoasua voi parantaa lisäämällä tekstin alkuun vielä yhden välilyönnin. Koodin uudelleenkäytettävyyttä voi vielä parantaa siten, että funktion parametriksi laitetaan id, tämä id haetaan dokumentista ja tämän jälkeläisinä olevat linkit "tekstitetään".

Tapahtumat

Tapahtumankäsittelijän lisääminen

  1. Luo johonkin kohtaan dokumenttia painike button-elementillä. Anna painikkeelle jokin id-arvo ja näkyvä teksti Hello world. Älä laita painiketta form-elementin sisään.
  2. Luo uusi funktio varoitus. Tapahtumankäsittelijänä toimivat funktiot ottavat aina yhden parametrin, jonka nimi on yleensä e.
    function varoitus(e) {
    console.log("Hello world!")
    }
    
  3. Lisää onload-tapahtuman käsittelijään koodi, joka hakee painikkeen väliaikaismuuttujaan. Tämä onnistuu document-olion getElementById-metodilla.
  4. Aseta painikkeelle click-tapahtuman käsittelijäksi funktio varoitus addEventListener-metodilla.
    painike.addEventListener("click", varoitus);

    When to use "use capture" in your event listeners

  5. Testaa painikkeen toimintaa.
  6. Muuta ohjelman toimintaa siten, että Hello world ilmestyy vain tuplaklikkauksesta (kts. HTML:n tapahtumat).
  7. Muuta tuplaklikkauksen käsittely takaisin tavalliseksi click-tapahtumaksi. Siirrä painikkeesi lomakkeen (form-elementti) sisään. Miten ohjelmasi toimii nyt?
    form action="">
      
      </form>
    • Lomakkeen sisällä oleva painike toimii lomakkeen lähetyspainikkeen (submit-painike). Jos painikkeen tyypiksi määrää "button", niin painike ei lähetä lomaketta:
      <button type="button">Tavallinen painike</button>
      submit-painike voi siis olla jompi kumpi seuraavista:
      
      <button>submit-painike</button>
      <label><input type="submit" />submit-painike</label>
      
      Oletuksena lomakkeen sisältö lähetetään www-palvelimelle osoitteeseen, joka on ilmoitettu form-elementin action-ominaisuudessa, jossa on ohjelma, joka käsittelee lomakkeen tiedot. Tällä kurssilla ei käsitellä www-palvelimeen liittyviä ohjelmia. Käytännössä selain vain lataa sivusi uudelleen.
    • Jos halutaan javascriptilla käsitellä lomake, niin täytyy estää lomakkeen lähettäminen www-palvelimelle. Tämä tehdään preventDefault-metodilla lomakkeen submit-tapahtumankäsittelijänä toimivassa funktiossa:
      
         document.forms[0].addEventListener("submit", varoitus);
      
         function varoitus(e) {
          e.preventDefault();
          ...  
        }
        
      Samalla metodilla voidaan estää minkä tahansa tapahtuman oletustoiminta. Useimmiten estetään lomakkeen submit-tapahtuma, tai linkin tai buttonin click-tapahtuma.

Menu

Avautuva menu

Tehdään yksinkertainen menu, jossa alakohtia voidaan piilottaa. Tässä tarvitaan tapahtumankäsittelyä.

  1. Luo dokumentin alkuun HTML:ää kirjoittamalla kaksitasoinen sisäkkäinen lista, jossa on pääkohdat Artikkelit, Harkat ja Linkit ja näiden alle pari listakohtaa, joiden sisällä on linkit. Kts. edellä oleva kuva. Lisää näiden listakohtien eteen img-elementillä minus.jpg-kuva merkitsemään avattua menukohtaa.
  2. Merkitse ulompi lista sopivalla id-attribuutilla.
  3. Luo nanonano.js-tiedostoon uusi funktio muuta_nakyvyys ja aseta funktion ensimmäisen parametrin nimeksi e tai event.
  4. Hae window.onload-tapahtumankäsittelijässä edellä määrittelemälläsi id:llä varustettu ul-elementti johonkin muuttujaan document.getElementById-funktiolla. Tämä funktio palauttaa aina yhden elementin.
  5. Pyydä kaikkia tämän ul-elementin alla olevia img-elementtejä getElementsByTagName-metodilla.
  6. Käy silmukalla img-elementtilista läpi ja lisää kunkin elementin click-tapahtuman käsittelijäksi funktio muuta_nakyvyys.
    elementti.addEventListener("click", muuta_nakyvyys);
  7. Selvitä muuta_nakyvyys-funktion alussa mikä elementti on aiheuttanut tapahtuman. Tämä selviää muuta_nakyvyys-funktiolle parametrina tulevan event-olion target-attribuutista. Voit esim. kokeilla mitä tekee console.log(event.target.nodeName) tai katsoa debuggerilla mitä event-parametri pitää sisällään.
  8. Muuta kuvan (event.target) src-attribuutin arvoksi plus.jpg. Kts. setAttribute. Kokeile selaimella.
  9. Lisää logiikkaa ja aseta src-attribuutiksi plus.jpg jos ennen oli minus.jpg, muutoin toisinpäin. Muuta myös alt-attribuutin tekstiksi Auki/Kiinni tilan mukaan. Testaa toimintaa.
  10. Tutki Inspectorilla millainen puu sisäkkäisestä listasta muodostuu.

    HTML inspector firebugissa - vanha versio

  11. Selvitä, miten pääset käsiksi tapahtuman aiheuttanutta img-elementtiä seuraavaan (sibling) ul-elementtiin. Tapoja on monia, voit joutua käyttämään esim. joitain seuraavista attribuuteista/metodeista: children, parentNode, childNodes, nextSibling, nextElementSibling, nodeName ja getElementsByTagName (kts. DOM - Element).
  12. Piilota sisempi ul-elementti samalla kun asetat src-attribuutin arvoksi plus.jpg, muutoin näytä elementti.
    • Lisää CSS-tiedostoon luokka, jossa on ominaisuus display: none;. Käytä tätä luokkaa javascript-koodissasi
      .piilota { 
        display: none; 
      }
    • Elementin näkyvyyttä voi muuttaa esimerkiksi className-attribuuttia muokkaamalla. Voit classNamen avulla ottaa elementissä käyttöön haluamasi css-tyylin.
    • Elementin saa näkyväksi poistamalla class-attribuutin. Tämä onnistuu removeAttribute-metodilla.
  13. Poista vielä navigointipalkin uloimman listan alkioiden pallukat. Tämä onnistuu helpoiten manipuloimalla näiden elementtien css-ominaisuuksia.
    elementti.style.display = "block";
    Vinkki: käy silmukalla läpi navigointipalkin lapsielement (childNodes) ja tee muutoksia niille, joiden nodeName on LI. getElementsByTagName-metodia voit myös käyttää, mutta tässä tulee ongelma. Hoksaatko mikä?
  14. Voit vielä liu'uttaa (float: right;) koko listan sivun oikeaan laitaan.

Yhteenlaskupeli

Rivejä joissa kaksinumeroisia lukuja ja tekstikenttä niiden summaa varten

Tehdään Nanonanon sivuille päässälaskun harjoitteluun pieni yhteenlaskutehtäviä generoiva peli.

  1. Lisää h2-otsikko ja sen alle div-lohko, jonka id-ominaisuuden arvo on laskut. Lisää myös painike Tarkista, jolla on myös oma id-ominaisuuden arvonsa. Huom. id ei saa alkaa numerolla.
  2. Tee funktio luo_lasku, joka palauttaa p-elementin, joka sisältää arvotun laskutehtävän tekstinä ja tekstilaatikon vastaukselle. Tuotettu HTML-koodi on muotoa:
    <p>LUKU1 + LUKU2 = <input type="number" size="3" /></p>
    • Arvo tekstien LUKU1 ja LUKU2 tilalle jotkin luvut väliltä 0-99. Tämä onnistuu Math.random-metodilla.
    • Tallenna arpomasi luvut luomasi input-elementin yhteyteen käyttämällä omia propertyja. Esim. luku1 ja luku2. input.luku1 = LUKU1;
  3. Tee funktio luo_laskut, joka luo kymmenen laskua silmukassa ja lisää ne laskut-div-lohkon sisälle. Kutsu luo_lasku-funktiota silmukassa. Lisää luo_laskut-funktiokutsu window.onload-tapahtuman käsittelijään.
  4. Tee CSS-tiedostoon uusi luokka .virhe, joka muuttaa elementin taustavärin punaiseksi ja tekstin värin mustaksi.
  5. Tee funktio tarkista_laskut. Lisää tämä funktio window.onload-tapahtuman käsittelijässä tarkista-napin click-tapahtuman käsittelijäksi.
  6. Käy tarkista_laskut-funktiossa kaikki laskurivit läpi silmukassa ja tarkista ovatko kaikki laskut oikein.
    • Summaa luvut yhteen ja vertaile onko luku sama kuin tekstilaatikossa.
    • Vertailua varten tekstikenttään syötetty luku on muutettava integer-tyyppiseksi parseInt- ja Number-funktioilla. Toteuta tavalla, jossa esim. "100a" ei kelpaa vaikka summa olisi 100.
    • Jos laskettu luku ei ole oikein, niin laita tekstilaatikon class-attribuuttiin arvo virhe. Tätä vastaavaa property on className, koska class on javascriptissa varattu sana.
    • Jos rivi on kunnossa, niin poista class-attribuutti
    • Jos kaikki rivit ovat ok, niin ilmoita painikkeen alapuolella sopivalla tekstillä pelin läpipääsystä.
  7. Testaa "pelin" toimintaa.

Jos aikaa jäi, niin kokeile vielä toteuttaa uuden pelin arpominen ja vastaamiseen kuluneen ajan laskeminen pelin uudelleenaloituksesta hyväksyttyyn tarkistukseen.

Mallivastaus (mallivastaus.zip)

Käyttäjien kommentit

Kommentoi Lisää kommentti
Informaatioteknologia - Jyväskylän yliopiston informaatioteknologian tiedekunta