Plugin jQuery PlaceHolder

HTML5 introduit de nombreuses nouveautés pour les formulaires et notamment dans le registre de l'aide à la saisie. Nous allons nous focaliser sur l'attribut placeholder.

L'attribut placeholder permet d'afficher une valeur ou un renseignement par défaut, qui disparait et est remplacé par la saisie de l'utilisateur. Cette fonctionnalité est particulièrement pratique dans le cas de formulaires pour lesquels aucun libellé de champ n'est proposé.

Le plugin développé permet de réaliser la même fonctionnalité, sans pour autant que le navigateur ne soit compatible avec HTML 5.

La structure de base du plugin

La structure du plugin ne présente aucune particularité par rapport aux recommandations jQuery.

Tout au plus, vous remarquerez le nom du plugin, placeHolder ainsi que les paramètres par défaut.

                                            
                                                <script language="javascript">
                            
                                                    (function($) {
                                                            $.fn.extend({
                                                                
                                                                placeHolder : function(options) {
                                                            
                                                                    // Paramètres par défaut
                                                                    var defaults = {
                                                                        defaultValue	: "",
                                                                        callback		: null,
                                                                        checkTag		: true,
                                                                        allowedTags		: ["input", "select", "textarea"]
                                                                    };
                                                    
                                                                    // Gestion des paramètres
                                                                    var opts = $.extend( defaults, options );
                                                            
                                                            
                                                                    // La fonction principale
                                                                    // -------------------------------------------------
                                                                    this.each( function() {
                                                                        
                                                                        // ...
                                                                        // ...
                                                                        // ...
                                                                                
                                                                    });
                                                                    
                                                                    // Permettre le chaînage par jQuery
                                                                    return this;
                                                                                    
                                                                } // -- fin de placeHolder
                                                                
                                                            });	// -- fin de $.fn.extend
                                                            
                                                    })(jQuery);
                                                    
                                                </script>
                                            
                                        

L'initialisation

lignes 1 à 18 ci-dessous :

Pour chaque balise html adressée, une initialisation spécifique de paramètres par défaut est réalisée afin de rendre totalement autonomes les uns des autres. Vous noterez qu'une lecture de l'attribut placeholder est tentée, ce qui assouplit l'utilisation future.

Lignes 20 à 29 ci-dessous :

Dans le cas des <SELECT>, on vérifie que la valeur par défaut est présente dans la liste des <OPTION>. Si ce n'est pas le cas, un <OPTION> est créé en première place de la liste et sélectionné par défaut.

Lignes 31 à 37 ci-dessous :

Dans le cas des <INPUT type="password">, un <INPUT type="text"> est ajouté afin de permettre la lecture de l'information par défaut. Bien entendu, le champ du mot de passe est masqué.

Lignes 39 à 44 ci-dessous :

L'initialisation est ensuite réalisée par l'intermédiaire des functions incluses dans le plugin et décrite ultérieurement.

Lignes 46 à 48 ci-dessous :

Enfin, si une action de callback est souhaitée, elle est exécutée.


                                            
                                                // Paramètres par défaut pour un tag
                                                var defaultsTag = {
                                                    tag 			: $(this),
                                                    allowed 		: true,
                                                    thisTag 		: $(this)[0].tagName.toLowerCase(),
                                                    currentValue	: null,
                                                    isEmpty 		: true,
                                                    isEditing		: false,
                                                    isPassword 		: ($(this).attr("type") == "password"),
                                                    tagText			: null
                                                }
                                                
                                                // Extension des paramètres
                                                var optsTag = $.extend( defaultsTag, opts );
                                                
                                                // Récupère l'attribut placeHolder
                                                if ($(this).attr("placeholder"))
                                                    optsTag.defaultValue = $(this).attr("placeholder");
                                                
                                                // Traitement pour les selects
                                                if (optsTag.thisTag == "select") {				// SELECT
                                                
                                                    // Si la valeur par défaut n'existe pas, on l'ajoute
                                                    if ($(this).text().toLowerCase().indexOf(optsTag.defaultValue.toLowerCase()) < 0)
                                                        $(this).find("option:first").before("");	
                            
                                                    // Sélection de la valeur par défaut
                                                    $(this).val(optsTag.defaultValue).attr("selected", "selected");
                                                }
                                                
                                                // Traitement pour les passwords
                                                if (optsTag.tag.attr("type") == "password") {	// INPUT Password						
                                                    // Ajout d'un champ texte pour le placeholder lisible
                                                    optsTag.tag.after($("", {id:optsTag.tag.attr("id")+"Text", type:"text", value:optsTag.tag.defaultValue, style:"display:none;"}));
                                                    optsTag.tagText = $("input[id='"+ optsTag.tag.attr("id") +"Text']");
                                                    optsTag.tagText.placeHolder({isCachedPassword:optsTag.tag.attr("id")});
                                                }
                                                
                                                // Mise en conformité de l'affichage
                                                if (checkTagContent()) {
                                                    setEmpty();
                                                } else {
                                                    setFull();
                                                }
                            
                                                // Appel d'une fonction de callback
                                                if (optsTag.callback) 
                                                    optsTag.callback();
                                            
                                        

