Фильтрация и сортировка данных #
Проблема отображения большого объёма данных #
В рамках данной работы рассмотрим типичные сценарии работы с большими наборами данных. Ключевая особенность заключается в том, что невозможно предоставлять пользователю сразу большой набор данных:
- Большой объём данных долго обрабатывает система (как на сервере, так и на клиенте), что делает работу пользователя с системой неудобной.
- Пользователю неудобно работать с очень большими документами.
- Системы хотят самостоятельно обрабатывать свои данные и не делиться этими данными с другими системами.
Для быстрого предоставления данных применяется подход с частичным отображением информации, постраничный вывод. Для решения проблемы нахождения информации реализуется фильтрация элементов по различным критериям.
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 поле, с помощью которого пользователь может управлять сортировкой данных в списке. Данное поле должно содержать следующие варианты сортировки:
- По порядку добавления с увеличением номера.
- По порядку добавления с уменьшением номера.
- Сначала самые большие по площади.
- Сначала самые маленькие по площади.
- Сначала самые большие по периметру.
- Сначала самые маленькие по периметру.
Для кодирования возможных вариантов рекомендуется использовать классы-перечисления.