React 动态创建 tabindex

7
我正在寻找一种方法,在我的React应用程序中为每个元素动态创建一个tabindex。有多个包含输入元素的组件,我想为它们创建一个tabindex,但我不确定如何协调它们并避免冲突。
我的React应用程序由多个包含输入元素的表格组成。在运行时,用户可以创建额外的输入元素来扩展表格。我想改变用户通过这些输入元素进行制表的方向,但我不确定如何为每个元素生成一个tabindex
我想到的方法有:
  • 为每个表格设置固定偏移量(但我不知道单个表格中可能有多少输入元素,而且tabindex应该保持在32767以下,所以我不能仅仅假设存在巨大的间隔)
  • tabindex偏移量传递给表格,并从React对象中获取使用的tabindex数量来计算下一个偏移量(对我来说似乎会破坏模块化和难以实现)
  • 通过全局状态跟踪下一个tabindex(hacky,当表格扩展时可能会出现问题)
  • 通过dom树跟踪下一个tabindex(不知道如何实现)
是否有工具或常见做法可以创建tabindex我不知道的?

1
我认为这个有效的问题需要以不同的方式解决 - 一些有趣的Web组件想法 - Ovidiu Dolha
通常最佳实践是避免使用正整数作为 tabindex。您能否提供一些上下文,解释一下“更改用户通过这些输入框进行选项的方向”的含义?这可能有助于我们给出更好的答案。 - Tharaka Wijebandara
我有多个表格,希望通过选项卡首先按列遍历它们(从上到下),然后在达到最后一行后从左到右遍历到下一列。 - thammi
3个回答

7
我不知道你是否已经理解了这个问题。但是,这里有一个技巧可以使用。
为了实现所需的选项卡导航,您无需为表中每个单元格设置不同的tabindex。您只需要为表中的每一列设置不同的tabindex。您可以在同一列中重复使用相同的tabindex。因为当tabindex相同时,浏览器仅使用HTML元素顺序来决定下一个控件,这正是我们在这种情况下所需要的。因此,表的tabindex分配将如下所示。
1   2   3   4
1   2   3   4
1   2   3   4
1   2   3   4

这是一个重要的胜利。因为对于一个10X10的表格,我们不需要100个不同的标签索引,只需要使用10个索引即可。
以下是使用React演示此想法的小例子。您可以运行它并查看。

// Table
class Table extends React.Component {

  generateRows(){
    const offset = this.props.tabIndexOffSet;
    const cols = this.props.cols;
    const data = this.props.data;

    return data.map(function(item) {
        const cells = cols.map(function(colData, index) {
          return (
            <td key={colData.key}>
              <input type="text"
               value={item[colData.key]}
               tabIndex={offset+index} />
            </td>
          );
        });
        return (<tr key={item.id}> {cells} </tr>);
    });
   }

  generateHeaders() {
    var cols = this.props.cols;
    return (
      <tr>
        {
          cols.map(function(colData) {
            return <th key={colData.key}> {colData.label} </th>;
          })
        }
      </tr>
    );
  }

  render(){
    const headerComponents = this.generateHeaders();
    const rowComponents = this.generateRows();;

    return (
      <table>
          <thead> {headerComponents} </thead>
          <tbody> {rowComponents} </tbody>
      </table>
    );
  }
}

// App
class App extends React.Component{

  constructor(){
    super();
    this.state = { 
      cols: [
          { key: 'firstName', label: 'First Name' },
          { key: 'lastName', label: 'Last Name' }
      ],
      data: [
          { id: 1, firstName: 'John', lastName: 'Doe' },
          { id: 2, firstName: 'Clark', lastName: 'Kent' },
          { id: 3, firstName: 'Tim', lastName: 'Walker' },
          { id: 4, firstName: 'James', lastName: 'Bond' }
      ]
    }
  }
  