Les fonctions de traitement

function checkTagContent(), lignes 1 à 24 ci-dessous :

Cette fonction réalise une vérification de la valeur de la balise et retourne son état (optsTag.isEmpty).

Tout d'abord, on s'assure que la balise doit être vérifiée (lignes 5 à 9) en fonction des balises autorisées dans les paramètres généraux (allowed et allowedTags).

Deux cas se présentent alors : pour une balise <SELECT> (lignes 11 à 13), il faut vérifier que la valeur par défaut correspond à l'élément sélectionné ; pour les autres balises (lignes 14 à 18), on mémorise la valeur courante (currentValue) et on vérifie si elle est vide ou égale à la valeur par défaut (defaultValue).

Enfin, l'état est retourné (isEmpty) ligne 22.


function setEmpty(), lignes 26 à 41 ci-dessous :

Cette fonction rend l'affichage conforme à nos attentes lorsque la balise est vide ou représente la valeur par défaut.

Dans le cas d'un <SELECT>, l'affichage du contenu ne doit pas être modifié.

Pour un <INPUT type="password">, on affiche le champ texte créé, avec la valeur par défaut (lignes 30 à 33). Dans les autres cas, on s'assure de la valeur par défaut (lignes 34 à 35).

Enfin, un peu de cosmétique avec l'ajout de la classe empty et la suppression de la classe something (lignes 38 à 40).


function setFull(), lignes 43 à 54 ci-dessous :

Cette fonction rend l'affichage conforme à nos attentes lorsque la balise contient une valeur différente de la valeur par défaut.

Dans le cas d'un <SELECT>, l'affichage du contenu ne doit pas être modifié.

Dans tous les autres cas, on s'assure de la valeur saisie (lignes 47 à 48).

Enfin, un peu de cosmétique avec l'ajout de la classe something et la suppression de la classe empty (lignes 52 à 53).


function setEditing(pEditing), lignes 56 à 64 ci-dessous :

Cette fonction modifie l'affichage en fonction d'une édition en cours en appliquant ou supprimant la classe editing.


                                            
                                                // Contrôle du contenu du tag
                                                // ----------------------------------------------------------
                                                function checkTagContent() {
                            
                                                    // Contrôle du tag
                                                    if (optsTag.checkTag) 
                                                        optsTag.allowed = (optsTag.allowedTags.indexOf(optsTag.thisTag) > -1 );
                            
                                                    if (optsTag.allowed) {
                                                        // Valeur actuelle du tag
                                                        if (optsTag.thisTag == "select") {	// SELECT
                                                            // Sélection par défaut
                                                            optsTag.isEmpty = (optsTag.defaultValue == optsTag.tag.find("option:selected").val());
                                                        } else {							// INPUT, TEXTAREA
                                                            // Un contenu ?
                                                            if (optsTag.tag.val) 
                                                                optsTag.currentValue = $.trim(optsTag.tag.val());
                                                                optsTag.isEmpty = (optsTag.currentValue == '') || (optsTag.currentValue == optsTag.defaultValue) || (optsTag.currentValue.length == 0);
                                                        }
                                                        
                                                        // Valeur de retour
                                                        return optsTag.isEmpty;
                                                    }
                                                }
                            
                                                // Elément vide
                                                // ----------------------------------------------------------
                                                function setEmpty() {
                                                    if (optsTag.thisTag != "select") {		// INPUT, TEXTAREA
                                                        if (optsTag.isPassword) {				// INPUT Password
                                                            optsTag.tagText.val(optsTag.defaultValue);
                                                            optsTag.tagText.css({"display":"block"});
                                                            optsTag.tag.css({"display":"none"});
                                                        } else if (optsTag.tag.val)				// INPUT Text, TEXTAREA
                                                            optsTag.tag.val(optsTag.defaultValue);
                                                    }
                                                    
                                                    // Gestion des class
                                                    optsTag.tag.addClass("empty");
                                                    optsTag.tag.removeClass("something");
                                                }
                                                
                                                // Element renseigné
                                                // ----------------------------------------------------------
                                                function setFull() {
                                                    if (optsTag.thisTag != "select") {		// INPUT, TEXTAREA
                                                        if (optsTag.tag.val)
                                                            optsTag.tag.val(optsTag.currentValue);
                                                    }
                                                    
                                                    // Gestion des class
                                                    optsTag.tag.addClass("something");
                                                    optsTag.tag.removeClass("empty");
                                                }
                            
                                                // Edition
                                                // ----------------------------------------------------------
                                                function setEditing(pEditing) {
                                                    // Edition du contenu
                                                    optsTag.isEditing = pEditing;
                                                    
                                                    // Gestion des class
                                                    optsTag.tag.toggleClass("editing", optsTag.isEditing);
                                                }
                                            
                                        

