Oggi ti presento Former, un package in PHP per Laravel che ti permette di gestire i form in modo incredibilmente semplice.
Former fa tanto lavoro al posto tuo: si occupa della ripopolazione dei campi, della validazione, della gestione dei grouped field e si distingue per l’integrazione con framework CSS quali Bootstrap e Foundation.
Introduzione
Lo scopo di Former è quello di ripensare in maniera elegante il processo di creazione dei form trasformando ogni campo in un proprio “oggetto” con attributi e metodi. In pratica una cosa del genere:
Former::horizontal_open()
->id(‘MyForm’)
->secure()
->rules([‘name’ => ‘required’])
->method(‘GET’)
Former::xlarge_text(‘name’)
->class(‘myclass’)
->value(‘Joseph’)
->required();
Former::textarea(‘comments’)
->rows(10)->columns(20)
->autofocus();
Former::actions()
->large_primary_submit(‘Submit’)
->large_inverse_reset(‘Reset’)
Former::close()
In riferimento all’esempio, se chiami un metodo che non esiste, Former assumerà che tu stia tentando di settare un attributo e quindi lo creerà. Scrivendo ->rows(10)
definirai un attributo che indica il numero di righe.
Se invece vuoi definire un metodo che contiene dei trattini puoi farlo utilizzando l’underscore nel nome del metodo: con ->data_foo('bar')
definisci l’attributo data-foo="bar"
.
Ora ti starai chiedendo: e se voglio definire un attributo con underscore? Fugo subito i tuoi dubbi: setAttribute('data_foo', 'bar')
ed è fatta.
Iniziamo
Concetti di base
Former va concepito come un helper per le tue view. Questo significa che puoi utilizzare i suoi metodi all’interno delle stesse così:
method(‘GET’) ?>
required() ?>
Se utilizzi un template engine come Twig o simili, puoi usare Former all’esterno delle tue view in questo modo:
$form = Former::open()->method(‘GET’);
$form .= Former::text(‘name’)->required();
$form .= Former::close();
Installazione
La procedura è semplicissima. Aggiungi la seguente riga al file composer.json
:
“anahkiasen/former”: “dev-master”
Aggiungi il service provider di Former alla lista providers nel file app/config/app.php
:
‘Former’ => ‘FormerFacadesFormer’,
Installazione con il Package Installer
Semplicemente, lancia il seguente comando Artisan:
artisan package:install anahkiasen/former:dev-master
Installazione fuori dal framework
Per installare il package in un progetto esterno al framework usa comunque Composer, non scordandoti però di aggiungere, dove necessario, il
use FormerFacadesFormer;
Features
Integrazione con Bootstrap e Foundation
Finora potrai esserti chiesto se questa non sia altro che una variante della classe Form già inclusa in Laravel. Dov’è la sua potenzialità? La risposta è semplice: Former si integra con vari framework e permettendoti di riconoscere, ad esempio, se stai definendo un form orizzontale o verticale inserendolo direttamente all’interno dei tag giusti per l’inclusione nel form.
Un esempio? Il codice:
Former::select(‘clients’)->options($clients, 2)
->help(‘Pick some dude’)
->state(‘warning’)
genera questo html (con Bootstrap):
Pick some dude
Former usa come Framework di default Bootstrap ma puoi scegliere di cambiarlo con il metodo Former::framework()
. Al momento Former supporta le seguenti opzioni 'TwitterBootstrap','ZurbFoundation'
and 'Nude'
(per non usare framework)
/ /disabilita la sintassi Bootstrap
Former::framework(‘Nude’);
// Rabilita la sintassi Bootstrap
Former::framework(‘TwitterBootstrap’);
Ecco un esempio con Foundation:
Former::framework(‘ZurbFoundation’);
Former::four_text(‘foo’)->state(‘error’)->help(‘bar’)
e il relativo output:
Bar
Collegamento con il Validatore di Laravel
Bene, negli esempi visti prima hai fatto un bel passo avanti: non dovrai più chiamare la funzione Form::control_group()
e sarai più spedito nella costruzione del form. Ma ora ti starai chiedendo: “come faccio per la validazione? dovrò comunque validare a mano tutti i campi”.
Buone notizie: non dovrai. Il metodo withErrors()
di Former accorre in tuo aiuto.
Former non solo si occupa di fare il wrapping dei tuoi campi all’interno dei control group, ma controlla anche l’oggetto Message
verificando se un campo contiene o meno errori ed evidenziandolo, eventualmente, con l’attributo .help-inline
. Vediamo come.
Ecco due esempi dopo una validazione non andata a buon fine:
Se renderizzi una view dopo una validazione fallita (senza redirect):
if($validation->fails()) {
Former::withErrors($validation);
return View::make(‘myview’);
}
Se fai un redirect dopo una validazione fallita:
if($validation->fails()) {
return Redirect::to(‘login’)
->with_errors($validation);
}
Avrai notato come nell’ultimo esempio non chiami Former. Il perché è presto detto. Ogni volta che costruisci un form con Former, questo si occuperà di controllare se nell’array Session è presente l’oggetto errors
e, in caso affermativo, lo userà senza richiedere altro. Puoi disabilitare la possibilità di fetching automatico degli errori impostando l’opzione seguente: Former::config('fetch_errors', false)
.
Popolare i Form
Popolare un form con Former è semplice, basta usare il metodo Former::populate
. Puoi farlo in due modi. Il primo è quello di passare un array di valori:
//Popolerà il campo ‘name’ con il valore ‘value’
Former::populate( array(‘name’ => ‘value’) )
Il secondo è quello di passare un Eloquent model. Se ad esempio hai un model “Client” puoi operare così:
Former::populate( Client::find(2) )
Former riconosce il model e lo popola con i suoi attributi. Supponi che il model abbia l’attributo name
impostato a Foo e l’attributo firstname impostato a Bar. Bene, Former non farà altro che ripopolare i campi name e firstname rispettivamente con i valori Foo e Bar.
In alternativa puoi anche ripopolare un campo dopo aver ripopolato l’intero form (è il caso delle relazioni ad esempio) operando così:
Former::populate($project)
Former::populateField(‘client’, $project->client->name)
Per il resto ripopolare un form è cosa banale grazie al comando: ->value('something')
. Per generare una lista di option per una <select>
chiama il metodo Former::select('foo')->options([array], [facultative: selected value])
. Puoi anche usare il risultato di una query (Eloquent o Fluent) per popolare gli option di una select:
Former::select(‘foo’)->fromQuery(Client::all(), ‘name’, ‘id’)
Il secondo argomento rappresenta il testo dell’option mentre il terzo rappresenta il valore (di default il valore è id
). Ma Former può fare molto di più. Supponi di passare un model Eloquent alla tua select senza aver specificato il secondo e il terzo parametro. Former tenterà di recuperare la chiave usando il metodo Eloquent get_key()
e userà il metodo __toString()
per bindare le variabili. Guarda questo esempio:
class Client extends Eloquent
{
public static $key = ‘code’;
public function __toString()
{
return $this->name;
}
}
Former::select(‘clients’)->fromQuery(Client::all());
Sicuro e secondo il paradigma “stay DRY”. Il seguente codice è equivalente ma molto più oneroso: usa tutte le chiavi di default del model Client e come output l’attributo name per la label delle option.
Foo
@foreach(Client::all() as $client)
@if(Input::get(‘foo’, Input::old(‘foo’)) == client->code)@else@endif
@endforeach
Former è anche in grado di popolare dei campi coinvolti in una relazione. Un esempio vale più di mille parole (a meno che il vostro esempio non superi le mille parole).
Former::populate(Client::find(2))
// Sarà popolato con $client->name
Former::text(‘name’)
//Sarà popolato con $client->store->name
Former::text(‘store.name’)
// Puoi scegliere il livello di profondità che vuoi
Former::text(‘customer.name.adress’)
// Sarà popolato con la data di tutte le date di reservation di Client
Former::select(‘reservations.date’)
// Stessa cosa di ^
Former::select(‘reservations’)->fromQuery($client->reservations, ‘date’)
// Se stai usando un testo e non una select, invece di elencare i modelli della relazione come opzioni li concatenerà
Former::text(‘customers.name’) // Mostrerà “name, name, name”
// Puoi rinominare un campo successivamente per una più facile gestione dell’Input
Former::text(‘comment.title’)->name(‘title’)
Datalist
Un’altra cosa che Former può fare è creare dei Datalist.
Sai cosa sono? Hai presente quando a volte vuoi far scegliere all’utente qualcosa da una selezione ma nello stesso tempo vuoi anche dargli la possibilità di scriverlo se quello che vuole non c’è nella lista? Bene, quello è un Datalist e crearlo con Former è molto semplice:
Former::text(‘clients’)->useDatalist($clients)
//Puoi usare un Query object, stessa sintassi poi per fromQuery()
Former::text(‘projects’)->useDatalist(Project::all(), ‘name’)
Puoi anche (se ne hai bisogno) assegnare un id al datalist appena creato in questo modo: Former::text('foo')->list('myId')->useDatalist()
. In questo modo genererai il corrispondente <datalist>
e lo linkerai tramite id
a quel campo. Ciò significa che il tuo text input sarà popolato dai valori del tuo array fino a quando l’utente non inserisce ciò che vuole.
Live Validation
Bene, andiamo avanti. Come molti di voi sapranno, oggi praticamente tutti i browser moderni supportano la validazione lato client con il solo utilizzo di attributi html. È il caso di attributi come pattern
, required
, max/min
ecc…. Quando validi un form lato server hai il tuo array $rules
in cui definisci le regole di validazione. Non sarebbe fantastico trasferire quelle regole automaticamente anche lato client in modo da non dover ridefinire due volte le stesse cose?
Former fa proprio questo e con estrema facilità. Ecco un esempio:
Former::open()->rules(array(
‘name’ => ‘required|max:20|alpha’,
‘age’ => ‘between:18,24’,
‘email’ => ‘email’,
‘show’ => ‘in:batman,spiderman’,
‘random’ => ‘match:/[a-zA-Z]+/’,
‘birthday’ => ‘before:1968-12-03’,
‘avatar’ => ‘image’,
));
Quello che Former fa è creare l’html con gli attributi in base alle regole che abbiamo definito sopra. Non ci sono molte regole per ora ma la cosa è in via di sviluppo. Ecco come diventa il codice di sopra lato client:
Inoltre puoi sempre aggiungere regole personalizzate usando espressioni Regex:
Former::number(‘age’)->min(18)
Former::text(‘client_code’)->pattern(‘[a-z]{4}[0-9]{2}’)
Bello vero?! E non finisce qui. Poiché Bootstrap riconosce la validazione in tempo reale, prova a fare un test scrivendo qualcosa che non faccia match con la regola alpha
del campo nome e vedrai come si evidenzierà in rosso. Proprio come quando viene assegnata la classe error.
Non ti rimane che aprire il tuo Chrome/Firefox/o-quel-che-è e vedere come ogni volta si aprirà un piccolo pop up che dirà cose come: “Devi riempire quel campo” oppure “L’email non è nel formato giusto, mi credi stupido o cosa?”
Puoi anche impostare manualmente lo stato di un control group (disponibile solo per Bootstrap e Foundation) usando uno degli attributi messi a disposizione come: success
, warning
, error
and info
.
Former::text(‘name’)->state(‘error’)
Gestione dei File
In Former come in Laravel, puoi creare un campo file semplicemente così: Former::file
.
Cosa c’è di nuovo in questo caso? Beh, puoi creare campi file multipli semplicemente così Former::files
generando il seguente html corrispondente: <input type=""file"" name=""foo[]"" multiple="">
.
Uno dei metodi speciali è ->accept()
che ti permette di:
// Usare uno shortcut per accettare tipi di file (image, video o audio)
Former::files(‘avatar’)->accept(‘image’)
// Usare un’estensione che verrà converita in MIME da Laravel
Former::files(‘avatar’)->accept(‘gif’, ‘jpg’)
//O usare direttamente un MIME
Former::files(‘avatar’)->accept(‘image/jpeg’, ‘image/png’)
Puoi settare la grandezza del file esprimendola in bit o byte:
Former::file(‘foo’)->max(2, ‘MB’)
Former::file(‘foo’)->max(400, ‘Ko’)
Former::file(‘foo’)->max(1, ‘TB’)
Questo tipo di comandi creerà un campo hidden MAX_FILE_SIZE
con il valore corretto in bytes.
Checkbox e Radio Button
I checkbox e i radio button sono di una noia mortale. Soprattutto quando ne devi creare molti. Con Former è tutto un pò più semplice.
// Creare un checkbox
Former::checkbox(‘checkme’)
// Creare un checkbox con testo e checkarlo
Former::checkbox(‘checkme’)
->text(‘YO CHECK THIS OUT’)
->check()
// Creare 4 checkbox connessi
Former::checkboxes(‘checkme’)
->checkboxes(‘first’, ‘second’, ‘third’, ‘fourth’)
// Creare checkbox connessi inline
Former::checkboxes(‘checkme’)
->checkboxes($checkboxes)->inline()
// Ciò che è valido per un checkbox vale anche per i radio button
Former::radios(‘radio’)
->radios(array(‘label’ => ‘name’, ‘label’ => ‘name’))
->stacked()
// Puoi definire checkbox e radio stacked o inline chiamando i relativi metodi
Former::inline_checkboxes(‘foo’)->checkboxes(‘foo’, ‘bar’)
Former::stacked_radios(‘foo’)->radios(‘foo’, ‘bar’)
//Impostare quali checkbox sono checkati o no in un solo comando
Former::checkboxes(‘level’)
->checkboxes(0, 1, 2)
->check(array(‘level_0’ => true, ‘level_1’ => false, ‘level_2’ => true))
// Fine tune checkable elements
Former::radios(‘radio’)
->radios(array(
‘label’ => array(‘name’ => ‘foo’, ‘value’ => ‘bar’, ‘data-foo’ => ‘bar’),
‘label’ => array(‘name’ => ‘foo’, ‘value’ => ‘bar’, ‘data-foo’ => ‘bar’),
))
Un aspetto importante: Former ti offre un’opzione per forzare il pushing dei checkbox. Cosa vuol dire?
Si tratta di quando i tuoi checkbox appaiono nell’array POST anche se non sono checked. E’ un comportamento non molto naturale. Per questo Former ti da la possibilità di cambiare il valore di un checkbox non checked nell’array POST attraverso l’opzione unchecked_value
.
Quando crei dei molti campi checkabili attraverso i metodi checkboxes/radios()
, per orgnuno di loro verrà utilizzato il nome che hai specificato legato ad un numero seriale tramite un underscore, ecco un esempio: <input type=""checkbox"" name=""checkme_2"">
Il campo verrà anche ripopolato nel caso fosse stato “checkato”.
Helper per la Localizzazione
Former ti aiuta anche nel caso tu stessi lavorando su un progetto multilingua.
Quando crei un campo, se non hai specificato una label, Former utilizza il nome di dafault e in maniera automatica tenterà anche di tradurlo. Questo vale per le label dei checkbox, gli help text e le legend dei form. Vediamo alcuni esempi:
// Questo…
Former::label((‘validation.attributes.name’))
Former::text(‘name’, (‘validation.attributes.name’))
Former::text(‘name’)->inlineHelp((‘help.name’))
Former::checkbox(‘rules’)->text((‘my.translation’))
// ...è lo stesso di questo
Former::label(‘name’)
Former::text(‘name’)
Former::text(‘name’)->inlineHelp(‘help.name’)
Former::checkbox(‘rules’)->text(‘my.translation’)
Former::legend(‘mylegend’)
Non male vero? Former prima tenterà di tradurre la stringa da solo, my.text
ritornerà __('my.text')
, e in caso di fallimento cercherà in un posto alternativo che puoi impostare attraverso la variabile Former::config('translate_from', [boolean])
(di default validation.attributes
). Ricorda: deve essere un array.
Note sulle impostazioni dei Valori dei Campi
- i dati in POST hanno la precedenza su tutto il resto – se un utente digita qualcosa in un campo è anche probabile che è quello che si aspetta di vedere quando questo deve essere ripopolato;
- poi ci sono i campi impostati usando il metodo
->forceValue()
– è un caso particolare del metodo->value()
che forza un valore in un campo indipendentemente da ciò che accade; - successivamente ci sono i campi settati con il metodo
Former::populate()
. È il metodo usato per popolare un campo e, in caso di editing dello stesso, può essere sovrascritto da forceValue; - l’ultimo in ordine di priorità è il classico metodo
->value()
– è usato per popolare con valori di default un campo e viene sovrascritto da tutti gli altri e dai dati in POST;
Conclusioni
Per oggi basta: abbiamo analizzato le varie feature di Former e fatto un giro completo. Vuoi saperne di più? Aspetta la prossima settimana: entreremo più nel dettaglio.