web-dev-qa-db-fra.com

Impossible de sélectionner un élément dans la liste déroulante Select2

Je travaille sur une application qui utilise Select2 (version 3.5.1). Le code HTML pour configurer ce champ déroulant/autocomplete ressemble à ceci:

<input id="mySelect" class="form-control" type="hidden">

La classe form-control de cet extrait provient de Bootstrap. J'initialise ce champ à partir de JavaScript en utilisant les éléments suivants:

function getItemFormat(item) {
  var format = '<div>' + item.ItemName + '</div>';
  return format;
}

$(function() {
  $('#mySelect').select2({
    minimumInputLength: 5,
    placeholder: 'Search for an item',
    allowClear: true,
    ajax: {
      url: '/api/getItems',
      dataType: 'json',
      quietMillis: 250,
      data: function (term, page) {
        return {
          query: term
        };
      },
      results: function (data, page) {
        return { results: data, id: 'ItemId', text: 'ItemText' };
      }
    },
    formatResult: getItemFormat,
    dropdownCssClass: "bigdrop",
    escapeMarkup: function (m) { return m; }
  });
});

Lorsque mon champ de sélection est chargé, il s'affiche correctement. Une fois que je tape au moins le cinquième caractère, il extrait avec succès les éléments du serveur et les répertorie en tant qu'options. Cependant, si j'essaie de sélectionner l'un d'entre eux, rien ne se passe. La liste déroulante reste ouverte. Rien n'est mis dans le champ réel. Il n'y a pas d'erreur dans la console JavaScript. C'est comme si je n'avais rien cliqué.

De plus, j'ai remarqué que rien n'est mis en surbrillance lorsque je place la souris sur un élément ou que je tente de parcourir la liste des options avec les touches fléchées. 

Qu'est-ce que je fais mal?

26
user70192

Qu'est-ce qui se passe:

Par défaut, results de l'objet que vous renvoyez dans ajax.results doit être un tableau de cette structure [{id:1,text:"a"},{id:2,text:"b"}, ...].

  results: function (data, page) {
    var array = data.results; //depends on your JSON
    return { results: array };
  }


Dans Select2.js , il est en fait indiqué:

* @param options.results a function(remoteData, pageNumber, query) that converts data returned form the remote request to the format expected by Select2.
*      The expected format is an object containing the following keys:
*      results array of objects that will be used as choices
*      more (optional) boolean indicating whether there are more results available
*      Example: {results:[{id:1, text:'Red'},{id:2, text:'Blue'}], more:true}


En lisant le code source, nous pouvons voir que ajax.results est appelé en cas de succès AJAX:

   success: function (data) {
                        // TODO - replace query.page with query so users have access to term, page, etc.
                        // added query as third paramter to keep backwards compatibility
                        var results = options.results(data, query.page, query);
                        query.callback(results);
                    }


Donc, ajax.results est en réalité une fonction permettant de formater vos données dans la structure appropriée (par exemple, [{id:a,text:"a"},{id:b,text:"b"}, ...]) avant que les données ne soient transmises à query.callback:

 callback: this.bind(function (data) {

                    // ignore a response if the select2 has been closed before it was received
                    if (!self.opened()) return;


                    self.opts.populateResults.call(this, results, data.results, {term: term, page: page, context:context});
                    self.postprocessResults(data, false, false);

                    if (data.more===true) {
                        more.detach().appendTo(results).html(self.opts.escapeMarkup(evaluate(self.opts.formatLoadMore, self.opts.element, page+1)));
                        window.setTimeout(function() { self.loadMoreIfNeeded(); }, 10);
                    } else {
                        more.remove();
                    }
                    self.positionDropdown();
                    self.resultsPage = page;
                    self.context = data.context;
                    this.opts.element.trigger({ type: "select2-loaded", items: data });
                })});


Et ce que query.callback finit par faire est de configurer correctement la logique afin que tout fonctionne correctement lorsque vous choisissez l'un des éléments et que vous déclenchez .selectChoice.

selectChoice: function (choice) {

            var selected = this.container.find(".select2-search-choice-focus");
            if (selected.length && choice && choice[0] == selected[0]) {

            } else {
                if (selected.length) {
                    this.opts.element.trigger("choice-deselected", selected);
                }
                selected.removeClass("select2-search-choice-focus");
                if (choice && choice.length) {
                    this.close();
                    choice.addClass("select2-search-choice-focus");
                    this.opts.element.trigger("choice-selected", choice);
                }
            }
        } 


Ainsi, s'il existe une mauvaise configuration (par exemple, results n'est pas dans la structure correcte) qui empêche la classe .select2-search-choice-focus d'être ajoutée à l'élément DOM avant l'appel de .selectChoice, voici ce qui se produit:

La liste déroulante reste ouverte. Rien n'est mis dans le champ réel. Il n'y a pas d'erreur dans la console JavaScript. C'est comme si je n'avais rien cliqué.


Solutions

Il y a beaucoup de solutions à cela. L’un d’eux est, bien sûr, de manipuler des clés de tableaux dans ajax.results.

  results: function (data, page) {
  //data = { results:[{ItemId:1,ItemText:"a"},{ItemId:2,ItemText:"b"}] };
    var array = data.results;
    var i = 0;
    while(i < array.length){
        array[i]["id"] = array[i]['ItemId'];
        array[i]["text"] = array[i]['ItemText'];
        delete array[i]["ItemId"];
        delete array[i]["ItemText"];
    i++;
    }
    return { results: array };
  }

