· 7 min read

Découvrons Symfony UX avec Chart.js, Dropzone.js et Cropper.js

Symfony UX c'est une nouvelle initiative de Symfony pour simplifier l'utilisation de composants Javascript dans nos projets Symfony. Dans cet article nous allons découvrir ensemble 3 composants disponibles lors de la sortie de Symfony UX.

Symfony UX c'est une nouvelle initiative de Symfony pour simplifier l'utilisation de composants Javascript dans nos projets Symfony. Dans cet article nous allons découvrir ensemble 3 composants disponibles lors de la sortie de Symfony UX.

Introduction

Le 3 décembre 2020, lors de la conférence Symfony World, la sortie de Symfony UX a été annoncé. L’idée générale c’est d’offrir une expérience fluide et simplifié pour utiliser un certain nombre de composants Javascript.

Symfony UX consists of three main components:
- a Symfony integration for Stimulus to provide a new organization for JavaScript code in applications
- updates to Symfony Flex and Symfony Webpack Encore to automatically configure JavaScript code shipped by PHP packages
- a series of core UX packages designed to be reliable and composable to create amazing interfaces quickly

https://symfony.com/blog/new-in-symfony-the-ux-initiative-a-new-javascript-ecosystem-for-symfony

Pour découvrir cette nouveauté, je vous propose d’utiliser 3 composants UX disponible lors de la sortie de Symfony UX:

Création d’un nouveau projet Symfony et mise en place de Webpack

Histoire de partir sur de bonne bases, nous allons mettre en place un nouveau projet Symfony et y installer Webpack Encore.

symfony new UX --full
cd UX
composer require symfony/webpack-encore-bundle
npm install --force

Lançons le serveur interne Symfony.

symfony serve -d

Et vérifions le bon fonctionnement (chez moi sur l’adresse https://127.0.0.1:8004).

Chart.js

Chart.js c’est un super outil JavaScript pour fabriquer des graphiques très rapidement et simplement, et on va le voir ensemble, avec Symfony UX le travail est vraiment super simple ! Cool non !?

Commençons par installer ux-chartjs, télécharger les dépendances, et faire un build.

composer require symfony/ux-chartjs
npm install
npm run build

Mettons également à jour le fichier base.html.twig avec les CSS et JS générer par Webpack, comme indiqué dans la doc de Symfony.

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>{% block title %}Welcome!{% endblock %}</title>
        {% block stylesheets %}{{ encore_entry_link_tags('app') }}{% endblock %}
    </head>
    <body>
        {% block body %}{% endblock %}
        {% block javascripts %}{{ encore_entry_script_tags('app') }}{% endblock %}
    </body>
</html>

Histoire de rendre le test un peu plus proche d’un usage réel que l’exemple de la doc, nous allons créer une entité “DailyResult” qui va nous servir à stocker une date et une valeur associée. À vous de transposer dans votre cas d’usage ;-)

symfony console make:entity

Class name of the entity to create or update (e.g. GentleChef):
 > DailyResult

 New property name (press <return> to stop adding fields):
 > date

 Field type (enter ? to see all types) [string]:
 > datetime

 Can this field be null in the database (nullable) (yes/no) [no]:
 > no

 Add another property? Enter the property name (or press <return> to stop adding fields):
 > value

 Field type (enter ? to see all types) [string]:
 > integer

 Can this field be null in the database (nullable) (yes/no) [no]:
 > no

Pour cette démo, nous allons utiliser une base de données SQLite, pour cela nous modifions le fichier .env dans ce sens.

###> doctrine/doctrine-bundle ###
DATABASE_URL="sqlite:///%kernel.project_dir%/var/data.db"
###< doctrine/doctrine-bundle ###

Créons la base de donnée, créons la migration et appliquons la migration.

symfony console doctrine:database:create
php bin/console make:migration
php bin/console doctrine:migrations:migrate

Pour insérer un peu de données, nous allons rapidement mettre en place un CRUD sur cette entité.

symfony console make:crud

 The class name of the entity to create CRUD (e.g. AgreeableElephant):
 > DailyResult

