Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URL's added here will be added as <link>s in order, and before the CSS in the editor. You can use the CSS from another Pen by using it's URL and the proper URL extention.

+ add another resource

JavaScript

Babel includes JSX processing.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Packages

Add Packages

Search for and use JavaScript packages from npm here. By selecting a package, an import statement will be added to the top of the JavaScript editor for this package.

Behavior

Save Automatically?

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

HTML

              
                <div>
    <input type=password value=secret>
</div>

<div>
    <input type=password value=secret>
</div>

<div>
    <input type=password value=secret>
</div>
              
            
!

CSS

              
                div {
    margin: 1em;
}

.pw-shown::after {
    content: "❌";
}
.pw-hidden::after {
    content: "👀";
}

.sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border-width: 0;
}
              
            
!

JS

              
                // Je n'aime pas les fonctions anonymes pour les event handler car on est obligé
// de tout lire pour comprendre ce qui est fait.
// En donnant un nom à ces fonctions, on aide le lecteur à comprendre ce qui se
// passe et lui laisser le choix de ne pas plonger dans l'implémentation

// Lorsque je nomme mes variables, j'utilise ce que représente la variable
// (buttonTemplate) plutôt que le type de la variable (xml)

// J'ai changé le nom des classes CSS. Comme elles représentent un état,
// j'utilise des noms ou des adjectifs (shown, hidden) mais surtout pas un verbe
// qui doit être réservé aux actions, c'est à dire les functions.

// Ceci est une Immediately Invoked Function Expression (IIFE), utile pour ne
// pas polluer window avec nos fonctions et variables.
(function () {
    // Je ne sais pas d'où vient msg dans le code original donc j'ai ajouté
    // cet objet
    const msg = {
        show_password: "Show password",
        hide_password: "Hide password"
    };

    function togglePasswordShown(e) {
        e.preventDefault();
        const button = e.currentTarget;
        const isPasswordShown = button.classList.contains("pw-shown");

        const inputType = isPasswordShown ? "password" : "text";
        const buttonContent = isPasswordShown
            ? msg.show_password
            : msg.hide_password;

        // classList.toggle('pw-show') regarde si 'pw-show' est dans classList
        // et l'inverse. Ça permet de ne pas dupliquer les lignes .add et
        // .remove. Pour être encore plus robuste, j'y ajoute le 2e paramètre.
        // Si quelqu'un joue avec les classList dans les devtools, on est sur de
        // retomber sur ces pattes
        button.classList.toggle("pw-shown", !isPasswordShown);
        button.classList.toggle("pw-hidden", isPasswordShown);

        // En ayant les variables inputType et buttonContent définies au-dessus,
        // on évite de dupliquer des lignes presque identiques. Cela nous permet
        // d'être sur que l'on oublie aucun changement nécessaire dans l'une des
        // branches. Cela assure aussi que title et sr-only contiennent le même
        // texte.
        button.previousElementSibling.setAttribute("type", inputType);
        button.setAttribute("title", buttonContent);
        button.querySelector("span").textContent = buttonContent;
    }

    function installShowPassword() {
        const buttonTemplate = new DOMParser().parseFromString(
            `<button type="button" class="pw-hidden" title="${msg.show_password}"><span class="sr-only">${msg.show_password}</span></button>`,
            "text/html"
        ).body.firstChild;
        const passwordFields = document.querySelectorAll(
            "input[type=password]"
        );

        // for of est assez nouveau. Je préfère au style forEach parce que ça
        // fait des parenthèses en moins à la fin de la boucle. Mais c'est
        // équivalent
        for (const passwordField of passwordFields) {
            // Un bug du code original : Il ne marchait pas super avec plusieurs
            // champs password. Lorsque qu'on insère un nœud dans le DOM, il ne
            // peut y en avoir qu'un. Il faut donc faire une copie avant de
            // l'insérer. Dans le code original, uniquement le dernier champ
            // password obtient le bouton magique
            const button = buttonTemplate.cloneNode(true);

            // https://developer.mozilla.org/fr/docs/Web/API/ChildNode/after
            // La compat avec Safari n'est pas à jour donc j'ai ouvert:
            // https://github.com/mdn/browser-compat-data/pull/6232
            passwordField.after(button);
            button.addEventListener("click", togglePasswordShown);
        }
    }

    // $() utilise DOMContentLoaded qui arrive dès que le HTML a fini d'être parsé
    // (approximation de la réalité un peu plus complexe)
    // load n'arrive que quand toutes les resources de la page sont téléchargées
    window.addEventListener("DOMContentLoaded", installShowPassword);
})();

              
            
!
999px

Console