Ajax

Hyödynnetään Ajaxia (XMLHttpRequest) lomakkeen täytön apuna. Tutustu ensin ajax-luentoon ja ajax-esimerkkeihin. Jos jQuery ja Javascript eivät ole sinulle ennestään tuttuja niin kertaa TIEA2120 Web-käyttöliittymien ohjelmointi -kurssin ohjaustehtäviä

Katso malliratkaisusta millainen sovellus on tarkoitus luoda. Lähdekoodi (flask / python), template, Lähdekoodi (javascript)

Yksinkertaisin ajax-malli, flask-osuuden lähdekoodi

Luo tietokanta

  1. Luo ohjaus5-kansioosi SQLitellä uusi tietokanta:
    • Hae oppilaitos.sql piilo-hakemistoon, jonka on oltava jossain muualla kuin cgi-bin-kansion alaisuudessa:
      wget http://users.jyu.fi/~tjlahton/cgi-bin/tiea2080/ohjaus5/oppilaitos.sql
    • Avaa sql-komentotulkki:
      sqlite3 oppilaitos
    • Suorita SQL-tiedostossa olevat SQL-lauseet
      .read oppilaitos.sql
    • Poistu komentotulkista
      .quit
    • Lisää luku- ja kirjoitusoikeudet tietokantatiedostoon ja hakemistoon kaikille käyttäjille. Huom! Älä vain anna muille kirjoitusoikeutta cgi-bin-kansioon tai sen alikansioihin.

Flask ja JSON

Luodaan flask-ohjelma, joka palauttaa listan postitoimipaikoista JSON-muodossa.

Flask ja XML

Toteuta samaan tapaan kuin edellä kaikkien oppilaitosryhmien hakeminen mutta palauta listaus sellaisessa XML-muodossa, että sitä voidaan suoraan käyttää HTML-dokumentissa. Käytä mediatyyppinä text/xml. Muodosta tarvittava XML Jinja-templaten avulla:

<?xml version="1.0" encoding="UTF-8"?>
<select id="{{name}}" name="{{name}}" xmlns="http://www.w3.org/1999/xhtml">
{% for o in ryhmat %}
{% if loop.first %}
    <option selected="selected" value="{{o["ryhmaid"]}}">{{ o["nimi"] }}</option>
{% else %}
    <option value="{{o["ryhmaid"]}}">{{ o["nimi"] }}</option>
{% endif %}
{% endfor %}
</select>

Kts. malli

Käyttäminen poikkeaa hieman aiemmista, koska myös nyt halutaan erikseen varmistaa mikä on merkistö ja mediatyyppi:

    resp = make_response( render_template("ryhmat.xml",ryhmat=ryhmat, name="oppilaitosryhma"))
    resp.charset = "UTF-8"
    resp.mimetype = "text/xml"
        
    return resp

Saman voi toteuttaa myös minidom-kirjaston avulla jolloin templatea ei tarvita. Vrt. Javascript ja DOM. Kokeile:

    from xml.dom.minidom import getDOMImplementation, parse, parseString

    impl = getDOMImplementation()
    # createDocument(nimiavaruus, juurielementti, dokumenttityyppi)
    doc = impl.createDocument("http://www.w3.org/1999/xhtml", "select", None)
    # pakko laittaa seuraava, koska jostakin syystä edellinen ei riitä
    doc.documentElement.setAttribute("xmlns", "http://www.w3.org/1999/xhtml")
    doc.documentElement.setAttribute("name", "ryhmat")
    doc.documentElement.setAttribute("id", "ryhmat")

    for ryhma in cur.fetchall():
        option = doc.createElement("option");
        txt = doc.createTextNode( ryhma['ryhmanimi'] );
        option.appendChild(txt)
        # minidom ei tue textContent-ominaisuutta
        # li.textContent = ryhma['ryhmanimi']
        option.setAttribute("value", str(ryhma["ryhmaid"]))
        doc.documentElement.appendChild(option)

    resp = make_response( doc.toxml('UTF-8') )
    resp.charset = "UTF-8"
    resp.mimetype = "text/xml"
    return resp

Voit myös tutustua pythonin xml.etree.ElementTree-kirjastoon ja muodostaa sen avulla tarvittavan xml-dokumentin:

    import xml.etree.ElementTree as ET
    xml = """<?xml version="1.0" encoding="UTF-8"?><select id="ryhmat" name="ryhmat"></select>"""

    root = ET.fromstring( xml )
    root.attrib["xmlns"] = "http://www.w3.org/1999/xhtml"
    for ryhma in cur.fetchall():
        option = ET.SubElement(root, "option")
        option.text = ryhma['ryhmanimi']
        option.attrib['value'] = str(ryhma["ryhmaid"])

    resp = make_response( ET.tostring( root, encoding="UTF-8", method="xml" ) )

Toteuta samaan tapaan oppilaitosten hakeminen (vrt. edellä tehty yhden postitoimipaikan hakeminen). Rajaa haettavia oppilaitoksia jollakin oppilaitosryhmällä. Jos ryhmää ei ole annettu niin silloin hae kaikki oppilaitokset. Palauta oppilaitoslistaus samanlaisessa XML-muodossa kuin oppilaitosryhmätkin.

Kts. malli

jQuery, $.ajax ja JSON

Ota käyttöön valmis pohja. Pura samaan kansioon flask-sovelluksesi kanssa.

