Пишем компонент — таблицу, не совсем обычным способом
Еще одна небольшая статейка попроще вдогонку. Расскажу, как я рисую таблицы во Vue.
Компонентов-таблиц для Vue наделано немало. С различными возможностями. И везде по-разному таблица собирается в template страницы или какого-то компонента.
В основном происходит это как-то так:
Тут мы передаем в компонент cmp-table данные (items) и настройки колонок (columns). А сам компонент уже рендерит таблицу по этим настройкам. Настройки бывают организованны по всякому. Просто отдельно настройки колонок или вообще все в кучу — настройки колонок, таблицы, каких-то действий и т.п.
Мне в таком подходе не нравится то, как организована настройка рендера колонок. Как их названий в шапке thead таблицы, так и самого содержимого колонок. Этот функционал хочется видеть в самом template, там строить колонки (их содержимое и шапку). Так нагляднее и удобнее. Как по мне.
Такой подход, например, используется в самом популярном вроде на сегодняшний день сборнике компонентов — Element. Там это выглядит так:
Здесь все наглядно. Мы сразу представляем себе общий вид колонок и ячеек. А в компонент el-table передаем лишь данные и настройки самой таблицы.
И все бы ничего. Нравится мне в общем Element. Но часто замечал, что их таблицы подтормаживают. Полез в их код. И офигел от того, как заморочисто там происходит рендер строк, ячеек и всего остального. Просто тонны кода. И стал я думать, как можно сделать построение таблицы так-же как у них, только проще. Намного проще.
Сейчас расскажу и покажу, что у меня получилось.
Сразу о том, что в итоге будет представлять собой наш компонент. Чтобы происходящее дальше проще понималось:
- построение колонок аналогично Element (el-table)
- возможность кастомизации вида ячеек
- очень мало кода
- и возможность расширения функционала без особых проблем
- компонента TableColumn — с его помощью мы будем формировать вид шапки таблицы и ячеек
- компонента Table — в нем будет все собираться и рендериться
table-column.js:
Это компонент Vue c именем vu-table-column и парой входящих параметров:
- prop — в него передаем наименование свойства строки переданных данных в таблицу.
- title — название этого свойства, которое будет отображаться в шапке таблицы.
И пройдемся по коду.
В начале импортируем стили таблицы.
И еще я тут использую get-функцию lodash-а. Это не обязательно. Она здесь чтобы код в итоге был короче.
Дальше входящий параметр rows, куда передаем наши данные, в виде массива строк.
Теперь по render-функции:
В columnOptions мы формируем настройки наших колонок и ячеек. Для этого сначала собираем, фильтруя, все элементы с тегом vu-table-column ( компонент TableColumn) из дефолтного слота (this.$slots.default) компонента Table.
Компонент TableColumn нам нужен пока только для того, чтобы передать настройки колонки удобным и наглядным образом. Вот почему в TableColumn нет render-функции. Потому-что мы не рендерим этот компонент. Только забираем данные.
И пробегаемся по массиву отфильтрованных vu-table-column, формируем массив объектов с входящими props-ами из vu-table-column и добавляем свойство scopedSlot. В нем будет храниться дефолтный слот с ограниченной областью видимости, если таковой передан в vu-table-column в template страницы. Представляет он собой функцию, которая рендерит содержимое этого слота. И в нее можно передать любые параметры, которые используются в шаблоне этого слота. Этот слот мы и будем использовать для кастомного вида ячеек.
Дальше собираем columnsHead (ячейки шапки таблицы) — пробегаемся по выше определенным настройкам колонок (columnOptions) выдергивая title — название колонки, которое мы передали в vu-table-column.
Формируем массив rows (собственно итоговых строк таблицы) — пробегаемся по нашим входящим rows, и в каждом элементе tr выводим ячейки с помощью метода renderColumns:
В метод мы передаем h-функцию (псевдоним функции $createElement, которая рендерит vNode), данные строки и массив настроек колонок columnsOptions.
И собираем массив ячеек, в которых рендерим:
- либо кастомный вид, если в настройках колонки есть слот с ограниченной видимостью, запуская функцию scopedSlot с параметрами row (содержащим объект строки) и items (данные, переданные в vu-table). Здесь мы можем передать все, что нам надо
- либо просто значение свойства с именем column.prop из строки row
И в конце render-функции выводим итоговую таблицу, с вставленными в нее ячейками шапки и отрендеренными строками. Все!
Вот и весь код, для рисования ячеек и шапки. Дальше уже можно прилеплять фильтрацию, скрытие/показ колонок, сортировку и все, что душа пожелает. Это уже следующая история. И будет в следующей статье. Дабы не писать бесконечные тексты.
И пример использования написанного компонента:
Тут мы используем как обычные title, так и кастомный вид ячеек.
И в ячейках Rating мы, используя данные items (которые передали в функцию scopedSlot и содержащие входящий массив со строками) и значение свойства rating определяем, является ли текущая строка с наибольшим рейтингом. Если да, выводим жирным текстом 'Best choice!'
И готовый результат:
Вот такой компонент в итоге получился.
В данный момент я его пилю. Добавляю функционал. И в следующей статье опишу уже расширение возможностей.