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.
- 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:

- index.html:
- 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>
- Avaa jQueryn API-dokumentaatio ja jQueryn learning center avuksesi. Voit näistä aina tarkistaa miten asiat jQuerylla pitää / voi tehdä.
- 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.
- 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.
- Helpointa tässä tapauksessa on
tutkia kaikkien span-elementtien
XMLHttpRequest ja responseText
Toteutaan postitoimipaikan valinta postinumeron perusteella.
- Lisää postinumeron change-tapahtumaan toinenkin tapahtumankäsittelijä. Tämän funktion tarkoitus on tarkistaa onko postinumeroa tietokannassa ja lisätä vastaava postitoimipaikka tekstikenttään.
- 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.
- Hae oppilaitos.sql ohjaus5-hakemistoon:
- 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
- Kokeile selaimella mitä sovelluksesi palauttaa. Tuloksen pitäisi näyttää tältä:
40740 Jyväskylä 40100 Jyväskylä 31600 Jokioinen 00100 Helsinki 33101 Tampere
- 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.
- Valmistele kysely:
- 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.
- 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 ); } - Kokeile toimiiko sovellus ja muuttuuko postitoimipaikan sisältö. Katso Firebugin Console-ikkunasta, että XMLHttp-kutsu todella tapahtuu ja vasteena tulee järkevä postitoimipaikka.
- Piilota myös postitoimipaikka_req-id:llä merkitty span-elementti, jos järkevä postitoimipaikka löytyy.
- Jos vaste on tyhjä merkkijono, niin laitetaan postitoimipaikaksi tyhjä merkkijono ja laitetaan postitoimipaikan (*)-merkintä näkyväksi.
- 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.
- 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.
- 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.
- Tee ensimmäisestä option-elementistä valittu (
- Lisää otsikkotietoihin MIME-tyypin määritys text/xml ja merkistö UTF-8.
- Kokeile selaimella toimiiko sivusi
- 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_ryhmatonnistuneelle (success) HTTP-pyynnölle. Aseta lisaa_ryhmat Ajax-kutsun parametrien success-attribuuttiin. - Toteuta
lisaa_ryhmat-funktioonselect-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
- 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.
- Luo Javascript-ohjelmaasi uusi funktio
hae_oppilaitokset. Lisää tämä tapahtumankäsittelijäksi oppilaitosryhma-valintalistallechange-tapahtumaanlisaa_ryhmat-funktion lopussa.- 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
hae_oppilaitokset. - Laita
Type-attribuuttiin GET. - Laita
data-attribuuttiin oppilaitosryhma-id:llä olevan select-elementin valinnan 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ä.- 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.
- Itseasiassa on mahdollista käyttää samaa funktiota kuin oppilaitosryhmien kanssa,
jos ensin selvität saadusta vasteesta kumpi select-elementti pitää vaihtaa
(
- Lisää vielä kertaalleen normaali
hae_oppilaitokset-kutsulisaa_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ä.

Käyttäjien kommentit