Rendez-vous sur la page https://127.0.0.1:8004/daily/result/ (à adapter avec votre numéro de port) et enregistrons 4 entrées.

Créons-nous un petit contrôleur pour tester Chartjs.

symfony console make:controller Chartjs

Et modifions le fichier src/Controller/ChartjsController.php pour y récupérer nos données, et les envoyer dans le graphique Chartjs.

<?php

namespace App\Controller;

use App\Repository\DailyResultRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\UX\Chartjs\Builder\ChartBuilderInterface;
use Symfony\UX\Chartjs\Model\Chart;

class ChartjsController extends AbstractController
{
    /**
     * @Route("/chartjs", name="chartjs")
     */
    public function index(DailyResultRepository $dailyResultRepository, ChartBuilderInterface $chartBuilder): Response
    {

        $dailyResults = $dailyResultRepository->findAll();

        $labels = [];
        $data = [];

        foreach ($dailyResults as $dailyResult) {
            $labels[] = $dailyResult->getDate()->format('d/m/Y');
            $data[] = $dailyResult->getValue();
        }

        $chart = $chartBuilder->createChart(Chart::TYPE_LINE);
        $chart->setData([
            'labels' => $labels,
            'datasets' => [
                [
                    'label' => 'My First dataset',
                    'backgroundColor' => 'rgb(255, 99, 132)',
                    'borderColor' => 'rgb(255, 99, 132)',
                    'data' => $data,
                ],
            ],
        ]);
        
        $chart->setOptions([]);

        return $this->render('chartjs/index.html.twig', [
            'controller_name' => 'ChartjsController',
            'chart' => $chart,
        ]);
    }
}

Il ne nous reste plus qu’a éditer le fichier twig templates/chartjs/index.html.twig pour y intégrer le graphique chartjs.

{% extends 'base.html.twig' %}

{% block title %}Hello ChartjsController!{% endblock %}

{% block body %}
<style>
    .example-wrapper { margin: 1em auto; max-width: 800px; width: 95%; font: 18px/1.5 sans-serif; }
    .example-wrapper code { background: #F5F5F5; padding: 2px 6px; }
</style>

<div class="example-wrapper">
    {{ render_chart(chart, {'id': 'my-chart'})}}
</div>
{% endblock %}

Rafraichissons la page, et magie, notre graphique s’affiche bien ! Simple et efficace !

Dropzone.js

Passons maintenant au test de Dropzone avec Symfony UX ! Dropzone c’est un petit module permettant le glissé-déposé de fichier, pour un formulaire d’upload par exemple ! Indispensable en 2020 !

Installons ux-dropzone, téléchargeons les dépendances et générons les fichiers.

composer require symfony/ux-dropzone
npm install
npm run build

Pour la démonstration nous allons créer une entité qui pourrait stocker le chemin vers un fichier. Pour la démo nous ne mettrons pas en place l’enregistrement du fichier, ou l’inscription en base de donnée.

symfony console make:entity upload

New property name (press <return> to stop adding fields):
 > file

 Field type (enter ? to see all types) [string]:
 > 

 Field length [255]:
 > 

 Can this field be null in the database (nullable) (yes/no) [no]:
 > no

Créons la migration et appliquons-la.

php bin/console make:migration
php bin/console doctrine:migrations:migrate

Générons un formulaire d’upload.

symfony console make:form

 The name of the form class (e.g. BravePopsicleType):
 > UploadType

 The name of Entity or fully qualified model class name that the new form will be bound to (empty for none):
 > Upload

Modifions le fichier src/Form/UploadType.php pour y intégrer le DropZoneType.

<?php

namespace App\Form;

use App\Entity\Upload;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\UX\Dropzone\Form\DropzoneType;

class UploadType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('file', DropzoneType::class)
        ;
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'data_class' => Upload::class,
        ]);
    }
}

Histoire de tester tout cela, nous allons créer un contrôleur pour tester Dropzone.

symfony console make:controller Upload

