Ajax ja WWW-grafiikka - Demo 9

Kertaillaan ensin hieman JavaScriptin tapahtumakäsittelyä. Hyödynnetään XMLHttpRequestia lomakkeen täytön apuna. Tehdään muutamia kaaviota tietokantatietojen perusteella. Lopuksi tehdään kuvagalleria, jossa hyödynnetään Ajaxia, tietokantoja ja GD-grafiikkakirjastoa.

Rekisteröinti - Ajax

Tehdään osoitelomake, joka hakee automaattisesti postitoimipaikan tietokannasta.

Tapahtumankäsittelyä

  1. Tee peopleen hakemisto demo9:iä varten. Luo uusi XHTML-dokumentti ja lomake, jossa on kysytään nimeä, lähiosoitetta ja postinumeroa.
    • index.html:
      <?xml version="1.0" encoding="ISO-8859-1"?>
      <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
      <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="fi">
      <head>
        <title>Syötä osoitetiedot</title>
        <link href="rek.css" rel="StyleSheet" type="text/css" />
      </head>
      <body>
      <form action="lisaa.php" method="post">
      <fieldset>
      <legend>Tilaajan tiedot</legend>
      <div>
        <label for="etunimi">Etunimi</label>
        <input type="text" id="etunimi" name="etunimi" size="40" maxlength="100"/>
        <span id="etunimi_req">(*)</span>
      </div>
      <div>
        <label for="sukunimi">Sukunimi</label>
        <input type="text" id="sukunimi" name="sukunimi" size="40" maxlength="100"/>
        <span id="sukunimi_req">(*)</span>
      </div>
      <div>
        <label for="lahiosoite">Lähiosoite</label>
        <input type="text" id="lahiosoite" name="lahiosoite" size="40" maxlength="100"/>
        <span id="lahiosoite_req">(*)</span>
      </div>
      <div>
        <label for="postinro">Postinumero</label>
        <input type="text" id="postinro" name="postinro" size="5" maxlength="5"/>
        <span id="postinro_req">(*)</span>
      </div>
      <div>
        <label for="postipaikka">Postitoimipaikka</label>
        <input type="text" id="postitoimipaikka" name="postitoimipaikka" size="40" disabled="disabled"/>
        <span id="postitoimipaikka_req">(*)</span>
      </div>
      <div>
        <label for="oppilaitosryhma">Oppilaitosryhmä</label>
        <select id="oppilaitosryhma" name="oppilaitosryhma"></select>
      </div>
      <div>
        <label for="oppilaitos">Oppilaitos</label>
        <select id="oppilaitos" name="oppilaitos"></select>
      </div>
      </fieldset>
      <p><input type="submit" name="laheta" id="laheta" value="Lähetä!" /></p>
      </form>
      </body>
      </html>
      
    • rek.css:
      label {
        width: 20%;
        float: left;
        display: block;
      }
      
      .hidden {
        display: none;
      }
      
  2. Lataa samaan hakemistoon MochiKit.js ja tee uusi skriptitiedosto osoite.js. Lisää skripti XHTML:n head-osaan script-elementillä.
  3. Lisää kaikille lomake-elementeille tarkistus, että niissä on jotain tekstiä.
    • Käytä tapahtumankäsittelijän lisäämiseen MochiKitin connect-metodia.
    • Jos kyseisessä elementissä on jotain, niin piilota sitä vastaava (*)-merkki (class-ominaisuuteen luokka hidden). Elementin id:n saa tapahtumankäsittelijässä this.getAttribute("id")-metodilla.
    • Jos jossain kentässä on vikaa, niin poista lähetä-nappi käytöstä (disabled-attribuutti). Helpointa tässä tapauksessa on tutkia kaikkien span-elementtien class-attribuutteja (getElementByTagName). Tarkistuksesta kannattaa tehdä erillinen funktio, jota kutsutaan tapahtumankäsittelijästä.

XMLHttpRequest ja responseText

