javascript - problem - select2 search box not working in modal
No se puede seleccionar el elemento en el menĂº desplegable Select2 (3)
Estoy trabajando en una aplicación que utiliza Select2 (versión 3.5.1). El HTML para configurar este campo desplegable / autocompletar se ve así:
<input id="mySelect" class="form-control" type="hidden">
La clase de form-control
en este fragmento proviene de Bootstrap. Estoy inicializando este campo desde JavaScript usando lo siguiente:
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; }
});
});
Cuando mi campo de selección se carga, se reproduce correctamente. Una vez que escribo al menos el quinto carácter, saca con éxito los elementos del servidor y los enumera como opciones. Sin embargo, si intento seleccionar uno de ellos, no pasa nada. El desplegable desplegable permanece abierto. Nada se pone en el campo real. No hay errores en la consola de JavaScript. Es como si no hubiera hecho clic en nada.
Además, me di cuenta de que nada está resaltado cuando coloco el mouse sobre un elemento o intento navegar por la lista de opciones con las teclas de flecha.
¿Qué estoy haciendo mal?
Que esta pasando:
Por defecto, los results
del objeto que está devolviendo en ajax.results
deben ser una matriz en esta estructura [{id:1,text:"a"},{id:2,text:"b"}, ...]
.
results: function (data, page) {
var array = data.results; //depends on your JSON
return { results: array };
}
En Select2.js en realidad dice:
* @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}
Al leer el código fuente, podemos ver que ajax.results
se llama en el éxito de 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);
}
Entonces, ajax.results
es realmente solo una función para que formatee sus datos en la estructura apropiada (por ejemplo, [{id:a,text:"a"},{id:b,text:"b"}, ...]
) antes de pasar los datos a 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 });
})});
Y lo que query.callback
finalmente hace es configurar la lógica correctamente para que todo funcione bien cuando elija uno de los elementos y active .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);
}
}
}
Entonces, si hay algún error de configuración (por ejemplo, los results
no están en la estructura correcta) que hace que la clase .select2-search-choice-focus
no se agregue al elemento DOM antes de .selectChoice
, esto es lo que sucede:
El desplegable desplegable permanece abierto. Nada se pone en el campo real. No hay errores en la consola de JavaScript. Es como si no hubiera hecho clic en nada.
Soluciones
Hay muchas soluciones para esto. Uno de ellos es, por supuesto, hacer manipulación de algunas claves de matriz en 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 };
}
Pero puede preguntar: ¿por qué el id debe ser "id" y el texto debe ser "texto" en la matriz?
[{id:1,text:"a"},{id:2,text:"b"}]
¿Puede la matriz estar en esta estructura en su lugar?
[{ItemId:1,ItemText:"a"},{ItemId:2,ItemText:"b"}]
La respuesta es sí. Solo tiene que sobrescribir las funciones de id
y text
con sus propias funciones.
Estas son las funciones originales para .selecte2
en 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;
}
},
Para sobrescribirlos, simplemente agregue sus propias funciones dentro del objeto que está pasando a .selecte2
:
$(''#mySelect'').select2({
id: function (item) { return item.ItemId },
text: function (item) { return item.ItemText }
......
});
Actualizaciones
¿Qué más está pasando?
Sin embargo, el texto del elemento seleccionado no aparece en el campo después de que se cierre la lista.
Esto significa que .selectChoice
ha sido ejecutado con éxito. Ahora el problema radica en .updateSelection
. En el código fuente:
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");
}
}
Desde aquí podemos ver que, antes de colocar la cadena de texto correspondiente en la entrada, se llamará formatSelection
.
formatSelection: function (data, container, escapeMarkup) {
return data ? escapeMarkup(this.text(data)) : undefined;
},
Actualización: Solución
Anteriormente, pensé que se podía sobrescribir this.text(data)
con text: funcion(item){ ... }
en los parámetros, pero lamentablemente no funciona de esa manera.
Por lo tanto, para representar correctamente el texto en el campo, debe sobrescribir formatSelection
haciendo
$(''#mySelect'').select2({
id: function (item) { return item.ItemId },
formatSelection: function (item) { return item.ItemText }
//......
});
en lugar de tratar de sobrescribir text
(que supuestamente debería tener el mismo efecto, pero esta forma de sobrescritura aún no es compatible / implementada en la biblioteca)
$(''#mySelect'').select2({
id: function (item) { return item.ItemId },
text: function (item) { return item.ItemText } //this will not work.
//......
});
Debe proporcionar una ID que devuelva su API como dijo @itsmejodie. El otro problema es que debes proporcionar las select2
formatResult
y formatSelection
, una vez que lo hayas cargado desde Ajax pero no puedes poner html
en eso. p.ej:
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
});
});
El problema al que te enfrentas es que select2
quiere que todos tus resultados tengan una propiedad de id
. Si no es así, debe inicializar con una función de id
que devuelva la identificación de cada resultado.
No le permitirá seleccionar un resultado a menos que cumpla con uno de estos. Así que en el caso de tu ejemplo:
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; }
});
});