Skip to content

显示或隐藏表列

这篇文章展示了如何通过单击表的标题来显示或隐藏表的任何列

html
<table id="table">...</table>
<ul id="menu"></ul>

构建菜单

#menu添加一个菜单, 用于显示或隐藏表的列

html
<ul id="menu">
  <li>
    <!-- The check box to toggle the first column -->
    <label>
      <input type="checkbox" />
      Label of first column
    </label>

    <!-- Other items ... -->
  </li>
</ul>

创建菜单项, 遍历表头的每一列, 并为每一列创建一个菜单项

js
const menu = document.getElementById('menu')
const table = document.getElementById('table')
const headers = [].slice.call(table.querySelectorAll('th'))
// const headers = Array.from(table.querySelectorAll('th'));

headers.forEach(function (th, index) {
  // Build the menu item
  const li = document.createElement('li')
  const label = document.createElement('label')
  const checkbox = document.createElement('input')
  checkbox.setAttribute('type', 'checkbox')

  // Create the text node
  const text = document.createTextNode(th.textContent)

  label.appendChild(checkbox)
  label.appendChild(text)

  li.appendChild(label)
  menu.appendChild(li)
})

监听复选框事件, 当复选框被选中时, 显示对应的列, 否则隐藏对应的列

js
headers.forEach(function(th, index) {
    // Build the menu item
    ...
    // Handle the event
    checkbox.addEventListener('change', function(e) {
        e.target.checked ? showColumn(index) : hideColumn(index);
    });
});

INFO

稍后我们将将实现showColumn, hideColumn方法。

切换菜单

  • 右键单击表标题时将显示菜单
  • 当用户在菜单外单击时,它将隐藏
js
const thead = table.querySelector('thead');

// Handle the `contextmenu` event of the header
thead.addEventListener('contextmenu', function(e) {
    // Prevent the default context menu from being shown
    e.preventDefault();

    // Show the menu
    ...

    document.addEventListener('click', documentClickHandler);
});

// Hide the menu when clicking outside of it
const documentClickHandler = function(e) {
    ...
};

切换表列

先给 th 和 td 添加一个data-column-index属性, 用于标识列的索引

js
const numColumns = headers.length

const cells = [].slice.call(table.querySelectorAll('th, td'))
cells.forEach(function (cell, index) {
  cell.setAttribute('data-column-index', index % numColumns)
})

然后根据data-column-index属性获取列的索引,找到要隐藏的列, 并将其隐藏,实现hideColumn方法

js
const hideColumn = function (index) {
  cells
    .filter(function (cell) {
      return cell.getAttribute('data-column-index') === `${index}`
    })
    .forEach(function (cell) {
      cell.style.display = 'none'
    })
}

同样的思路实现showColumn方法

js
const showColumn = function (index) {
  cells
    .filter(function (cell) {
      return cell.getAttribute('data-column-index') === `${index}`
    })
    .forEach(function (cell) {
      cell.style.display = ''
    })
}

不允许隐藏最后一列

为了正常使用, 禁止隐藏最后一列, 让我们稍微修改一下构建菜单代码

js
headers.forEach(function(th, index) {
    // Build the menu item
    ...
    // 为checkbox添加一个属性, 用于标识列的索引
    checkbox.setAttribute('data-column-index', index);
});

当每个列都隐藏时,我们添加一个自定义属性data-shown来指示该列已被隐藏:

js
const hideColumn = function(index) {
  cells
      .filter(function(cell) {
          ...
      })
      .forEach(function(cell) {
          ...
          cell.setAttribute('data-shown', 'false');
      });
};

然后我们可以计算隐藏了多少列,如果只剩下一列,我们将禁用关联的复选框:

js
const hideColumn = function (index) {
  // How many columns are hidden
  const numHiddenCols = headers.filter(function (th) {
    return th.getAttribute('data-shown') === 'false'
  }).length

  if (numHiddenCols === numColumns - 1) {
    // There's only one column which isn't hidden yet
    // We don't allow user to hide it
    const shownColumnIndex = thead
      .querySelector('[data-shown="true"]')
      .getAttribute('data-column-index')

    const checkbox = menu.querySelector(
      `[type="checkbox"][data-column-index="${shownColumnIndex}"]`
    )
    checkbox.setAttribute('disabled', 'true')
  }
}

