Article mis à jour le 29/10/2015
Lorsqu'on crée une application on a finalement pas mal de travail pour créer les vues. Même en utilisant un framewok CSS comme Bootstrap on se retrouve à écrire de nombreuses lignes de code. Et on a du mal à respecter la doctrine DRY (Dont Repeat Yourself).
La version 4 de Laravel comportait le composant Illuminate\Html. Ce composant existe toujours mais il n'est plus chargé par défaut, Laravel pouvant servir à autre chose, par exemple créer une API. D'autre part ce composant n'évoluera plus. Son successeur est laravelcollective/html. Vous pouvez trouver sa documentation ici.
Cet article est destiné à montrer comment ce composant a été utilisé et étendu dans l'application.
FormBuilder
Installation
L'installation de ce composant se fait de façon classique à partir de composer.json :"require": { "laravel/framework": "5.1.*", "laravelcollective/html": "5.1.*" },
Il suffit ensuite de faire un composer update pour charger le composant et mettre à jour le chargement automatique des classes. On a alors le composant présent dans le dossier vendor :
Il faut aussi prévoir de signaler à Laravel que ce composant existe dans config/app.php :'providers' => [ // ... Collective\Html\HtmlServiceProvider::class, // ... ],Mais vous ne trouverez pas cette ligne de code dans l'application pour une raison que nous allons voir au prochain chapitre. Il faut aussi enregistrer les façades :
'aliases' => [ // ... 'Form' => Collective\Html\FormFacade::class, 'Html' => Collective\Html\HtmlFacade::class, // ... ],Avec tout ça le composant est directement utilisable dans les vues.
Les macros
La façon la plus classique d'étendre les possibilités des classes FormBuilder et HtmlBuilder est d'utiliser des macros.
En effet si on regarde la classe FormBuilder par exemple :
class FormBuilder { use Macroable;
On trouve la déclaration du trait Macroable. Ce trait fait partie de Laravel :
Il donne la possibilité de créer des macros pour la classe concernée. Par exemple :
Form::macro('specialField', function() { return '<input type="special">'; });
On peut alors l'utiliser dans une vue :
{!! Form::specialField() !!}
J'ai choisi de ne pas utiliser cette possibilité mais de plutôt étendre la classe FormBuilder.
Étendre FormBuilder
Vous trouverez dans les services le dossier Html :
La classe FormBuilder étend celle du composant :
<?php namespace App\Services\Html; class FormBuilder extends \Collective\Html\FormBuilder {
L'initialisation se fait dans le provider HtmlServiceProvider qui étend lui aussi le provider du composant :
<?php namespace App\Services\Html; class HtmlServiceProvider extends \Collective\Html\HtmlServiceProvider { /** * Register the form builder instance. * * @return void */ protected function registerFormBuilder() { $this->app->bindShared('form', function($app) { $form = new FormBuilder($app['html'], $app['url'], $app['session.store']->getToken()); return $form->setSessionStore($app['session.store']); }); } }
En effet on veut seulement surcharger la méthode registerFormBuilder pour que le conteneur de Laravel instancie la classe du service au lieu de celle du composant.
Il faut aussi prévoir de signaler à Laravel que ce composant existe dans config/app.php :
'providers' => [ // ... App\Services\Html\HtmlServiceProvider::class, // ... ],
L'intendance est ainsi en place et il ne reste plus qu'à coder la classe et l'utiliser.
Les méthodes ajoutées
Bouton de soumission
Comme il y a de nombreux formulaires on retrouve autant de fois le bouton de soumission. Son code de base est celui-ci :
<div class="form-group *"> <input class="btn btn-default" type="submit" value="Envoyer"> </div>
L'astérisque représente une classe de la grille de Bootstrap. Voici en conséquence le code de la méthode pour générer le bouton :
public function submit($value = null, $options = []) { return sprintf(' <div class="form-group %s"> %s </div>', empty($options) ? '' : $options[0], parent::submit($value, ['class' => 'btn btn-default']) ); }
Le deuxième paramètre est un tableau pour correspondre à la signature de la méthode de la classe parente.
Voici un cas d'utilisation avec le paramètre renseigné :
{!! Form::submit(trans('front/form.send'), ['col-lg-12']) !!}
Bouton de suppression
On a aussi plusieurs fois la présence d'un bouton de suppression avec ce code :<input class="btn btn-danger btn-block " type="submit" value="*" onclick="return confirm(*)">Les astérisques représentent les valeurs variables. Voici le code de la méthode :
public function destroy($text, $message, $class = null) { return parent::submit($text, ['class' => 'btn btn-danger btn-block ' . ($class? $class:''), 'onclick' => 'return confirm(\'' . $message . '\')']); }Avec un cas d'utilisation :
{!! Form::destroy(trans('back/blog.destroy'), trans('back/blog.destroy-warning')) !!}
Les contrôles
Il y a de nombreux contrôles utilisés dans les formulaires de l'application. Ils ont tous la même structure :<div class="form-group *"> <label class="control-label" for="*">*</label> <input id="*" class="form-control" type="*" name="*" placeholder="*"> </div>
Cette fois on a pas mal d'astérisques parce que les possibilités sont très variées. Voici la méthode correspondante :
public function control($type, $colonnes, $nom, $errors, $label = null, $valeur = null, $pop = null, $placeholder = '') { $attributes = ['class' => 'form-control', 'placeholder' => $placeholder]; return sprintf(' <div class="form-group %s %s"> %s %s %s %s </div>', ($colonnes == 0)? '': 'col-lg-' . $colonnes, $errors->has($nom) ? 'has-error' : '', $label ? $this->label($nom, $label, ['class' => 'control-label']) : '', $pop? '<a href="#" tabindex="0" class="badge pull-right" data-toggle="popover" data-trigger="focus" title="' . $pop[0] .'" data-content="' . $pop[1] . '"><span>?</span></a>' : '', call_user_func_array(['Form', $type], ($type == 'password')? [$nom, $attributes] : [$nom, $valeur, $attributes]), $errors->first($nom, '<small class="help-block">:message</small>') ); }Ça permet des vues très épurées, par exemple :
{!! Form::control('text', 6, 'name', $errors, trans('front/contact.name')) !!} {!! Form::control('email', 6, 'email', $errors, trans('front/contact.email')) !!} {!! Form::control('textarea', 12, 'message', $errors, trans('front/contact.message')) !!}Ce qui génère ceci :
<div class="form-group col-lg-6 "> <label class="control-label" for="name">Votre nom</label> <input id="name" class="form-control" type="text" name="name" placeholder=""> </div> <div class="form-group col-lg-6 "> <label class="control-label" for="email">Votre Email</label> <input id="email" class="form-control" type="email" name="email" placeholder=""> </div> <div class="form-group col-lg-12 "> <label class="control-label" for="message">Votre message</label> <textarea id="message" class="form-control" rows="10" cols="50" name="message" placeholder=""></textarea> </div>
Case à cocher
On a aussi quelques fois une case à cocher avec ce code :<div class="checkbox col-lg-12"> <label> <input type="checkbox" value="" name="*"> * </label> </div>Voici la méthode :
public function check($name, $label) { return sprintf(' <div class="checkbox col-lg-12"> <label> %s%s </label> </div>', parent::checkbox($name), $label ); }Et un cas d'utilisation :
{!! Form::check('memory', trans('front/login.remind')) !!}
Liste
On a aussi des listes avec du code dans ce genre :<select id="*" class="form-control" name="*"> <option value="*">*</option> ... </select>Voici la méthode :
public function selection($nom, $list = [], $selected = null, $label = null) { return sprintf(' <div class="form-group" style="width:200px;"> %s %s </div>', $label ? $this->label($nom, $label, ['class' => 'control-label']) : '', parent::select($nom, $list, $selected, ['class' => 'form-control']) ); }Et un cas d'utilisation :
{!! Form::selection('role', $select, $user->role_id, trans('back/users.role')) !!}Avec ce code généré :
<select id="role" class="form-control" name="role"> <option value="1">Administrator</option> <option value="2">Redactor</option> <option selected="selected" value="3">User</option> </select>Toutes les méthodes créées pourraient évidemment l'être différemment. Le seul but est de fournir un exemple de réalisation.
Les vues partielles
Il est souvent judicieux pour éviter les répétitions de code d'utiliser des vues partielles. Regardez le template du front-end :
On y trouve code :
<main role="main" class="container"> @if(session()->has('ok')) @include('partials/error', ['type' => 'success', 'message' => session('ok')]) @endif @if(isset($info)) @include('partials/error', ['type' => 'info', 'message' => $info]) @endif @yield('main') </main>
On voit que si on veut afficher une barre d'erreur on appelle la vue partielle partials/error en lui transmettant des variables. Voici cette vue partielle :
<div class="alert alert-{{ $type }} alert-dismissible" role="alert"> <button type="button" class="close" data-dismiss="alert"> <span aria-hidden="true">×</span> <span class="sr-only">Close</span> </button> {!! $message !!} </div>
On évite ainsi de répéter le code de mise en forme de la barre de Bootstrap.
On a aussi deux vues partielles dans le back-end :
Le principe est le même. Par exemple la vue partielle pannel.blade.php est destinée à mettre en forme un petit panneau de l'administration :
<div class="col-lg-4 col-md-6"> <div class="panel panel-{{ $color }}"> <div class="panel-heading"> <div class="row"> <div class="col-xs-3"> <span class="fa fa-{{ $icone }} fa-5x"></span> </div> <div class="col-xs-9 text-right"> <div class="huge">{{ $nbr['new'] }}</div> <div>{{ $name }}</div> </div> </div> </div> <a href="{{ $url }}"> <div class="panel-footer"> <span class="pull-left">{{ $nbr['total'] . ' ' . $total }}</span> <span class="pull-right fa fa-arrow-circle-right"></span> <div class="clearfix"></div> </div> </a> </div> </div>
Ainsi avec les deux vues partielles la vue index de l'administration est légère et lisible :
@extends('back.template') @section('main') @include('back.partials.entete', ['title' => trans('back/admin.dashboard'), 'icone' => 'dashboard', 'fil' => trans('back/admin.dashboard')]) <div class="row"> @include('back/partials/pannel', ['color' => 'primary', 'icone' => 'envelope', 'nbr' => $nbrMessages, 'name' => trans('back/admin.new-messages'), 'url' => 'contact', 'total' => trans('back/admin.messages')]) @include('back/partials/pannel', ['color' => 'green', 'icone' => 'user', 'nbr' => $nbrUsers, 'name' => trans('back/admin.new-registers'), 'url' => 'user', 'total' => trans('back/admin.users')]) @include('back/partials/pannel', ['color' => 'yellow', 'icone' => 'pencil', 'nbr' => $nbrPosts, 'name' => trans('back/admin.new-posts'), 'url' => 'blog', 'total' => trans('back/admin.posts')]) @include('back/partials/pannel', ['color' => 'red', 'icone' => 'comment', 'nbr' => $nbrComments, 'name' => trans('back/admin.new-comments'), 'url' => 'comment', 'total' => trans('back/admin.comments')]) </div> @stop
Organiser ainsi les vues n'est pas forcément facile parce que les combinaisons sont nombreuses. Il faut s'efforcer de faire le plus simple possible.
Par bestmomo
Nombre de commentaires : 6