Tässä artikkelissa kerrotaan, miten verkkosivustoon toteutetaan lomaketta käyttävä toiminto CGI-tekniikalla siten, että se toimii kaikenlaisissa käyttötilanteissa. Erityisesti kiinnitetään huomiota siihen, että käyttäjän saama palaute on selkeää ja myös ääneen luettuna helposti ymmärrettävä.
Tarkemmin sanoen tekniikkana käytetään Perl-kielellä kirjoitettuja ohjelmia, jotka on asennettu toimimaan CGI-skripteinä. Jos palvelimessa käytettävä tekniikka on jokin muu, esimerkiksi ASP tai PHP, tässä kuvattavat menettelyt eivät sovellu sellaisinaan, mutta esitetyistä yleisistä periaatteista on silti hyötyä.
Lukijan oletetaan tuntevan HTML-lomakkeiden, Perl-kielen ja CGI:n käytön perusteet taikka opiskelevan niitä rinnan tämän aineiston kanssa. Näistä aiheista löytyy tietoja esimerkiksi seuraavien sivujen kautta: WWW-lomakkeet, Suomenkielistä aineistoa Perl-kielestä Webissä ja Web-julkaisemisen oppaan kohta Palvelinskriptit (server-side scripting).
Yksi tavoite on, että lomakkeen voi täyttää sananmukaisesti silmät kiinni. Tämä on vain osa yleistä tavoitetta: lomake tulisi voida täyttää erilaisissa käyttötilanteissa ja -muodoissa. Tähän sisältyy myös esimerkiksi se, että lomakkeen voi täyttää myös kokonaan ilman hiirtä tai vaikkapa puhumalla, sikäli kuin käyttäjän laite ja ohjelmisto tätä tukevat.
Alla on esimerkki lomakkeen osasta, joka ei kovin hyvin täytä tätä vaatimusta:
Tämän voisi selain esittää ääneen esimerkiksi näin: "Lomake alkaa. Sukunimi etunimi synt asiakasnumero. Kirjoita teksti." Käyttäjän kirjoitettua ensimmäisen syöttötiedon kuuluisi seuraavaksi uusi kehote: "Kirjoita teksti". Kirjoittaminen käy hankalaksi, jos kenttiä on paljon, koska käyttäjän pitää muistaa tietojen järjestys.
Harjoitustehtävä: Millainen olisi vaihtoehto, joka toimisi paremmin ääniesityksessä? Mitä etuja tai haittoja siitä olisi näkyvässä esityksessä?
Harjoitustehtävä: Mene Googlen sivulle, jolla monipuolinen hakutoiminto
("advanced search", suomenkielisessä versiossa "lisähaku"); osoite on
<http://www.google.com/advanced_search>
.
Siirrä hiiri syrjään niin, ettet pääse siihen käsiksi. Tee haku, joka etsii
ruotsinkielisiä sivuja .fi
-domainista hakusanana kiva. Millaisia vaikeuksia
hiirettömyys aiheutti?
Aihetta käsitellään jäljempänä kohdassa Esteettömän lomakkeen tekeminen. Lomakkeen täyttämisen ja lähettämisen sujuvuuden lisäksi on tärkeää, millaisen vastauksen käyttäjä saa, kun hän on lähettänyt lomakkeen. Tätä aihetta on yleensä käsitelty melko vähän, yllättävän vähän.
Jos kyse on esimerkiksi hakutoiminnosta, jossa käyttäjä on antanut hakusanan ja napsauttanut Hae-painiketta, hänen saamansa vastauksen tulisi alkaa olennaisilla tiedoilla. Hyvin olennaista on, montako osumaa kaikkiaan löytyi, koska varsin usein osumien määrästä voidaan heti päätellä, onko hakua syytä tarkentaa tai muuten muuttaa, suunnilleen seuraavaan tapaan:
Haku sanalla "kissa" tuotti 47 900 osumaa: - -
Tällaisen tiedon olisi hyvä olla heti alussa ja mielellään myös korostettuna - ei siis siihen tapaan, kuin eräs monella muulla tavalla hyvä hakutoiminto aloittaa vastauksensa:
Lisähaku Asetukset Kielivalinnat Hakuvihjeitä
Webbi
Kuvat
Keskusteluryhmät
Hakemisto
Jos edellä oleva luettaisiin puhesyntetisaattorin avulla, esitys alkaisi tähän tapaan: "siirry googlen kotisivulle lisähaku asetukset kielivalinnat hakuvihjeitä kissa syöttökenttä google-haku valintanappi etsi vee vee veestä valintanappi etsi kielellä suomi vebbi kuvat keskusteluryhmät hakemisto". Vasta tämän jälkeen käyttäjä kuulisi, mikä oli haun tulos. Lisäksi sivulla näkyvästä esityksestä monen on hankala löytää olennaisin tieto, ellei se ole alussa.
Käyttäjälle tulee antaa riittävästi, mutta ei liikaa palautetta siitä, mitä tapahtui. Palautteen tulee olla helposti ymmärrettävässä muodossa, ei koodattuna eikä ammattikielellä.
Edellä sanottu koskee ensiksikin virhetilanteita. Jos käyttäjä on kirjoittanut vuosiluvun tilalle "tänä vuonna", hänen tulisi saada sentapainen virheilmoitus kuin "ilmoita vuosi nelinumeroisella vuosiluvulla", eikä siis esimerkiksi "digit expected", kuten helposti käy, jos lomakkeen käsittelyn koodaaja käyttää valmiita rutiineita ilman, että hän ottaa huomioon niiden vaikutuksen käyttöliittymään.
Myös normaalitilanteissa käyttäjän tulisi saada tietoa siitä, että kaikki menee hyvin, ja myös riittävästi tietoa esimerkiksi tallennettavaksi myöhempää käyttöä varten. Jos kyse on palautelomakkeesta, lomakkeen käsittelyn tulisi antaa vastaukseksi vaikkapa sivu, joka kertoo, milloin palaute on lähtenyt ja joka sisältää kopion käyttäjän kirjoittamasta tekstistä. On sitten käyttäjän ratkaistavissa, haluaako hän tulostaa sivun itselleen, tallentaa sen levylle, lähettää tiedoksi jonnekin tai muuta sellaista.
Jos kyse on esimerkiksi ilmoittautumis- tai tilauslomakkeesta, saatavan palautteen tulisi selvästi kertoa tapahtumista luomatta vääriä mielikuvia. Usein lomakkeen käsittelevä koodi vain tallentaa tiedot, jotka ihminen sitten myöhemmin käsittelee. Ilmoittautumista ei ole ehkä hyväksytty tai tilatun tavaran saatavuutta ei kenties ole mitenkään varmistettu. Saattaa olla tarpeen sanoa tämä erikseen, selvyyden vuoksi sekä myönteisesti että kielteisesti esimerkiksi seuraavaan tapaan: "Tämä on automaattinen vastaus tilaukseenne. Tilauksenne on tallentunut järjestelmäämme; se tullaan käsittelemään ja teille tullaan ilmoittamaan, voidaanko tuote tai tuotteet toimittaa ja milloin. Tämä ei ole tilausvahvistus, vaan vain ilmoitus siitä, että tilauksenne on nyt odottamassa käsittelyä."
On mahdollista kirjoittaa lomake (form
-elementti) suoraan HTML-dokumenttiin jollakin
editorilla tai sivunteko-ohjelmalla. Yleensä on kuitenkin parempi tuottaa lomake (ja koko sivu, jolla se
on) samalla ohjelmalla, joka käsittelee lomakedatan. Tästä on muun muassa se etu, että
tällöin voidaan toteuttaa helposti se, että käyttäjän saama vastaus
sisältää uuden kopion lomakkeesta esimerkiksi uutta, tarkennettua hakua varten. Näin toimivat
muun muassa useimmat yleiset hakupalvelut. Kätevimmin tämä käy Perlin CGI.pm-modulilla, jota
jatkossa käytämme.
Seuraava alkeisesimerkki on Perl-ohjelma, joka CGI-skriptinä kutsuttuna tulostaa syötteenä annettujen lukujen tulon, jos syötteitä on. Joka tapauksessa ohjelma tulostaa HTML-lomakkeen, jolla voi antaa syötteenä kaksi lukua ja lähettää ne saman ohjelman käsiteltäviksi. Tämä voi olla aluksi käsitteellisesti hämäävää, mutta kyse on sekä näppärästä että hyödyllisestä menettelystä.
#!/usr/local/gnu/bin/perl
use CGI qw(:standard);
print header, start_html(-title=>'Kertolasku',-lang=>'fi'),
h1('Kertolasku');
$eka = param('eka');
$toka = param('toka');
if($eka && $toka) {
$tulo = $eka * $toka;
print p("Lukujen $eka ja $toka tulo on $tulo.");
print hr, start_form(-method=>'get'),
p('Voit tehdä uuden kertolaskun:'),
div(defaults('Tyhjennä lomakkeen kentät')); }
else {
print start_form(-method=>'get'); }
print
div('Tällä lomakkeella voit laskea kahden luvun tulon.'),
div('<label for="k1">1. luku:</label>',
textfield(-name=>'eka',-id=>'k1')),
div('<label for="k2">2. luku:</label>',
textfield(-name=>'toka',-id=>'k2')),
div(submit(-value => 'Laske tulo')),
end_form,
end_html;
#!/usr/local/gnu/bin/perl
kertoo Perl-tulkin sijainnin palvelimen
tiedostojärjestelmässä. Sijainti ilmaistaan tiedostopolkuna, joka kirjoitetaan suoraan merkkien
#!
perään. Korjaa rivi sen mukaiseksi, mikä on sijainti käyttämässi
palvelimessa. Se on luultavimmin jotain muuta kuin tässä esimerkissä.use CGI qw(:standard);
ottaa käyttöön CGI.pm-kirjaston.print
-lauseet rakentavat sen HTML-dokumentin, jonka palvelin lähettää selaimelle
vastaukseksi, kun selain on lähettänyt pyynnön, joka viittaa tähän skriptiin.header
tuottaa merkkijonon, joka palvelimen vastauksen alkuun liitettynä sisältää
tarpeelliset tiedot siitä, että vastaus koostuu HTML-dokumentista.start_html
tuottaa merkkijonon, joka aloittaa HTML-dokumentin (ja sisältää muun
muassa <html>
-tägin). Rakenteella -
nimi=
arvo
annetaan lisätietoja niille CGI.pm:n funktioille, joilla rakennetaan HTML-elementtejä.-title=>'...'
tässä yhteydessä tekee dokumenttiin <title>
-elementin,
jossa on haluttu sisältö eli niin sanottu ulkoinen nimi dokumentille.-lang='fi'
tuottaa <html>
-tägin sisään määritteen
lang="fi"
, joka ilmoittaa dokumentin kieleksi suomen. Tämä on periaatteessa hyvin olennaista,
koska oletustoimintona CGI.pm:ssä on, että se tuottaa määritteen lang="en-US"
(amerikanenglanti)! Kielen ilmoittamisen merkitystä käsittelee dokumentti
Kielimerkkaus: tekstin kielen ilmoittaminen
merkkausjärjestelmissä.h1('Kertolasku')
tuottaa h1
-elementin (ylimmän tason otsikon),
jonka sisältönä on annettu merkkijono. Tässä otsikko on yksinkertaisuuden vuoksi sama eri
tilanteissa: sekä alkutilanteessa, jossa tulostuu vain lomake että muissa tilanteissa, joissa tulostuu
myös tuloksia. Käytännössä on usein parempi, että tämä otsikko (ja
mahdollisesti myös edellä mainittu title
-elementti) riippuu tilanteesta ja on esimerkiksi
virhetilanteissa aivan erilainen kuin normaalitilanteissa. Tällöin on ohjelman rakennetta vastaavasti
muutettava. Olennaista on, että niin title
-elementti kuin pääotsikkokin on
informatiivinen; käytännössä niiden on hyvä olla merkittävästi
pidempiä kuin tässä, esimerkiksi tyyliin "Haku XYZ-tietokannasta" tai "XYZ-tietokannasta tehdyn
haun tuloksia".param('nimi')
saadaan lomakkeen tietynnimisen kentän (parametrin) arvo
sellaisena, kuin se on tullut, kun käyttäjä on lähettänyt lomakkeen. Alkutilanteessa,
kun mitään lomaketta ei ole lähetetty, tämä arvo on määrittelemätön.if($eka && $toka)
testaa, että
lomakedata ylipäänsä sisältää tietyt kaksi kenttää ja että kumpikaan
niistä ei ole tyhjä. (Perlissähän muuttujannimi kuten $eka
antaa arvon epätosi
ehtolausekkeena käytettynä, jos muuttujan arvo on määrittelemätön tai
tyhjä merkkijono.)p
, hr
ja div
tuottavat vastaavannimiset elementit siten,
että elementin sisällöksi tulee se, mitä funktioille annetaan argumentteina.start_form
aloittaa lomake-elementin rakentamisen, eli käytännössä se
tuottaa <form>
-aloitustägin. Koska CGI.pm:ssä on tällöin oletusarvona
method="post"
, olemme muuttaneet tämän argumentilla -method=>'get'
, sillä
esimerkiksi yksinkertaiselle laskentaohjelmalle ei ole mitään syytä käyttää
post-metodia
.defaults
-funktiolla siinä
tapauksessa, että ohjelma myös tulostaa vastauksia. (Tällainen alustuskenttä palauttaa
syöttökentät alkutilaan, jonka itse skripti alun perin luo, siis tässä tapauksessa
sellaiseen tilaan, että tekstinsyöttökentät ovat tyhjiä.)textfield
-elementillä, jolle annetaan tässä
määritteiksi nimi (-name=
...) ja tunniste (-id=
...). Jälkimmäistä
määritettä tarvitaan, jotta elementti voidaan yhdistää selitteeseen eli kehotteeseen, joka
kertoo, mitä kenttään pitäisi syöttää.label
-kenttää ei voida suoraan tuottaa CGI.pm:n
nykyversiolla, vaan se joudutaan tekemään tuottamalla "raakaa HTML:ää". Toisaalta tämä
havainnollistaa, miten tuotettaessa HTML-dokumentti CGI.pm:llä voidaan tarvittaessa käyttää
CGI.pm:n valmiita, usein näppäriä funktiota sekaisin HTML:n suoran tuottamisen lisäksi.label
-kentän yhteys toisiinsa kuvataan siten, että syöttökentässä on
id
-määrite, jolla on jokin arvo, jota ei ole dokumentin millään muulla
id
-määritteellä, ja label
-kentässä on sitä vastaava
for
-määrite. Tämä on usealla tavalla kömpelö järjestely, mutta
siitä on apua monille käyttäjille, joiden käyttämät ohjelmat osaavat tällaisen
merkkauksen perusteella auttaa käyttäjää tietämään, mitä mihinkin
kenttään kuuluu kirjoittaa.label
-elementin ja vastaavan syöttökentän
peräkkäin samalle riville, jolla ei ole mitään muuta. Tämä on yleensä selkein ja
esteettömin ratkaisu. Tässä se on tehty teknisesti niin, että ne ovat div
-elementin
jälkeen. (Toinen tapa olisi kirjoittaa ne peräkkäin ja niiden jälkeen br
-elementti.)Harjoitustehtävä: Selvitä, miten voit asentaa käyttämääsi
palvelimeen Perl-ohjelman CGI-skriptiksi, ja tarkista, että CGI.pm on tällöin
käytettävissä. Asenna sitten yllä oleva ohjelma ja kokeile sitä. Huomaa, että kyseinen
selvittely ei aina ole ihan helppoa. Jos tämä ei onnistu, kokeile kyseistä skriptiä valmiiksi
asennettuna osoitteessa <http://www.cs.tut.fi/cgi-bin/run/~jkorpela/kerro.cgi>
.
Joka tapauksessa kokeile lomakkeen käyttöä myös ilman hiirtä.
Ihmiset tekevät virheitä, joskus hyvinkin paljon. Esimerkiksi lukihäiriöinen kirjoittaa hakusanoja väärin, kättään vaikeasti liikuttava saattaa napsauttaa väärää painiketta ja hätäisesti päätöksiä tekevä lähettää tiedot, vaikka kaikkia välttämättömiä kenttiä ei ole täytetty.
Hyvässä lomakkeenkäsittelyssä on sekä selaimessa tapahtuvia tarkistuksia että palvelimessa tapahtuvia tarkistuksia, yleensä niin, että sama asia tarkistetaan kahteen kertaan. Selaimessa tapahtuva tarkistus on hyödyksi, koska se antaa käyttäjälle nopean palautteen, jopa niin, että heti kirjoitettuaan vaikkapa sanan johonkin kenttään hän saa tiedon, että pitää kirjoittaa luku. Käytännössä kyse on JavaScript-koodista, joka liitetään sopivalla tavalla HTML-dokumenttiin. Palvelimessa tapahtuva tarkistus taas on varmistus, koska on monia syitä, joiden takia selaimessa tapahtuva tarkistus voi jäädä suorittamatta. Palvelimessa tapahtuva tarkistus on tärkeämpi yleisen toimivuuden ja turvallisuuden takia. Lisäksi siihen voi sisältyä sellaisia asioita, joita olisi mahdoton tehdä selaimessa, kuten tietokantoihin liittyvät tarkistukset.
Jos esimerkiksi testaamme edellä esitettyä lomaketta tekemällä kertolaskua tarpeeksi monta kertaa, huomaamme, että voimme kirjoittaa kenttiin vaikkapa kirjaimet a ja b ja saada mielettömän tuloksen "Lukujen a ja b tulo on 0".
Harjoitustehtävä: Miten kertolaskuohjelmaa pitäisi kehittää, jotta se
asianmukaisesti havaitsisi ja käsittelisi virhetilanteet kuten sen, että syöttötiedot eivät
ole lukuja lainkaan? Perlissä on useita erilaisia tapoja
tarkistaa, onko muuttujan arvo luku. Tällöin joudutaan määrittelemään tarkemmin,
mitä luku tässä tarkoittaa. Yksi tapa on seuraavanlainen testi:
if($foo == 0 && $foo ne "0") ...
(Tämä perustuu siihen, että operaattori ==
tekee numeerisen vertailun: jos
$foo
:n arvo on ei-numeerinen, tulos on nolla. Testin toinen osa huolehtii siitä, että
tämä erotetaan tapauksesta, jossa arvo on nimenomaan 0.) Halutessasi voit tarkastella asiaa myös
vain periaatteellisesti: oletetaan, että sopivat testit ovat käytettävissä. Miten ohjelman tulisi
ilmoittaa virheistä, jotta on käyttäjäkin ymmärtää, että tällä kertaa ei tullutkaan tulosta vaan
virheilmoitus? Miten muotoilisit ilmoitukset ja miten auttaisit käyttäjää jatkamaan virhetilanteen
jälkeen?
Todelliset lomakkeet ovat usein melko laajoja, eli niissä on paljon kenttiä. Tällöin ei riitä, että kullekin kentälle on tarkistukset, vaan lisäksi kokonaisuuden tulee olla helposti hahmotettavissa. Lomaketta lähetettäessä (selaimessa) tai lomakkeen lähettämisen jälkeen (palvelimessa) tarkistuksen suorittava koodi saattaa esimerkiksi havaita, että kahden kentän arvot muodostavat loogisesti mahdottoman yhdistelmän. Tällöin on olennaista kertoa,
Edellä on käsitelty sitä, miten lomakkeilla lähetettyjen tietojen käsittely saadaan esteettömäksi, ja ennen muuta sitä, miten käyttäjän saamat vastaukset ovat käyttökelpoisia kaikille. Myös itse lomakkeen täyttäminen voi olla ongelmallista. Asiaintilan parantamiseksi lomakkeisiin tarvitaan erityisiä esteettömyyspiirteitä hyvän ja loogisen yleisen rakenteen lisäksi.
Edellä esitetyssä esimerkissä oli jo yksi keskeinen esteettömyyspiirre: kukin
syöttökenttä on omalla rivillään, jolla on myös sen selite, ja nämä on
liitetty toisiinsa label
-merkkauksella. Selitteen on syytä olla ennen
tekstinsyöttökenttää. Jos taas kyseessä on valintanappi (radio
buttons, input type="radio"
) tai asetusnappi (input type="checkbox"
) eli
valintaruutu, on syytä noudattaa yleistä tapaa kirjoittaa selite sen jälkeen. Näiden kenttien
yhteydessä on label
-merkkauksesta erityinen etu, koska sen ansiosta käyttäjä voi napsauttaa myös
selitetekstiä tehdäkseen valinnan. Tämä on olennaista, koska napit ovat yleensä aika pieniä ja siksi niihin
osuminen vaatii tarkkaa motoriikkaa. Seuraavassa on asetusnapista ja selitteestä esimerkki, jonka avulla
voit testata, tukeeko oma selaimesi tässä kuvattua piirrettä eli voiko asetuksen muuttaa myös tekstiä
klikkaamalla:
Käytetty merkkaus on:
<div><input type="checkbox" name="lounas" id="chk"><label for="chk">Haluan ilmaisen
lounaan</label></div>
submit(-value=>'Hae')
tai submit(-value=>'Ilmoittaudu')
.defaults
-funktiolla.Harjoitustehtävä: Hahmottele, miten aiemmin tarkasteltua Googlen monipuolista hakutoimintoa voisi kehittää edellä esitettyjen periaatteiden mukaisesti. Voit myös tehdä vastaavan hahmottelun ja ehkä toteutusluonnoksenkin jollekin muulle suhteellisen isolle lomakkeelle.
Lisätietoja lomakkeiden käytön tekemisestä esteettömäksi on Tieken Esteettömyysoppaassa kohdassa Lomakkeet, jossa on myös linkkejä englanninkieliseen lisäaineistoon.
CGI.pm:ää käsittelee johdattelevasti The Fool's Guide to CGI.pm, the Perl module for CGI scripting ja yksityiskohtaisesti alkuperäisdokumentaatio CGI.pm - a Perl5 CGI Library.
Virheilmoitusten tekemistä ymmärrettäviksi käsittelee Error Message Guidelines .