Ajax ja jQuery

Harjoitellaan ensin hieman JavaScriptin tapahtumankäsittelyä jQueryn avulla. Hyödynnetään Ajaxia (XMLHttpRequest) lomakkeen täytön apuna. Tutustu ensin jQuery- ja ajax-materiaaleihin.

Katso malliratkaisusta millainen sovellus on tarkoitus luoda. Lähdekoodi (python), template, Lähdekoodi (javascript), Malli (JSON), Lähdekoodi-json (javascript)

Yksinkertaisin ajax-malli, flask-osuuden lähdekoodi

Rekisteröinti - Ajax

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

  1. Tee users.jyu.fi:hin web-sovellukset-kansioosi alihakemisto ohjaus5. Luo uusi HTML5-dokumentti ja lomake, jossa kysytään nimeä, lähiosoitetta ja postinumeroa.
    • index.html:
      <!DOCTYPE html>
      <html>
      <head>
        <title>Syötä osoitetiedot</title> 
        <link href="rek.css" rel="StyleSheet" type="text/css" />
      </head>
      <body> 
      <form action="" 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;
      }
      

      Lomakkeen pitäisi olla seuraavanlainen:

      lomake
  2. Lataa samaan hakemistoon jQuery-kirjaston uusin versio (3.x) ja tee uusi Javascript-tiedosto osoite.js. Lisää skriptit XHTML:n head-osaan script-elementillä.
    <script type="text/javascript" src="jquery-3.1.1.min.js"></script>
    <script type="text/javascript" src="osoite.js"></script>
  3. Avaa jQueryn API-dokumentaatio ja jQueryn learning center avuksesi. Voit näistä aina tarkistaa miten asiat jQuerylla pitää / voi tehdä.
  4. Kirjoita Javascriptillä osoite.js-tiedostoon kaikille input type="text"-lomake-elementeille tarkistus, että niissä on jotain tekstiä. Jos tekstiä on niin piilotetaan kentän vieressä oleva *-merkki.
    "use strict";
    
    window.onload = function() {
     /* tänne tarkistukseen liittyvä koodi */
    }
    

    Esimerkki yhdestä käsiteltävästä input-elementistä ja siihen liittyvästä span-elementistä:

    <div>
      <label for="etunimi">Etunimi</label>
      <input type="text" id="etunimi" name="etunimi" size="40" maxlength="100"/>
      <span id="etunimi_req">(*)</span>
    </div>
    
    • Etsi kaikki input-elementit joilla type="text": $( "input[type='text']" ) kts. Selecting Elements
    • Käytä jQueryn on-metodia. Kts. jQuery Event Basics. Lisää kaikille valitsemillesi input-elementeille change-tapahtumankäsittelijä.

      Tässä on kyseessä anonyymi funktio ja closure eli funktion sisällä oleva funktio. Sisempi funktio "muistaa" ympäristön jossa se on määritelty eli voi käyttää ylemmän tason funktiossa esiteltyjä muuttujia. Nyt tätä ominaisuutta ei tarvita mutta jatkossa ehkä.

      $( ... ).on("change", function() {
        /* tänne tarkistus */
      }
      );
      // sama kuin edellä mutta funktio on kirjoitettu erikseen
      $( ... ).on("change", tarkista);
      
      function tarkista() {
        /* tänne tarkistus */
      }
      

      Ole erityisen tarkkana sulkujen, kaarisulkujen ja ;-merkkien kanssa

      Käsittelijän pitää toimia seuraavalla tavalla:
      • Jos kyseisessä input-elementissä on jotain tekstiä ($(this).val()), niin piilota sitä vastaava (*)-merkki
      • Piilottaminen onnistuu kun asetat span-elementin class-ominaisuuteen arvoksi hidden.
      • Tapahtuman aiheuttaneen elementin id:n saa tapahtumankäsittelijässä $(this).attr("id")-metodilla.
      • Lisää saamaasi id:hen "_req" ja vastaavan span-elementin saat jQueryn $('#elementin_id')-funktiolla.
  5. Jos jossain kentässä on vikaa tai kenttä on tyhjä eli jokin *-merkki on näkyvissä, niin poista Lähetä-nappi käytöstä (disabled-attribuutti).
    • Helpointa tässä tapauksessa on tutkia kaikkien span-elementtien class-attribuutteja.
    • Tarkistuksesta kannattaa tehdä erillinen funktio, jota kutsutaan input-elementtien change-tapahtumankäsittelijästä.
    • Tarkistusfunktiota kannattaa kutsua myös kertaalleen window.onload-funktiosta eli heti sivun latauduttua.
    • Huomaa, että jos käytät jQueryn metodeja niin voit jossain tilanteessa joutua pakottaman tavallisen DOM-objektin jQuery-objektiksi $( objekti )-merkinnällä. Pääset helpoimmalla jos käytät koko ajan jqueryn omia metodeja.