Mais vous pouvez vous demander: pourquoi l'identifiant doit-il être "id" et le texte, "text" dans le tableau? 

[{id:1,text:"a"},{id:2,text:"b"}] 

Le tableau peut-il être dans cette structure à la place?

[{ItemId:1,ItemText:"a"},{ItemId:2,ItemText:"b"}]

La réponse est oui. Vous devez simplement écraser les fonctions id et text avec vos propres fonctions.


Voici les fonctions d'origine de .selecte2 dans Select2.js :

    id: function (e) { return e == undefined ? null : e.id; },
    text: function (e) {
      if (e && this.data && this.data.text) {
        if ($.isFunction(this.data.text)) {
          return this.data.text(e);
        } else {
          return e[this.data.text];
        }
      } else {
        return e.text;
      }
    },


Pour les écraser, ajoutez simplement vos propres fonctions dans l'objet que vous transmettez à .selecte2:

$('#mySelect').select2({
id: function (item) { return item.ItemId },
text: function (item) { return item.ItemText }
......
});

Mises à jour

Quoi d'autre se passe:

Toutefois, le texte de l'élément sélectionné n'apparaît pas dans le champ après la fermeture de la liste.

Cela signifie que .selectChoice a été exécuté avec succès. Maintenant, le problème réside dans .updateSelection. Dans le code source:

   updateSelection: function (data) {

        var container=this.selection.find(".select2-chosen"), formatted, cssClass;

        this.selection.data("select2-data", data);

        container.empty();
        if (data !== null) {
            formatted=this.opts.formatSelection(data, container, this.opts.escapeMarkup);
        }
        if (formatted !== undefined) {
            container.append(formatted);
        }
        cssClass=this.opts.formatSelectionCssClass(data, container);
        if (cssClass !== undefined) {
            container.addClass(cssClass);
        }

        this.selection.removeClass("select2-default");

        if (this.opts.allowClear && this.getPlaceholder() !== undefined) {
            this.container.addClass("select2-allowclear");
        }
    }


À partir de là, nous pouvons voir que, avant que la chaîne de texte correspondante soit placée dans l'entrée, elle devrait s'appeler formatSelection.

formatSelection: function (data, container, escapeMarkup) {
            return data ? escapeMarkup(this.text(data)) : undefined;
        },

Mise à jour: solution

Auparavant, je pensais que this.text(data) pouvait être écrasé en ayant text: funcion(item){ ... } dans les paramètres, mais malheureusement, cela ne fonctionne pas de cette façon.

Par conséquent, pour rendre le texte correctement dans le champ, vous devez écraser formatSelection en faisant

$('#mySelect').select2({
id: function (item) { return item.ItemId },
formatSelection: function (item) { return item.ItemText }
//......
});

au lieu d'essayer d'écraser text (ce qui devrait avoir le même effet mais cette méthode d'écrasement n'est pas encore prise en charge/implémentée dans la bibliothèque)

$('#mySelect').select2({
id: function (item) { return item.ItemId },
text: function (item) { return item.ItemText }  //this will not work.
//......
});
45
Archy Wilhes

Le problème que vous rencontrez est que select2 veut que tous vos résultats aient une propriété id. Si ce n'est pas le cas, vous devez initialiser avec une fonction id qui renvoie l'identifiant de chaque résultat.

Il ne vous permettra pas de sélectionner un résultat à moins que vous ne satisfaisiez l’un d’eux. Donc dans le cas de votre exemple:

function getItemFormat(item) {
  var format = '<div>' + item.ItemName + '</div>';
  return format;
}

$(function() {
  $('#mySelect').select2({
    minimumInputLength: 5,
    placeholder: 'Search for an item',
    allowClear: true,
    id: function(item) { return item.ItemId; },    /* <-- ADDED FUNCTION */
    ajax: {
      url: '/api/getItems',
      dataType: 'json',
      quietMillis: 250,
      data: function (term, page) {
        return {
          query: term
        };
      },
      results: function (data, page) {
        return { results: data, id: 'ItemId', text: 'ItemText' };
      }
    },
    formatResult: getItemFormat,
    dropdownCssClass: "bigdrop",
    escapeMarkup: function (m) { return m; }
  });
});
8
itsmejodie

Vous devez fournir un identifiant renvoyé par votre API, comme @itsmejodie, a déclaré… .. L'autre problème est que vous devez fournir les fonctions select2formatResult et formatSelection, une fois que vous l'avez chargé depuis Ajax mais vous ne pouvez pas y ajouter html . par exemple.:

function format (item) { 
  return item.name; 
}

$(function() {
    $('#mySelect').select2({
      minimumInputLength: 2,
      placeholder: 'Search for an item',
      allowClear: true,
      ajax: {
        url: '/api/getItems',
        dataType: 'jsonp',
        quietMillis: 250,
        data: function (term, page) {
          return {
            query: term
          };
        },
        results: function (data, page) {
          return { results: data };
        }
      },
      formatResult: format,
      formatSelection: format
    });
});
2
Danillo Corvalan

Pour la version 4 de Select2, utilisez

processResults: function (data) { 

au lieu de 

results: function (data) {
0
Manic Depression