Toteutaan postitoimipaikan valinta postinumeron perusteella.

  1. Lisää postinumeron onchange-tapahtumaan vielä toinen tapahtumankäsittelijä. Tämän funktion tarkoitus on tarkistaa onko postinumeroa tietokannassa ja lisää vastaavan postitoimipaikan tekstikenttään.
  2. Luo demo9-hakemistoon SQLitellä uusi tietokanta, jossa on yksi taulu:
    CREATE TABLE Postiosoite (
    postinro CHAR(5) PRIMARY KEY NOT NULL,
    postitoimipaikka VARCHAR(100) NOT NULL
    );
  3. Lisää muutama testinumero:
    INSERT INTO Postiosoite (postinro, postitoimipaikka) VALUES ('40740', 'Jyväskylä');
    INSERT INTO Postiosoite (postinro, postitoimipaikka) VALUES ('40100', 'Jyväskylä');
    INSERT INTO Postiosoite (postinro, postitoimipaikka) VALUES ('31600', 'Jokioinen');
    INSERT INTO Postiosoite (postinro, postitoimipaikka) VALUES ('00100', 'Helsinki');
    INSERT INTO Postiosoite (postinro, postitoimipaikka) VALUES ('33101', 'Tampere');
  4. Tee samaan hakemistoon PHP-tiedosto haetoimipaikka.php.
    • Avaa PDO:lla tietokantayhteys luomaasi tietokantaan.
    • Preparoi kysely
      SELECT postitoimipaikka FROM Postiosoite WHERE postinro = :postinro
    • Tee muuttuja $postinro ja aseta sen arvoksi $_GET['postinro']. Kytke $postinro preparoituun kyselyyn.
    • Hae tuloksista ensimmäinen rivi (pitäisi tulla vain 0-1 riviä)
    • Palautus tehdään pelkkänä tekstinä, joten aseta sivun MIME-tyypiksi text/plain ja merkistöksi ISO-8859-1:
      header("Content-type: text/plain; charset=ISO-8559-1");
    • Printtaa tämän jälkeen hakutuloksista postitoimipaikka mikäli sellainen saatiin kyselystä. Tulosta muuten tyhjä merkkijono.
  5. Lisää seuraava Javascript-ohjelmaasi:
    var baseurl = "http://people.cc.jyu.fi/~tunnus/sovellukset/demo9/haetoimipaikka.php";
    // hakasulkeet tarvitaan, koska queryString vaatii parametreiksi Array-muuttujia
    var query = queryString(["postinro"], [document.getElementById("postinro").value]);
    var url = baseurl + "?" + query;
    
    Muodosta baseurl-muuttujaan edellä tekemäsi PHP-ohjelman osoite.
  6. Lisää MochiKitin avulla doSimpleXMLHttpRequest-kutsu ja sille muodostettu osoite. Tee funktiot lisaaPaikka(response) ja lisaaPaikkaVirhe. Lisää nämä kutsun palauttaman olion callback-funktioiksi (addCallbacks).
  7. Aseta lisaaPaikka-funktiossa postitoimipaikka-kenttään (value) saatu teksti (response.responseText).
    • Jos vaste on jotain muuta kuin tyhjä merkkijono (ts. löytyi jokin postitoimipaikka), niin sijoita se postitoimipaikka-kenttään ja piilota postitoimipaikka_req -id:llä merkitty span-elementti.
    • Jos vaste on tyhjä merkkijono, niin sijoitetaan se postitoimipaikkakenttään, mutta ei poisteta postitoimipaikan (*)-merkintää.
    • Hyväksy myös submit-nappulan käyttö, jos kaikki (*) on piilossa.
  8. lisaaPaikkaVirhe-funktio suoritetaan jos palvelimelta ei saada HTTP-vastetta tai vasteena tuli jokin virheilmoitus. Näytä silloin postitoimipaikka_req -id:llä merkitty span-elementti ja estä submit-nappulan käyttö.

XMLHttpRequest ja responseXML

Toteutetaan vielä oppilaitoksen valinta.

Oppilaitoksien lisääminen tietokantaan

Lisää luomaasi tietokantaan vielä taulut oppilaitosryhmistä ja oppilaitoksista seuraavien SQL-lauseiden avulla:

Ryhmien hakeminen tietokannasta ja lisääminen valintalistaan

  1. Toteuta haeryhmat.php-sivu, joka hakee oppilaitosryhmät tietokannasta ja tulostaa ne select-option-listana.
    • Avaa yhteys PDO:lla
    • Preparoi SQL-kysely
      SELECT RyhmaID, RyhmaNimi FROM Oppilaitosryhma
    • Lisää otsikkotietoihin MIME-tyypin määritys text/xml (header).
    • Tulosta xml-deklaraatio ja select-elementti. Lisää select-elementtiin attribuutit
      • xmlns="http://www.w3.org/1999/xhtml" Nimiavaruusmääritys tarvitaan, että elementti voidaan siirtää XHTML-dokumenttiin.
      • id="oppilaitosryhma" Elementin valintaa varten.
      • name="oppilaitosryhma" Lomakkeenkäsittelijää varten.
    • Tulosta select-elementin loppu. XML-palasen voisi luoda print-lauseiden sijaan myös PHP:n DOM-rajapinnan avulla (kts. luento 18)
  2. Lisää pääsivun window.onload-funktioon doSimpleXMLHttpRequest, joka pyytää haeryhmat.php-sivua.
  3. Lisää callback-funktiot lisaaRyhmat(resp) ja lisaaRyhmatVirhe onnistuneelle ja epäonnistuneelle HTTP-pyynnölle. Epäonnistuneessa tapauksessa voi toistaiseksi ilmoittaa virheestä esim. alert-funktiolla.
  4. Toteutetaan lisaaRyhmat-funktioon select-elementin vaihto. Hae saadusta XML-vasteesta juurielementti ja sen lapsielementit (resp.responseXML.documentElement) importNode-funktiolla johonkin muuttujaan. Saatu select-elementti voidaan vaihtaa toiseen swapDOM-funktiolla.
  5. IE:n puutteellinen DOM-tuki ja erilliset XML- ja HTML-komponentit aiheuttavat kuitenkin vielä ongelmia. Tehdään vielä vaihtoehtoinen viritys IE:lle. Tutki ensin onko käytössä document.importNode-funktiota. Jos ei ole, niin suorita seuraava koodi:
    var temp = createDOM("div");
    temp.innerHTML = toHTML(resp.responseXML.documentElement);
    swapDOM("oppilaitosryhma",temp.firstChild);
    
    Tämä muuntaa saadun sisällön HTML-tekstiksi ja lisää sen itse luodun väliaikaisen DOM-elementin sisälle. Nyt elementin sisältö voidaan siirtää sivun dokumenttipuuhun.

Oppilaitoksen vaihtaminen valinnan perusteella

  1. Toteuta haeoppilaitokset.php-sivu, jonka ottaa GET-parametriksi ryhmaid:n ja palauttaa sen perusteella ryhmään kuuluvat oppilaitokset. Pohjana voit käyttää haeryhmat.php:ta eli tee siitä kopio.
    • Avaa yhteys PDO:lla tietokantaasi.
    • Valmistele SQL-kysely
      SELECT OppilaitosID, Nimi FROM Oppilaitos WHERE Oppilaitosryhma_RyhmaID = :ryhmaid
    • Luo muuttuja $ryhmaid, johon asetetat $_GET-taulukosta saadun ryhmätiedon.
    • Kytke :ryhmaid ja $ryhmaid bindParam-funktiolla.
    • Lisää otsikkotietoihin MIME-tyypin määritys text/xml (header).
    • Tulosta xml-deklaraatio ja select-elementti. Lisää select-elementtiin attribuutit xmlns="http://www.w3.org/1999/xhtml" id="oppilaitos" name="oppilaitos".
  2. Lisää window.onload-funktioon MochiKitin connect-funktiolla onchange-tapahtumankäsittely oppilaitosryhmat-valintalistalle.
  3. Kutsu tämän tapahtumankäsittelijässä doSimpleXMLHttpRequest-funktiolla haeoppilaitos.php-sivua query-muuttujan parametreilla. query-muuttujaan tulee ryhmän id, joka saadaan valintalistan arvosta:
    var baseurl = "http://people.cc.jyu.fi/~tunnus/sovellukset/demo9/haeoppilaitos.php";
    var query = queryString(["ryhmaid"], [getElement("oppilaitosryhma").value]);
    var url = baseurl + "?" + query;
    
  4. Lisää callback-funktiot tälle XMLHttpRequest-kutsulle. Virhetapaus voi olla vaikka sama kuin edellä. Onnistuneessa tapauksessa täytä oppilaitoslista saadulla XML-vasteella kuten edellä.
    • Itseasiassa on mahdollista käyttää samaa funktiota, jos ensin selvität saadusta vasteesta kumpi select-elementti pitää vaihtaa (resp.responseXML.documentElement.getAttribute("id")), mutta voit tehdä myös erilliset funktiot.
  5. Lisää vielä window.onload-funktioon vielä kutsu signal("oppilaitosryhma","onchange"), että listaan tulee ensimmäiset vaihtoehdot.

WWW-grafiikka

Diagrammit Libchart-kirjastolla

Tehdään muutama diagrammi tietokantaan tallennettujen tietojen perusteella.