La gestion événements utilisateurs

Le plugin placeHolder prend en charge les balises INPUT, TEXTAREA et SELECT. Les événements FOCUS et CLICK doivent donc être assimilés à un début d'édition du champs. De la même manière, les événements BLUR et CHANGE sont associés à une fin d'édition.


Focus et Click, lignes 1 à 26 ci-dessous :

En début d'édition, on regarde s'il s'agit du champ texte créé dans le cas d'un <INPUT type="password"> (lignes 5 à 6). Si oui, le champ texte est masqué pour ne laisser que le champ de mot de passe accessible (lignes 8 à 11).

S'il ne s'agit pas d'un champ de mot de passe, on vérifie l'état de la valeur et on ajuste l'affichage en fonction (lignes 13 à 22).

Enfin, on matérialise l'édition en appelant la fonction setEditing(true) (lignes 24 à 25).


Blur et Change, lignes 28 à 44 ci-dessous :

En fin d'édition, on regarde si la valeur saisie est différente de la valeur par défaut (ligne 31).

Si ce n'est pas le cas, on ajuste l'affichage en conséquence par un appel à la fonction setFull() (ligne 39).

Dans le cas contraire, on vérifie s'il s'agit d'un champ de mot de passe à masquer (lignes 32 à 36) avant de s'occuper de l'affichage par un appel à la fonction setEmpty() (ligne 37).

Enfin, on matérialise la fin de l'édition en appelant la fonction setEditing(false) (lignes 42 à 43).


                                            
                                                // Evénement : FOCUS || CLICK
                                                // ----------------------------------------------------------
                                                $(this).bind("focus click", function() {
                                                    
                                                    // Mise en conformité de l'affichage
                                                    if (optsTag.isCachedPassword) {			// INPUT Password
                            
                                                        // Masque le champ texte et affiche le champ password
                                                        optsTag.tag.css({"display":"none"});
                                                        $("#"+ optsTag.isCachedPassword).css({"display":"block"});
                                                        $("#"+ optsTag.isCachedPassword).focus();
                            
                                                    } else if (checkTagContent()) {			// INPUT Text, TEXTAREA, SELECT
                            
                                                        // Si vide
                                                        optsTag.currentValue = "";
                                                        setFull();
                                                    } else {
                                                        
                                                        // Si renseigné
                                                        setFull();
                                                    }
                                                    
                                                    // Edition du contenu
                                                    setEditing(true);
                                                });
                            
                                                // Evénements : BLUR || CHANGE
                                                // ----------------------------------------------------------
                                                $(this).bind("blur change", function() {
                                                    if (checkTagContent()) {
                                                        if (optsTag.isPassword) {			// INPUT Password
                                                            optsTag.tag.currentValue = "";
                                                            optsTag.tag.css({"display":"none"});
                                                            optsTag.tagText.removeClass("editing");
                                                        }
                                                        setEmpty();
                                                    } else {
                                                        setFull();
                                                    }
                                                    
                                                    // Validation du contenu
                                                    setEditing(false);
                                                });
                                            
                                        

Les attributs

Comme décrit précédemment, chaque balise gère son propre groupe d'attributs, établi sur des valeurs communes.