Muokkaa valmista osoite.js-tiedostoa. Toteutetaan postitoimipaikan valinta postinumeron perusteella.

  1. Lisää postinumeron change-tapahtumaan tapahtumankäsittelijä ($('#postinumero').on("change", hae_postitoimipaikka);). Tämän käsittelijän tarkoitus on tarkistaa onko postinumeroa tietokannassa ja lisätä vastaava postitoimipaikka tekstikenttään.
  2. Luo hae_postitoimipaikka-funktio, jossa jqueryn avulla kutsut aiemmin flaskilla tekemääsi postitoimipaikka-sivua. Kts. jQuery.ajax() ja 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/tiea2080/ohjaus5/flask.cgi/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: "json",
            // parametrina viety data objektina eli avain:arvo. Haetaan postinro-kenttään syötetty postinunero 
            data: { 
                // postinro on sama kuin get-pyynnössä oleva parametri. Arvoksi haetaan lomakkeelta
                // postinumero-kentän arvo
                "postinro": $('#postinumero').val()
            },
            success: lisaa_postitoimipaikka, // funktio jota kutsutaan jos kaikki onnistuu
            error: ajax_virhe  // funktio jota kutsutaan jos tulee virhe
    });
    }
    
    // tämä suoritetaan jos ajax-kutsu onnistui
    function lisaa_postitoimipaikka(data, textStatus, request) {
    // data-muuttujassa saadaan javascript-objekti, josta löytyy tarvittava postitoimipaikka
    console.log(data);
    try {
        $('#postitoimipaikka').val( data[0]["postitoimipaikka"] );
    }
    // jos jotain menee pieleen niin asetetaan postitoimipaikaksi tuntematon
    catch (e) {
        $('#postitoimipaikka').val( "tuntematon" );
    }
    }
    
    // tänne saavutaan jos ajax-kutsussa tapahtui jokin virhe
    function ajax_virhe(xhr, status, error) {
            console.log( "Error: " + error );
            console.log( "Status: " + status );
            console.log( xhr );
    }
  3. Kokeile toimiiko sovellus ja muuttuuko postitoimipaikan sisältö. Katso Web Developer -työkalujen Network-välilehdeltä (CTRL+SHIFT+Q) mitä tapahtuu, kun vaihdat postinumerokentän sisältöä. Tarkista myös Javascript Consolen (CTRL+SHIFT+K) tulosteet.

jQuery, $.ajax ja XML

Toteutetaan vielä oppilaitoksen valinta.

  1. Tee Javascript-tiedostoon uusi funktio hae_ryhmat. Tee funktiossa Ajax-kutsu, joka pyytää hae_ryhmat-sivua.
    function hae_ryhmat() {
    $.ajax({ 
    	async: true,
        url: "/~omatunnus/cgi-bin/tiea2080/ohjaus5/flask.cgi/hae_ryhmat",
        // datatyyppi on nyt xml eikä json
    	dataType: "xml",
    	type: "GET",
        // lisaa_ryhmat-funktio on myös luotava
    	success: lisaa_ryhmat,
    	error: ajax_virhe
    });
    }
  2. Luo lisaa_ryhmat-funktio:
    function lisaa_ryhmat(data, textStatus, request) {
          // importoidaan palvelimelta saatu xml-dokumentti tähän dokumenttiin
          let select = document.importNode(request.responseXML.documentElement, 1);
          // korvataan vanha select-elementti uudella
          $('#oppilaitosryhma').replaceWith(select);
          
    }
  3. Kutsu hae_ryhmat-funktiota sivun window.onload-funktiossa niin oppilaitosryhmälistaus päivittyy heti sivun latauduttua.

Oppilaitoksen vaihtaminen valinnan perusteella

  1. Luo Javascript-ohjelmaasi uusi funktio hae_oppilaitokset. Lisää tämä tapahtumankäsittelijäksi oppilaitosryhma-valintalistan change-tapahtumaan lisaa_ryhmat-funktion lopussa.

    Huomaa, että sinun pitää myös flask-sovelluksen palauttamassa select-elementissä käyttää samaa id-attribuutin arvoa (oppilaitosryhma) kuin mitä valmiissa pohjadokumentissa käytetään.

    • Mieti miksi tapahtumankäsittelijää ei voi kytkeä suoraan window.onload-funktiossa kuten muita tarkistusfunktioita?
  2. Tee hae_oppilaitokset-tapahtumankäsittelijässä uusi Ajax-kutsu
    • Laita URL:ksi oppilaitokset.
    • Laita Type-attribuuttiin GET.
    • Laita data-attribuuttiin valittuna olevan oppilaitosryhmän arvo
    • Lisää success-attribuuttiin tapahtumankäsittelijäfunktio lisaa_oppilaitokset. error-funktiona voit käyttää aiemmin luotua ajax_virhe-funktiota.
  3. 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ä.
  4. Lisää vielä kertaalleen normaali hae_oppilaitokset-kutsu lisaa_ryhmat-funktion loppuun. Näin saat myös ensimmäiset oppilaitosvaihtoehdot näkyviin.

Tilaajan lisääminen

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/tiea2080/ohjaus/ohjaus5/
© Tommi Lahtonen (tommi.j.lahtonen@jyu.fi) <http://hazor.iki.fi/>
2018-03-07 13:48:23
Informaatioteknologia - Jyväskylän yliopiston informaatioteknologian tiedekunta