
Компоненты сетки, как известно, сложно построить. Есть так много аспектов, и требуется целая вечность, чтобы построить идеальный. При этом создание сетки для ваших конкретных нужд не должно быть таким сложным.
В этой статье мы рассмотрим, как создать компонент сетки «Griddy» на Vue.js. Общая идея состоит в том, чтобы изучить способы создания этого компонента, чтобы люди могли изучить свои конкретные варианты использования и расширить его.
Предупреждение
Компонент ни в коем случае не готов к производству, он предназначен исключительно для образовательных целей.
Понятия, которые мы исследуем.
Мы рассмотрим следующие концепции, они являются лишь минимальным набором, и мы будем лишь поверхностно.
- Фиксированный заголовок
- Изменить размер столбцов
- Получение данных
- Навигация
Почему именно Vue.js?
Мне это просто нравится, кодировать на Vue.js - это весело, он отлично подходит для разработки компонентов пользовательского интерфейса.
Мы будем использовать файл .vue для определения компонентов. Файл .vue будет разделен на разделы шаблона, сценария и стиля.
Разметка
<template>
<div class="wrapper" :style="{width: width, height: height + 'px'}">
<div class="grid-header">
<span class="title">{{title}}</span>
</div>
<div class="grid-body" :style="{height: tableBodyHeight + 'px'}">
<div class="table-header-wrapper">
<table class="table-header">
<tr>
<th v-for="column in columns" :data-column-name= "column.name" :width="column.width ? column.width : '100'"> {{column.label}}</th>
</tr>
</table>
</div>
<div class="table-body-wrapper">
<table class="table-body">
<tr v-for="row in rows">
<td v-for="column in columns" :width="column.width ? column.width : '100'"> {{val(row, column.field)}}</td>
</tr>
</table>
</div>
</div>
<div class="grid-footer">
<div v-if="!error">
<span><a @click="handlePrevious">Previous</a></span>
<span>{{page}}</span>
<span><a @click="handleNext">Next</a></span>
</div>
<span v-else>{{error}}</span>
</div>
</div>
</template>
В шаблоне есть оболочка для размещения компонента сетки. Обертка имеет значение overflow-y для прокрутки. Оболочка содержит заголовок сетки, тело сетки и нижний колонтитул сетки.
Заголовок сетки можно использовать для отображения заголовка, удерживающих кнопок и т. Д., В нашем примере есть только заголовок. Нижний колонтитул можно использовать для управления разбивкой на страницы. В нашем примере нижний колонтитул содержит предыдущую и следующую ссылку, а в центре отображается номер текущей страницы.
Тело сетки содержит две таблицы. Две таблицы необходимы для создания фиксированного эффекта заголовка. Техника для этого была адаптирована из статьи Джоша Мариначчи Священный Грааль: таблицы прокрутки на чистом CSS с фиксированным заголовком. Я не собираюсь подробно объяснять CSS, я прошу читателя изучить код и статью Джоша.
Код
Компонент Vue.js выполняет следующие функции
- title - Заголовок сетки
- columns - метаданные для столбцов, содержащих имя, поле для извлечения из ответа и ширину столбца
- width - Ширина сетки
- height - Высота сетки
- resourceURL - конечная точка API для получения данных
Когда компонент смонтирован, мы инициализируем его.
mounted () {
const vm = this
vm.header = vm.$el.getElementsByClassName('table-header')[0]
vm.body = vm.$el.getElementsByClassName('table-body')[0]
vm.setResizeGrips()
vm.syncColumnWidths()
},
Ссылка на table-header и table-footer хранится в компоненте. Метод setResizeGrips, как следует из его названия, добавляет ручки изменения размера в таблицу заголовков. Метод syncColumnWidths синхронизирует ширину столбцов между таблицей заголовков и основной таблицей. Как вы увидите, syncColumnWidths будет вызываться каждый раз при изменении размера столбца заголовка.
setResizeGrips () {
const vm = this
const headerCols = Array.from(vm.header.getElementsByTagName('th'))
headerCols.forEach((th) => {
th.style.position = 'relative'
const grip = document.createElement('div')
grip.className = 'grip'
grip.innerHTML = ' '
grip.style.top = 0
grip.style.right = 0
grip.style.bottom = 0
grip.style.width = '5px'
grip.style.position = 'absolute'
grip.style.cursor = 'col-resize'
grip.addEventListener('mousedown', this.onMouseDown)
th.appendChild(grip)
vm.grips.push(grip)
})
document.addEventListener('mousemove', this.onMouseMove)
document.addEventListener('mouseup', this.onMouseUp)
}
События мыши прикрепляются к каждой ручке и к документу.
syncColumnWidths () {
const vm = this
const headerCols = Array.from(vm.header.getElementsByTagName('th'))
const widths = headerCols.map((h) => h.width ? h.width : h.clientWidth)
const bodyCols = Array.from(vm.body.querySelectorAll('tr:first-child>td'))
bodyCols.forEach((c, i) => {
c.width = widths[i] + 'px'
})
}
События мыши
onMouseDown (e) {
const vm = this
vm.thElm = e.target.parentNode
vm.startOffset = vm.thElm.offsetWidth - e.pageX
},
onMouseMove (e) {
const vm = this
if (vm.thElm) {
const colName = vm.thElm.getAttribute('data-column-name')
const width = vm.startOffset + e.pageX
if (vm.greaterThenMinWidth(colName, width)) {
vm.thElm.width = width + 'px'
vm.syncColumnWidths()
}
}
},
onMouseUp (e) {
const vm = this
vm.thElm = undefined
vm.syncColumnWidths()
}
Код для синхронизации таблицы заголовков и основной таблицы был вдохновлен плагином jQuery colResizable.
Поскольку мы добавили прослушиватели событий к элементам DOM, мы должны нести достаточную ответственность, чтобы отменить их регистрацию при уничтожении компонента. Добавляем код в хук beforeDestroy. Ранее мы кэшировали захваты, мы будем использовать то же самое для удаления слушателей событий.
beforeDestroy () {
const vm = this
vm.grips.forEach((grip) => grip.removeEventListener('mousedown', vm.onMouseDown))
document.removeEventListener('mousemove', vm.onMouseMove)
document.removeEventListener('mouseup', vm.onMouseUp)
}
Получение данных осуществляется с помощью плагина vue-resource и свойства resourceURL. Это очень элементарно, для надежной реализации необходимо реализовать концепцию хранилища, чтобы абстрагироваться от выборки данных.
Наш выбор данных прост
fetchData () {
this.resource.get({results: this.records, page: this.page}).then((response) => {
this.rows = response.body.results
}).catch(() => {
this.error = 'Error occured while trying to fetch data'
})
}
Я не собираюсь объяснять вспомогательные методы, такие как handlePrevious click и т. Д., Они очень простые, пожалуйста, следуйте коду.
Хотя я явно не показал, как компонент может использоваться потребителем, это очень просто и понятно.
<griddy :columns="columns"
:resourceURL = "resourceURL"
width="200"
height="500">
</griddy>
Где столбцы содержат метаданные столбца, а URL-адрес ресурса содержит конечную точку API для запроса
resourceURL: 'https://randomuser.me/api', columns: [ {name: 'title', label: 'Title', field: 'name.title', width: '100', type: 'text', isMandatory: true}, {name: 'firstName', label: 'First Name', field: 'name.first', width: '200', type: 'text', isMandatory: true}, {name: 'lastName', label: 'Last Name', field: 'name.last', width: '200', type: 'text', isMandatory: true}, {name: 'dataOfBirth', label: 'Date of Birth', field: 'dob', type: 'date', width: '200', format: 'yyyy-MM-dd'} ]
Результат
Что-то довольно интересное для 200+ строк HTML, JS и CSS, есть огромные возможности для улучшения. Недавно я прочитал статью Эндрю Койла о дизайне сетки данных под названием Лучшее проектирование таблиц данных. Это был исчерпывающий набор шаблонов проектирования для таблиц данных. Я хочу, чтобы когда-нибудь наш скромный элемент управления сеткой реализовал все эти шаблоны.
Я надеюсь, что это дало вам представление о том, как можно построить сеточный компонент. Не стесняйтесь оставлять комментарии для предложений, улучшений и т. Д.
Код компонента сетки доступен по адресу https://github.com/harin76/griddy.
Благодарности и благодарности
- Джош Мариначчи - За Святой Грааль: таблицы прокрутки на чистом CSS с фиксированным заголовком
- Альваро Прието Лауроба - плагин jQuery colResizable
- VanillaJS - манипуляции с DOM
- Эндрю Койл - Создавайте лучшие таблицы данных
- randomuser.me - REST API для тестирования.