XMLHttpRequest ja responseText

Toteutaan postitoimipaikan valinta postinumeron perusteella.

  1. Lisää postinumeron change-tapahtumaan toinenkin tapahtumankäsittelijä. Tämän funktion tarkoitus on tarkistaa onko postinumeroa tietokannassa ja lisätä vastaava postitoimipaikka tekstikenttään.
  2. Luo ohjaus5-hakemistoon SQLitellä uusi tietokanta:
    • Hae oppilaitos.sql ohjaus5-hakemistoon:
      wget http://appro.mit.jyu.fi/web-sovellukset/ohjaus/ohjaus5/oppilaitos.sql
    • Avaa sql-komentotulkki:
      sqlite3 omatietokantatiedosto
    • Suorita tiedostossa olevat sql-lauseet
      .read oppilaitos.sql
    • Poistu komentotulkista
      .quit
    • Lisää luku- ja kirjoitusoikeudet tietokantatiedostoon ja hakemistoon kaikille käyttäjille.
  3. Tee cgi-bin-hakemistoon flask-ohjelma ohjaus5.py, jonka on tarkoitus hakea tietokannasta postinumeroa vastaava toimipaikan nimi. Käytä pohjana seuraavaa mallikoodia:
    #!/usr/bin/python
    # -*- coding: utf-8 -*-
    
    from flask import Flask, session, redirect, url_for, escape, request, Response, render_template, make_response
    import sqlite3
    import logging
    import os
    # korjaa tähän polku oikeaan paikkaan. lokia ei saa tehdä cgi-bin-kansion alaisuuteen
    # varmista, että piilo-kansioon on kaikilla kirjoitusoikeus
    # älä luo kansioita windowsin kautta vaan vain ja ainoastaan unix-komentoriviltä
    logging.basicConfig(filename=os.path.abspath('../piilo/flask.log'),level=logging.DEBUG)
    app = Flask(__name__) 
    app.debug = True
    
    @app.route('/hae_postitoimipaikka', methods=['GET']) 
    def hae_postitoimipaikka():
        con = sqlite3.connect( os.path.abspath('../ohjaus5/kanta')) # korjaa tähän oikea polku tietokantaasi
        con.row_factory = sqlite3.Row
        
        cur = con.cursor()
    
        try:
            cur.execute("""
            SELECT *
            FROM Postiosoite
            """)
        except:
            logging.debug( "Kysely ei onnistu" )
            logging.debug( sys.exc_info()[0] )
        
        osoitteet = ""
        for o in cur:
            osoitteet = osoitteet + o["postinro"] + "\t" + o["postitoimipaikka"] + "\n"
        # luodaan oma response, joka palauttaa pelkkää tekstiä        
        resp = make_response(osoitteet, 200)
        resp.charset = "UTF-8"
        resp.mimetype = "text/plain"
        return resp
    
    if __name__ == '__main__':
        app.debug = True
        app.run(debug=True)
    

    Varmista, että python-koodisi rivinvaihdot ovat unix-tyyppisiä! Varmista tämä myös flask.cgi:stä. Huomaa, että flask.cgi:ssä pitää lukea näin:

    from ohjaus5 import app as application
  4. Kokeile selaimella mitä sovelluksesi palauttaa. Tuloksen pitäisi näyttää tältä:
    40740	Jyväskylä
    40100	Jyväskylä
    31600	Jokioinen
    00100	Helsinki
    33101	Tampere
    
  5. Muokkaa koodia siten, että ohjelma palauttaa vain pyydetyn postinron postitoimipaikan. Ohjelmaa pitäisi kyetä käyttämään seuraavaa muotoa olevalla osoitteella:
    http://users.jyu.fi/~omatunnus/cgi-bin/ohjaus5/flask.cgi/hae_postitoimipaikka?postinro=40740
    
    • Valmistele kysely:
      SELECT postitoimipaikka FROM Postiosoite WHERE postinro = :postinro
    • Tee muuttuja postinro ja aseta sen arvoksi request.args.get("postinro", "-"). Kytke postinro preparoituun kyselyyn.
    • Hae tuloksista ensimmäinen rivi (pitäisi tulla vain 0-1 riviä) (cur.fetchone()).
    • Tulosta vastaussivulle pelkästään postitoimipaikka
    • Kokeile selaimella toimiiko ohjelmasi:
      http://users.jyu.fi/~tunnus/.../.../hae_postitoimipaikka?postinro=40740
      Kokeile eri postinumeroilla.
  6. Nyt olet tehnyt sovelluksen jota voit Ajaxin avulla kutsua Javascript-ohjelmasta. Muutetaan seuraavaksi alussa tehtyä lomaketta siten, että postitoimipaikkakenttään haetaan automaattisesti postitoimipaikan nimi postinumerokenttään syötetyn postinumeron perusteella.
  7. Lisää seuraavaksi postinumeron muutostapahtumaan ($('#postinro').on("change", hae_postitoimipaikka);) uusi jQuery.ajax()-kutsu. Kts. jQuery’s Ajax-Related Methods.
    function hae_postitoimipaikka() {
    $.ajax({
            // tämä on oltava aina true. synkronista versiota ei pidä käyttää koska se voi jumittaa koko ohjelman
            async: true,
            // osoite jota kutsutaan eli aiemmin tehdyn flask-sovelluksen osoite
            url: "/~omatunnus/cgi-bin/ohjaus5/flask.cgi/hae_postitoimipaikka",
            // POST tai GET. Nyt vain pyydetään tietoja eikä tehdä muutoksia joten GET
            type: "GET",
            // tietotyyppi jossa muodossa odotetaan vastausta vrt. flask-sovelluksessa määritetty text/plain
            dataType: "text",
            // parametrina viety data avain:arvo 
            data: { 
                "postinro": $('#postinro').val()
            },
            success: lisaa_postitoimipaikka, // funktio jota kutsutaan jos kaikki onnistuu
            error: ajax_virhe  // funktio jota kutsutaan jos tulee virhe
    });
    }
    
    function lisaa_postitoimipaikka(data, textStatus, request) {
        // sijoitetaan tuloksena saatu teksti postitoimipaikan arvoksi
     // val-metodi päivittää lomakekentän arvon 
     $('#postitoimipaikka').val( data );
    // jos haluat lisätä sisältöä tavallisen elementin sisään esim. divin sisään niin käytä:
    // $('#elementin-id').replaceWith( data );
    }
    
    function ajax_virhe(xhr, status, error) {
            console.log( "Error: " + error );
            console.log( "Status: " + status );
            console.log( xhr );
    }
    
  8. Kokeile toimiiko sovellus ja muuttuuko postitoimipaikan sisältö. Katso Firebugin Console-ikkunasta, että XMLHttp-kutsu todella tapahtuu ja vasteena tulee järkevä postitoimipaikka.
  9. Piilota myös postitoimipaikka_req-id:llä merkitty span-elementti, jos järkevä postitoimipaikka löytyy.
  10. Jos vaste on tyhjä merkkijono, niin laitetaan postitoimipaikaksi tyhjä merkkijono ja laitetaan postitoimipaikan (*)-merkintä näkyväksi.
  11. Hyväksy myös Lähetä-nappulan käyttö, jos kaikki (*) on piilossa. Voit ajaa aiemmin tekemäsi valmiin funktion, joka tekee tämän.
  12. ajax_virhe-funktio suoritetaan, jos palvelimelta tuli vasteena statuksena jokin virheilmoitus. Tulosta toistaiseksi Firebugin console.log-funktiolla virheen sisältö