  render () {
    return (
      <div>
        <Table
          tabIndexOffSet={1}
          cols={this.state.cols}
          data={this.state.data}/>
        <Table
          tabIndexOffSet={3}
          cols={this.state.cols}
          data={this.state.data}/>
      </div>
    );
  }
}


// Render
ReactDOM.render(
  <App/>,
  document.getElementById('container')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="container"></div>

如果列数可通过这种方法进行管理,我建议您采用第一种选项,为每个表分配不同的偏移量(如上面的示例所示)。如果列数固定,则很容易。即使不是,您也可以尝试根据用例基于固定硬编码偏移量保持一些合理的间隔。
如果这种方法无法奏效,下一步可以尝试从数据中计算偏移值。我假设您将有一些由这些表表示的数据,例如对象数组或数组的数组,可能在应用程序状态中。因此,您可以基于这些数据为表派生偏移量。如果您正在使用Redux等状态管理库,那么它将非常容易,并且我建议查看reselect,它可以在Redux中用于计算派生状态。
希望这能帮到您!

谢谢,这是我应用程序所需的完美提示。特别是因为它允许我在不绕过React的情况下解决这个问题。 - thammi
@thammi,我很高兴能帮到你。但是我认为你忘记将这个标记为正确答案了。 :) - Tharaka Wijebandara

2
对于普通的从左到右,从上到下的DOM相关表示,它可能是这样的:
var el = document.documentElement,
    rebuildIndex = function () {
        document.getElementsByTagName('input').forEach(function (input, idx) {
            input.setAttribute('tabindex', idx);
        });
    };
// Firefox, Chrome
if (support.DOMSubtreeModified) {
    el.addEventListener('DOMSubtreeModified', rebuildIndex, false);
// Safari, Opera
} else {
    el.addEventListener('DOMNodeInserted', rebuildIndex, false);
    el.addEventListener('DOMNodeRemoved', rebuildIndex, false);
}

1
下面是一个函数,每当新的输入被添加到您的布局中时,您可以调用它。它会为每个输入分配一个tabIndex,没有任何间隔,并且适应各种大小的表格,其中每个单元格可以有任意数量的输入元素。您可以在this jsfiddle中测试它。
输入元素存储在Map对象中,其中每个键是表格、列和行索引的组合。然后对这些键进行排序,并按排序后的键顺序设置tabIndex属性。
function setTabIndices() {
    var tableIndex, rowIndex, colIndex, inputIndex;
    var table, row, cell, inputs;
    var map = new Map();
    var tables = document.getElementsByTagName("table");
    for (tableIndex = 0; tableIndex < tables.length; tableIndex++) {
        table = tables[tableIndex];
        for (rowIndex = 0; rowIndex < table.rows.length; rowIndex++) {
            row = table.rows[rowIndex];
            for (colIndex = 0; colIndex < row.cells.length; colIndex++) {
                cell = row.cells[colIndex];
                inputs = cell.getElementsByTagName("input");
                for (inputIndex = 0; inputIndex < inputs.length; inputIndex++) {
                    // Define key based on table, column, and row indices
                    map.set(format(tableIndex, 4) + format(colIndex, 6) +
                             format(rowIndex, 6) + format(inputIndex, 3), 
                             inputs[inputIndex]);
                }
            }
        }
    }
    var input;
    var sortedKeys = [...map.keys()].sort(); // Not supported in IE11
    for (var tabIndex = 1; tabIndex <= sortedKeys.length; tabIndex++) {
        input = map.get(sortedKeys[tabIndex - 1]);
        input.tabIndex = tabIndex;
    }
}

function format(value, digits) {
    return ("0000000000" + value.toString()).slice(-digits);
}


注意:以下这行代码在IE中会出现问题,因为它不支持扩展语法

var sortedKeys = [...map.keys()].sort();

如果您必须支持IE,您可以调用map.forEach来填充未排序的数组,如此修改后的jsfiddle所示。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接