Как я могу реализовать сворачивание строк, как это делает datatables. js? - PullRequest
2 голосов
/ 21 июня 2020

Я хотел бы реализовать адаптивное поведение таблиц данных для скрытия столбцов, когда ширина таблицы уменьшается после определенной точки (в основном, точки, когда произойдет переполнение элемента tr).

Я знаю, как я может сворачивать строки: просто при изменении размера окна проверьте, когда ширина таблицы больше, чем ширина ее контейнера # table-wrapper. Когда это происходит, я скрываю самый внешний столбец таблицы и помещаю его в стек, добавляя эти значения к каждой расширенной части строк (которая будет переключаться, чтобы быть видимой, как это делают таблицы данных).

Если доступ к веб-сайту осуществляется, когда окно имеет небольшой размер, при загрузке таблица может проверять условие переполнения (table.width> table-wrapper.width) и перебирать самые внешние столбцы, скрывая их и помещая в стек до тех пор, пока условие переполнения не станет ложным.

Однако, как я могу вернуть элементы? То есть, когда таблица растет, я не уверен, при каких условиях я могу вытащить столбцы из стека и показать их.

Я подумываю каким-то образом получить минимальный размер таблицы, и при изменении размера окна проверьте, не превышает ли ширина обертки минимальный размер таблицы плюс минимальный размер первого элемента в стеке? Однако я не знаю, как получить эти минимальные значения ширины.

Есть ли надежный способ получить эти минимальные значения ширины для любого размера шрифта, или вы можете порекомендовать лучший способ?

<table class="test">
     <tbody>

         <tr>
           <th> heading1 </th>
           <th> heading2 </th>
           <th> heading3 </th>
           <th> heading4 </th>
         </tr>
       
         <tr>
             <td>data1</td>
             <td>data2</td>
             <td>data3</td>
             <td>data4</td>
         </tr>

          <tr>
             <td>data1</td>
             <td>data2</td>
             <td>data3</td>
             <td>data4</td>
         </tr>

     </tbody>

</table>

* Обновление:

Я подумал, что это очень нелегкое решение, в котором я мог бы разместить невидимую копию таблицы непосредственно за ней, а при запуске события изменения размера окна я мог бы постоянно добавлять невидимый столбец в невидимая таблица и проверьте условие переполнения (table_border> wrapper_border). Это кажется действительно неэффективным ...

Я также узнал, что минимальная ширина для элементов

не определена. Тем не менее, можно разместить или

внутри каждого элемента, который имеет минимальную ширину. Для каждого столбца минимальное значение равно столбцу максимальной длины. Итак, я думаю, я мог бы проверить все данные для каждого столбца и таким образом получить минимальную длину. Их в. ширина таблицы - вот что мне нужно для обоснования того, вызовет ли добавление еще одного столбца переполнение.

Что вы думаете; у вас есть другие идеи?

Ответы [ 2 ]

1 голос
/ 02 июля 2020

Один из подходов состоит в том, чтобы иметь данные с использованием элементов white-space: nowrap внутри ячеек таблицы и вычислять минимальную ширину каждой строки, которая будет максимальной шириной элементов данных строки. Эти "ширины разрыва" будут рассчитаны только один раз, когда таблица будет заполнена данными ( при загрузке или заново создать данные ). Имея эти "ширины разрыва" , мы можем вычислить минимальную ширину таблицы и сравнить ее с шириной основного клиента и соответственно скрыть столбцы при изменении размера окна.

Расчет "разрыва -width "

Это выполняется при загрузке или при каждом обновлении таблицы новыми данными ( т.е. изменение страницы разбивки на страницы ). Мы go через элементы TH и TD ячеек таблицы и сохраняем самую большую ширину текста ячейки из элементов span.

function calcBreakWidths() {
  // Each column one base index
  let thIndex = 1;

  for (let th of document.querySelectorAll('thead th')) {
    // Get the width of every text span of every TH column,
    // this way, we'll have also header break-widths
    let breakWidth = document.querySelector(`thead th:nth-child(${thIndex}) > span`).offsetWidth;

    // Go through all column TD elements and keep the biggest text span width
    for (let span of document.querySelectorAll(`tbody td:nth-child(${thIndex}) > span`)) {
      if (span.offsetWidth > breakWidth) {
        breakWidth = span.offsetWidth;
      }
    }

    // Save the biggest text span break-width to the TH dataset
    th.dataset.breakwidth = breakWidth;

    // Next column index
    thIndex++;
  }
}

Скрыть / Показать столбцы при изменении размера окна

Вкл window.resize мы вычисляем минимальную ширину таблицы, суммируя все ширины разрыва . Затем мы go просматриваем каждый столбец, начиная с самого правого, и проверяем, не превышает ли минимальная ширина таблицы, включая текущий столбец, ширину основного клиента; если это так, то мы скрываем текущий столбец, иначе мы показываем текущий столбец. Это делается с использованием classList.add и classList.remove с использованием класса с именем hidden со стилем display: none CSS. В конце каждой итерации столбца мы вычитаем текущий столбец ширина разрыва из минимальной ширины таблицы, чтобы получить следующую правильную минимальную ширину таблицы без текущего столбца ширина разрыва .

