...

Как создать сложный отчет, который будет прост в поддержке

Тема в разделе "Примеры решений и дополнительных модулей", создана пользователем an.bogdanova, 7 июн 2024.

  1. an.bogdanova

    an.bogdanova Новичок

    Чаще всего, когда мы создаем какой-либо кастомный табличный отчет, спустя какое-то время появляется необходимость внести в него правки. Например, нужно поменять столбцы местами, добавить дополнительные столбцы, группировку, сортировку или фильтрацию. Это может занять довольно много времени, если идти стандартным путем. Обычно мы прописываем шапку таблицы в html, потом создаем строки в JS копированием template строки или поочередно создавая каждую ячейку в ней с добавлением множества параметров. Если это простая таблица, то сделать это довольно быстро, но что произойдет, если отчет будет дорабатываться несколько раз, несколькими людьми, будет расширяться функционал? В какой-то момент даже самые незначительные правки станут занимать большое количество времени.

    Рассмотрим метод, который поможет облегчить создание и дальнейшие правки отчетов.

    Допустим, нам нужно создать отчет по сотрудникам компании с полями «Имя», «Фамилия», «Возраст» и «Должность», где должность — это приложение. Первым делом создадим метод «getData» или «getUsersData» в клиентских сценариях, который будет возвращать массив подготовленных данных примерно в таком формате:
    [​IMG]

    В виджет коде мы будем вызывать этот метод и получать данные, по которым будем строить таблицу. На этапе создания таблицы можно использовать и тестовые данные, но все же лучше сначала написать клиентский скрипт, чтобы получить реальные значения и предусмотреть, как будут выглядеть неидеальные данные в верстке. Чтобы использовать реальные данные, выведем в консоль на onInit результат выполнения метода getData, кликнем в консоли на объект правой кнопкой мыши, «copy object», после чего вставим его в переменную data в локальный файл, в котором и будем разрабатывать таблицу. Если разрабатываем с использованием gulp, то переносим значение в файл с тестовыми данными и импортируем их, если же просто в локальной папке, то дальнейшее использование переменной data нужно будет заменить на вызов <%= Scripts%>.getData()
    Заранее создадим контейнер для будущего отчета:
    [​IMG]

    Теперь можно приступить к JS. Основа этого метода создания отчетов – хранение основной логики таблицы в объекте, а не в разных местах кода. Сначала нам нужно создать шапку таблицы, поэтому создадим объект tableCols, в котором будут храниться настройки колонок с полями «name» и «code»:
    [​IMG]

    Также можно хранить объект настроек таблицы в клиентских сценариях и получать его в виджет коде, чтобы все хранилось в одном месте и было легче поддерживать.
    Теперь можно пройтись циклом по этому объекту и создать шапку таблицы:
    [​IMG]

    В этой статье не будем углубляться в стили, применим только самые простые для позиционирования.
    Теперь можно приступить к созданию тела таблицы. Для этого пройдемся циклом по массиву данных. Мы не будем для каждой ячейки в строке прописывать отдельный блок кода для ее создания, а так же пройдемся циклом по массиву колонок. Но тут у нас появится вопрос – как определить контент ячейки? Для этого в объект колонки добавим свойство-функцию «getValue»:
    [​IMG]
    [​IMG]

    Теперь, если нам нужно будет удалить колонку таблицы, то мы просто удалим из массива tableCols объект колонки. Если нужно будет поменять порядок столбцов, то сделаем это там же.

    Рассмотрим еще один вариант, когда одна из колонок - это ссылка. Для этого добавим в объекты колонок метод getLink. Если колонка не является ссылкой, то он будет равен undefined:
    [​IMG]
    [​IMG]

    Таким образом можно добавлять различные параметры для отрисовки ячеек. Например, можно добавить свойство «postfix», если после значения ячейки нужно подставить «руб», но мы не можем получать значение сразу в формате строки, потому что нужно будет считать по значениям отфильтрованных строк тотал. Если нам нужен тотал, но не все колонки должны суммироваться, мы можем добавить свойство «hasTotal». Также можно добавить свойство «classes», чтобы не искать классы ячеек по всему документу, в том случае, если названия поменялись; если классов достаточно много; если у колонок вообще разные классы.

    Теперь разберем пример с группировкой данных. Допустим, у нас есть панель с фильтрами, в которых пользователь может выбрать способ группировки, после чего таблица перерисовывается. Создадим дополнительный объект «filters» и добавим в него свойство «groupBy»:
    [​IMG]

    Нужно предусмотреть, чтобы при изменении значения этого фильтра оно записывалось в текущую переменную. Значение этого свойства должно быть равно свойству «code» соответствующей колонки. Также добавим свойство-метод «getGroupCode» в настройки колонок, которое возвращает значение для определения принадлежности к группе. Обратите внимание, что это значение должно быть уникальным, лучше всего использовать id, если это возможно:
    [​IMG]

    Теперь добавим группировку в createBody. Для начала нам необходимо сгруппировать данные по массивам:
    [​IMG]

    Когда массивы сгруппированы, нужно понять, как мы будем их отображать. Для сгруппированных данных не подходит тот метод отрисовки, который мы написали ранее. Теперь нам нужно проходиться циклом по массиву групп, а по каждой группе отрисовывать строки, но только если группировка включена. Для удобства разделения методов вынесем код создания строки в отдельную функцию:
    [​IMG]

    Я буду группировать строки, просто добавив над группой дополнительную строку с общим свойством группы. Доработаем функцию createBody:
    [​IMG]

    Группировка готова. Теперь рассмотрим пример с фильтрацией. Будем рассматривать такой же вариант, как с группировкой – допустим, у нас есть панель с фильтрами, в которых заполняются параметры фильтрации, после чего записываются в объект с фильтрами. Добавим в объект filters свойство «filterBy». Оно будет объектом, где ключом является code колонки, а значением объект с двумя полями – «value» (выбранное значение в удобном формате) и «filterData» (метод, который принимает объект данных строки и выбранные значения фильтра, а возвращает результат фильтрации). Проведем фильтрацию по свойству «Должность». Для наглядности пока не будем группировать:
    [​IMG]

    Фильтрацию лучше проводить до сортировки и группировки, так при следующих шагах мы будем перебирать меньше элементов:
    [​IMG]

    Фильтрация готова. Раскомментируем группировку:
    [​IMG]

    Все работает корректно. Теперь приступим к сортировке. Принцип работы тот же, что в группировке и фильтрации. Сортировку лучше проводить после фильтрации, чтобы перебирать меньше данных, но перед группировкой, тогда сортировка отлично ляжет в группировку и не нужно будет сортировать группы по одной. Добавим в filters свойство «sortBy», значение которого будет равно code выбранной колонки, и напишем сортировку. При сортировке мы уже смотрим не на id, а на value:
    [​IMG]

    Также можно добавить направление сортировки. Добавим в filters свойство «sortDirection» со значением Boolean – если true, то по убыванию, если false, то по возрастанию:
    [​IMG]

    Вернем группировку – внутри групп данные также сортируются:
    [​IMG]

    При использовании сортировок, группировок и фильтраций важно не допускать лишних запросов данных. Перед разработкой нужно определить, при изменении каких фильтров необходимо делать запрос, чтобы с отчетом было приятно работать. В большинстве случаев подходит такой вариант – если меняются даты отчета, то делаем запрос, в других случаях работаем с уже запрошенными датами по периоду. Чтобы это реализовать, в функцию «createTable» можно передавать аргумент «needReq» со значением Boolean, а потом передавать в функцию «getData» на клиентских сценариях, которая либо делает запрос, а потом сохраняет новые данные в глобальную переменную на клиенте, либо просто возвращает сохраненные данные, если needReq === false. Также обратите внимание, что не всегда нужно при каждой перерисовке таблицы заново заполнять шапку и тотал, поэтому в createTable нужно поставить условие и на вызов этих методов тоже.
    Теперь можно добавить тотал. Тотал нужно делать после фильтрации, чтобы считалась сумма только по отфильтрованным данным. Допустим, мы хотим посчитать сумму возрастов сотрудников. Добавим в настройки колонок свойство «hasTotal»:
    [​IMG]

    Добавим в html контейнер для тотала:
    [​IMG]

    Теперь в createTable создадим переменную total и передадим ее в createBody, а из createBody в createRow:
    [​IMG]
    [​IMG]
    [​IMG]

    Можно было бы не прокидывать total в несколько функций, а посчитать его после фильтрации или в отдельной функции, но тогда нам пришлось бы создавать еще один цикл по data и по колонкам. Теперь создадим метод «createTotal» после createBody:
    [​IMG]

    Готово! Теперь ваш отчет можно легко и быстро править и его код занимает всего 300 строк.