XMLHttpRequest ja responseXML

Toteutetaan vielä oppilaitoksen valinta.

  1. Lisää ohjaus5.py-tiedostoon hae_ryhmat-sivu, joka hakee oppilaitosryhmät tietokannasta ja tulostaa ne select-option-listana.
    • Avaa yhteys tietokantaan.
    • Valmistele SQL-kysely:
      SELECT RyhmaID, RyhmaNimi FROM Oppilaitosryhma
    • Tarvittavan response-objektin saat tehtyä seuraavaan tapaan:
          resp = make_response( render_template("ryhmat.xml",ryhmat=ryhmat))
    • Käy templatessa läpi kyselyn tulos ja muodosta Jinja-templaten avulla kyselyn tuloksesta pieni XML-dokumentti, joka näyttää seuraavalta:
      <?xml version="1.0" encoding="UTF-8"?>
      <select id="oppilaitosryhma" name="oppilaitosryhma" xmlns="http://www.w3.org/1999/xhtml">
      <option selected="selected" value="1">Yliopisto</option>
      <option value="2">Ammattikorkeakoulu</option>
      <option value="3">Lukio</option>
      <option value="4">Ammatillinen oppilaitos</option>
      </select>
    • Tulosta xml-deklaraatio ja select-elementti. Lisää select-elementtiin seuraavat attribuutit:
      • xmlns="http://www.w3.org/1999/xhtml" Nimiavaruusmääritys tarvitaan, että elementti voidaan siirtää HTML-dokumenttiin.
      • id="oppilaitosryhma" Elementin valintaa varten.
      • name="oppilaitosryhma" Lomakkeenkäsittelijää varten.
    • Käy läpi kaikki ryhmävaihtoehdot ja tulosta kustakin option-elementti.
      • Tee ensimmäisestä option-elementistä valittu (selected="selected").
      • Sijoita RyhmaID option-elementin valueksi.
      • Sijoita RyhmaNimi option-elementin tekstiksi.
    • Lisää otsikkotietoihin MIME-tyypin määritys text/xml ja merkistö UTF-8.
    • Kokeile selaimella toimiiko sivusi
  2. Tee Javascript-tiedostoon uusi funktio hae_ryhmat.
    • Tee Ajax-kutsu, joka pyytää hae_ryhmat.py-sivua.
    • Aseta ajax-kutsun datatyypiksi nyt "xml".
    • Aseta error-attribuuttiin funktio ajax_virhe.
    • Luo callback-funktio lisaa_ryhmat onnistuneelle (success) HTTP-pyynnölle. Aseta lisaa_ryhmat Ajax-kutsun parametrien success-attribuuttiin.
    • Toteuta lisaa_ryhmat-funktioon select-elementin vaihto. Hae saadusta XML-vasteesta juurielementti ja sen lapsielementit (.responseXML.documentElement) importNode-funktiolla johonkin muuttujaan.
      function lisaa_ryhmat(data, textStatus, request) {
         var select = document.importNode(request.responseXML.documentElement, 1);
         $('#oppilaitosryhma').replaceWith(select);
      }
      Saatu select-elementti voidaan vaihtaa toiseen jqueryn replaceWith-funktiolla.
    • Kutsu hae_ryhmat-funktiota sivun window.onload-funktiossa niin oppilaitosryhmälistaus päivittyy heti sivun latauduttua.

