web-dev-qa-db-fra.com

Autocomplétion de style Twitter dans textarea

Je recherche une implémentation Javascript à complétion automatique incluant les éléments suivants:

  • Peut être utilisé dans une zone de texte HTML
  • Permet de taper du texte normal sans invoquer la saisie semi-automatique
  • Détecte le caractère @ et démarre la saisie semi-automatique lorsqu'il est tapé
  • Charge la liste des options via AJAX

Je pense que cela ressemble à ce que fait Twitter lorsque vous insérez un tweet, mais je ne parviens pas à trouver une implémentation agréable et réutilisable.
Une solution avec jQuery serait parfaite.

Merci.

46
Martin Wiboe

Je ne pouvais trouver aucune solution correspondant parfaitement à mes besoins. Je me suis donc retrouvé avec ce qui suit:

J'utilise l'événement jQuery keypress() pour vérifier si l'utilisateur appuie sur le caractère @.
Si c'est le cas, une boîte de dialogue modale est affichée à l'aide de l'interface utilisateur jQuery. Cette boîte de dialogue contient un champ de texte autocomplete (de nombreuses options peuvent être utilisées ici, mais je recommande jQuery Tokeninput ).
Lorsque l'utilisateur sélectionne une option dans la boîte de dialogue, une balise est ajoutée au champ de texte et la boîte de dialogue est fermée.

Ce n'est pas la solution la plus élégante, mais cela fonctionne et cela ne nécessite pas de pression supplémentaire sur les touches par rapport à ma conception d'origine.

