Make a password display with Symfony’s FormType when you press your eyes

Aug 25, 2020 PHP Symfony

Common

image

Just add it to formBuilder as shown below and create a FormType that will be like ↑.

$formBuilder()->add('password', ShowHidePasswordType::class);

#How to make

Project creation

# sensio/framework-extra-bundle is not supported by the latest version of symfony, but somehow it is installed by default, so deleted
$ symfony new show_hide_password --full
$ composer remove sensio/framework-extra-bundle

Base form screen creation

Create a Controller with the following command.


$ bin/console make:controller

 Choose a name for your controller class (e.g. TinyGnomeController):
 > home

 created: src/Controller/HomeController.php
 created: templates/home/index.html.twig

Create a form by rewriting Controller and template as follows.

<?php

namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Routing\Annotation\Route;

class HomeController extends AbstractController
{
    /**
     * @Route("/home", name="home")
     */
    public function index()
    {
        $form = $this->createFormBuilder()
            ->add('username', TextType::class)
            ->add('password', PasswordType::class)
            ->add('submit', SubmitType::class)
            ->getForm()
        ;

        return $this->render('/home/index.html.twig', [
            'form' => $form->createView()
        ]);
    }
}
{% extends'base.html.twig' %}

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

{% block body %}
    <div class="container mt-5">
        <div class="row">
            <div class="col-8 offset-2">
                <div class="card">
                    <div class="card-body">
                        {{ form_start(form) }}
                        {{ form_rest(form) }}
                        {{ form_end(form) }}
                    </div>
                </div>
            </div>
        </div>
    </div>
{% endblock %}

Also, since it uses the bootstrap form theme, add a CDN link to the activation settings & template.

twig:
    default_path:'%kernel.project_dir%/templates'
    form_theme: ['bootstrap_4_layout.html.twig'] # Added
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>{% block title %}Welcome!{% endblock %}</title>
        {% block stylesheets %}
            // from here
            <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9ZwR1T2JwR1T2JJR1 ="anonymous">
            // So far
        {% endblock %}
    </head>
    <body>
        {% block body %}{% endblock %}
        {% block javascripts %}
            // from here
            <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
            <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4s0861IHNDGainJNDZwrnQq4s086I" script>
            <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jan"mousorigin" crossorigin
            // So far
        {% endblock %}
    </body>
</html>

Start the server with the following command and access https://127.0.0.1:8000 to display a page like the image.

$ symfony server:start -d
                                                                                                                        
 [OK] Web server listening
      The Web server is using PHP FPM 7.4.9
      https://127.0.0.1:8000

image

Make an example

First, create a FormType. The name is appropriate (laughs). This is a child Type of PasswordType. Controller should use ShowHidePasswordType instead of PasswordType.

<?php

namespace App\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;

class ShowHidePasswordType extends AbstractType
{
    public function getParent()
    {
        return PasswordType::class;
    }
}
...
use App\Form\ShowHidePasswordType; // add
...
        $form = $this->createFormBuilder()
            ->add('username', TextType::class)
            //->add('password', PasswordType::class)
            ->add('password', ShowHidePasswordType::class) // add->add('submit', SubmitType::class)
            ->getForm()
        ;
...

Then create a custom form theme and change the settings to use this theme. Inherit bootstrap_4_layout.html.twig and overwrite only the display method of ShowHidePasswordType.

{% extends'bootstrap_4_layout.html.twig' %}
twig:
    default_path:'%kernel.project_dir%/templates'
# form_theme: ['bootstrap_4_layout.html.twig']
    form_theme: ['form_theme.html.twig'] # Added

By default, Symfony form themes use the {% block form_row %} ... {% endblock %} block for rendering, but with ShowHidePasswordType {% block show_hide_password %} ... {% endblock If a block called %} is defined, that block will be used preferentially for rendering. Please refer to the document 1 for detailed explanation of naming conventions.

The {% block form_row %} ... {% endblock %} of bootstrap_4_layout.html.twig is as follows. In this, the part {{-form_widget(form, widget_attr) -}} is the part that actually renders the input element. So, wrap it here and try to display the example by clobbering with css or JavaScript.

