Компоненты сетки, как известно, сложно построить. Есть так много аспектов, и требуется целая вечность, чтобы построить идеальный. При этом создание сетки для ваших конкретных нужд не должно быть таким сложным.

В этой статье мы рассмотрим, как создать компонент сетки «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 = '&nbsp'
        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.

Благодарности и благодарности

  1. Джош Мариначчи - За Святой Грааль: таблицы прокрутки на чистом CSS с фиксированным заголовком
  2. Альваро Прието Лауроба - плагин jQuery colResizable
  3. VanillaJS - манипуляции с DOM
  4. Эндрю Койл - Создавайте лучшие таблицы данных
  5. randomuser.me - REST API для тестирования.