Oppilaitoksen vaihtaminen valinnan perusteella

  1. Lisää ohjaus5.py-tiedostoon vielä hae_oppilaitokset-sivu, joka ottaa GET-parametriksi oppilaitosryhman id:n ja palauttaa sen perusteella ryhmään kuuluvat oppilaitokset. Pohjana voit käyttää hae_ryhmat.py:ta eli tee siitä ensin kopio.
    • Avaa yhteys tietokantaasi.
    • Valmistele SQL-kysely:
      SELECT OppilaitosID, Nimi 
      FROM Oppilaitos 
      WHERE Oppilaitosryhma_RyhmaID = :ryhmaid
    • Luo muuttuja ryhmaid, johon asetat request.args.get("oppilaitosryhma", "")in kautta saadun ryhmätiedon.
    • Kytke :ryhmaid ja ryhmaid
    • Lisää otsikkotietoihin MIME-tyypin määritys text/xml
    • Tee samaan tapaan kuin aiemmin tekemäsi XML-dokumentti, jonka juurena on select-elementti ja sisältönä lista oppilaitoksista option-elementteinä. Select-elementin id- ja name-attribuuttien on nyt oltava oppilaitos. Tee taas ensimmäisestä option-elementistä valittu.
  2. Luo Javascript-ohjelmaasi uusi funktio hae_oppilaitokset. Lisää tämä tapahtumankäsittelijäksi oppilaitosryhma-valintalistalle change-tapahtumaan lisaa_ryhmat-funktion lopussa.
    • Mieti miksi tapahtumankäsittelijää ei voi kytkeä suoraan window.onload-funktiossa kuten muita tarkistusfunktioita?
  3. Tee hae_oppilaitokset-tapahtumankäsittelijässä uusi Ajax-kutsu
    • Laita URL:ksi hae_oppilaitokset.
    • Laita Type-attribuuttiin GET.
    • Laita data-attribuuttiin oppilaitosryhma-id:llä olevan select-elementin valinnan arvo
    • Lisää success-attribuuttiin tapahtumankäsittelijäfunktio lisaa_oppilaitokset. error-funktiona voit käyttää aiemmin luotua ajax_virhe-funktiota.
  4. Lisää callback-funktiot tälle XMLHttpRequest-kutsulle. Virhetapauksen käsittely voi olla sama kuin edellä eli funktio ajax_virhe. Onnistuneessa tapauksessa (funktio lisaa_oppilaitokset) täytä oppilaitoslista saadulla XML-vasteella kuten edellä.
    • Itseasiassa on mahdollista käyttää samaa funktiota kuin oppilaitosryhmien kanssa, jos ensin selvität saadusta vasteesta kumpi select-elementti pitää vaihtaa (.responseXML.documentElement.getAttribute("id")), mutta voit tehdä myös erilliset funktiot.
  5. Lisää vielä kertaalleen normaali hae_oppilaitokset-kutsu lisaa_ryhmat-funktion loppuun. Näin saat myös ensimmäiset oppilaitosvaihtoehdot näkyviin.

