Проект Java Servlet + jsp. Часть 3. Автозаполнение полей формы с jQuery

Продолжаю цикл статей, посвящённый моему пет-проекту с 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 странице в сервлете.

Оставьте комментарий