在前端开发中,表格控件是非常常见的组件之一。然而,大多数表格控件都只支持单一的输入方式,例如只能使用鼠标或只能使用键盘进行输入,这对于用户的使用体验来说并不友好。
本文将介绍如何使用 Custom Elements 构建一个支持多种输入方式的表格控件,并提供示例代码以供参考。
Custom Elements
Custom Elements 是 Web Components 规范的一部分,它允许开发者自定义 HTML 元素,并定义其行为和样式。使用 Custom Elements 可以将一些常见的组件封装成自定义元素,使其更易于使用和维护。
在本文中,我们将使用 Custom Elements 来封装一个表格控件,并添加多种输入方式的支持。
构建表格控件
首先,我们需要创建一个自定义元素来表示表格控件。以下是一个简单的示例:
// www.javascriptcn.com code example
<template id="table-template">
<style>
/* 表格样式 */
</style>
<table>
<!-- 表格内容 -->
</table>
</template>
<script>
class Table extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
const template = document.querySelector('#table-template');
const content = template.content.cloneNode(true);
this.shadowRoot.appendChild(content);
}
}
customElements.define('my-table', Table);
</script>
<my-table></my-table>在上面的代码中,我们创建了一个名为 Table 的自定义元素,并在其中添加了一个 template 元素来定义表格的样式和内容。在 Table 类的构造函数中,我们使用 attachShadow 方法创建了一个 Shadow DOM,然后将 template 元素的内容复制到其中。最后,我们使用 customElements.define 方法将自定义元素注册到文档中。
现在,我们已经创建了一个基本的表格控件,接下来我们将添加多种输入方式的支持。
添加多种输入方式的支持
鼠标输入
首先,我们添加鼠标输入的支持。在表格中,鼠标输入主要用于选择单元格和拖动调整列宽。
为了支持鼠标输入,我们需要在 Table 类中添加一些事件处理方法。以下是一个示例:
// www.javascriptcn.com code example
class Table extends HTMLElement {
constructor() {
super();
// ...
this._initMouseEvents();
}
_initMouseEvents() {
let isDragging = false;
let startColumnWidth = 0;
this.shadowRoot.addEventListener('mousedown', e => {
if (e.target.tagName === 'TH') {
isDragging = true;
startColumnWidth = e.target.offsetWidth;
}
});
this.shadowRoot.addEventListener('mousemove', e => {
if (isDragging) {
const th = e.target;
const table = th.closest('table');
const colIndex = Array.from(table.querySelectorAll('th')).indexOf(th);
const newWidth = startColumnWidth + e.pageX - th.getBoundingClientRect().left;
const cells = table.querySelectorAll(`tr td:nth-child(${colIndex + 1})`);
th.style.width = `${newWidth}px`;
cells.forEach(cell => cell.style.width = `${newWidth}px`);
}
});
this.shadowRoot.addEventListener('mouseup', e => {
isDragging = false;
});
}
}在上面的代码中,我们添加了三个事件处理方法:mousedown、mousemove 和 mouseup。当用户按下鼠标时,如果目标元素是 th 元素,则表示用户正在调整列宽,我们记录下当前列宽和鼠标位置。在鼠标移动时,如果用户正在调整列宽,则根据鼠标位置计算出新的列宽,并更新表头和单元格的宽度。最后,当用户释放鼠标时,我们将 isDragging 标志设置为 false。
键盘输入
除了鼠标输入,我们还需要支持键盘输入。在表格中,键盘输入主要用于导航和编辑单元格。
为了支持键盘输入,我们需要在 Table 类中添加一些键盘事件处理方法。以下是一个示例:
// www.javascriptcn.com code example
class Table extends HTMLElement {
constructor() {
super();
// ...
this._initKeyboardEvents();
}
_initKeyboardEvents() {
let currentCell = null;
this.shadowRoot.addEventListener('keydown', e => {
switch (e.key) {
case 'ArrowUp':
e.preventDefault();
this._moveUp(currentCell);
break;
case 'ArrowDown':
e.preventDefault();
this._moveDown(currentCell);
break;
case 'ArrowLeft':
e.preventDefault();
this._moveLeft(currentCell);
break;
case 'ArrowRight':
e.preventDefault();
this._moveRight(currentCell);
break;
case 'Enter':
e.preventDefault();
this._editCell(currentCell);
break;
}
});
this.shadowRoot.addEventListener('click', e => {
if (e.target.tagName === 'TD') {
this._editCell(e.target);
}
});
this.shadowRoot.addEventListener('focusin', e => {
if (e.target.tagName === 'TD') {
currentCell = e.target;
}
});
this.shadowRoot.addEventListener('focusout', e => {
if (e.target.tagName === 'TD') {
currentCell = null;
}
});
}
_moveUp(cell) {
// ...
}
_moveDown(cell) {
// ...
}
_moveLeft(cell) {
// ...
}
_moveRight(cell) {
// ...
}
_editCell(cell) {
// ...
}
}在上面的代码中,我们添加了四个键盘事件处理方法:keydown、click、focusin 和 focusout。当用户按下方向键时,我们根据当前单元格的位置计算出下一个单元格的位置,并将焦点设置到该单元格中。当用户点击单元格时,我们将焦点设置到该单元格中。当单元格获得焦点时,我们记录下当前单元格的位置。当单元格失去焦点时,我们将当前单元格的位置设置为 null。
在 _moveUp、_moveDown、_moveLeft 和 _moveRight 方法中,我们根据当前单元格的位置计算出下一个单元格的位置,并将焦点设置到该单元格中。在 _editCell 方法中,我们将单元格设置为可编辑状态,并将焦点设置到单元格中。
完整示例代码
以下是一个完整的示例代码,包括了支持鼠标输入和键盘输入的代码:
// www.javascriptcn.com code example
<template id="table-template">
<style>
table {
border-collapse: collapse;
width: 100%;
}
th,
td {
border: 1px solid #ddd;
padding: 8px;
text-align: left;
}
th {
background-color: #f2f2f2;
cursor: pointer;
user-select: none;
}
td[contenteditable] {
outline: none;
}
</style>
<table>
<thead>
<tr>
<th>姓名</th>
<th>年龄</th>
<th>性别</th>
</tr>
</thead>
<tbody>
<tr>
<td>张三</td>
<td>18</td>
<td>男</td>
</tr>
<tr>
<td>李四</td>
<td>20</td>
<td>女</td>
</tr>
<tr>
<td>王五</td>
<td>22</td>
<td>男</td>
</tr>
</tbody>
</table>
</template>
<script>
class Table extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
const template = document.querySelector('#table-template');
const content = template.content.cloneNode(true);
this.shadowRoot.appendChild(content);
this._initMouseEvents();
this._initKeyboardEvents();
}
_initMouseEvents() {
let isDragging = false;
let startColumnWidth = 0;
this.shadowRoot.addEventListener('mousedown', e => {
if (e.target.tagName === 'TH') {
isDragging = true;
startColumnWidth = e.target.offsetWidth;
}
});
this.shadowRoot.addEventListener('mousemove', e => {
if (isDragging) {
const th = e.target;
const table = th.closest('table');
const colIndex = Array.from(table.querySelectorAll('th')).indexOf(th);
const newWidth = startColumnWidth + e.pageX - th.getBoundingClientRect().left;
const cells = table.querySelectorAll(`tr td:nth-child(${colIndex + 1})`);
th.style.width = `${newWidth}px`;
cells.forEach(cell => cell.style.width = `${newWidth}px`);
}
});
this.shadowRoot.addEventListener('mouseup', e => {
isDragging = false;
});
}
_initKeyboardEvents() {
let currentCell = null;
this.shadowRoot.addEventListener('keydown', e => {
switch (e.key) {
case 'ArrowUp':
e.preventDefault();
this._moveUp(currentCell);
break;
case 'ArrowDown':
e.preventDefault();
this._moveDown(currentCell);
break;
case 'ArrowLeft':
e.preventDefault();
this._moveLeft(currentCell);
break;
case 'ArrowRight':
e.preventDefault();
this._moveRight(currentCell);
break;
case 'Enter':
e.preventDefault();
this._editCell(currentCell);
break;
}
});
this.shadowRoot.addEventListener('click', e => {
if (e.target.tagName === 'TD') {
this._editCell(e.target);
}
});
this.shadowRoot.addEventListener('focusin', e => {
if (e.target.tagName === 'TD') {
currentCell = e.target;
}
});
this.shadowRoot.addEventListener('focusout', e => {
if (e.target.tagName === 'TD') {
currentCell = null;
}
});
}
_moveUp(cell) {
const prevRow = cell.parentNode.previousElementSibling;
if (prevRow) {
const prevCell = prevRow.querySelector(`td:nth-child(${cell.cellIndex + 1})`);
prevCell.focus();
}
}
_moveDown(cell) {
const nextRow = cell.parentNode.nextElementSibling;
if (nextRow) {
const nextCell = nextRow.querySelector(`td:nth-child(${cell.cellIndex + 1})`);
nextCell.focus();
}
}
_moveLeft(cell) {
const prevCell = cell.previousElementSibling;
if (prevCell) {
prevCell.focus();
}
}
_moveRight(cell) {
const nextCell = cell.nextElementSibling;
if (nextCell) {
nextCell.focus();
}
}
_editCell(cell) {
if (!cell.hasAttribute('contenteditable')) {
cell.setAttribute('contenteditable', '');
cell.focus();
}
}
}
customElements.define('my-table', Table);
</script>
<my-table></my-table>结语
本文介绍了如何使用 Custom Elements 构建一个支持多种输入方式的表格控件,并提供了示例代码以供参考。通过使用 Custom Elements,我们可以将表格控件封装成自定义元素,使其更易于使用和维护。如果您感兴趣,可以尝试使用 Custom Elements 构建其他类型的组件,例如按钮、下拉框等。
来源:JavaScript中文网 ,转载请注明来源 https://www.javascriptcn.com/post/67d4dbf2a941bf7134913ee3