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ä

Yksinkertaisin ajax-malli, flask-osuuden lähdekoodi

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

Fetch API -versio, Lähdekoodi (javascript). Flask-koodi identtinen edellisen kanssa

Luo tietokanta

tietokannan rakenne

  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

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

Vaihtoehtoisia toteutustapoja

XML-dokumentin voit toteuttaa myös muilla tavoilla kuin Jinja-templatella.

DOM-rajapinta

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
ElementTree

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" ) )

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+E) 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

Fetch API

jQueryn käyttäminen ei ole välttämätöntä vaan kaiken saman voi toteuttaa nyt myös Fetch API-rajapinnan avulla käyttäen Promiseja. Tämä voi olla hieman yksinkertaisempi toteutustapa kuin jQueryn $.ajax-kutsut.

Kokeile Fetch API-malliversiota. Katso Fetch API-version lähdekoodi. Voit kokeilla itse toteuttaa saman.

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/>
2019-03-05 12:35:58
Informaatioteknologia - Jyväskylän yliopiston informaatioteknologian tiedekunta