web-dev-qa-db-fra.com

Comment implémenter "mustMatch" et "selectFirst" dans la saisie semi-automatique de jQuery UI?

J'ai récemment migré quelques-uns de mes plugins Autocomplete de celui produit par bassistance vers la jQuery UI autocomplete .

Comment les "mustMatch" et "selectFirst" peuvent-ils être implémentés avec seulement des rappels et d'autres options sans modifier le code de base de saisie semi-automatique?

38
dochoffiday

Je pense avoir résolu les deux fonctionnalités ...

Pour faciliter les choses, j'ai utilisé un sélecteur personnalisé commun:

$.expr[':'].textEquals = function (a, i, m) {
    return $(a).text().match("^" + m[3] + "$");
};

Le reste du code:

$(function () {
    $("#tags").autocomplete({
        source: '/get_my_data/',
        change: function (event, ui) {
            //if the value of the textbox does not match a suggestion, clear its value
            if ($(".ui-autocomplete li:textEquals('" + $(this).val() + "')").size() == 0) {
                $(this).val('');
            }
        }
    }).live('keydown', function (e) {
        var keyCode = e.keyCode || e.which;
        //if TAB or RETURN is pressed and the text in the textbox does not match a suggestion, set the value of the textbox to the text of the first suggestion
        if((keyCode == 9 || keyCode == 13) && ($(".ui-autocomplete li:textEquals('" + $(this).val() + "')").size() == 0)) {
            $(this).val($(".ui-autocomplete li:visible:first").text());
        }
    });
});

Si l'une de vos suggestions de saisie semi-automatique contient des caractères "spéciaux" utilisés par regexp, vous devez les échapper dans m [3] dans le sélecteur personnalisé:

function escape_regexp(text) {
  return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
}

et changez le sélecteur personnalisé:

$.expr[':'].textEquals = function (a, i, m) {
  return $(a).text().match("^" + escape_regexp(m[3]) + "$");
};
40
dochoffiday

J'ai utilisé quelque chose d'aussi simple que cela pour mustMatch et cela fonctionne. J'espère que ça aide quelqu'un.

        change: function (event, ui) {
            if (!ui.item) {
                 $(this).val('');
             }
        }
32
Anonymous

Je pense que je dois le mustMatch travailler avec ce code ... besoin de tester à fond si:

<script type="text/javascript">
    $(function() {
        $("#my_input_id").autocomplete({
            source: '/get_my_data/',
            minChars: 3,
            change: function(event, ui) {
                // provide must match checking if what is in the input
                // is in the list of results. HACK!
                var source = $(this).val();
                var found = $('.ui-autocomplete li').text().search(source);
                console.debug('found:' + found);
                if(found < 0) {
                    $(this).val('');
                }
            }
        });
    });
</script>
3
Esteban Feldman

J'ai trouvé cette question utile.

Je pensais publier le code que j'utilise actuellement (adapté de la réponse d'Esteban Feldman ).

J'ai ajouté ma propre option mustMatch et une classe CSS pour mettre en évidence le problème avant de réinitialiser la valeur de la zone de texte.

       change: function (event, ui) {
          if (options.mustMatch) {
            var found = $('.ui-autocomplete li').text().search($(this).val());

            if (found < 0) {
              $(this).addClass('ui-autocomplete-nomatch').val('');
              $(this).delay(1500).removeClass('ui-autocomplete-nomatch', 500);
            }
          }
        }

CSS

.ui-autocomplete-nomatch { background: white url('../Images/AutocompleteError.gif') right center no-repeat; }
2
GordonB

This JQuery-UI official demo has mustMatch, amongst other cool stuff: http://jqueryui.com/demos/autocomplete/#combobox

I've updated it to add autoFill, and a few other things.

Javascript:



/* stolen from http://jqueryui.com/demos/autocomplete/#combobox
 *
 * and these options added.
 *
 * - autoFill (default: true):  select first value rather than clearing if there's a match
 *
 * - clearButton (default: true): add a "clear" button
 *
 * - adjustWidth (default: true): if true, will set the autocomplete width the same as
 *    the old select.  (requires jQuery 1.4.4 to work on IE8)
 *
 * - uiStyle (default: false): if true, will add classes so that the autocomplete input
 *    takes a jQuery-UI style
 */