JSON

JSON on dataformaatti, joka pohjautuu suoraan javascriptiin. JSON on javascriptin kannalta kaikista kevyin dataformaatti ja sitä kannattaakin käyttää usein Ajaxin yhteydessä. Jää oman harkinnan varaan, että kannattaako tehdä enemmän valmista html:ää webbipalvelimella vai siirtääkö vain puhdasta dataa ja jättää html:n generoinnin javascriptin vastuulle. Yleensä jälkimmäinen ratkaisu toimii ihan hyvin mutta jos sovelluksen javascript-osuus käy hyvin raskaaksi sitä voi keventää siirtämällä valmiimpaa dataa.

Yritä toteuttaa yksi edellä tekemistäsi ajax-kutsuista siten, että käytätkin datatyyppinä jsonia.

JSON-data pitää jakaa mediatyypillä application/json

Katso mallia Javascript-tietorakenne-esimerkeistä ja Python-tietorakenne-esimerkeistä.

Malli (JSON), Lähdekoodi-json (javascript)

Käyttäjien kommentit

Kommentoi Lisää kommentti
Kurssimateriaalien käyttäminen kaupallisiin tarkoituksiin tai opetusmateriaalina ilman lupaa on ehdottomasti kielletty!
http://appro.mit.jyu.fi/web-sovellukset/ohjaus/ohjaus5/
© Tommi Lahtonen (tommi.j.lahtonen@jyu.fi) <http://hazor.iki.fi/>
2017-03-14 11:39:01
Informaatioteknologia - Jyväskylän yliopiston informaatioteknologian tiedekunta