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 :

img35

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 :

img36

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 :

img37

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 :

img38

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 :

img39

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.

  1. Gch

    Bonjour, je voulais faire un code très simple rien que pour essayer l’utilisation de  » @yield « , voilà le code : – Le fichier parent :

    Laravel

    @yield(‘containertest’)

    – Le fichier enfant :
    @extends(‘test’)

    @section(‘containertest’)
    Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
    @endsection

    ==> L’actualisation n’affiche pas le contenu de la section ‘containertest’

    • Author bestmomo

      Bonjour,

      Ce code devrait fonctionner, est-ce que les noms des fichiers comportent bien « blade » dans l’extension et se trouvent dans le même dossier ?

      • Gch

        En effet, les noms des fichiers sont test.blade.php et test2.blade.php ; ils se trouvent dans le même dossier.
        J’ai essayé avec un autre code, on dirait que  » @yield(‘nomdela section’)  » n’est pas pris en compte!!!

        • Author bestmomo

          C’est un comportement suspect, il faudrait réinstaller Laravel au cas où…

  2. paguemaou

    Je n’ai pas très bien compris pourquoi Illuminate\Html était sur le déclin et qu’il ne faisait plus partie de Laravel 5 en standard. As-tu des liens à ce sujet ?

    D’autre part, laravelcollective/html sera-t’il LE composant leader de remplacement, où y aura t’il des forks ?

    Cordialement,

    Paguemaou

    • Author bestmomo

      Tout simplement Taylor a décidé d’arrêter de développer ce composant. Laravelcollective l’a repris et le fera vivre, tout comme les annotations qui ont un moment été intégrées à L5 puis finalement abandonnées (je les avais utilisées pour l’application et j’aimais bien le concept). Il existe d’autres composants pour le HTML (comme https://github.com/Braunson/laravel-html5-forms pour les formulaires) et il en paraîtra bien d’autres je pense.

Laisser un commentaire