Komplex példa - Validálás - 3. rész

Attila | 2022. 07. 19. 11:57 | Olvasási idő: 6 perc

Címkék: #Érvényesítés (Validation) #HTML #JavaScript #Laravel #Laravel Mix #Nézet (View) #npm #Telepítés (Installation) #Űrlap (Form)

Megvizsgálok és ki is próbálok egy Javascript osztálykönyvtárat, valamint a dátumokkal kapcsolatos kiterjesztését, amelyeket specifikusan a validáció miatt hoztak létre. Emellett persze sok tanulságra is rájövök, amelyet az alábbi bejegyzésben osztok meg veletek.
javascript_validation_just_validate

Bevezetés

Az előző blogbejegyzésem végén felsoroltam néhány olyan Javascript csomagot, amelyeket tipikusan a validáció miatt hoztak létre. Természetesen közel sem az összeset soroltam ott fel, de most azok közül fogok megismerni egyet példákon keresztül. A célom az, hogy az így kipróbált osztálykönyvtár helyett akár másféléket is tudjatok majd alkalmazni, tehát egy megalapozó tudást szeretnék adni ahhoz, hogy bármilyen más Javascript osztálykönyvtárat ezután már sikerrel tudjatok beépíteni a saját alkalmazásotokba.

Elöljáróban még annyit megjegyeznék (ezt már a bejegyzés megírása után írom), hogy én részletesebben megnéztem még néhány osztálykönyvtárat, de - talán a túl magasa igényeim miatt? - mindegyikben találtam valami olyan kivetnivalót, amely miatt inkább nem javasolnám a használatát. Viszont itt alább egy olyat vizsgálok és mutatok be részletesen, amelynek a működtetése egyszerű, logikus, sokrétű, úgyhogy ennek a használatát javasolnám nektek.


A vizsgálati alany: Just Validate

Azt mondják, a külső megfog, a belső megtart, emiatt én legelőször a FormValidation.io-val próbálkoztam (a külső megfogott, de aztán a belső elég gyorsan elengedett...), viszont ez olyan egyéb osztálykönyvtárakat is igényel, amiket én első körben nem akartam használni (jQuery, Bootstrap - ráadásul ezek régebbi verziószámaival működik együtt), úgyhogy mivel csak a lényegre, a validációra akartam koncentrálni, emiatt módosítottam a választásomat. Megpróbálkozok egy olyannal osztálykönyvtárral, aminek a weboldala design-os és megbízhatónak tűnik, fejlesztik is (Github legutóbbi commit dátumából lehet erre következtetni), valószínűleg egyszerű is, ez a Just Validate - szintén beszédes - névre hallgat.

Szerencsére van dokumentációja is a működő példák mellett, úgyhogy ez alapján fogok haladni, de természetesen úgy, hogy a saját Laravel projektembe építem bele továbbra is az űrlap validációját. Az előző bejegyzésben azt kértem a végén, hogy aki gyakorolni szeretne, csinálja meg a create-ek után az edit nézetek validálását is. Én ezt most már a validációs osztálykönyvtárak segítségével fogom megtenni, az utasok szerkesztési űrlapjával fogom kezdeni, majd utána a repülőjáratok létrehozási és szerkesztési nézetével fogom folytatni, de előtte mindenképpen szükség van a Just Validate importálására.

Just Validate: telepítés és használat

