Skjemavalidering med JavaScript
Jeg har validert skjemaer med JavaScript mange ganger, og på mange måter. Nå har jeg laget skjemavalidering i JavaScript nok en gang, men nå forhåpentligvis så brukandes at det blir siste gang. Kanskje er det nyttig for andre au?
Problemet
Du har et nettsted med ett eller flere skjemaer som brukerne dine skal fylle ut. Siden dataene skal sendes eller lagres et sted er du avhengig av å sjekke integriteten på data som brukeren oppgir - altså, at enkelte felter ikke er tomme, noen skal være gyldig e-post, telefonnummer, ikke lengre enn x antall tegn osv. Dette sjekkes på serveren, og hvis det oppdages feil returneres brukeren til skjemaet med en liste over problemer.
Selvom validering på serveren er helt nødvendig er det en tungvindt prosess for brukeren, særlig hvis hun gjør flere feil. Så for å forbedre denne prosessen legger vil til tilsvarende validering med JavaScript. På denne måten kan valideringen gjøres langt raskere, kanskje allerede med en gang brukeren er ferdig med et felt. Dette gir brukeren umiddelbar respons, og tillater en mer smidig prosess.
Løsningen(e)
JavaScript
Så hvordan gjør vi dette? Det aller enkleste er å kode opp en funksjon og hekte den på skjemaets onSubmit. I funksjonen henter vi noen felter og sjekker innholdet i dem. Dette holder for helt enkle ting, men blir overraskende fort møysomt og lett å gjøre feil med. Så vi dropper det.
Semantisk HTML
En mer smidig måte er å bruke semantisk HTML som kroker for validering. Dette gir mindre rom for feil, og er langt enklere siden man ikke trenger å kode hver eneste sjekk. På denne måten kan man bruke klassenavn som "required" "email" osv for å angi forskjellig type validering av felter. Hvis man ikke anser HTML-validering som hellig kan man også bruke custom attributter for å angi validering.
Styrken i validering med kode er kompleksitet/fleksibilitet. Så lenge ethvert felt bare opptrer en gang i HTML-en er det vanskelig å få til flere forskjellige valideringer i forskjellige settinger med HTML alene. Dermed trenger man en blanding av kode og semantisk markup for de mer komplekse skjemaene.
Tilgjengelige løsninger
Det finnes rammeverk/komponenter som gjør begge disse tingene, men som alltid - ikke eksakt sånn jeg vil. Enten er det ikke fleksibelt nok, eller så er det avhengig av et rammeverk, eller så er det noe annet som ikke stemmer. Så jeg tenkte jeg skulle lage meg noe som funker akkurat som jeg vil ha det, og lage det skikkelig så jeg ikke trenger å gjøre det igjen.
Følgende er kravene jeg startet med da jeg begynte å skrive koden:
- Lett å endre visning av feilmeldinger, enten med CSS, konfigurering, eller vilkårlig tilpasset
- Lett å legge validering med semantisk HTML
- Et klart og simpelt JavaScript-API for de mer sammensatte tilfellene
- Mulig å bruke uten rammeverk, men skal også fungere med alle de store rammeverkene
- Flerspråkelig
- Lettvekt/lite script
- Lett å legge til egen valideringslogikk
Dette er litt å gape over, og det skulle vise seg at særlig fleksibilitet og filstørrelse bare er kompatibelt inntil et visst nivå.
Eksempler
Alle eksemplene kan prøves på denne siden. Kildekode kan sees derfra, eller med forklaring nedenfor.
Kodekomponenten har en relativt fullstendig test-suite, og denne kjører uten problemer i Firefox 3, Opera 9, Safari 3 og IE6 og 7. Skulle du oppleve problemer med eksempelet så legg gjerne igjen en kommentar med nettleser og operativsystem så ser jeg på det.
Semantisk HTML
Et vanlig eksempel på skjema i bloggverdenen er kommentarskjemaer. Disse består nesten alltid av navn, e-post, link og kommentar. Det er vanlig at navn, e-post og kommentar er påkrevd, mens link er valgfritt, men må være en gyldig URL om man først benytter feltet. Alt dette kan sjekkes på følgende måte:
<form action="#" method="get" class="validate">
<fieldset>
<div class="field">
<label for="name">Navn</label>
<input name="name" id="name" class="required" type="text" />
</div>
<div class="field">
<label for="email">E-post</label>
<input name="email" id="email" class="required email" type="text" />
</div>
<div class="field">
<label for="site">Link</label>
<input name="site" id="site" class="url" type="text" />
</div>
<div class="field">
<label for="comment">Kommentar</label>
<textarea name="comment" id="comment" class="required"></textarea>
</div>
</fieldset>
<fieldset>
<div class="button"><input type="submit" value="Send inn" /></div>
</fieldset>
</form>
HTML-en som trigger validering her er:
-
class="validate"på skjemaet. Dette klassenavnet kan konfigureres -
class="required",class="url", osv påinput-elementene (gjelder ogsåselectogtextarea). Alle navn som gjenkjennes som validatorer blir snappet opp. Det kan konfigureres opp en prefiks for disse navnene, alaclass="v_required"
Ved hjelp av dette og litt minimalt med CSS får vi det første skjemaet i lenken over. Det er verdt å merke seg litt default-funksjonalitet:
- Når du klikker knappen valideres skjemaet og en feilmelding vises over hvert felt som feiler
- Kun én feilmelding vises ad gangen. Prøv å la e-post-feltet stå tomt og klikk knappen. Prøv deretter å fylle inn noe som ikke er en e-post-adresse. Meldingen endrer seg
- Etter at du først har trykket på knappen vil feltene revalidere seg når du endrer verdien i dem
Hvis du taster inn en ugyldig URL i link-feltet så får du opp meldingen "Link må være en gyldig URL". Det er flere måter å spesifisere feilmeldinger enten per validator eller per felt. Den enkleste er å sette title-attributtet på input-taggen. Følgende fungerer for å ytterligere spesifisere meldingen på link-feltet:
<div class="field">
<label for="site">Link</label>
<input name="site" id="site" class="url" type="text" title="Link må være en gyldig URL, husk http://" />
</div>
JavaScript-API-et
Hvis vi skulle gjort det samme som i eksempelet over med kjerne-API-et ville det sett ut som omtrent:
var form = new v2.Form('form1');
var name = new v2.Field('name');
name.addValidator('required');
form.add(name);
var email = new v2.Field('email');
email.addValidator('required');
email.addValidator('email');
form.add(email);
var link = new v2.Field('link');
// Andre parameter her er eventuelle parametere
// ${field} slås opp som feltnavnet som normalt leses fra <label>
link.addValidator('url', null, '${field} må være en gyldig URL, husk http://');
form.add(link);
var comment = new v2.Field('comment');
comment.addValidator('required');
form.add(comment);
Forhåpentligvis er dette relativt selvforklarende.
Et mer konsist API
En tredje måte er også tilgjengelig. Jeg har laget en valgfri plugin som gir deg et slags validerings- DSL. Et DSL er et "domenespesifikt språk", eller en API som har egenskaper/syntaks som et eget språk dedikert til en spesifik oppgave. Dette tillegget er ekstra nyttig når man skal gjøre komplekse valideringer. Det er overkill for dette eksempelet, men det kan være interessant å se det for det samme skjemaet alikevel, for sammenlingingens del:
validate(
"name".is("required"),
"email".is("required").and("email"),
"link".isAn("url").explain("${field} må være en gyldig URL, husk http://"),
"comment".is("required")
);
Det bør være liten tvil om at dette gir en langt mer kompakt syntaks. Det er også svært lett å gjøre sammensatte regler med and() og or(), som kan nestes vilkårlig.
Mer funksjonalitet
Dette er bare toppen av isberget. Jeg jobber med å lage flere eksempler og litt dokumentasjon på dette slik at jeg kan dele det med de som skulle være interessert. Før jeg runder av vil jeg alikevel få nevne noe rundt hvordan jeg løste de forskjellige designmålene jeg nevnte tidligere.
Visning av feilmeldinger
Visning og skjuling av feilmeldinger skjer gjennom custom events: onFailure og onSuccess, som er tilgjengelig for skjemaer, blokkelementer og enkeltfelter. Kodekomponenten har en ganske fleksibel standardimplementasjon av disse som lar deg konfigurere antall feilmeldinger som skal vises per felt, hvorvidt de skal legges til i starten eller slutten av elementet osv. Disse kan selvfølgelig også styles med CSS.
Validering med semantisk HTML
Som vist over. Validator-navn kan brukes som klassenavn på skjemaelementer. Hvis disse navnene kolliderer med eksisterende navn kan det brukes prefiks.
Standalone/fungere med andre rammeverk
Jeg har skilt ut en del typisk rammeverksfunksjonalitet i et eget lite API. I standalone-varianten er det gjort en minimal implementasjon av dette (feks en implementasjon av querySelectorAll-type funksjonalitet som dekker akkurat de tilfellene valideringen trenger). Alt er gjort i navnerommet v2 slik at det ikke skal kollidere med andre rammeverk.
Brukes det andre rammeverk kan man også benytte seg av denne styrken ved å implementere det ovenfornevnte API-et med rammeverket. Jeg har gjort et forsøk med Prototype og en ganske rask løsning trimmet av 5kB fra standalone-versjonen.
Standalone-komponenten bruker forøvrig Dean Edwards' Base, og hans event-normalisering. Dette er småkomponenter som er bakt inn i js-filen, sånn at resultatet stadig vekk er en enkelt fil som fungerer uten andre rammeverk.
Enkelt JavaScript-API-et
Som vist over er kjerneAPI-et forståelig nok, og skal man programmere mye validering gir DSL-komponenten en svært behagelig syntaks.
Flerspråkelig
Dette er gjort gjennom å skille alle standardfeilmeldinger ut i en egen fil. I tillegg kan man som vist selv overstyre default-meldingene, eller bare meldinger for enkelte felt.
Lettvekt
Dette er kriteriet jeg føler jeg ikke er helt i mål med. Minifiserte versjoner av komponenten gir ca 23kB med all funksjonalitet. Klarer man seg uten DSL-API-et kommer man rett under 20kB. Serveres dette med gzip er det ikke snakk om mye, men jeg hadde alikevel håpet at jeg skulle lande litt lavere. Men, for en standalone-komponent er det vel ikke så verst, ihvertfall ikke gitt mengden funksjonalitet.
Lett å legge til egen valideringslogikk
Denne har jeg ikke vist enda. La oss vise denne med et eksempel. Si at vi ønsker å validere styrken på et passord. Passordet bør være minst 6 tegn langt, og inneholde minst én liten bokstav, én stor og ett tall. Dette kan vi legge til som følger:
v2.Validator.add({
name: 'password',
message: '${field} må være minst seks tegn og inneholde små og store bokstaver samt tall',
fn: function(field, value, params) {
return /[a-z]/.test(value) &&
/[A-Z]/.test(value) &&
/[0-9]/.test(value) &&
value.length >= 6;
}
});
Jepp, det er helt sikkert mulig å lage ett eneste regulært uttrykk som sjekker dette...
Som sagt så jobber jeg med å få ut en nedlastbar versjon sammen med flere eksempler og litt dokumentasjon. Forhåpentligvis vil flere enn meg finne dette nyttig.
Apropos ingenting: For de av dere som mot all formodning bekymrer dere for at dette er første innlegg på fem uker så kan jeg informere om at jeg har hatt ferie, og nå er i pappapermisjon, så produksjonen vil ikke ligge høysetet helt med det første.
Comments
tool
(http://my-addr.com/ )
24. desember, 03:48
Comments are closed