ER-kaavio pienelle osakekannalle

  1. Luo uusi SQLite-tietokanta, joka pitää kirjaa pienen kuvitteellisen sijoittajaporukan osakesalkuista
    • Lataa luontiin tarvittavat sql-lauseet
      wget http://appro.mit.jyu.fi/sovellukset/demot/demo9/osake.sql
    • Avaa sql-komentotulkki:
      sqlite3 osake
    • Suorita tiedostossa olevat sql-lauseet
      .read osake.sql
    • Poistu komentotulkista
      .quit
    • Lisää luku- ja kirjoitusoikeudet tietokantatiedostoon ja hakemistoon kaikille käyttäjille.
  2. Hae Libchart-kirjasto
    wget http://naku.dohcrew.com/libchart/files/libchart-1.1.tar.gz
  3. Pura paketti
    tar -xvzf libchart-1.1.tar.gz
  4. Luo uusi PHP-sivu, joka palauttaa henkilöiden osakemäärät pylväsdiagrammina osakkeen koodin perusteella
    • Liitä sivulle libchart-kirjaston luokat:
      require_once ("libchart-1.1/libchart/libchart.php");
    • Luo PDO:lla tietokantayhteys osake-kantaan
    • Valmistele kysely, joka hakee henkilöt, määrät ja osakkeet:
      SELECT OsakeNimi, Maara, HenkiloNimi
      FROM Osake, Omistaa, Henkilo
      WHERE Omistaa.Osake_OsakeKoodi = Osake.OsakeKoodi AND
      Omistaa.Henkilo_HenkiloID = Henkilo.HenkiloID AND
      Osake.OsakeKoodi = :koodi
    • Aseta muuttujaan $koodi GET-metodista saatu tieto. Kytke $koodi-muuttujaan kyselyn :koodi
    • Luo uusi VerticalChart (kts. esimerkkikoodi).
    • Suorita SQL ja käy läpi saadut tiedot rivi kerrallaan.
    • Luo uusi piste, jonka labelina on henkilön nimi ja arvopisteenä saatu määrä. Sijoita osakkeen nimi johonkin muuttujaan.
    • Tulosta MIME-tyypiksi image/png header-funktiolla.
    • Lisää palkille otsikoksi osakkeen nimi.
    • Piirrä kaavio render-funktiolla. Älä anna tiedostoparametria, jolloin kuvadata tulee suoraan HTTP-vasteeseen.
  5. Kokeile kaaviosivun toimintaa esimerkiksi osoitteilla:
    http://people.cc.jyu.fi/~tunnus/sovellukset/demo9/kaavio.php?koodi=NOK1V
    http://people.cc.jyu.fi/~tunnus/sovellukset/demo9/kaavio.php?koodi=OUT1V
    
  6. Tehdään vielä piirakkadiagrammi, joka kertoo koko porukan osakkeiden jakautumisen eri toimialoittain
    • Kopioi edellinen PHP-sivu toiselle nimelle
    • Vaihda kyselyksi seuraava:
      SELECT TyyppiNimi, SUM(Maara) as Osakemaara
      FROM Osake, Tyyppi, Omistaa
      WHERE Omistaa.Osake_OsakeKoodi = Osake.OsakeKoodi AND
      Osake.Tyyppi_TyyppiID = Tyyppi.TyyppiID
      GROUP BY TyyppiNimi
    • Poista bindParam, koska nyt toimitaan pelkästään tietokannasta saatujen tietojen perusteella.
    • Vaihda VerticalChart-olion tilalle PieChart-olion luonti. Luonti toimii vastaavasti.
    • Vaihda uuden pisteen luontiin labeliksi riviltä saatu TyyppiNimi ja arvoksi Osakemaara
    • Kaavion otsikoksi voi laittaa esim. Osakkeiden jakautuminen aloittain.
  7. Kokeile kuvaa www-sivulta, esim.
    http://people.cc.jyu.fi/~tunnus/sovellukset/demo9/piirakka.php
  8. Luo uusi XHTML-sivu, jossa on useampi edellä tehty kaavio. Käytä img-elementtiä ja laita src-attribuuttiin sopiva PHP-tiedosto sopivilla parametreilla. Tietokannassa on seuraavia osakekoodeja: NOK1V, TIE1V, ELQAV, UPM1V, OUT1V, RTRKS ja ORNBS.

Kuvien siirto palvelimelle ja muokkaaminen GD-kirjastolla

Toteutetaan pieni sovellus, jonka avulla voidaan siirtää kuvia palvelimelle ja näyttää ne pienennettynä nimen kera.

  1. Luo uusi tietokanta ja sinne seuraava taulu:
    CREATE TABLE Kuva (
    KuvaID INTEGER PRIMARY KEY AUTOINCREMENT,
    KuvaNimi VARCHAR(100) NOT NULL,
    TiedostoNimi VARCHAR(100) NOT NULL
    )
    ;
    Anna tietokantatiedostoon kirjoitusoikeus.
  2. Tehdään uusi PHP-sivu, jolla voidaan siirtää kuvia palvelimelle. Lisää lomake, jossa kysytään kuvan nimeä ja tiedostoa. Tiedostovalinnan saa tehtyä <input type="file" name="tiedosto" /> -elementillä. Lisäksi form-elementin attribuuttiin on lisättävä enctype="multipart/form-data".
  3. Tee demo-hakemistoon uusi hakemisto kuvat. Anna tähän hakemistoon kaikille kirjoitusoikeus (mikä ei yleensäottaen ole turvallista..).
  4. Tee jollekin sivulle lomakkeelle käsittelijä. Tiedostoa pystyy nyt käsittelemään $_FILES-taulukon avulla. Tutkittava väliaikaistiedosto löytyy taulukosta indekseillä $_FILES['tiedosto']['tmp_name'].
  5. Tutki onko lähetetty tiedosto tullut perille is_uploaded_file-funktiolla.
    • Lisäksi olisi syytä tarkistaa, että kuvan pääte on jpg tai jpeg (funktioilla substr ja strrpos) ja että kuva on oikeasti jpeg-tyyppinen (testaamalla onnistuuko kuvan lataaminen GD-kirjastolla) ja että tiedoston koko on järkevä ($_FILES['tiedosto']['size'] ja PHP:n MAX_FILE_SIZE-asetus). Näitä ei nyt tarvitse kuitenkaan toteuttaa.
  6. Jos siirto on onnistunut hyvin, niin siirrä tiedosto kuvat-kansioon move_uploaded_file-funktiolla. Pelkän tiedoston nimen saa $_FILES['tiedosto']['name']-muuttujasta. Lisää tietokantaan käyttäjä antama kuvan nimi ja tiedoston nimi. Tarvittava SQL:
    INSERT INTO Kuva (KuvaNimi, TiedostoNimi) VALUES (:kuvanimi, :tiedostonimi)
    :kuvanimi ja :tiedostonimi voidaan kytkeä bindParam-metodilla $_POST-muuttujasta saatuihin tietoihin. Sijoita tiedot ensin sopiviin väliaikaismuuttujiin.
  7. Tee uusi PHP-tiedosto, joka antaa kuvan pienennettynä ja lisää yläkulmaan kuvan nimen
    • Avaa tietokantayhteys ja hae kuvan nimi ja tiedostonimi kuvaid:n perusteella. Tarvittava SQL:
      SELECT KuvaNimi, TiedostoNimi FROM Kuva WHERE KuvaID = :kuvaid
    • Kytke GET-parametrista saatu kuvaid SQL-kyselyn :kuvaid:hen. Suorita SQL ja hae ensimmäinen (ja ainoa) rivi $row-muuttujaan.
    • Lataa kuva
      $kuva = imagecreatefromjpeg("./kuvat/".$row['TiedostoNimi']);
    • Luo uusi kuva, jonka koko on 100x100:
      $smallkuva = imagecreatetruecolor(100, 100);
    • Haetaan alkuperäisen tiedoston koko ja muutetaan se pienemmäksi
      list($leveys, $korkeus) = getimagesize("./kuvat/".$row['TiedostoNimi']);
      imagecopyresized($smallkuva, $kuva, 0, 0, 0, 0, 100, 100, $leveys, $korkeus)
    • Lisää teksti yläkulmaan seuraavasti
      // asetetaan väriarvot
      $white = imagecolorallocate($smallkuva, 255, 255, 255);
      $black = imagecolorallocate($smallkuva, 0, 0, 0);
      // tehdään pohjalle valkoinen laatikko tekstin pituuden mukaan
      imagefilledrectangle($smallkuva, 0, 0, 7.5*strlen($row['KuvaNimi']), 15, $white);
      // lisätään teksti kuvaan
      imagestring($smallkuva, 3, 0, 0, $row['KuvaNimi'], $black);
      
    • Tulosta MIME-tyypiksi image/jpeg header-funktiolla.
    • Tulosta kuvadata kutsumalla imagejpeg($smallkuva).
  8. Tee vielä PHP-tiedosto, joka hakee kaikki KuvaID:t tietokannasta
    SELECT KuvaID FROM Kuva;
  9. Käy läpi palautusta riveittäin ja tulosta joka kierroksella:
    print '<img src="kuva.php?kuvaid="'.$row['KuvaID'].'" alt="" />';
Kurssimateriaalien käyttäminen kaupallisiin tarkoituksiin tai opetusmateriaalina ilman lupaa on ehdottomasti kielletty!
http://appro.mit.jyu.fi/sovellukset/demot/demo9/
© Jukka Mäntylä (jmantyla@mit.jyu.fi)< http://www.iki.fi/jmantyla/>
2006-05-01 19:54:35