Les attributs communs :

                                            
                                                // Paramètres par défaut
                                                var defaults = {
                                                    defaultValue	: "",
                                                    callback		: null,
                                                    checkTag		: true,
                                                    allowedTags		: ["input", "select", "textarea"]
                                                };
                                            
                                        
  • defaultValue (string) : la valeur par défaut, i.e. le contenu textuel du placeholder. Ce paramètre est initialisé par l'existence d'un attribut placeholder="" dans la balise (recommandation), ou par une extension des valeurs par défaut lors de l'appel du plugin.
    • <input type="text" placeholder="Une valeur ?" />
      pour une déclaration inline
       
    • $("input").placeHolder({ defaultValue : "Une valeur ?" });
      pour une déclaration au cas par cas

  • callback (function) : permet la définition d'une fonction de callback exécutée à la fin de l'initialisation du plugin.
    • $("input").placeHolder({ callback : function(){alert("Initialisation terminée");} });
      pour afficher un message à la fin de l'initialisation du plugin

  • checkTag (boolean) : permet de forcer (= true, valeur par défaut) ou non (= false) un contrôle de la balise suivant la liste décrite dans allowedTags.
    • $("input").placeHolder({ checkTags : false });
      pour ne pas réaliser de contrôle sur le type de balise

  • allowedTags (array) : liste les balises autorisées par le plugin.
    • $("input").placeHolder({ allowedTags : ["input", "textarea"] });
      pour modifier la liste des balises contrôlées à <INPUT> et <TEXTAREA>

Les attributs spécifiques :

                                            
                                                // Paramètres par défaut pour un tag
                                                var defaultsTag = {
                                                    tag 			: $(this),
                                                    allowed 		: true,
                                                    thisTag 		: $(this)[0].tagName.toLowerCase(),
                                                    currentValue	: null,
                                                    isEmpty 		: true,
                                                    isEditing		: false,
                                                    isPassword 		: ($(this).attr("type") == "password"),
                                                    tagText			: null
                                                }
                                            
                                        

Rappel : ces attributs sont complétés par les paramètres par défaut généraux décrits ci-dessus et ne sont normalement pas à modifier.

  • tag (object) : la balise concernée
  • allowed (boolean) : permet de savoir si la balise est autorisée ou non
  • thisTag (string) : le nom de la balise
  • currentValue (string | number) : la valeur associée à la balise
  • isEmpty (boolean) : la valeur est vide ou égale au placeholder
  • isEditing (boolean) : édition en cours
  • isPassword (boolean) : champ de type mot de passe
  • tagText (object) : la balise texte ajoutée dans le cas d'un champ mot de passe

La feuille de style

Ce plugin s'appuie sur une feuille de style qui décrit les différents paramètres d'affichage.

                                            
                                                 <style>
                            
                                                 .placeHolder { }
                            
                                                .empty {
                                                    color: #999;
                                                    background: #FFC;
                                                    border: 1px solid;
                                                }
                                                
                                                .something {
                                                    color: #000;
                                                    border: 1px solid inset;
                                                }
                                                
                                                .editing {
                                                    -moz-box-shadow: 0px 0px 3px #900;
                                                    -webkit-box-shadow: 0px 0px 3px #900;
                                                    box-shadow: 0px 0px 3px #900;
                                                }
                                                </style>
                                            
                                        
  • .placeHolder : pour identifier rapidement plusieurs champs dans une page
  • .empty : lorsque le champ est vide
  • .something : lorsque le champ est renseigné
  • .editing : édition en cours

L'usage

L'utilisation du plugin est particulièrement simple, comme le montre l'exemple ci-dessous.

                                            
                                                <html>
                                                <head>
                                                    <!-- ------------------------------------- 
                                                        Eléments du HEADER
                                                    -------------------------------------- -->
                                                    
                                                    <!-- Feuille de style -->
                                                    <link rel="stylesheet" type="text/css" href="jquery.placeHolder.css" media="screen" />
                                                    
                                                    <!-- jQuery & JavaScript -->
                                                    <script type="text/javascript" src="jquery.1.4.2.min.js"></script>
                                                    <script type="text/javascript" src="jquery.placeHolder.js"></script>
                                                    
                                                    <!-- Exécution après chargement du DOM -->
                                                    <script language="javascript">
                                                        $(document).ready(function(){ 
                                                            $(".placeHolder").placeHolder();
                                                        });
                                                    </script>
                                                </head>
                                                
                                                <body>
                                                    <!-- ------------------------------------- 
                                                        Eléments du BODY
                                                    -------------------------------------- -->
                                                    
                                                    <input class="placeHolder" id="demo1" type="text" value="" placeholder="Une valeur ?" />
                                                    <input class="placeHolder" id="demo2" type="password" value="" placeholder="Un mot de passe ?" /><br />
                                                    <textarea class="placeHolder" id="demo3" placeholder="Un texte libre ?"></textarea><br />
                                                    <select class="placeHolder" id="demo4" placeholder="Une sélection ?">
                                                        <option value="volvo">Volvo</option>
                                                        <option value="saab">Saab</option>
                                                        <option value="mercedes">Mercedes</option>
                                                        <option value="audi">Audi</option>
                                                    </select>
                                                    
                                                </body>
                                                </html>