```twig:bootstrap_4_layout.html.twig` {% block form_row -%} {%- if compound is defined and compound -%} {%- set element ='fieldset’ -%} {%- endif -%} {%- set widget_attr = {} -%} {%- if help is not empty -%} {%- set widget_attr = {attr: {‘aria-describedby’: id ~”_help”}} -%} {%- endif -%} <{{ element|default(‘div’) }}{% with {attr: row_attr|merge({class: (row_attr.class|default('') ~'form-group’)|trim})} %} {{ block(‘attributes’) }}{% endwith %}> {{- form_label(form) -}} {{- form_widget(form, widget_attr) -}} # Coco {{- form_help(form) -}} </{{ element|default(‘div’) }}> {%- endblock form_row %}



Here is what was finally completed. I don't do much because I just wrap the input element and the eye icon in a div and display it nicely with `position:absolute`. When the eye icon is clicked, the input type is changed with JavaScript.

The eye icon uses the fontawesome resource, so add the CDN to `base.html.twig`. If you reload the browser, you will see the common thing like the beginning.

```twig:templates/form_theme.html.twig
{% extends'bootstrap_4_layout.html.twig' %}

{% block show_hide_password_row %}
    <style>
        .showHiddenPassword-wrapper {
            position: relative;
        }
        .showHiddenPassword-wrapper .is-invalid {
            background-image: none!important;
            background-size: 0!important;
        }

        .showHiddenPassword-toggle {
            position: absolute;
            top: 50%;
            right: 1.5em;
            transform: translateY(-50%);
        }
    </style>

    <script>
        function __togglePassword__{{ form.vars.id }}() {
            const _passwordField = document.querySelector('#{{ form.vars.id }}');
            const _showHideToggle = document.querySelector('#showHideToggle-{{ form.vars.id }}');
            if (_showHideToggle.classList.contains('fa-eye-slash')) (
                _showHideToggle.classList.remove('fa-eye-slash')
                _showHideToggle.classList.add('fa-eye')
                _passwordField.type ='text'
            } else {
                _showHideToggle.classList.remove('fa-eye')
                _showHideToggle.classList.add('fa-eye-slash')
                _passwordField.type ='password'
            }
        }
    </script>

    {%- if compound is defined and compound -%}
        {%- set element ='fieldset' -%}
    {%- endif -%}
    {%- set widget_attr = {} -%}
    {%- if help is not empty -%}
        {%- set widget_attr = {attr: {'aria-describedby': id ~"_help",'class':'showHiddenPassword-widget'}} -%} {# class added #}
    {%- endif -%}
    <{{ element|default('div') }}{% with {attr: row_attr|merge({class: (row_attr.class|default('') ~'form-group')|trim})} %} {{ block('attributes') }}{% endwith %}>
    {{- form_label(form) -}}

    {# from here #}
    <div class='showHiddenPassword-wrapper'>
        {{- form_widget(form, widget_attr) -}}
        <span class='showHiddenPassword-toggle'
              onclick='__togglePassword__{{ form.vars.id }}()'
        >
            <i id='showHideToggle-{{ form.vars.id }}' class="fa fa-eye-slash"></i>
        </span>
    </div>
    {# So far #}

    {{- form_help(form) -}}
    </{{ element|default('div') }}>
{% endblock %}

...
        {% block stylesheets %}
            <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9ZwR1T2JwR1T2JJR1 ="anonymous">
            // from here
            <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.13.0/css/all.min.css" rel="stylesheet" crossorigin="anonymous">
           // So far
        {% endblock %}
...

Of course, it’s better to define CSS and JavaScript in separate files. However, in that case, it takes time to read that file, so if you want to complete with only FormType, I personally think that it is ant to do it like this. (How about that)

This time source code

https://github.com/ggg-mzkr/show_hide_password

References


  1. https://symfony.com/doc/current/form/form_themes.html#custom-fragment-naming-for-individual-fields ↩︎