Фильтрация и сортировка данных #
Проблема отображения большого объёма данных #
В рамках данной работы рассмотрим типичные сценарии работы с большими наборами данных. Ключевая особенность заключается в том, что невозможно предоставлять пользователю сразу большой набор данных:
- Большой объём данных долго обрабатывает система (как на сервере, так и на клиенте), что делает работу пользователя с системой неудобной.
- Пользователю неудобно работать с очень большими документами.
- Системы хотят самостоятельно обрабатывать свои данные и не делиться этими данными с другими системами.
Для быстрого предоставления данных применяется подход с частичным отображением информации, постраничный вывод. Для решения проблемы нахождения информации реализуется фильтрация элементов по различным критериям.
HTML-формы #
Для обеспечения передачи данных от пользователя к веб-приложению используются интерактивные элементы, формы. Каждая форма состоит из нескольких полей, в которые пользователь может вводить данные, выбирать элементы из списка и т.д.
Формы описываются с помощью HTML-элемента form, который включает в себя ряд элементов input, textarea, button, описывающие конкретные поля ввода и интерактивные элемента. В рамках одного HTML-документа может находится несколько форм, предлагающие пользователю выполнить разные действия.
Для указания URI-адреса, который должен обработать данные, переданные с формы, используется атрибут action элемента form. По умолчанию данный адрес совпадает с адресом самого HTML-документа. Для большинства форм данное поведение является удобным и дополнительно определять путь не нужно.
Формы могут посылать GET и POST-запросы. Для указания HTTP-метода, который будет послан, используется атрибут method. По умолчанию форма будет отправлять GET-запрос. Это поведение необходимо использовать в данной практической работе.
При использовании GET-запросов данные с формы передаются в формате параметров, записанных в строку запроса. Названием параметра зачастую выступает название поля ввода, а значением — данные, которые указал или не указал пользователь.
Вы можете посмотреть на примеры форм на следующих страницах:
Сценарий фильтрации, сортировки #
В рамках данного сценария разработчику необходимо предоставить пользователю интерактивные элементы для ввода данных рядом с отображаемым списком элементов. Решение задач фильтрации и сортировки не должно изменять внутреннее состояние приложения, а влиять только на выводимые пользователю данные. Таким образом для отправки данных веб-приложению необходимо использовать GET-запросы.
Рассмотрим принципиальную последовательность действий, которые позволят пользователю выполнить фильтрацию или сортировку отображаемых данных:
sequenceDiagram
autonumber
actor user as Пользователь
participant brow as Браузер
participant serv as Сервер
user ->> brow : Открывает ссылку
на документ со списком
brow ->> serv : Выполняет GET-запрос
документа по ссылке
serv ->> brow : HTML-документ
с данными и формой
brow ->> user : Отображает полученный документ
user ->> brow : Пользователь заполняет
поля формы и нажимает
кнопку «Применить»
brow ->> serv : Посылает GET-запрос
с параметрами формы
serv ->> brow : HTML-документ с данными
и заполненной формой
brow ->> user : Отображает документ
с нужной страницей
Ключевые особенности подхода:
- Для указания параметров фильтрации используются HTML-формы, интерактивные элементы.
- Данные с формы передаётся на сервер с помощью GET-запроса, а не POST-запроса.
- Данные с формы не должны «пропадать» после передачи их пользователю. Т.е. если пользователь указал какие-то параметры внутри формы, то они снова должны быть показаны пользователю.
- Пользователь может скопировать ссылку и поделиться ей с другими пользователями, чтобы они увидели такой же результат фильтрации.
- Пользователь может открыть несколько вкладок с одним сайтом и применить разные параметры фильтрации и сортировки для отображения одних и тех же данных.
Данные с формы передаются в качестве параметров маршрута: https://some.dev/list?owner=maria&version=15
Работа со строкой запроса #
В рамках URI, который передаётся от клиента к серверу, можно передать набор именованных параметров в строке запроса. Строка запроса отделяется от пути знаком ?
, затем идут пары параметров в формате название=значение
, разделённые символом &
. Рассмотрим следующий пример:
http://localhost:9000/objects?shape=triangles&min-area=90
Запрос от клиента происходит по протоколу HTTP по пути /objects
к серверу localhost:9000
. Данному пути передаются два параметра:
- параметр
shape
со значениемtriangles
; - параметр
min-area
со значением90
.
В рамках одной строки запроса может быть передано несколько параметров с одинаковым названием. Эта возможность обычно используется для передачи информации из HTML-форм. Важно понять, что набор параметров невозможно свести к ассоциативному массиву с уникальными ключами.
Классы Uri и Parameters #
Библиотека http4k предоставляет удобный класс для работы с URI-идентификаторами, Uri. Все объекты класса Request предоставляют свойство uri
, в котором находятся данные запроса от пользователя. Объекты класса Uri можно также создать самостоятельно из строки с помощью метода Uri.of(value: String): Uri
:
val uri = Uri.of("https://lms.crafted.su/tech-notes/")
Для работы со строкой запроса класс Uri предоставляет средства, отражённые в списке ниже.
- Свойство
query
для низкоуровневого доступа к строке, пришедшей от пользователя. - Функция
queries(): Parameters
позволяет получить доступ к списку параметров для получения значений параметров. - Функция
query(name: String, value: String?): Uri
добавляет ещё один параметр к запросу. При применении данной функции создаётся новый объект класса Uri, в значение свойстваquery
которого записывается строка с добавленным параметром. Оригинальный объект не изменяется. - Функция
removeQuery(name: String): Uri
удаляет все вхождения параметров с данным названием. - Функция
fun query(query: String): Uri
позволяет заменить целиком строку запроса на новую. Результатом работы функции является новый объект Uri, включающий новую строку запроса. Оригинальный объект класса Uri не изменяется.
Parameters является псевдонимом для списка параметров:
typealias Parameters = List<Parameter>
Для получения значений из данного списка можно воспользоваться средствами, описанными в списке ниже.
- Функция
Parameters.findSingle(name: String): String?
позволяет выполнить поиск первого значения для параметра с указанным названием. - Функция
Parameters.findMultiple(name: String): List<String?>
позволяет найти все значения для параметра с указанным названием. - Функция
Parameters.toParametersMap(): Map<String, List<String?>>
позволяет преобразовать параметры в ассоциативный массив, где для каждого названия (строки) будет указан список переданных значений.
Сценарий постраничного вывода #
Рассмотрим базовый сценарий реализации постраничного вывода.
sequenceDiagram autonumber actor user as Пользователь participant brow as Браузер participant serv as Сервер user ->> brow : Открывает ссылку со списком brow ->> serv : Выполняет GET-запрос документа по ссылке serv ->> brow : HTML-документ с данными и навигационной панелью brow ->> user : Отображает полученный документ user ->> brow : Выбирает номер страницы на панели и нажимает на ссылку brow ->> serv : Посылает GET-запрос на документ с номером страницы serv ->> brow : HTML-документ с данными и навигационной панелью brow ->> user : Отображает документ с нужной страницей
Ключевые особенности подхода:
- У маршрута для отображения документа появляется необязательный параметр — номер страницы.
- По умолчанию номер страницы равен единице (или нулю в зависимости от удобства дальнейшей обработки).
- На HTML-странице отображается навигационный элемент для перехода между страницами. Навигационный элемент может быть информативным, либо состоять из одной кнопки «показать больше».
- Внутри навигационной панели находятся подготовленные ссылки, включающие номера страниц, на которые пользователь может перейти.
- Пользователь может скопировать ссылку и поделиться ей с другими пользователями.
В ссылках на другие страницы необходимо сохранять все параметры, которые были переданы при отправке на страницу. Это необходимо для сохранения параметров, которые пользователь ввёл ранее или они были заданы . Эти параметры необходимо использовать при формировании ссылок постраничного вывода, чтобы не потерять данные.
Задача № 1. Реализация фильтрации списка треугольников #
Реализуйте фильтрацию списка добавленных треугольников по следующим критериям:
- Минимальная длина наименьшей стороны треугольника.
- Максимальная длина наибольшей стороны треугольника.
При отсутствии значения аргумента данный фильтр применяться не должен.
Поход к реализации:
- Реализуйте отображение формы на странице со списком документов.
- С помощью шаблона формы из Bootstrap добавьте на страницу вывода списка треугольников форму, состоящую из двух полей ввода и кнопки для отправки данных.
- Для каждого поля ввода укажите его уникальное имя (атрибут name), а также определитесь с типом компонента (атрибут type) , который наиболее точно позволит пользователю ввести корректные данные.
- Убедитесь, что данные, которые пользователь ввёл в форму, передаются обработчику HTTP-запроса в формате параметров запроса. Самый простой способ — заполнить поля формы и отправить её веб-приложению. Поля формы должны передаться с помощью параметров HTTP-запроса.
- Отобразите параметры, которые ввёл пользователь на HTML-документе. Это необходимо, чтобы пользователь смог исправить свой некорректный ввод.
- Модифицируйте модель (класс ViewModel) так, чтобы она хранила в себе параметры, которые пользователь указал с помощью формы. Данные параметры будет удобно описать как строковые типы, способные принимать
null
-значения, т.к. пользователь может не передать их или передать их в неправильном формате. - Модифицируйте шаблон HTML-документа таким образом, чтобы он использовал передаваемые через модель данные. Атрибут value полей ввода позволяет установить изначальное значение.
- Обработайте параметры запроса в HTTP-обработчике и передайте их модели, чтобы их можно было отобразить пользователю в HTML-документе.
- Модифицируйте модель (класс ViewModel) так, чтобы она хранила в себе параметры, которые пользователь указал с помощью формы. Данные параметры будет удобно описать как строковые типы, способные принимать
- Отфильтруйте данные, отображаемые пользователю.
- По возможности преобразуйте данные, которые передал пользователь, из строкового значения в вещественные числа. В рамках данной практической считаем, что пользователь введёт данные в корректном формате. В случае ошибочного значения просто преобразуйте строку к
null
-значению. Это будет совпадать с ситуацией, когда пользователь ничего не указал в поле ввода. - Сформируйте метод у класса
Triangles
, возвращающий список отфильтрованных треугольников согласно данным, приходящим от пользователя. Данный метод должен принимать в качестве аргументов 2 вещественных числа, способных приниматьnull
-значения. При наличии значения и у каждого из параметров, он должен учитываться при фильтрации элементов. - Используйте данный метод для формирования списка данных для отображения пользователю.
- По возможности преобразуйте данные, которые передал пользователь, из строкового значения в вещественные числа. В рамках данной практической считаем, что пользователь введёт данные в корректном формате. В случае ошибочного значения просто преобразуйте строку к
Задача № 2. Формирование данных для постраничного вывода #
Создайте класс Paginator, в задачу которого должно входить формирование данных для отображения компонента переходами между страниц. Данный класс должен предоставлять следующие данные:
- Есть ли возможность перейти к предыдущей странице или нет.
- Список страниц (номер страницы и URI к странице) для передвижения назад.
- Есть ли возможность перейти к следующей странице или нет.
- Список страниц (номер страницы и URI к страние) для передвижения впрёд.
- Номер текущей страницы.
Базовыми данными для вычисления этих параметров можно использовать:
- Базовый URI-адрес страницы.
- Текущий номер страницы.
- Количество страниц.
Напишите модульные тесты для данного класса.
Задача № 3. Отображение страниц #
Модифицируйте приложение для отображения списка треугольников. Реализуйте постраничный вывод информации о треугольниках на странице списка треугольников, которые вводились в систему. Для облегчения реализации:
- Инициализируйте список треугольников 10 разными треугольниками.
- Ограничьте количество данных на одной странице 6 треугольниками.
Рекомендуется реализовать метод внутри класса-списка треугольников, возвращающий подмножество списка треугольников в зависимости от переданной страницы. Страница передаётся в качестве аргумента метода.
fun trianglesByPageNumber(pageNumber: Int): List<Triangles>
Также потребуется реализовать метод для подсчёта количества страниц в классе-списке треугольников:
fun pageAmount(): Int
Проверьте краевые ситуации:
- В списке треугольников нет нужных данных.
- На последней страницы корректно отображается укороченный список.
- При передаче номера страницы за пределами возможных значений приложение показывает пустой список.
Используйте данные методы и созданный класс Paginator для реализации постраничного отображения данных в списке треугольников.
Задача № 4. Реализация сортировки #
К форме фильтрации данных добавьте ещё 1 поле, с помощью которого пользователь может управлять сортировкой данных в списке. Данное поле должно содержать следующие варианты сортировки:
- По порядку добавления с увеличением номера.
- По порядку добавления с уменьшением номера.
- Сначала самые большие по площади.
- Сначала самые маленькие по площади.
- Сначала самые большие по периметру.
- Сначала самые маленькие по периметру.
Для кодирования возможных вариантов рекомендуется использовать классы-перечисления.