A "telepítéshez" többféle módszert is tudok használni: npm vagy yarn csomagkezelővel is működhet (lásd majd később ezt), de én inkább először csak betöltöm a Just Validate legfrissebb verzióját CDN-en (Content Delivery Network szolgáltatáson) keresztül az oldalamra. Ezek a CDN-ek azért jók, mert olyan csomagokat tárolnak, amiket rengeteg webprogramozó használ és nagyon gyors elérést biztosítanak hozzájuk, így nem kell a saját alkalmazásunkba telepíteni a csomagot és figyelni a frissítéseire (mindig nagyon gyorsan elérhetőek ezek a csomagok a távoli szerveren). Persze a másik telepítési módnak is vannak előnyei, de ezekről majd később. Én most, mivel először a CDN-es használata mellett döntöttem, innentől kényelmes megoldásként azt választhatnám, hogy a script-et simán beírom a layout.blade.php fájlom head részébe, és akkor mindenhol elérhető a validáció. Ezt én mégsem javasolnám, mert érdemes figyelni az optimalizálásra is: olyan aloldalunkon, ahol nincs szükség a validációra, ott ne töltsük be a validációs fájlt. Inkább csak a helyőrzőjét csináljuk meg a layout head részében, így:

@stack('head-scripts')

Amelyik "alnézetben" pedig alkalmazni szeretnénk ezt a fajta validációt, ott majd importáljuk a szükséges fájlt, például a passengers / edit.blade.php -n belül így:

@push('head-scripts')
  <script src="https://unpkg.com/just-validate@latest/dist/just-validate.production.min.js"></script>
@endpush

Ahogy említettem, ez a Just Validate legutóbbi (vegyük észre a linkben a "latest" kulcsszót, ami majd valós verziószámmá fog átalakulni betöltéskor), élesben használatos (erre utal a "production" kulcsszó a linkben), minimalizált és optimalizált Javascript kódját tartalmazza.

A szerkesztési űrlap nagyjából rendben van, csak egyetlen dolgot bővítek ki: a form nyitó tag-jéhez hozzáadok egy id attribútumot:

id="passengerEditForm"

Ezzel fogok majd tudni hivatkozni az űrlapra a validáláshoz. Tegyük is ezt meg: egy újabb blokkot adunk hozzá a fájlhoz, ami a tényleges validálást hajtja végre:

@push('scripts')
<script>
  const validation = new window.JustValidate('#passengerEditForm');

  validation
    .addField('#utasneve', [
	  {
        rule: 'required',
        errorMessage: 'Név mező megadása kötelező!',
      },
      {
        rule: 'minLength',
        value: 10,
        errorMessage: 'A Név mező legalább 10 betűből álljon!',
      },
      {
        rule: 'maxLength',
        value: 50,
        errorMessage: 'A Név mező legfeljebb 50 betűből álljon!',
      },
    ])
    .addField('#email', [
	  {
        rule: 'required',
        errorMessage: 'E-mail mező megadása kötelező!',
      },
      {
        rule: 'email',
        errorMessage: 'Az e-mail érvénytelen!',
      },
      {
        rule: 'maxLength',
        value: 100
      },
    ])
    .onSuccess((ev) => {
      ev.target.submit();
    }
  );
</script>
@endpush

