Ajax
- Luo tietokanta
- Flask ja JSON
- Flask ja XML
- jQuery, $.ajax ja JSON
- jQuery, $.ajax ja XML
- Tilaajan lisääminen
- Fetch API
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
- 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.
- Hae oppilaitos.sql piilo-hakemistoon, jonka on oltava
jossain muualla kuin cgi-bin-kansion alaisuudessa:
Flask ja JSON
Luodaan flask-ohjelma, joka palauttaa listan postitoimipaikoista JSON-muodossa.
- Luo uusi Flask-sovellus samaan tapaan kuin aiemmilla viikoilla. Kopioi edellisiltä viikoilta valmis pohja.
- Avaa tietokantayhteys edellä luomaasi tietokantatiedostoon
def tietokanta(): #tietokantayhteyden avaaminen ja olennaiset asetukset try: con = sqlite3.connect(os.path.abspath('../../piilo/oppilaitos')) con.row_factory = sqlite3.Row con.execute("PRAGMA foreign_keys = ON") except: #virheenkäsittely... logging.debug("tietokantayhteys ei aukea") for err in sys.exc_info(): logging.debug(err) return None return con
- Kirjoita seuraavanlainen funktio, jotka palauttaa listan kaikista postinumeroista ja postitoimipaikoista JSON-muodossa:
from flask import Flask, session, redirect, url_for, escape, request, Response, render_template, make_response import json @app.route('/postitoimipaikat', methods=['GET']) def postitoimipaikat(): con = tietokanta() cur = con.cursor() cur.execute(""" SELECT postinro, postitoimipaikka FROM Postitoimipaikka """) data = cur.fetchall() # muunnetaan kyselyn tulos json-muotoon. Tarvitaan oma konversiofunktio row_to_json json_data = json.dumps(data, default=row_to_json) # tehdään flaskin edellyttämä response json-datasta. HTTP-koodina käytetään 200 eli OK resp = make_response(json_data, 200) # määritetään responsessa käytetty merkistö ja mediatyyppi resp.charset = "UTF-8" resp.mimetype = "application/json" return resp # muuntaa sqlite3 row -tyyppisen objektin tavalliseksi dictiksi def row_to_json(row): d = dict() for key in row.keys(): d[key] = row[key] return d
Kts. malli
- Kokeile toimiiko funktiosi. Jos toimii niin tee uusi funktio (ja osoite) postitoimipaikka,
joka palauttaa vain tietyn postinron ja postitoimipaikan sen mukaan mitä annetaan sivun parametreissa.
Esim. osoite http://users.jyu.fi/~tjlahton/cgi-bin/tiea2080/ohjaus5/flask.cgi/postitoimipaikka?postinro=40740 palauttaisi vain:
[{"postinro": "40740", "postitoimipaikka": "Jyv\u00e4skyl\u00e4"}]
- Varmista, että sovellus ei kaadu vaikka postinroa ei annettaisikaan parametrina tai annetaan postinumero, jota ei löydy tietokannasta.
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.
- 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.
- 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 ); }
- 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.
- 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 }); }
- 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); }
- Kutsu hae_ryhmat-funktiota sivun
window.onload
-funktiossa niin oppilaitosryhmälistaus päivittyy heti sivun latauduttua.
Oppilaitoksen vaihtaminen valinnan perusteella
- Luo Javascript-ohjelmaasi uusi funktio
hae_oppilaitokset
. Lisää tämä tapahtumankäsittelijäksi oppilaitosryhma-valintalistanchange
-tapahtumaanlisaa_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?
- Mieti miksi tapahtumankäsittelijää ei voi kytkeä suoraan
- 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äfunktiolisaa_oppilaitokset
.error
-funktiona voit käyttää aiemmin luotuaajax_virhe
-funktiota.
- Laita URL:ksi
- Lisää callback-funktiot tälle XMLHttpRequest-kutsulle. Virhetapauksen käsittely voi olla sama kuin edellä eli
funktio
ajax_virhe
. Onnistuneessa tapauksessa (funktiolisaa_oppilaitokset
) täytä oppilaitoslista saadulla XML-vasteella kuten edellä. - Lisää vielä kertaalleen normaali
hae_oppilaitokset
-kutsulisaa_ryhmat
-funktion loppuun. Näin saat myös ensimmäiset oppilaitosvaihtoehdot näkyviin.
Tilaajan lisääminen
- Lisää lomakkeen alapuolelle listaus kaikista tietokantaan tallennetuista tilaajista. Lataa lista
ajax-kutsulla sivun latautuessa. Tässä on helpointa käyttää xml-muotoa, jolloin voi käyttää suoraan
jqueryn load-metodia tähän tapaan:
$('#tilaajat').load("/~omatunnus/cgi-bin/tiea2080/ohjaus5/flask.cgi/tilaajat");
Ladattu sivu korvaa suoraan tilaajat-tunnisteella varustetun elementin. - Tee lomakkeesta tavalliseen tapaan toimiva eli painiketta klikattaessa lisätään tietokantaan
uusi tilaaja. Toteuta lisäys siten, että lisäyksen tekevä flask-funktio palauttaa tiedon lisäämisen
onnistumisesta tai mahdollisista virheistä json-muodossa. Käytä lisäämisessä POST-metodia.
- Tarkista, että kaikkiin kenttiin on syötetty arvo
- Saatuasi lomakkeen ja lisäämisen toimimaan niin muuta lisäys tapahtumaan ajax-kutsulla. Päivitä
onnistuneen lisäämisen jälkeen sivulla oleva tilaajalistaus. Jos lisäys epäonnistuu niin näytä sivulla
virheilmoitukset. Jos mahdollista niin aseta virheilmoitukset niitä vastaavien kenttien viereen.
Koko lomakkeen sisällön saa helposti ajax-kutsuun:
$.ajax({ async: true, url: "/~omatunnus/cgi-bin/tiea2080/ohjaus5/flask.cgi/lisaa_tilaaja", type: "POST", dataType: "json", // parametrina viedään koko lomakkeen sisältö data: $("#lomake").serialize(), success: lisaa_success, error: ajax_virhe });
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