(function( $ ) {
    $.widget( "ui.combobox", {
        options: {
            autoFill: true,
            clearButton: true,
            adjustWidth: true,
            uiStyle: false,
            selected: null,
        },
    _create: function() {
        var self = this,
          select = this.element.hide(),
          selected = select.children( ":selected" ),
          value = selected.val() ? selected.text() : "",
              found = false;
        var input = this.input = $( "" )
                .attr('title', '' + select.attr("title") + '')
        .insertAfter( select )
        .val( value )
        .autocomplete({
            delay: 0,
            minLength: 0,
            source: function( request, response ) {
                var matcher = new RegExp( $.ui.autocomplete.escapeRegex(request.term), "i" );
                        var resp = select.children( "option" ).map(function() {
                    var text = $( this ).text();
                    if ( this.value && ( !request.term || matcher.test(text) ) )
                    return {
                        label: text.replace(
                        new RegExp(
                            "(?![^&;]+;)(?!]*)(" +
                            $.ui.autocomplete.escapeRegex(request.term) +
                            ")(?![^]*>)(?![^&;]+;)", "gi"
                        ), "$1" ),
                        value: text,
                        option: this
                    };
                });
                        found = resp.length > 0;
                response( resp );
            },
            select: function( event, ui ) {
                ui.item.option.selected = true;
                self._trigger( "selected", event, {
                    item: ui.item.option
                });
            },
            change: function( event, ui ) {
                if ( !ui.item ) {
                    var matcher = new RegExp( "^" + $.ui.autocomplete.escapeRegex( $(this).val() ) + "$", "i" ),
                    valid = false;
                    select.children( "option" ).each(function() {
                    if ( $( this ).text().match( matcher ) ) {
                        this.selected = valid = true;
                        return false;
                    }
                    });
                    if ( !valid || input.data("autocomplete").term=="" ) {
                    // set to first suggestion, unless blank or autoFill is turned off
                                var suggestion;
                                if(!self.options.autoFill || input.data("autocomplete").term=="") found=false;
                                if(found) {
                                    suggestion = jQuery(input.data("autocomplete").widget()).find("li:first")[0];
                                    var option = select.find("option[text="+suggestion.innerText+"]").attr('selected', true);
                                    $(this).val(suggestion.innerText);
                        input.data("autocomplete").term = suggestion.innerText;
                            self._trigger( "selected", event, { item: option[0] });
                                } else {
                                    suggestion={innerText: ''};
                                    select.find("option:selected").removeAttr("selected");
                                    $(this).val('');
                        input.data( "autocomplete" ).term = '';
                                    self._trigger( "selected", event, { item: null });
                                }
                    return found;
                    }
                }
            }
        });

            if( self.options.adjustWidth ) { input.width(select.width()); }

            if( self.options.uiStyle ) {
                input.addClass( "ui-widget ui-widget-content ui-corner-left" );
            }


        input.data( "autocomplete" )._renderItem = function( ul, item ) {
            return $( "
  • " ) .data( "item.autocomplete", item ) .append( "" + item.label + "" ) .appendTo( ul ); }; this.button = $( " " ) .attr( "tabIndex", -1 ) .attr( "title", "Show All Items" ) .insertAfter( input ) .button({ icons: { primary: "ui-icon-triangle-1-s" }, text: false }) .removeClass( "ui-corner-all" ) .addClass( "ui-corner-right ui-button-icon" ) .click(function() { // close if already visible if ( input.autocomplete( "widget" ).is( ":visible" ) ) { input.autocomplete( "close" ); return; } // work around a bug (likely same cause as #5265) $( this ).blur(); // pass empty string as value to search for, displaying all results input.autocomplete( "search", "" ); input.focus(); }); if( self.options.clearButton ) { this.clear_button = $( " " ) .attr( "tabIndex", -1 ) .attr( "title", "Clear Entry" ) .insertAfter( input ) .button({ icons: { primary: "ui-icon-close" }, text: false }) .removeClass( "ui-corner-all" ) .click(function(event, ui) { select.find("option:selected").removeAttr("selected"); input.val( "" ); input.data( "autocomplete" ).term = ""; self._trigger( "selected", event, { item: null }); // work around a bug (likely same cause as #5265) $( this ).blur(); }); } }, destroy: function() { this.input.remove(); this.button.remove(); this.element.show(); $.Widget.prototype.destroy.call( this ); } }); })( jQuery );

    CSS (.hjq-combobox is a wrapping span)

    .hjq-combobox .ui-button { margin-left: -1px; }
    .hjq-combobox .ui-button-icon-only .ui-button-text { padding: 0; }
    .hjq-combobox button.ui-button-icon-only { width: 20px; }
    .hjq-combobox .ui-autocomplete-input { margin-right: 0; }
    .hjq-combobox {white-space: nowrap;}
    

    Note: this code is being updated and maintained here: https://github.com/tablatom/hobo/blob/master/hobo_jquery_ui/vendor/assets/javascripts/combobox.js

    1
    Bryan Larsen

    La solution que j'ai utilisée pour implémenter 'mustMatch':

    <script type="text/javascript">
    ...
    
    $('#recipient_name').autocomplete({
        source: friends,
        change: function (event, ui) {
            if ($('#message_recipient_id').attr('rel') != $(this).val()) {
                $(this).val('');
                $('#message_recipient_id').val('');
                $('#message_recipient_id').attr('rel', '');
            }
        },
        select: function(event, ui) {
            $('#message_recipient_id').val(ui.item.user_id);
            $('#message_recipient_id').attr('rel', ui.item.label);
        }
    }); 
    
    ...
    </script>
    
    1
    varelse

    C’est peut-être simplement parce qu’il s’agit d’un problème ancien, mais j’ai trouvé que la solution la plus simple existe déjà dans le plug-in, il vous suffit d’utiliser les fonctions appropriées pour y accéder.

    Ce code traitera les cas où la saisie semi-automatique perd le focus avec une valeur non valide:

    change: function(e, ui) {
        if (!ui.item) {
            $(this).val("");
        }
    }
    

    Et ce code, un peu comme la fonctionnalité originale de bassistance, gérera les cas où il n'y a pas de correspondance lors de la saisie dans la saisie semi-automatique:

    response: function(e, ui) {
        if (ui.content.length == 0) {
            $(this).val("");
        }
    }
    

    Cela fonctionne bien avec une source de tableau statique ou une source de données JSON. Combiné à l'option autoFocus: true, il semble que tout ce qu'il faut faire soit efficace.

    Le dernier cas que vous souhaiterez peut-être gérer concerne ce qu'il faut faire lorsque la touche Échap est enfoncée avec une valeur non valide dans la zone de texte. Ce que je fais est d'utiliser la valeur du premier résultat correspondant. Et c'est comme ça que je fais ça ...

    Commencez par déclarer une variable pour la meilleure correspondance. Faites-le en dehors de votre plugin autocomplete.

    var bestMatch = "";
    

    Ensuite, utilisez l'option suivante:

    open: function(e, ui) {
        bestMatch = "";
    
        var acData = $(this).data('uiAutocomplete');
        acData.menu.element.find("A").each(function () {
            var me = $(this);
    
            if (me.parent().index() == 0) {
                bestMatch = me.text();
            }
        });
    }
    

    Enfin, ajoutez l'événement suivant à votre saisie semi-automatique:

    .on("keydown", function(e) {
        if (e.keyCode == 27)        // ESCAPE key
        {
            $(this).val(bestMatch);
        }
    })
    

    Vous pouvez également forcer le champ à être vide lorsque vous appuyez sur la touche Échap. Tout ce que vous avez à faire est de définir la valeur sur une chaîne vide lorsque vous appuyez sur la touche au lieu de la variable bestMatch (inutile si vous choisissez de vider le champ).

    1
    Randy Cleary

    J'ai découvert un problème. Tant que la liste de suggestions est active, vous pouvez soumettre votre formulaire même si la valeur ne correspond pas à la suggestion. Pour dissiper cela, j'ai ajouté: 

    $('form').submit(function() {
            if ($(".ui-autocomplete li:textEquals('" + $(this).val() + "')").size() == 0) {
                $(this).val('');
                $("span").text("Select a valid city").show();
                return false;
            }
    });
    

    Cela empêche la soumission du formulaire et affiche un message. 

    1
    CyberJunkie

    Scott Gonzalez a écrit une extension selectFirst (ainsi que plusieurs autres) pour jQueryUI AutoComplete.

    0
    JCobb

    Basé sur la réponse acceptée:

    Mes exigences supplémentaires: autocomplets multiples , validation d'erreur discrète .

    change: function () {
        var target = $(this),
            widget = target.autocomplete('widget'),
            cond = widget.find('li:textEquals("' + target.val() + '")').length === 0;
    
        target.toggleClass('input-validation-error', cond);
    }
    
    0
    Tim Vermaelen

    Réponse tardive mais pourrait aider quelqu'un!

    Considérant les deux événements dans le widget autocomplete

    1) changer - déclenché lorsque le champ est flou et que la valeur est modifiée.

    2) réponse - déclenchée lorsque la recherche est terminée et que le menu est affiché.

    Modifiez les événements de modification et de réponse comme suit:

    change : function(event,ui)
    {  
    if(!ui.item){
    $("selector").val("");
    }
    },
    
    response : function(event,ui){
    if(ui.content.length==0){
      $("selector").val("");
    }
    }
    

    J'espère que cela t'aides!

    0
    RishikeshD

    Je le fais un peu différemment, mettant en cache les résultats et effaçant le champ de texte si le nombre de résultats pour un terme donné est nul:

    <script type='text/javascript'>
    function init_autocomplete( args )
    {
         var resultCache = {};
         var currentRequestTerm = null;
    
         var closeCallback = function()
         {
             // Clear text field if current request has no results
             if( resultCache[currentRequestTerm].length == 0 )
                 $(args.selector).val('');
         };
    
         var sourceCallback = function( request, responseCallback )
         {
             // Save request term
             currentRequestTerm = request.term;
    
             // Check for cache hit
             // ...
             // If no cache hit, fetch remote data
             $.post(
                 dataSourceUrl,
                 { ...  }, // post data
                 function( response )
                 {
                     // Store cache
                     resultCache[request.term] = response;
    
                     responseCallback( response );
                 }
         };
    
         $(args.selector).autocomplete({
             close:  closeCallback,
             source: sourceCallback
         });
    }
    </script>
    
    0
    Sam