window.addEventListener('resize', function() {
  const bodyWidth = document.body.clientWidth;

  // Get all columns break-widths from the TH element dataset
  const breakWidths = [...document.querySelectorAll('th')]
    .map(th => parseInt(th.dataset.breakwidth));

  // Sum-up all column break-widths (+2 pixels) to calculate table minimum width
  let tableMinWidth = breakWidths
    .reduce((total, breakWidth) => total + breakWidth + 2, 0);

  for (let column = breakWidths.length; column > 0; column--) {
    const tableIsLarger = tableMinWidth > bodyWidth;

    // const th = document.querySelector(`th:nth-child(${column})`);
    const cells = document.querySelectorAll(`th:nth-child(${column}), td:nth-child(${column})`);

    // If table min width is larger than body client width,
    // then hide the current column
    if (tableMinWidth > bodyWidth) {
      // We're hidding the column by iterating on each table cell
      // and add the hidden class only if the column does not already contain
      // the hidden class. We're doing this for performance reasons
      if (!cells[0].classList.contains('hidden')) {
        cells.forEach(cell => cell.classList.add('hidden'));
      }
    // Else if the table min width is not larger than body client width,
    // we remove the hidden class from the column to show each column cell
    } else if (cells[0].classList.contains('hidden')) {
      cells.forEach(cell => cell.classList.remove('hidden'));
    }

    // Subtract current column break-width from the total table min width
    // so to have the correct min table width for the next column
    tableMinWidth -= breakWidths[column - 1] + 2;
  }
});

Фрагмент показывает это в действии

Прочтите встроенные комментарии

// Each TH class is a field name
const fields = [...document.querySelectorAll('thead th')].map(el => el.className);

// Generate 20 rows with fake data
for (let i = 0; i <= 20; i++) {
  const tr = document.createElement('tr');

  fields.forEach(field => {
    const td = document.createElement('td');
    const text = document.createElement('span');
    td.className = field;
    text.textContent = fake(field);
    td.appendChild(text);
    tr.appendChild(td);
  });

  document.querySelector('table tbody').appendChild(tr);
}

// Calculate each column break width, the max data span element width
function calcBreakWidths() {
  let thIndex = 1;

  for (let th of document.querySelectorAll('thead th')) {
    let breakWidth = document.querySelector(`thead th:nth-child(${thIndex}) > span`).offsetWidth;
    for (let span of document.querySelectorAll(`tbody td:nth-child(${thIndex}) > span`)) {
      if (span.offsetWidth > breakWidth) {
        breakWidth = span.offsetWidth;
      }
    }

    th.dataset.breakwidth = breakWidth;
    thIndex++;
  }
}

calcBreakWidths();

// Handle window resize and hide each column exceeds BODY client width 
window.addEventListener('resize', function() {
  const bodyWidth = document.body.clientWidth;

  // Get the break widths saved to the TH datasets
  const breakWidths = [...document.querySelectorAll('th')]
    .map(th => parseInt(th.dataset.breakwidth));

  // Calculate table min width (+2 pixels for border + padding for each cell)
  let tableMinWidth = breakWidths
    .reduce((total, breakWidth) => total + breakWidth + 2, 0);

  // Loop from last to the first column and compare the widths
  for (let column = breakWidths.length; column > 0; column--) {
    const cells = document.querySelectorAll(`th:nth-child(${column}), td:nth-child(${column})`);

    if (tableMinWidth > bodyWidth) {
      if (!cells[0].classList.contains('hidden')) {
        cells.forEach(cell => cell.classList.add('hidden'));
      }
    } else if (cells[0].classList.contains('hidden')) {
      cells.forEach(cell => cell.classList.remove('hidden'));
    }

    tableMinWidth -= breakWidths[column - 1] + 2;
  }
});

// Function to create fake data
function fake(what) {
  switch (what) {
    case 'name': return faker.name.findName();
    case 'email': return faker.internet.exampleEmail();
    case 'address': return faker.address.streetAddress();
    case 'country': return faker.address.country();
    case 'account': return faker.finance.accountName();
  }
}
table {
  border-collapse: collapse;
  width: 100%;
}

table, th, td {
  border: 1px solid black;
}

tbody td > span,
thead th > span {
  white-space: nowrap;
  background-color: gold;
}

thead th > span {
  background-color: aquamarine;
}

.hidden {
  display: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/Faker/3.1.0/faker.min.js" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Faker/3.1.0/locales/en/faker.en.min.js" crossorigin="anonymous"></script>

<table>
  <thead>
    <tr>
      <th class="name"><span>Name<span></th>
      <th class="email"><span>Email</span></th>
      <th class="account"><span>Personal or Business Account</span></th>
      <th class="address"><span>Personal or Business Address</span></th>
      <th class="country"><span>Country</span></th>
    </tr>
  </thead>
  <tbody>
  </tbody>
</table>

Вы можете запустить приведенный выше фрагмент кода и попытаться изменить размер окна, но у вас будет лучшее представление об адаптивном поведении таблицы, если вы попробуйте изменить размер браузера после открытия фрагмента на Полная страница или перейдя на страницу с полным примером здесь: https://zikro.gr/dbg/so/62491859/responsive-table.html

Это не идеально, но работает плавно в моем браузере, и он скрывает столбцы справа налево в зависимости от ширины текста данных столбцов.

0 голосов
/ 03 июля 2020

Не могли бы вы привести пример желаемого поведения? Если вы говорите об этих таблицах данных. js поведение: https://www.datatables.net/examples/styling/semanticui.html, то простой ответ будет следующим: CSS Правило https://www.w3schools.com/cssref/css3_pr_mediaquery.asp

@media only screen and (max-width: 600px) {
  table td:nth-child(5) {
    display: none;
    visibility:hidden;
  }
}
@media only screen and (max-width: 700px) {
  table td:nth-child(6) {
    display: none;
    visibility:hidden;
  }
}

(вы поняли)

...