为了使它完全工作,我们需要初始化每个单元格的属性,并在显示列时将其转回:data-shown=true

js
cells.forEach(function(cell, index) {
  cell.setAttribute('data-shown', 'true');
});

const showColumn = function(index) {
  cells
      .filter(function(cell) {
          ...
      })
      .forEach(function(cell) {
          ...
          cell.setAttribute('data-shown', 'true');
      });

  menu.querySelectorAll(`[type="checkbox"][disabled]`)
      .forEach(function(checkbox) {
          checkbox.removeAttribute('disabled');
      });
};

完整代码

点击查看完整代码
vue
<template>
  <div class="container">
    <table id="table" class="table">
      <thead>
        <tr>
          <th data-type="number">No.</th>
          <th>First name</th>
          <th>Last name</th>
          <th>Date of birth</th>
          <th>Address</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td>1</td>
          <td>Andrea</td>
          <td>Ross</td>
          <td>1985-12-24</td>
          <td>95945 Rodrick Crossroad</td>
        </tr>
        <tr>
          <td>2</td>
          <td>Penelope</td>
          <td>Mills</td>
          <td>1978-8-11</td>
          <td>81328 Eleazar Fork</td>
        </tr>
        <tr>
          <td>3</td>
          <td>Sarah</td>
          <td>Grant</td>
          <td>1981-5-9</td>
          <td>5050 Boyer Forks</td>
        </tr>
        <tr>
          <td>4</td>
          <td>Vanessa</td>
          <td>Roberts</td>
          <td>1980-9-27</td>
          <td>765 Daryl Street</td>
        </tr>
        <tr>
          <td>5</td>
          <td>Oliver</td>
          <td>Alsop</td>
          <td>1986-10-30</td>
          <td>11424 Ritchie Garden</td>
        </tr>
        <tr>
          <td>6</td>
          <td>Jennifer</td>
          <td>Forsyth</td>
          <td>1983-3-13</td>
          <td>04640 Nader Ramp</td>
        </tr>
        <tr>
          <td>7</td>
          <td>Michelle</td>
          <td>King</td>
          <td>1980-8-29</td>
          <td>272 Alysa Fall</td>
        </tr>
        <tr>
          <td>8</td>
          <td>Steven</td>
          <td>Kelly</td>
          <td>1989-8-6</td>
          <td>5749 Foster Pike</td>
        </tr>
        <tr>
          <td>9</td>
          <td>Julian</td>
          <td>Ferguson</td>
          <td>1981-9-17</td>
          <td>6196 Wilkinson Parkways</td>
        </tr>
        <tr>
          <td>10</td>
          <td>Chloe</td>
          <td>Ince</td>
          <td>1983-10-28</td>
          <td>9069 Daniel Shoals</td>
        </tr>
      </tbody>
    </table>

    <ul id="menu" class="container__menu container__menu--hidden"></ul>
  </div>
