J'ai essayé de faire 5 actions, que je vais décrire dans cet article :

  1. obtenir le texte sélectionné dans un textarea donné ;
  2. obtenir la position du début de la selection du contenu du textarea (en commençant à 0) ;
  3. obtenir la position de la fin de la selection du contenu ;
  4. déplacer le curseur à une position, en précisant éventuellement une position de fin pour créer une selection ;
  5. remplacer le texte sélectionné par un autre.

D'abord, examinons le comportement standard (testé sous Mozilla Firefox >= 1.5).

  • Chaque textarea sauvegarde sa propre selection.
  • Chaque textarea contient 2 variables selectionStart et selectionEnd qui contiennent à tout moment les coordonnées de la selection (si rien n'est selectionné, selectionStart est égal à selectionEnd)
  • Chaque textarea possède une méthode setSelectionRange(start, end) qui permet de créer une selection ou de positionner le curseur (en passant 2 fois la même valeur).
  • Pour remplacer le texte selectionné, il faut couper le texte en 3 : avant, selection, après, et le remplacer par avant + nouvelle selection + après. Il faut penser à remettre d'indice de scroll afin de conserver la barre de défilement à la même position.

Voyons maintenant le comportement d'Internet Explorer, qui, comme a son habitude, est totalement illogique, incohérent et, disons-le, complètement con.

  • Internet Explorer ne gère qu'une seule sélection pour toute le document, accessible via document.selection. En conséquence de quoi, il faut toujours penser à faire un focus() sur le textarea à traiter, sans quoi on risque d'avoir des résultats totalement incohérents.
  • Cependant, il est possible de créer une selection pour chaque textarea. Mais ces selections ne contiendront pas le texte selectionné, elles servent uniquement à déplacer le curseur (!) (j'avais prévenu que c'était complètement con...).
  • Pour manipuler une selection, il faut créer un objet TextRange, qui permet de faire des tas de choses (y compris des choses documentées comme « opaque » par Microsoft), mais pas de récupèrer la position du curseur.
  • Il est impossible se spécifier directement la position du curseur.
  • Il y a une méthode propriétaire pour remplacer la sélection, en mettant à jour l'attribut text de l'objet range (range.text = 'toto').

Voyons maintenant comment dominer IE.

Note : Les fonctions proposées ici sont destinées à être utilisée dans une classe où this représente l'objet textarea. N'hésitez à pas adapter pour votre propre besoin :)

Obtenir la sélection

La fonction suivante permet d'obtenir le texte sélectionné dans un textarea.

getSelection: function ()
{
        if (this.setSelectionRange)
                return this.value.substring(this.selectionStart, this.selectionEnd);
        else if (document.selection) {
                this.focus();
                return document.selection.createRange().text;
        }
}

Obtenir les coordonnées du curseur

Alors là, ça devient rigolo. La solution : récupérer la selection globale, puis la recopier dans une selection locale au textarea. Ensuite, déplacer la fin de la selection le plus possible (la selection locale, contraiement à la selection globale du document, s'arrettera à la fin de texte). Enfin, effectuer une petite soustraction.

getSelectionStart: function()
{
        if ( typeof this.selectionStart != 'undefined' )
                return this.selectionStart;
        
        // IE Support
        this.focus();
        var range = this.createTextRange();
        range.moveToBookmark(document.selection.createRange().getBookmark());
        range.moveEnd('character', this.value.length);
        return this.value.length - range.text.length;
}

Même principe pour la fin.

getSelectionEnd: function()
{
        if ( typeof this.selectionEnd != 'undefined' )
                return this.selectionEnd;

        // IE Support
        this.focus();
        var range = this.createTextRange();
        range.moveToBookmark(document.selection.createRange().getBookmark());
        range.moveStart('character', - this.value.length);
        return range.text.length;
}

Positionner le curseur

Cette fois ci, la technique est de créer une selection vide, de la positionner au bon endroit, de d'utiliser la méthode select() pour la faire apparaitre.

setCaretPos: function(start, end)
{
        end = end || start;
        this.focus();
        if (this.setSelectionRange)
                this.setSelectionRange(start, end);
        else if (document.selection) {
                var range = this.createTextRange();
                range.moveStart('character', start);
                range.moveEnd('character', - this.value.length + end);
                range.select();
        }
}

Remplacer la sélection

Avec toutes ces méthodes cross-browser, la fonction de remplacement est un jeu d'enfant !

J'ai ajouté un paramètre keep indiquant s'il faut garder ou non le texte selectionné après remplacement.

replaceSelection: function (str, keep)
{
        this.focus();
        
        var start = this.getSelectionStart();
        var stop = this.getSelectionEnd();
        var end = start + str.length;
        var scrollPos = this.scrollTop;
                
        this.value = this.value.substring(0, start) + str + this.value.substring(stop);
        if ( keep ) this.setCaretPos(start, end);
        else this.setCaretPos(end);
        this.scrollTop = scrollPos;
}

En esperant que tout ceci vous servira :)