反应动态 tabindex 创建

React dynamic tabindex creation

我正在寻找一种方法来为我的 React 应用程序中的每个元素动态创建 tabindex。有多个组件包含我想为其创建 tabindex 的输入元素,但我不确定如何协调它们并避免碰撞。

我的 React 应用程序由多个包含输入元素的 table 组成。在运行时,table 可以由用户创建额外的输入元素来扩展。我想改变用户在这些输入中切换的方向,但我不确定如何为每个元素生成 tabindex

到目前为止我想到的方法:

是否有我缺少的 tabindex 创建工具或常见做法?

对于常见的从左到右向下的 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);
}

这是一个您可以在每次将新输入添加到您的布局时调用的函数。它为每个输入分配一个 tabIndex,没有任何间隙,并容纳 table 各种大小,其中每个单元格可以有任意数量的输入元素。你可以在this jsfiddle.

中测试

输入元素存储在 Map 对象中,其中每个键都是 table、列和行索引的组合。然后对key进行排序,tabIndex属性按照排序后key的顺序设置

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 中会导致问题,IE 不支持 the spread syntax:

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

如果一定要支持IE,可以调用map.forEach填充未排序数组,如图this modified jsfiddle.

不知道你有没有想出来。但是你可以在这里使用一个技巧。

您不需要为 table 中的每个单元格设置不同的 tabindex 来创建您想要的选项卡导航。您只需要为 table 中的每一列设置不同的 tabindex。您可以为同一列中的每个单元格重复相同的 tabindex。因为当 tabindex 相同时,浏览器只是使用 HTML 元素顺序来决定下一个控件,这就是我们在这种情况下所需要的。所以 tabindex 对 table 的分配将是这样的。

1   2   3   4
1   2   3   4
1   2   3   4
1   2   3   4

这是一次意义重大的胜利。因为对于 10X10 table 我们不需要 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>

如果使用这种方法可以管理列数,我建议您使用第一个选项,为每个 table 分配不同的偏移量(也在上面的示例中说明)。如果列数是固定的然后它会很容易。即使不是,您也可以尝试根据您的用例使用固定的硬编码偏移量来保持一些合理的差距。

如果这样不起作用,接下来您可以尝试根据数据计算偏移值。我假设您将拥有某种由此 table 表示的数据,例如对象数组或数组数组,可能处于您的应用程序状态。因此,您可以根据这些数据得出 tables 的偏移量。如果您正在使用像 Redux 这样的状态管理库,那将非常简单,我建议您查看 reselect,它可以用于计算 Redux 中的派生状态。

希望对您有所帮助!