</template>
<script setup lang="ts">
import { onMounted } from 'vue'
onMounted(() => {
  const menu = document.getElementById('menu') as HTMLElement
  const table = document.getElementById('table') as HTMLTableElement
  const headers = [].slice.call(table.querySelectorAll('th'))
  const cells = [].slice.call(table.querySelectorAll('th, td'))
  const numColumns = headers.length

  const thead = table.querySelector('thead') as HTMLTableSectionElement
  thead.addEventListener('contextmenu', function (e) {
    e.preventDefault()

    const rect = thead.getBoundingClientRect()
    const x = e.clientX - rect.left
    const y = e.clientY - rect.top

    menu.style.top = `${y}px`
    menu.style.left = `${x}px`
    menu.classList.toggle('container__menu--hidden')

    document.addEventListener('click', documentClickHandler)
  })

  // Hide the menu when clicking outside of it
  const documentClickHandler = function (e) {
    const isClickedOutside = !menu.contains(e.target)
    if (isClickedOutside) {
      menu.classList.add('container__menu--hidden')
      document.removeEventListener('click', documentClickHandler)
    }
  }

  const showColumn = function (index) {
    cells
      .filter(function (cell) {
        return cell.getAttribute('data-column-index') === `${index}`
      })
      .forEach(function (cell) {
        cell.style.display = ''
        cell.setAttribute('data-shown', 'true')
      })

    menu.querySelectorAll(`[type="checkbox"][disabled]`).forEach(function (checkbox) {
      checkbox.removeAttribute('disabled')
    })
  }

  const hideColumn = function (index) {
    cells
      .filter(function (cell) {
        return cell.getAttribute('data-column-index') === `${index}`
      })
      .forEach(function (cell) {
        cell.style.display = 'none'
        cell.setAttribute('data-shown', 'false')
      })
    // How many columns are hidden
    const numHiddenCols = headers.filter(function (th) {
      return th.getAttribute('data-shown') === 'false'
    }).length
    if (numHiddenCols === numColumns - 1) {
      // There's only one column which isn't hidden yet
      // We don't allow user to hide it
      const shownColumnIndex = thead?.querySelector('[data-shown="true"]')?.getAttribute('data-column-index')

      const checkbox = menu?.querySelector(
        `[type="checkbox"][data-column-index="${shownColumnIndex}"]`
      )
      checkbox?.setAttribute('disabled', 'true')
    }
  }

  cells.forEach(function (cell, index) {
    cell.setAttribute('data-column-index', index % numColumns)
    cell.setAttribute('data-shown', 'true')
  })

  headers.forEach(function (th, index) {
    // Build the menu item
    const li = document.createElement('li')
    const label = document.createElement('label')
    const checkbox = document.createElement('input')
    checkbox.setAttribute('type', 'checkbox')
    checkbox.setAttribute('checked', 'true')
    checkbox.setAttribute('data-column-index', index)
    checkbox.style.marginRight = '.25rem'

    const text = document.createTextNode(th.textContent)

    label.appendChild(checkbox)
    label.appendChild(text)
    label.style.display = 'flex'
    label.style.alignItems = 'center'
    li.appendChild(label)
    menu?.appendChild(li)

    // Handle the event
    checkbox.addEventListener('change', function (e) {
      e.target.checked ? showColumn(index) : hideColumn(index)
      menu?.classList.add('container__menu--hidden')
    })
  })
})
</script>
<style lang="scss" scoped>
.container {
  position: relative;
}

.container__menu {
  /* Absolute position */
  position: absolute;

  /* Reset */
  list-style-type: none;
  margin: 0;
  padding: 0;

  /* Misc */
  background-color: #f7fafc;
  border: 1px solid #cbd5e0;
  border-radius: 0.25rem;
  padding: 0.5rem;
}

.container__menu--hidden {
  display: none;
}

.table {
  border-collapse: collapse;
  width: 100%;
}

.table,
.table th,
.table td {
  border: 1px solid #ccc;
}

.table th,
.table td {
  padding: 0.5rem;
}

.table th {
  user-select: none;
}
</style>

示例

INFO

右键单击表头, 显示菜单, 选择要隐藏的列, 可以看到隐藏的列已经被隐藏, 只有最后一列时菜单中的复选框被禁用

No.First nameLast nameDate of birthAddress
1AndreaRoss1985-12-2495945 Rodrick Crossroad
2PenelopeMills1978-8-1181328 Eleazar Fork
3SarahGrant1981-5-95050 Boyer Forks
4VanessaRoberts1980-9-27765 Daryl Street
5OliverAlsop1986-10-3011424 Ritchie Garden
6JenniferForsyth1983-3-1304640 Nader Ramp
7MichelleKing1980-8-29272 Alysa Fall
8StevenKelly1989-8-65749 Foster Pike
9JulianFerguson1981-9-176196 Wilkinson Parkways
10ChloeInce1983-10-289069 Daniel Shoals

如有转载或 CV 的请标注本站原文地址