Продолжаю цикл статей, посвящённый моему пет-проекту с Java Servlet, Hibernate и jsp страницами. В этой статье расскажу про то, как реализовать автозаполнение полей с помощью jQuery. В принципе, инструмент очень полезный не только в моём случае и не только с моим стэком технологий.
Про мой проект в этой статье нужно знать только, что он у меня для составления музыкальных чартов. В базе есть таблица с информацией по песням Song, с которой я буду работать, и в проекте соответствующий класс Song.
Задача следующая: при заполнении полей в форме (у меня их по два в каждой из 40 строк) предлагать в подсказках уже забитые в базе песни, чтобы каждый раз не прописывать артистов и их названия полностью. То есть при выборе подсказки заполняется сразу несколько полей.
Но начну с более простого примера. Не будем кидаться сразу на объекты из базы и два поля, а вместо этого заполним одно поле из массива строк.
Автозаполнение с jQuery в одно поле input из массива строк
Итак, у нас есть поле и именем artists. Нужно настроить автоввод.
Для работы с jQuery подключаем его:
<head>
http://jquery-3.7.1.min.js
</head>
Так как я хочу, чтобы автоввод работал для двух полей в каждой строке, выбираю поля и artists, и song:
$('[name="artists[]"], [name="song[]"]').autocomplete({});
Так как у нас уже есть актуальный набор песен в переменной songs, то я должен был написать source: songs (как в первом примере выше). Но у меня там массив не строк, а Json-объектов, который не соответствует стандартам интерфейса jQuery. Поэтому мне нужно преобразовать результат, прежде, чем передавать его response.
Проще говоря, мне нужно сначала составить адекватные подсказки, так как в нашем json-объекте слишком много лишних данных. Оставим из всех полей только имена артистов и названия песен:
$('[name="artists[]"], [name="song[]"]').autocomplete({
minLength: 2,
source: function (request, response) {
response($.map(songs, function (obj, key) {
var name = obj.artists + " - " + obj.name;
if (name.toUpperCase().indexOf(request.term.toUpperCase()) != -1) {
return {
label: obj.artists + " - " + obj.name, // Лейбл - текст подсказки
value: obj // Значение подсказки
}
} else {
return null;
}
}));
}
});
Здесь я превращаю каждый объект в строку из его полей artists и name, разделённых дефисом (с помощью map). Делаю поиск по песням нечувствительным к регистру с помощью toUpperCase(). И возвращаю строку для отображение в label, а сам объект песни в value.
В итоге при вводе больше двух символов будут отображаться понятные подсказки:

Реагируют они на вводе в оба поля в строке, как я и хотел.
Но пока я не описал, что должно происходить при выборе подсказки. Пока что с дефолтным поведением мы видим текст label, но при выборе просто получим в поле input значение value. В нём у нас хранится весь объект, который превратится в строку [object Object]. Не особо информативно.
А должно происходить следующее: артисты помещаются в поле artists, названия песен — в name, а id размещается в скрытое поле idSong. Это поведение описывается в select.
select: function(event, ui) {
event.preventDefault();
var idSong = ui.item.value.id;
var songName = ui.item.value.name
var artists = ui.item.value.artists;
$(this).closest('[name="position"]').find('[name="idSong[]"]').val(idSong);
$(this).closest('[name="position"]').find('[name="artists[]"]').val(artists);
$(this).closest('[name="position"]').find('[name="name[]"]').val(songName);
}
В этом коде я получаю значение value (ui.item.value), которое мы выше привязали к каждой подсказке. И размещаю три нужных поля из этого json-объекта в нужные поля ближайшей строки таблицы под именем position.
Полностью код выглядит так:
$(document).ready(function() {
var songs = ${songs};
$('[name="artists[]"], [name="song[]"]').autocomplete({
minLength: 2,
source: function (request, response) {
response($.map(songs, function (obj, key) {
var name = obj.artists + " - " + obj.name;
if (name.toUpperCase().indexOf(request.term.toUpperCase()) != -1) {
return {
label: obj.artists + " - " + obj.name, // Лейбл - текст подсказки
value: obj // Значение подсказки
}
} else {
return null;
}
}));
},
select: function(event, ui) {
event.preventDefault();
var idSong = ui.item.value.id;
var songName = ui.item.value.name
var artists = ui.item.value.artists;
$(this).closest('[name="position"]').find('[name="idSong[]"]').val(idSong);
$(this).closest('[name="position"]').find('[name="artists[]"]').val(artists);
$(this).closest('[name="position"]').find('[name="name[]"]').val(songName);
}
});
});
Может, объяснение получилось немного путанным, но главная идея в том, что сначала мы готовим source: оформляем из массива данных подходящие подсказки и к каждой привязываем объект (value). Потом, уже в select его обрабатываем.
Автозаполнение полей с jQuery и подгрузкой данных с ajax
Альтернативой передачи содержимого всей таблицы на страницу было бы обращение к базе при каждом вводе. Не уверен, что для меня это тоже хороший вариант, так как тогда бы при заполнении моей таблицы было бы около 40 дополнительных запросов к базе. Поэтому этот вариант я не использую, но вы можете.
В моём примере не поменяется ничего, кроме части source. Вместо фильтрации готового массива данных я каждый раз запрашиваю этот массив.
$('[name="artists[]"], [name="song[]"]').autocomplete({
source: function( request, response ) {
$.ajax({
dataType: "json",
type : 'GET',
url: '/chartAdd',
success: function(data) {
//работа с данными - в моём случае фильтрация (см.выше)
},
select: // размещение данных в поля при выборе подсказки без изменений (см. выше)
});
},
minLength: 2,
});
Минус этого подхода в том, что мы опять же каждый раз передаём из базы всю таблицу, а потом сортируем её на месте. Если мы хотим выполнять фильтрацию в бэке (сразу при обращении в базу или в сервлете), то выглядеть код будет так:
$('[name="artists[]"], [name="song[]"]').autocomplete({
source: function( request, response ) {
$.ajax({
dataType: "json",
type : 'GET',
url: '/chartAdd',
data: {
searchTerm: request.term,
},
success: function(data) {
//работа с данными - в моём случае фильтрация (см.выше)
},
select: // размещение данных в поля при выборе подсказки без изменений (см. выше)
});
},
minLength: 2,
});
В этом случае в бэке обработка запроса по поисковому запросу searchTerm будет выглядеть следующим образом:
@WebServlet(name = "chartAdd", value = "/chartadd")
public class ChartAddServlet extends HttpServlet {
@Override
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
String searchTerm = request.getParameter("searchTerm");
String json = new Gson().toJson(SongDAOImpl.getByTerm(searchTerm));
request.setAttribute("songs", json);
request.getRequestDispatcher("chartadd.jsp").forward(request, response);
response.flushBuffer();
}
<...>
Подразумевается, что в методе SongDAOImpl.getByTerm производится фильтрация по заданной строке.
Теперь в моём проекте есть таблица для создания нового чарта с удобным вводом данных. В следующей статье из цикла опишу сохранение данных в базу из формы на jsp странице в сервлете.