On modifie le contrôleur src/Controller/UploadController.php pour créer le formulaire et l’envoyer vers notre vue twig.

<?php

namespace App\Controller;

use App\Entity\Upload;
use App\Form\UploadType;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

class UploadController extends AbstractController
{
    /**
     * @Route("/upload", name="upload")
     */
    public function index(): Response
    {
        $upload = new Upload();

        $form = $this->createForm(UploadType::class, $upload);

        return $this->render('upload/index.html.twig', [
            'controller_name' => 'UploadController',
            'form' => $form->createView(),
        ]);
    }
}

Et insérons le formulaire dans le fichier twig templates/upload/index.html.twig.

{% extends 'base.html.twig' %}

{% block title %}Hello UploadController!{% endblock %}

{% block body %}
<style>
    .example-wrapper { margin: 1em auto; max-width: 800px; width: 95%; font: 18px/1.5 sans-serif; }
    .example-wrapper code { background: #F5F5F5; padding: 2px 6px; }
</style>

<div class="example-wrapper">
    {{form(form)}}
</div>
{% endblock %}

Il ne reste plus qu’à vérifier que tout fonctionne !

Encore une fois, Symfony UX nous simplifie grandement la vie !

Cropper.js

Dernière découverte avec l’utilisation de Cropper.js qui nous permet de cropper dans une image. Cela peut s’avérer utile par exemple pour proposer à un utilisateur de cropper dans une photo de profil.

Installons ux-cropperjs, téléchargeons les dépendances, et générons les fichiers.

composer require symfony/ux-cropperjs
npm install
npm run build

Copions une image au format jpg dans le répertoire public/.

cd public
wget https://blog.sensiolabs.com/wp-content/uploads/2020/10/Nicolas-Grekas-SymfonyLive-Paris-1024x768.jpg
mv Nicolas-Grekas-SymfonyLive-Paris-1024x768.jpg image.jpg
cd ..

Pour lé démo, on met en place un contrôleur dédié à cropperjs.

symfony console make:controller Cropper

Modifions le fichier src/Controller/CropperController.php pour mettre en place Cropper.

<?php

namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\UX\Cropperjs\Factory\CropperInterface;
use Symfony\UX\Cropperjs\Form\CropperType;

class CropperController extends AbstractController
{
    /**
     * @Route("/cropper", name="cropper")
     */
    public function index(CropperInterface $cropper, Request $request): Response
    {
        $crop = $cropper->createCrop('image.jpg');
        $crop->setCroppedMaxSize(2000, 1500);

        $form = $this->createFormBuilder(['crop' => $crop])
            ->add('crop', CropperType::class, [
                'public_url' => 'image.jpg',
                'aspect_ratio' => 2000 / 1500,
            ])
            ->getForm()
        ;

        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            // Get the cropped image data (as a string)
            $crop->getCroppedImage();

            // Create a thumbnail of the cropped image (as a string)
            $crop->getCroppedThumbnail(200, 150);

            // ...
        }
        return $this->render('cropper/index.html.twig', [
            'controller_name' => 'CropperController',
            'form' => $form->createView(),
        ]);
    }
}

Et enfin, insérons le Cropper dans la vue Twig templates/cropper/index.html.twig.

{% extends 'base.html.twig' %}

{% block title %}Hello CropperController!{% endblock %}

{% block body %}
<style>
    .example-wrapper { margin: 1em auto; max-width: 800px; width: 95%; font: 18px/1.5 sans-serif; }
    .example-wrapper code { background: #F5F5F5; padding: 2px 6px; }
</style>

<div class="example-wrapper">
    {{form(form)}}
</div>
{% endblock %}

Et vérifions que la magie opère ;-)

Conclusions et dépôt GitLab

Avec ces quelques exemples on a pu découvrir la puissance et la simplicité offerte par Symfony UX. Si cet écosystème se développe et que d’autres librairies viennent enrichir le dispositif, cela risque de simplifier grandement le travail pour fabriquer des fronts rapidement et simplement !

Vous trouverez les sources de ce tuto sur ce dépôt GitLab.

Back to Blog