Modifier
Donc, en gros, nous avons notre grande zone de texte où l’utilisateur peut saisir du texte. Il devrait être capable de "taguer" un utilisateur (cela signifie simplement d'insérer #<userid> dans le texte). J'attache à l'événement jQuery keyup et détecte le caractère @ à l'aide de (e.which == 64) pour afficher une modale avec un champ de texte permettant de sélectionner les utilisateurs à baliser.

La solution réside simplement dans ce dialogue modal avec une zone de texte jQuery Tokeninput . Au fur et à mesure que l'utilisateur tape ici, la liste des utilisateurs est chargée via AJAX. Voir les exemples sur le site Web pour savoir comment l'utiliser correctement. Lorsque l'utilisateur ferme la boîte de dialogue, j'insère les identifiants sélectionnés dans la grande zone de texte.

4
Martin Wiboe

Je suis sûr que votre problème est résolu depuis longtemps, mais jquery-textcomplete semble faire l'affaire.

18
Kevin Schaper

Une autre grande bibliothèque qui résout ce problème At.js

La source

Démo

17
Jernej Novak

Avez-vous essayé cela

GITHUB: https://github.com/podio/jquery-mentions-input

DEMO/CONFIG: http://podio.github.io/jquery-mentions-input/

C'est assez simple à mettre en œuvre.

7
Andrej Kaurin

Essaye ça:

(function($){
    
        $.widget("ui.tagging", {
            // default options
            options: {
                source: [],
                maxItemDisplay: 3,
                autosize: true,
                animateResize: false,
                animateDuration: 50
            },
            _create: function() {
                var self = this;
                
                this.activeSearch = false;
                this.searchTerm = "";
                this.beginFrom = 0;
    
                this.wrapper = $("<div>")
                    .addClass("ui-tagging-wrap");
                
                this.highlight = $("<div></div>");
                
                this.highlightWrapper = $("<span></span>")
                    .addClass("ui-corner-all");
    
                this.highlightContainer = $("<div>")
                    .addClass("ui-tagging-highlight")
                    .append(this.highlight);
    
                this.meta = $("<input>")
                    .attr("type", "hidden")
                    .addClass("ui-tagging-meta");
    
                this.container = $("<div></div>")
                    .width(this.element.width())
                    .insertBefore(this.element)
                    .addClass("ui-tagging")
                    .append(
                        this.highlightContainer, 
                        this.element.wrap(this.wrapper).parent(), 
                        this.meta
                    );
                
                var initialHeight = this.element.height();
                
                this.element.height(this.element.css('lineHeight'));
                
                this.element.keypress(function(e) {
                    // activate on @
                    if (e.which == 64 && !self.activeSearch) {
                        self.activeSearch = true;
                        self.beginFrom = e.target.selectionStart + 1;
                    }
                    // deactivate on space
                    if (e.which == 32 && self.activeSearch) {
                        self.activeSearch = false;
                    }
                }).bind("expand keyup keydown change", function(e) {
                    var cur = self.highlight.find("span"),
                        val = self.element.val(),
                        prevHeight = self.element.height(),
                        rowHeight = self.element.css('lineHeight'),
                        newHeight = 0;
                    cur.each(function(i) {
                        var s = $(this);
                        val = val.replace(s.text(), $("<div>").append(s).html());
                    });
                    self.highlight.html(val);
                    newHeight = self.element.height(rowHeight)[0].scrollHeight;
                    self.element.height(prevHeight);
                    if (newHeight < initialHeight) {
                        newHeight = initialHeight;
                    }
                    if (!$.browser.mozilla) {
                        if (self.element.css('paddingBottom') || self.element.css('paddingTop')) {
                            var padInt =
                                parseInt(self.element.css('paddingBottom').replace('px', '')) + 
                                parseInt(self.element.css('paddingTop').replace('px', ''));
                            newHeight -= padInt;
                        }
                    }
                    self.options.animateResize ?
                        self.element.stop(true, true).animate({
                                height: newHeight
                            }, self.options.animateDuration) : 
                        self.element.height(newHeight);
                    
                    var widget = self.element.autocomplete("widget");
                        widget.position({
                            my: "left top",
                            at: "left bottom",
                            of: self.container
                        }).width(self.container.width()-4);
                    
                }).autocomplete({
                    minLength: 0,
                    delay: 0,
                    maxDisplay: this.options.maxItemDisplay,
                    open: function(event, ui) {
                        var widget = $(this).autocomplete("widget");
                        widget.position({
                            my: "left top",
                            at: "left bottom",
                            of: self.container
                        }).width(self.container.width()-4);
                    },
                    source: function(request, response) {
                        if (self.activeSearch) {
                            self.searchTerm = request.term.substring(self.beginFrom); 
                            if (request.term.substring(self.beginFrom - 1, self.beginFrom) != "@") {
                                self.activeSearch = false;
                                self.beginFrom = 0;
                                self.searchTerm = "";
                            }
                            if (self.searchTerm != "") {
                                
                                if ($.type(self.options.source) == "function") {
                                    self.options.source(request, response);                   
                                } else {
                                    var re = new RegExp("^" + escape(self.searchTerm) + ".+", "i");
                                    var matches = [];
                                    $.each(self.options.source, function() {
                                        if (this.label.match(re)) {
                                            matches.Push(this);
                                        }
                                    });
                                    response(matches);
                                }
                            }
                        }
                    },
                    focus: function() {
                        // prevent value inserted on focus
                        return false;
                    },
                    select: function(event, ui) {
                        self.activeSearch = false;
                        //console.log("@"+searchTerm, ui.item.label);
                        this.value = this.value.replace("@" + self.searchTerm, ui.item.label) + ' ';
                        self.highlight.html(
                            self.highlight.html()
                                .replace("@" + self.searchTerm,
                                         $("<div>").append(
                                             self.highlightWrapper
                                                 .text(ui.item.label)
                                                 .clone()
                                         ).html()+' ')
                        );
                            
                        self.meta.val((self.meta.val() + " @[" + ui.item.value + ":]").trim());
                        return false;
                    }
                });
    
            }
        });
body, html {
        font-family: "lucida grande",tahoma,verdana,arial,sans-serif;
    }
    
    .ui-tagging {
        position: relative;
        border: 1px solid #B4BBCD;
        height: auto;
    }
    
    .ui-tagging .ui-tagging-highlight {
        position: absolute;
        padding: 5px;
        overflow: hidden;
    }
    .ui-tagging .ui-tagging-highlight div {
        color: transparent;
        font-size: 13px;
        line-height: 18px;
        white-space: pre-wrap;
    }
    
    .ui-tagging .ui-tagging-wrap {
        position: relative;
        padding: 5px;
        overflow: hidden;
        zoom: 1;
        border: 0;
    }
    
    .ui-tagging div > span {
        background-color: #D8DFEA;
        font-weight: normal !important;
    }
    
    .ui-tagging textarea {
        display: block;
        font-family: "lucida grande",tahoma,verdana,arial,sans-serif;
        background: transparent;
        border-width: 0;
        font-size: 13px;
        height: 18px;
        outline: none;
        resize: none;
        vertical-align: top;
        width: 100%;
        line-height: 18px;
        overflow: hidden;
    }
    
    .ui-autocomplete {
        font-size: 13px;
        background-color: white;
        border: 1px solid black;
        margin-bottom: -5px;
        width: 0;
    }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<textarea></textarea>

http://jsfiddle.net/mekwall/mcWnL/52/ Ce lien vous aidera

6
Duy NGuyen

J'ai créé un paquet Meteor à cet effet. Le modèle de données de Meteor permet une recherche multi-règles rapide avec des listes de rendu personnalisées. Si vous n'utilisez pas Meteor pour votre application Web, vous ne trouverez malheureusement rien d'aussi génial pour l'auto-complétion.

Complétion automatique des utilisateurs avec @, où les utilisateurs en ligne sont affichés en vert:

enter image description here

Dans la même ligne, compléter automatiquement quelque chose avec des icônes de métadonnées et d'amorçage:

enter image description here

Fourchez, tirez et améliorez:

https://github.com/mizzao/meteor-autocomplete

4
Andrew Mao

Récemment, j'ai dû faire face à ce problème et c'est comme ça que j'ai cloué ...

  1. Obtenez l'index de chaîne à la position du curseur dans la zone de texte à l'aide de selectionStart 
  2. couper la chaîne de l'index 0 à la position du curseur
  3. Insérez-le dans une étendue (car cette dernière a plusieurs zones de bordure)
  4. Obtenez les dimensions de la zone de bordure à l'aide de element.getClientRects () par rapport au port de vue. (voici le Référence MDN )
  5. Calculez le haut et la gauche et alimentez-le dans le menu déroulant

Cela fonctionne dans tous les derniers navigateurs. n'ont pas testé chez les anciens

Voici Bac de travail

3
selvagsz

Un autre plugin offrant des fonctionnalités similaires:

AutoSuggest

Vous pouvez l'utiliser avec des déclencheurs personnalisés ou sans aucun déclencheur. Fonctionne avec les champs de saisie, textareas et contenteditables. Et jQuery n'est pas une dépendance.

1
AvcS

Cette petite extension semble être la plus proche au moins dans la présentation de ce qui a été demandé. Comme il est petit, il peut être facilement compris et modifié. http://lookapanda.github.io/jquery-hashtags/

0
valk