9. Käytettävyys
Sovelluksen käytettävyyteen liittyvät muiden muassa seuraavat asiat:
- Sovellusta on helppo käyttää ja se toimii käyttäjän odottamalla tavalla.
- Sovellus ilmaisee selkeästi toiminnon onnistumisen tai näyttää ymmärrettävän virheilmoituksen.
- Käyttäjä ymmärtää sovelluksen rakenteen ja pystyy liikkumaan sujuvasti sovelluksessa.
- Sovellus toimii eri selaimilla ja eri laitteissa ja ottaa huomioon erilaiset käyttäjäryhmät.
Tässä osassa käymme läpi asioita, joiden avulla voimme parantaa sovelluksemme käytettävyyttä.
Ilmoitukset
Tällä hetkellä sovelluksemme tapa ilmoittaa virheistä ei ole käyttäjälle mukava. Esimerkiksi kun käyttäjä lähettää profiilikuvan, virheet käsitellään näin:
app.py
file = request.files["image"]
if not file.filename.endswith(".jpg"):
return "VIRHE: väärä tiedostomuoto"
image = file.read()
if len(image) > 100 * 1024:
return "VIRHE: liian suuri kuva"
Tarkastellaan tilannetta, jossa käyttäjä lähettää liian suuren kuvan:
Tämän seurauksena käyttäjä saa seuraavan virheilmoituksen:
Ei ole hyvä, että sovellus näyttää virheilmoituksen erillisellä sivulla, josta käyttäjän täytyy itse palata lähetyssivulle. Voimme parantaa asiaa seuraavasti:
app.py
file = request.files["image"]
if not file.filename.endswith(".jpg"):
flash("VIRHE: Lähettämäsi tiedosto ei ole jpg-tiedosto")
return redirect("/add_image")
image = file.read()
if len(image) > 100 * 1024:
flash("VIRHE: Lähettämäsi tiedosto on liian suuri")
return redirect("/add_image")
Nyt sovellus määrittelee virheilmoituksen Flaskin flash
-funktiolla sekä ohjaa käyttäjän takaisin lähetyssivulle. Tällä sivulla edellisen sivupyynnön virheet saadaan kutsumalla funktiota get_flashed_messages
:
add_image.html
{% for message in get_flashed_messages() %}
<p>
<b>{{ message }}</b>
</p>
{% endfor %}
Nyt virhe näkyy käyttäjälle selkeästi mukavammalla tavalla:
Tämän lisäksi voimme näyttää myös ilmoituksen käyttäjäsivulla siinä tilanteessa, jossa kuvan lisääminen onnistuu:
app.py
user_id = session["user_id"]
users.update_image(user_id, image)
flash("Kuvan lisääminen onnistui")
return redirect("/user/" + str(user_id))
Kuvan lisääminen voi nyt näyttää seuraavalta:
Lomakkeen kentät
Voimme vastaavasti parantaa tunnuksen luontisivua niin, että se näyttää virheilmoitukset. Käyttökokemus ei ole kuitenkaan vielä hyvä:
Virheilmoitus näkyy sinänsä hyvin, mutta ongelmana on, että lomake on unohtanut käyttäjän syöttämät tiedot.
Voimme toteuttaa lomakkeen paremmin seuraavasti:
app.py
@app.route("/register", methods=["GET", "POST"])
def register():
if request.method == "GET":
return render_template("register.html", filled={})
if request.method == "POST":
username = request.form["username"]
if len(username) > 16:
abort(403)
password1 = request.form["password1"]
password2 = request.form["password2"]
if password1 != password2:
flash("VIRHE: Antamasi salasanat eivät ole samat")
filled = {"username": username}
return render_template("register.html", filled=filled)
if users.create_user(username, password1):
flash("Tunnuksen luominen onnistui, voit nyt kirjautua sisään")
return redirect("/")
else:
flash("VIRHE: Valitsemasi tunnus on jo varattu")
filled = {"username": username}
return render_template("register.html", filled=filled)
Nyt sivupohjalle annetaan parametri filled
, jonka kautta voi antaa lomakkeen kentissä valmiiksi olevia arvoja. Oletuksena filled
on tyhjä, mutta virheen sattuessa sen kautta välitetään käyttäjänimi.
Sivupohjassa muutos näyttää seuraavalta:
register.html
<form action="/register" method="post">
<p>
Tunnus: <br />
<input type="text" name="username" value="{{ filled.username }}" maxlength="16" />
</p>
Jos filled
on tyhjä, sivupohja tulkitsee, että filled.username
on myös tyhjä eikä näytä kentässä mitään.
Nyt sivu näyttää aiemmin syötetyn käyttäjänimen näin:
Yleensä tämän tyyppisissä lomakkeissa on hyvä näyttää virheen sattuessa kaikki syötetyt tiedot salasanaa lukuun ottamatta.
Kirjautumispolku
Jos käyttäjä ei ole kirjautunut sisään, sivulla ei näytetä lomaketta, jolla voi lähettää uuden viestin keskusteluun:
Jos kuitenkin käyttäjä haluaisi lähettää viestin, hänen täytyy ensin etsiä kirjautumissivu, kirjautua sisään ja etsiä sitten uudestaan ketju, johon hän haluaa lähettää vastauksen. Voimme parantaa tätä prosessia huomattavasti.
Ensinnäkin voimme antaa käyttäjälle linkin, josta pystyy kirjautumaan sisään:
Lisäksi voimme toteuttaa kirjautumissivun niin, että se ohjaa käyttäjän automaattisesti takaisin sivulle, jolla käyttäjä oli ennen kirjautumista. Tätä varten laitetaan lomakkeeseen piilokenttä next_page
:
login.html
{% if next_page %}
<input type="hidden" name="next_page" value="{{ next_page }}" />
{% else %}
<input type="hidden" name="next_page" value="/" />
{% endif %}
Ideana on, että parametri next_page
ilmaisee sivun, jonne käyttäjä ohjataan kirjautumisen jälkeen. Jos parametri on tyhjä, käyttäjä ohjataan etusivulle. Sivun käsittelijä näyttää nyt tältä:
app.py
@app.route("/login", methods=["GET", "POST"])
def login():
if request.method == "GET":
return render_template("login.html", next_page=request.referrer)
if request.method == "POST":
username = request.form["username"]
password = request.form["password"]
next_page = request.form["next_page"]
user_id = users.check_login(username, password)
if user_id:
session["user_id"] = user_id
session["csrf_token"] = secrets.token_hex(16)
return redirect(next_page)
else:
flash("VIRHE: Väärä tunnus tai salasana")
return render_template("login.html", next_page=next_page)
Parametrin next_page
arvoksi tulee aluksi request.referrer
, joka on selaimen välittämä edellisen sivun osoite. Jos kirjautuminen onnistuu, käyttäjä ohjataan piilokentässä olevalle sivulle. Jos kirjautuminen ei onnistu, parametrin next_page
arvoksi tulee piilokentän sisältö.
Näiden muutosten jälkeen käyttäjä ohjataan kirjautumisen jälkeen suoraan takaisin sivulle, josta hän voi lähettää vastauksen ketjuun:
Viestin rivitys
Tarkastellaan tilannetta, jossa käyttäjän kirjoittamassa viestissä on rivinvaihtoja:
Kun viesti näytetään sivulla, rivinvaihdot ovat kadonneet:
Tämä on ongelma, koska käyttäjä odottaa rivinvaihtojen säilyvän. Tällainen ongelma saattaa tulla esille vasta, kun sovellus menee todelliseen käyttöön eivätkä kaikki viestit ole lyhyitä testiviestejä.
Ongelma johtuu siitä, että HTML-koodissa olevat rivinvaihdot eivät näy selaimessa. Sivun lähdekoodista näkee, että rivinvaihdot ovat kyllä edelleen olemassa:
<p>
Tässä on joitakin syitä:
1. Ananas on hyvää
2. Ananas sopii pizzaan
3. Ananas on terveellistä
</p>
Ratkaisu ongelmaan on sinänsä helppo: viestissä olevat rivinvaihdot tulisi korvata selaimessa näkyvillä <br />
-rivinvaihdoilla. Kuitenkin vaikeutena on, että sivupohjaa käytettäessä selain näyttää viestissä olevat HTML-komennot sellaisenaan eikä safe
-filtterin käyttäminen olisi turvallista.
Voimme ratkaista ongelman luomalla oman filtterin show_lines
:
app.py
import markupsafe
...
@app.template_filter()
def show_lines(content):
content = str(markupsafe.escape(content))
content = content.replace("\n", "<br />")
return markupsafe.Markup(content)
Tässä markupsafe.escape
muuttaa ensin viestissä olevat HTML-komennot niin, että ne näytetään sivulla sellaisenaan. Tämän jälkeen viestissä olevat rivinvaihdot muutetaan <br />
-rivinvaihdoiksi.
Voimme käyttää filtteriä show_lines
sivupohjassa näin:
thread.html
<p>
{{ message.content | show_lines }}
</p>
Tämän ansiosta viesti näkyy oikein:
Saavutettavuus
Sovellus on saavutettava, kun sen toteutuksessa on otettu huomioon eri käyttäjäryhmät. Esimerkiksi näkövammaiset käyttävät ohjelmia, jotka lukevat ääneen verkkosivujen sisältöä tekstimuodossa.
Hyvä tulikoe sovellukselle on testata sitä tekstipohjaisella selaimella. Seuraavassa on testattu keskustelualuetta Lynx-selaimella:
Tässä tapauksessa sovellusta pystyy käyttämään hyvin tekstiselaimessa, mikä on merkki hyvästä saavutettavuudesta.
Hyvä lähtökohta saavutettavan sovelluksen tekemiseen on käyttää HTML-elementtejä suoraviivaisesti ja loogisesti. Kuvissa alt
-attribuutilla voidaan antaa kuvan korvaava tekstisisältö. Voimme käyttää attribuuttia käyttäjäsivulla näin:
user.html
{% if user.has_image %}
<img src="/image/{{ user.id }}" alt="Käyttäjän {{ user.username }} kuva" />
{% endif %}
Lynx-selain näyttää kuvan alt-attribuutissa olevan tekstin näin:
Lomakkeissa saavutettavuutta voidaan parantaa käyttämällä label
-elementtiä, joka yhdistää lomakkeen kentän ja siihen liittyvän kuvauksen. Tarkastellaan esimerkkinä seuraavaa lomaketta:
thread.html
<p>
Viesti:<br />
<textarea name="content" rows="5" cols="40" maxlength="5000"></textarea>
</p>
Tässä “Viesti:” on tekstialueen kuvaus, mutta tämä ei tule ilmi HTML-koodissa. Voimme parantaa lomaketta käyttämällä label
-elementtiä näin:
thread.html
<p>
<label for="content">Viesti</label>:<br />
<textarea id="content" name="content" rows="5" cols="40" maxlength="5000"></textarea>
</p>
Tässä label
-elementin attribuutti for
viittaa tekstialueen attribuuttiin id
, mikä tuo esille otsikon ja tekstialueen yhteyden. Tästä on hyötyä esimerkiksi silloin, kun käyttäjällä on ruudunlukuohjelma, joka selostaa sivun sisältöä.
name
vs. id
Attribuutit name
ja id
ovat melko samanlaisia, koska molemmat antavat elementille tunnuksen. Joskus samalla elementillä on molemmat attribuutit, kuten yllä olevassa koodissa textarea
-elementillä.
Attribuuttia name
käytetään lomakkeissa, ja se lähetetään palvelimelle kentän nimenä. On mahdollista, että lomakkeessa on useita elementtejä, joilla on sama arvo name
-attribuutissa.
Attribuuttia id
käytetään antamaan elementille yksilöllinen tunnus, jolla siihen voidaan viitata selaimessa. Attribuutin id
sisältöä ei välitetä palvelimelle.
Lisää tietoa saavutettavuudesta on esimerkiksi suomeksi Ronja Ojan materiaalissa ja englanniksi W3C:n tutoriaalissa ja Mozillan materiaalissa.
Tässä on sovelluksen uusin versio, johon on lisätty tämän osan muutokset: