javascript - event - jquery autocomplete destroy
¿Cómo crear un combobox autocompletado? (9)
¿Alguien sabe la mejor manera de crear un combobox autocompletado con plantillas Knockout JS?
Tengo la siguiente plantilla:
<script type="text/html" id="row-template">
<tr>
...
<td>
<select class="list" data-bind="options: SomeViewModelArray,
value: SelectedItem">
</select>
</td>
...
<tr>
</script>
A veces esta lista es larga y me gustaría que Knockout juegue muy bien con quizás jQuery autocompletar o algún código JavaScript directo, pero ha tenido poco éxito.
Además, jQuery.Autocomplete requiere un campo de entrada. ¿Algunas ideas?
Aquí está mi solución:
ko.bindingHandlers.ko_autocomplete = {
init: function (element, params) {
$(element).autocomplete(params());
},
update: function (element, params) {
$(element).autocomplete("option", "source", params().source);
}
};
Uso:
<input type="text" id="name-search" data-bind="value: langName,
ko_autocomplete: { source: getLangs(), select: addLang }"/>
http://jsfiddle.net/7bRVH/214/ Comparado con los RP, es muy básico, pero tal vez satisfaga tus necesidades.
Aquí hay un enlace de Autocompletar jQuery UI que escribí. Está pensado para reflejar las options
, optionsText
, optionsValue
, value
binding paradigm utilizado con elementos seleccionados con un par de adiciones (puede consultar las opciones a través de AJAX y puede diferenciar lo que se muestra en el cuadro de entrada frente a lo que se muestra en el cuadro de selección que aparece.
No necesita proporcionar todas las opciones. Elegirá los valores predeterminados para usted.
Aquí hay una muestra sin la funcionalidad de AJAX: http://jsfiddle.net/rniemeyer/YNCTY/
Aquí está la misma muestra con un botón que hace que se comporte más como un cuadro combinado: http://jsfiddle.net/rniemeyer/PPsRC/
Aquí hay un ejemplo con las opciones recuperadas a través de AJAX: http://jsfiddle.net/rniemeyer/MJQ6g/
//jqAuto -- main binding (should contain additional options to pass to autocomplete)
//jqAutoSource -- the array to populate with choices (needs to be an observableArray)
//jqAutoQuery -- function to return choices (if you need to return via AJAX)
//jqAutoValue -- where to write the selected value
//jqAutoSourceLabel -- the property that should be displayed in the possible choices
//jqAutoSourceInputValue -- the property that should be displayed in the input box
//jqAutoSourceValue -- the property to use for the value
ko.bindingHandlers.jqAuto = {
init: function(element, valueAccessor, allBindingsAccessor, viewModel) {
var options = valueAccessor() || {},
allBindings = allBindingsAccessor(),
unwrap = ko.utils.unwrapObservable,
modelValue = allBindings.jqAutoValue,
source = allBindings.jqAutoSource,
query = allBindings.jqAutoQuery,
valueProp = allBindings.jqAutoSourceValue,
inputValueProp = allBindings.jqAutoSourceInputValue || valueProp,
labelProp = allBindings.jqAutoSourceLabel || inputValueProp;
//function that is shared by both select and change event handlers
function writeValueToModel(valueToWrite) {
if (ko.isWriteableObservable(modelValue)) {
modelValue(valueToWrite );
} else { //write to non-observable
if (allBindings[''_ko_property_writers''] && allBindings[''_ko_property_writers''][''jqAutoValue''])
allBindings[''_ko_property_writers''][''jqAutoValue''](valueToWrite );
}
}
//on a selection write the proper value to the model
options.select = function(event, ui) {
writeValueToModel(ui.item ? ui.item.actualValue : null);
};
//on a change, make sure that it is a valid value or clear out the model value
options.change = function(event, ui) {
var currentValue = $(element).val();
var matchingItem = ko.utils.arrayFirst(unwrap(source), function(item) {
return unwrap(item[inputValueProp]) === currentValue;
});
if (!matchingItem) {
writeValueToModel(null);
}
}
//hold the autocomplete current response
var currentResponse = null;
//handle the choices being updated in a DO, to decouple value updates from source (options) updates
var mappedSource = ko.dependentObservable({
read: function() {
mapped = ko.utils.arrayMap(unwrap(source), function(item) {
var result = {};
result.label = labelProp ? unwrap(item[labelProp]) : unwrap(item).toString(); //show in pop-up choices
result.value = inputValueProp ? unwrap(item[inputValueProp]) : unwrap(item).toString(); //show in input box
result.actualValue = valueProp ? unwrap(item[valueProp]) : item; //store in model
return result;
});
return mapped;
},
write: function(newValue) {
source(newValue); //update the source observableArray, so our mapped value (above) is correct
if (currentResponse) {
currentResponse(mappedSource());
}
}
});
if (query) {
options.source = function(request, response) {
currentResponse = response;
query.call(this, request.term, mappedSource);
}
} else {
//whenever the items that make up the source are updated, make sure that autocomplete knows it
mappedSource.subscribe(function(newValue) {
$(element).autocomplete("option", "source", newValue);
});
options.source = mappedSource();
}
ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
$(element).autocomplete("destroy");
});
//initialize autocomplete
$(element).autocomplete(options);
},
update: function(element, valueAccessor, allBindingsAccessor, viewModel) {
//update value based on a model change
var allBindings = allBindingsAccessor(),
unwrap = ko.utils.unwrapObservable,
modelValue = unwrap(allBindings.jqAutoValue) || '''',
valueProp = allBindings.jqAutoSourceValue,
inputValueProp = allBindings.jqAutoSourceInputValue || valueProp;
//if we are writing a different property to the input than we are writing to the model, then locate the object
if (valueProp && inputValueProp !== valueProp) {
var source = unwrap(allBindings.jqAutoSource) || [];
var modelValue = ko.utils.arrayFirst(source, function(item) {
return unwrap(item[valueProp]) === modelValue;
}) || {};
}
//update the element with the value that should be shown in the input
$(element).val(modelValue && inputValueProp !== valueProp ? unwrap(modelValue[inputValueProp]) : modelValue.toString());
}
};
Lo usarías como:
<input data-bind="jqAuto: { autoFocus: true }, jqAutoSource: myPeople, jqAutoValue: mySelectedGuid, jqAutoSourceLabel: ''displayName'', jqAutoSourceInputValue: ''name'', jqAutoSourceValue: ''guid''" />
ACTUALIZACIÓN: estoy manteniendo una versión de este enlace aquí: https://github.com/rniemeyer/knockout-jqAutocomplete
Eliminación necesaria ...
Ambas soluciones son geniales (con Niemeyer siendo mucho más fino) pero ambas se olvidan del manejo de la eliminación.
Deben manejar las eliminaciones destruyendo el autocompletado de jquery (evitar pérdidas de memoria) con esto:
init: function (element, valueAccessor, allBindingsAccessor) {
....
//handle disposal (if KO removes by the template binding)
ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
$(element).autocomplete("destroy");
});
}
La solución de Niemeyer es genial, sin embargo, tengo un problema cuando trato de usar la función autocompletar dentro de un modal. Autocompletar se destruyó en el evento de cierre modal (Error no detectado: no se puede invocar a los métodos en autocompletar antes de la inicialización, se intentó llamar al método ''opción'') Lo arreglé agregando dos líneas al método de suscripción del enlace:
mappedSource.subscribe(function (newValue) {
if (!$(element).hasClass(''ui-autocomplete-input''))
$(element).autocomplete(options);
$(element).autocomplete("option", "source", newValue);
});
Otra variación en la solución original de Epstone.
Intenté usarlo pero también encontré que el modelo de vista solo se actualizaba cuando se escribía un valor manualmente. Seleccionar una entrada de autocompletar dejó el modelo de vista con el valor anterior, lo cual es un poco preocupante porque la validación aún se aprueba: ¡solo cuando mira en la base de datos ve el problema!
El método que utilicé fue enganchar el manejador de selección del componente de interfaz de usuario de jquery en el inicio de enlace inactivo, que simplemente actualiza el modelo de desactivación cuando se elige un valor. Este código también incorpora la tubería de eliminación de la útil respuesta de George anterior.
init: function (element, valueAccessor, allBindingsAccessor) {
valueAccessor.select = function(event, ui) {
var va = allBindingsAccessor();
va.value(ui.item.value);
}
$(element).autocomplete(valueAccessor);
//handle disposal (if KO removes by the template binding)
ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
$(element).autocomplete("destroy");
});
}
...
<input class="form-control" type="text" data-bind="value: ModelProperty, ko_autocomplete: { source: $root.getAutocompleteValues() }" />
Esto ahora está funcionando bastante bien. Está pensado para trabajar contra una matriz precargada de valores en la página en lugar de consultar una API.
Pequeñas mejoras,
En primer lugar, estos son algunos consejos muy útiles, gracias a todos por compartir.
Estoy usando la versión publicada por Epstone con las siguientes mejoras:
Muestre la etiqueta (en lugar del valor) al presionar hacia arriba o hacia abajo, aparentemente esto se puede hacer manejando el evento de enfoque
Usar una matriz observable como fuente de datos (en lugar de una matriz)
- Se agregó el controlador desechable como lo sugirió George
...
conf.focus = function (event, ui) {
$(element).val(ui.item.label);
return false;
}
...
Por cierto, especificar minLength como 0 permite mostrar las alternativas simplemente moviendo las teclas de flecha sin tener que ingresar ningún texto.
Probé la solución de Niemeyer con JQuery UI 1.10.x, pero el cuadro de autocompletar simplemente no apareció, después de algunas búsquedas encontré una solución simple here . Agregar la siguiente regla al final de su archivo jquery-ui.css corrige el problema:
ul.ui-autocomplete.ui-menu {
z-index: 1000;
}
También utilicé Knockout-3.1.0, así que tuve que reemplazar ko.dependentObservable (...) con ko.computed (...)
Además, si su modelo KO View contiene algún valor numérico, asegúrese de cambiar los operadores de comparación: de === a == y! == a! =, De modo que se realice la conversión de tipo.
Espero que esto ayude a otros
Sé que esta pregunta es antigua, pero también estaba buscando una solución realmente simple para nuestro equipo al usar esto en un formulario, y descubrí que el autocompletado de jQuery genera un evento ''autocompletar select'' .
Esto me dio esta idea.
<input data-bind="value: text, valueUpdate:[''blur'',''autocompleteselect''], jqAutocomplete: autocompleteUrl" />
Con el controlador simplemente siendo:
ko.bindingHandlers.jqAutocomplete = {
update: function(element, valueAccessor) {
var value = valueAccessor();
$(element).autocomplete({
source: value,
});
}
}
Me gustó este enfoque porque mantiene el manejador simple, y no asocia eventos jQuery en mi modelo de vista. Aquí hay un violín con una matriz en lugar de una url como fuente. Esto funciona si hace clic en el cuadro de texto y también si presiona enter.
Se corrigió el borrado de la entrada en el problema de carga para la Solución de RP. Aunque es una especie de solución indirecta, cambié esto al final de la función:
$(element).val(modelValue && inputValueProp !== valueProp ?
unwrap(modelValue[inputValueProp]) : modelValue.toString());
a esto:
var savedValue = $(element).val();
$(element).val(modelValue && inputValueProp !== valueProp ? unwrap(modelValue[inputValueProp]) : modelValue.toString());
if ($(element).val() == '''') {
$(element).val(savedValue);
}