Az iménti kód annyit csinál, hogy létrehoz egy JustValidate objektumot, amit hozzáköt a passengerEditForm id-jú űrlapunkhoz. Utána pedig ehhez az objektumhoz szép sorban hozzáadogatjuk a mezőinket, szintén azonosítóval, ahogy a CSS-ben megismertük (#utasneve, #email). Egy tömbben pedig a mezőkhöz hozzáadjuk a szabályokat, például, hogy kötelező (required) volt a kitöltése, vagy a típusának e-mailnek kell lennie, de ugyanezt megtehetjük még a minimális/maximális hosszúsággal is stb. A szabályokat, ha "errorMessage" nélkül definiálom, akkor angolul fogja kiírni az alapértelmezett hibaüzenetet (kipróbálhatjuk úgy is majd, hogy az iménti kód szerinti űrlapon több mint 100 karakter hosszú e-mail címet próbálunk megadni, ekkor angol hibaüzenetet fogunk kapni, hiszen ahhoz nincsen errorMessage-ünk).

Teszteléskor a szabályok megszegését nem fogja jelezni az oldal rögtön, csak a Frissítés (küldés) gomb megnyomásakor. Tipp: teszteléshez megint érdemes lehet az űrlapon az utas neve és e-mail címe (input) mezőinél kitörölni a validációs elemeket (required, minlength, maxlength), hogy ténylegesen csak a Just Validate-es Javascript-es validálást tudjuk tesztelni.

A Frissítés gomb megnyomása után azonban már "élővé" válik a validáció, és ha például ilyen hibaüzenetet kapunk, mint az iménti képen is látszódik, akkor ha 10 karakternél hosszabb nevet írok be, akkor automatikusan eltűnik ez a hibaüzenet, de ha visszatörlöm a nevet néhány karakteresre, akkor ismét megkapom ezt a jelzést. Ha pedig teljesen kitörlöm a nevet, akkor megkapom a "Név mező megadása kötelező!" szövegezésű hibaüzenetet.

Persze, ha csak ilyen egyszerű validációkat szeretnénk használni, akkor nem feltétlenül lenne szükség külső osztálykönyvtár alkalmazására, azonban ennél sokkal komplexebb lehetőségeink is vannak szabályok létrehozására a Just Validate segítségével. A folyamat ugyanez lehetne, mint amit az előbb felvázoltam, csak sokkal specifikusabb szabályokat tudnánk még létrehozni, akár még a validációs CSS stílus szabályok megadására is lehetőségünk van, de a többnyelvű figyelmeztető üzenetek meghatározására is egyszerűen képesek vagyunk általa... Fontosnak tartom még megemlíteni, hogy a fájlfeltöltésnél és a dátumok ellenőrzésénél is jó szolgálatot tehet nekünk a Just Validate osztálykönyvtára (ez utóbbival fogjuk folytatni).


Dátumellenőrzéshez a Laravel-es háttér összerakása

Próbáljuk is ki a dátumok ellenőrzését és kezelését, mivel ezekkel sok probléma szokott lenni és talán nem árthat egy kis gyakorlás sem... Térjünk rá a repülőjáratokra (flights) és adjunk hozzá az adatbázishoz két új mezőt:

php artisan make:migration add_dates_to_flights_table

Azt ellenőrizzük majd, hogy a tervezett érkezés dátuma nem lehet korábbi vagy megegyező, mint a tervezett indulás dátuma, ehhez a két mező hozzáadása az up() metódusban (tudom, hogy vannak már hasonló mezőink, de most ezeket a tervezett értékeket fogom használni a validációhoz):

$table->timestamp('scheduled_departured_at');
$table->timestamp('scheduled_arrived_at');

Törlésük a down() metódusban:

$table->dropColumn(['scheduled_departured_at', 'scheduled_arrived_at']);

Utána futtassuk is az adatbázis migrálást, hogy bekerüljenek az új mezők a flights táblánkba: php artisan migrate

A Flight Model osztályban adjuk is hozzá ezt a két mezőt a $fillable változóhoz újabb tömbelemként, hogy majd sikeresen tudjuk az adatbázisba betölteni az értékeket.

Nálam a FlightsController-ből még hiányoznak a create, store (és az edit, update metódusok is, de azokat későbbre hagyom + gyakorlásként a destroy-t is meg lehet csinálni majd), úgyhogy ezeket most létre is hozom:

public function create()
{
  return view('flights.create', [
    'captains' => Captain::orderBy('name')->get(),
    'airlines' => Airline::orderBy('name')->get()
  ]);
}
public function store(Request $request)
{
  Flight::create(request()->all());
  return redirect(route('flights.index'));
}

Viszont az ezekhez köthető útvonalaink még hiányoznak a web.php-ból. Én az eddigi flights-hoz köthető útvonalakat kikommenteltem (törölhetjük is), és helyette ezt használom, mert egyszerűbb és rövidebb is:

Route::resource('flights', App\Http\Controllers\FlightsController::class);

Ezzel ugye mind a 7 RESTful útvonalat létrehozom a repülőjáratokhoz, az útvonal nevekkel (name) együtt.

Adjuk hozzá a létrehozó és szerkesztő weblinkeket a flights / index nézethez. A két linket helyezzük el logikusan (az elsőt a nézet címe alatt, a másodikat a repülőjáratok felsorolásába):

Ezután térjünk rá a létrehozó nézetre, hiszen, ha azt létrehozzuk és megfelelően implementáljuk, akkor utána a szerkesztési nézetet már könnyebb lesz másolni és pluszban egy kicsit még paraméterezni a megfelelő működéshez. Itt már mindent úgy állítok be, hogy a validációs szabályokat összehangolom az adatbázistábla megszorításaival és az általunk elvárt működéssel is.

Jó sok mező van most már benne, itt én most csak a form-ot magát emeltem ki képként (de figyeljünk, hogy lett id-ja a form-nak!). Tipp: Jó szokásom szerint, aki szeretné felhasználni a kódot, az a bejegyzés végén lévő Github commit részeként megtekintheti az érintett fájlokat és a teljes tartalmukat.

Dátumkiválasztók a böngészőkben

Amire a kódban külön fel szeretném hívni a figyelmet, hogy egy új bemeneti típust használtam, a datetime-local-t, ami egy nagyon hasznos elem. Segítségével együttesen tudunk dátumot és időt meghatározni. Van vele viszont "probléma" is sajnos, ami pedig a böngészők és nyelvi beállításaik különbségéből adódhat. Míg Chrome-ot (balra) én magyarul használom, emiatt a "magyar szemek" számára megfelelően jelenik meg, a Firefox-ot angolul használom, emiatt a Firefox-ban (jobbra) az angolszász dátum- (hónap/nap/év) és 12 órás időformátumot (AM/PM) követi.

Ha viszont a Firefox-hoz telepítem a magyar nyelvi csomagot és a beállításainál átállítom a nyelvet magyarra, majd újratöltöm a weboldalamat, akkor már a magyar dátum- és időformátumban fog megjelenni ez a bemeneti mező:

Látható tehát, hogy ennek a mezőnek a megjelenítése a böngészőnk nyelvi beállításától függ. Fontos viszont, hogy adatbázis szempontból (tábla beszúrásnál) működik mindkét formátummal, úgyhogy ott nincsen probléma (megy magyar nyelvű Chrome-ban és  angol nyelvű Firefox-ban is az új repülőjárat felvétele).

Validációs szempontból, látható, hogy a HTML input mezőhöz már adtam hozzá megszorításokat (lásd a fenti HTML kódot), így a dátumos mezőkbe bekerültek, minimális és maximális értékek a dátumhoz, ezek a korábbi / későbbi dátumok a lenyíló dátum kiválasztásánál szürkék, inaktívak, így a felhasználó nem is tudja őket kiválasztani. Ezek azonban statikus értékek, tehát olyat például nem tudok nekik beállítani, hogy a minimálisan kiválasztható dátum a mai nap legyen, vagy hogy a repülőjáratnál a megérkezés ideje mezőnél a minimálisan kiválasztható dátum az elindulás ideje legyen (erre persze 0% lenne még az esély, kivétel, ha fénysebességgel menne a repülő).


Dátumszűrés: Just Validate Plugin Date

Az imént felsorolt validációs beállításokhoz valamilyen további ellenőrzésre lenne szükségünk, amihez a HTML kevés, viszont a Javascript és a kiválasztott osztálykönyvtárunk már tud segítséget nyújtani, illetve annak egy plugin-ja (kiterjesztése). A telepítést ismét megpróbáltam CDN-en keresztül, de sajnos 404-es hibába futottam:

A böngészőben is jelzi, hogy nem található ez a verziójú plugin (a "3.8.1" helyett a "latest" szót írtam be természetesen, de automatikusan ugrott erre a verziószámra és jelezte is, hogy nem működik). Úgyhogy váltanom kellett és inkább az npm-es telepítést választottam ennél a plugin-ná l(mivel ez a fő csomagnak csak egy kiterjesztése), emiatt inkább a fő csomagot, a Just Validate-et is npm segítségével telepítettem, majd importáltam be az alkalmazásomba.

Just Validate telepítése

A terminal-ban így telepítettem:

npm install just-validate

Ennek hatására bekerül a csomag a node_modules mappába (ellenőrizhetjük és böngészhetjük is a tartalmát) és most már a Javascript kódjainkban is használható lesz, hasonló módon, mint ahogy a SCSS fájloknál megismertük itt. De ehhez a resources/js/app.js fájlt bővítsük ezzel a sorral:

window.JustValidate = require('just-validate');

Majd következhet a terminal-ban az npm run dev parancs kiadása, amivel "belefordítódik" ez az importálás a public/js/app.js fájlba. Ez azért is történik meg, hiszen a projektünk gyökerében lévő webpack.mix.js fájlban alapértelmezetten benne volt ennek az app.js fájlnak a fordítása a forrással és a céllal együtt.

Ez azonban még nem elég, hiszen ezt az app.js fájlt még el kell helyezni a használat miatt az "alnézet" oldalunk HTML kódjában is: most ugye mi a nézetek közül a flights/create.blade.php -t fejlesztjük, úgyhogy töltsük ki vele ezt a "szekciót":

@push('head-scripts')
  <script src="{!! mix('js/app.js') !!}"></script>
@endpush

Oké, most már használható lenne a Just Validate csomag a flights/create nézetben, azonban ez nekünk még mindig nem elég, hiszen mi egy plugin-ját akarjuk használni, amivel a dátumokat tudjuk ellenőrizni.

Just Validate Plugin Date telepítése

Telepítsük ezt is a projektünkbe a terminal-on keresztül:

npm install just-validate-plugin-date

Ezután a resources/js/app.js fájlt bővítsük ezekkel a sorokkal:

import JustValidatePluginDate from 'just-validate-plugin-date';
window.JustValidatePluginDate = JustValidatePluginDate;

Majd következhet a terminal-ban az npm run dev parancs kiadása (megjegyzés: ha tudjuk előre, hogy sokszor / többször kell majd fordítanunk ezeket a kijelölt fájlokat, akkor az npm run watch parancsot is alkalmazhatjuk, ilyenkor ő figyeli a webpack.mix.js-ben kijelölt fájlokat, és ha mi mentjük őket, akkor automatikusan és gyorsan elvégzi az újrafordításukat). Így importáltuk ezt a plugin-t is az app.js fájlba. Jöhet ismét a flights/create nézet fájl, és a scripts részt fogjuk bővíteni (ami gyakorlatilag a body záró tag-je elé fog bekerülni a végső kódban):

@push('scripts')
<script>
  const validation = new JustValidate('#flightCreateForm');

  validation.addField('#scheduled_arrived_at', [{
    plugin: JustValidatePluginDate(() => ({
      // format: 'dd MMM yyyy',
      // isBefore: '15/12/2022',
      isAfter: document.getElementById('scheduled_departured_at').value
    })),
    errorMessage: 'A tervezett érkezési dátumnak/időnek a tervezett indulási dátum/idő után kell lennie.',
  }])
  .onSuccess((ev) => {
    ev.target.submit();
  });
</script>
@endpush

Ezzel beállítjuk, hogy a tervezett érkezési dátumnak és időnek a tervezett indulási dátum és idő után kell lennie, ahogy a hibaüzenet - errorMessage - is jelzi a kódban, hogy ha nem teljesül ez a feltétel. Az isAfter-en kívül a következő szabályokat tudjuk még beállítani vele:

  • isBefore (valamilyen dátum előtt)
  • isBeforeOrEqual (valamilyen dátum előtt vagy megegyezően)
  • isAfterOrEqual (valamilyen dátum után vagy megegyezően)
  • isEqual (valamilyen dátummal megegyezően)
  • format (valamilyen specifikus dátumformátumot is meg tudunk neki határozni)

Ezek együttesen is alkalmazhatók, nyilván logikusan adjuk meg a dátumhatárokat (például ne legyen a minimális dátumlimit nagyobb, mint a maximális dátumlimit) és figyeljünk a beszédes hibaüzenetekre is. A kódban én benne is hagytam két ilyen validációs beállítást példaként, kikommentelve (format, isBefore).

Tesztelés

Teszteléskor meg is kapjuk a hibaüzenetet a Mentés gomb alatt, ha nem megfelelő a két dátum viszonya (sárgával kiemeltem a fontos részeket):


Repülőjáratok szerkesztése és frissítése

Innentől ez már nem is lenne nehéz, ugye? De azért segítek. A Laravel-es háttér kapcsán vegyük át a teendőket:

  1. Adattábla: ez már teljes ✅
  2. Útvonalak (edit, update): ezek megvannak ✅
  3. FlightsController megvan ✅, de hiányoznak belőle még az edit és update metódusok ❌ (a felsorolás után ezzel fogom folytatni rögtön)
  4. Index nézetből el tudunk jutni a szerkesztési űrlapra ✅
  5. Az edit nézetünk hiányzik ❌, de ahhoz fel tudjuk használni a create nézetet, csak kicsit kell átalakítani ✅

A FlightsController új metódusai:

public function edit(Flight $flight)
{
  return view('flights.edit', [
    'flight' => $flight,
    'captains' => Captain::orderBy('name')->get(),
    'airlines' => Airline::orderBy('name')->get()
  ]);
}

public function update(Request $request, Flight $flight)
{
  $flight->update(request()->all());
  return redirect(route('flights.index'));
}

A flights/edit nézet:

Mi változott a create űrlaphoz képest az edit-ben?

  • A form action attribútuma az update útvonalra küldi majd el az adatokat.
  • A form id attribútuma flightEditForm lett.
  • A @csrf direktíva után bekerült a @method('PUT') direktíva, ahogy ezeket már tanultuk itt.
  • Az egyszerűbb input mezőknél a value attribútum kap előre beállított értékeket. Amelyik mező nem kötelező az adatbázisban történő értékadás (lehetnek NULL-ok is), ott egy @if direktívát használva ellenőrzöm, hogy volt-e értéke a táblában.
  • A select-option-öknél a @selected direktívát használtam arra, hogy az adatbázisban lévő értéket állítsam be alapértelmezettnek.
  • A gomb feliratát pedig "Mentés"-ről "Frissítés"-re írtam át.
  • és ennyi...

A két @push szekcióban annyi a különbsége a create nézetben meghatározotthoz képest, hogy most a new JustValidate konstruktor paramétere már az új #flightEditForm lesz és a validáció ugyanúgy fog működni, mint a create esetében.


Összegzés

További információkhoz érdemes részletesen áttekinteni a Just Validate csomag dokumentációját, példákat (ha valaminek a működése nem világos, akkor az űrlap és a kapcsolódó Javascript kódját is lehet böngészgetni, hiszen kliens oldalon vagyunk). Ki is lehet próbálni azokat a funkcionalitásokat, amire még szükségetek van. Gyakorlásként tehát megnézhetitek és tesztelgethetik még a Just Validate és dátumellenőrzés plugin-jának funkcionalitásait is.

A bejegyzés kódjait ebben a Github commit-ben találjátok meg.

Összefoglalásként azt gondolom, hogy rengeteget tanulhatunk ebből a bejegyzésből is, főleg annak kapcsán, hogy hogyan kell egy telepített Javascript csomagot működésre bírni és használni.

Ez meglehetősen hosszú bejegyzés lett, sokat is küzdöttem némelyik probléma megoldásával, így másik kliens oldali Javascript validációs osztálykönyvtárat már nem ismertetek külön, viszont a funkcionalitások a többinél is hasonlóak vagy csak kicsit eltérőek. A következőkben már a Laravel-es, tehát a szerver oldali validációval fogom folytatni.