在HTML中将表格和分层列表结合起来

25

我正在重新设计一个传统的工具集,并考虑如何更好地呈现一些信息,既可以在表现形式上,也可以在语义上。

这些数据具有分层结构,但需要让用户快速看到其属性。期望的布局类似于下面的示例。

Seq     Item Name         Min  Max  - Anything under here isn't shown
 1      Identifier         1    1     (Required)
 2      Name               1    1
  2.1    First Name        1    1
  2.2    Middle Name       -    -     (Optional but unlimted)
  2.3    Last Name         1    1
 3      Age                -    1     (Optional)

目前这是一个完整的表格,而序列(Seq)号的缩进通过插入额外的表格单元格来实现,以使所有内容向右移动。

我的挑战在于如何有效地显示这些信息。

首先,这是表格数据吗?我认为不是,因为层次结构很重要,“列”仅是每个“行”中项目的属性。

如果它不是表格,那是什么,并且该如何处理?我个人认为这是一组嵌套的

    列表 - 序列号是可选的,不总是数字。如果它是一组列表,则会正确缩进子列表,但是如何呈现短属性最好呢?

    如果它是表格,那么如何呈现层次结构的语义存在?


你的实际信息层次结构是否仅限于两个级别(就像你的示例中一样)? - Zero Piraeus
@ZeroPiraeus 不,它具有任意深度,但实际上可能不超过5-8。 - user764357
1
我不知道表格出了什么问题。我认为这是表格数据,因为它按行和列排列。这并不会因为层次结构而改变。 - yunzen
7个回答

25

这里的挑战在于,您想要表示的信息一方面是表格形式(它是一组具有相同标签属性的项目),另一方面是分层的(项目具有父项)。

不幸的是,在 HTML 中,没有针对表格行的嵌套分组机制:tbody 可用于分组行,但是嵌套它是非法的(除非在 td 中使用一个中间的 table,这非常糟糕)。

那么您就有两个选择:

  1. 使用某种类型的嵌套列表来表示信息,并依赖 CSS 来使结果看起来像一个表。

  2. 将信息表示为表格,并依赖某些属性来表示其分层关系。

以下是如何实现这些选择的一些示例:

使用嵌套列表(天真的方法):

<ul>
  <li>
    <dl>
      <dt>Seq</dt> <dd>1</dd>
      <dt>Item Name</dt> <dd>Identifier</dd>
      <dt>Min</dt> <dd>1</dd>
      <dt>Max</dt> <dd>1</dd>
    </dl>
  </li>
  <li>
    <dl>
      <dt>Seq</dt> <dd>2</dd>
      <dt>Item Name</dt> <dd>Name</dd>
      <dt>Min</dt> <dd>1</dd>
      <dt>Max</dt> <dd>1</dd>
    </dl>
    <ul>
      <li>
        <dl>
          <dt>Seq</dt> <dd>2.1</dd>
          <dt>Item Name</dt> <dd>First Name</dd>
          <dt>Min</dt> <dd>1</dd>
          <dt>Max</dt> <dd>1</dd>
        </dl>
      </li>
      <li>
        <dl>
          <dt>Seq</dt> <dd>2.2</dd>
          <dt>Item Name</dt> <dd>Middle Name</dd>
          <dt>Min</dt> <dd>-</dd>
          <dt>Max</dt> <dd>-</dd>
        </dl>
      </li>
      <li>
        <dl>
          <dt>Seq</dt> <dd>2.3</dd>
          <dt>Item Name</dt> <dd>Last Name</dd>
          <dt>Min</dt> <dd>1</dd>
          <dt>Max</dt> <dd>1</dd>
        </dl>
      </li>
    </ul>
  </li>
  <li>
    <dl>
      <dt>Seq</dt> <dd>3</dd>
      <dt>Item Name</dt> <dd>Age</dd>
      <dt>Min</dt> <dd>-</dd>
      <dt>Max</dt> <dd>1</dd>
    </dl>
  </li>
<ul>

这种方法解决了信息的层次结构,但会导致反复重复自己,并且需要在 CSS 中跳过一些障碍才能以表格形式显示结果。通过谨慎使用 :first-child 可能是可行的,但最终结果是你不得不标记一些你想要呈现为非表格方式的东西,并因此给自己带来更多的工作。

这也使得项目之间真正的表格关系在标记中变得隐含而不是明确的 - 如果不参考渲染的输出,很难看出这些项目始终具有相同数量和类型的属性。

使用嵌套列表(“聪明”方法):

<dl>
  <dt>
    <ul> <li>Seq</li> <li>Item Name</li> <li>Min</li> <li>Max</li> </ul>
  </dt>
  <dd>
    <ul> <li>1</li> <li>Identifier</li> <li>1</li> <li>1</li> </ul>
  </dd>
  <dd>
    <dl>
      <dt>
        <ul> <li>2</li> <li>Name</li> <li>1</li> <li>1</li> </ul>
      </dt>
      <dd>
        <ul> <li>2.1</li> <li>First Name</li> <li>1</li> <li>1</li> </ul>
      </dd>
      <dd>
        <ul> <li>2.2</li> <li>Middle Name</li> <li>-</li> <li>-</li> </ul>
      </dd>
      <dd>
        <ul> <li>2.3</li> <li>Last Name</li> <li>1</li> <li>1</li> </ul>
      </dd>
    </dl>
  </dd>
  <dd>
    <ul> <li>3</li> <li>Age</li> <li>-</li> <li>1</li> </ul>
  </dd>
</dl>

在这里,我们使用 定义列表 来描述两个事物:

  1. 例如,“项目名称”和“标识符”之间的标题/详细信息关系。

  2. 例如,“名称”单元和“名字”单元之间的父/子关系。

这种方法确实更加紧凑,但不幸的是,每个标题及其详细元素之间的具体关系充其量只是隐含的,如果没有额外的样式来以表格方式视觉组织信息,那么渲染后所代表的内容将更加不明显。

使用一个 table

<table>
  <thead>
    <tr>
      <th>Seq</th> <th>Item Name</th> <th>Min</th> <th>Max</th>
    </tr>
  </thead>
  <tbody>
    <tr id=100>
      <td>1</th> <th>Identifier</th> <td>1</td> <td>1</td>
    </tr>
    <tr id=200>
      <th>2</th> <th>Name</th> <td>1</td> <td>1</td>
    </tr>
    <tr id=210 data-parent=200 class=level-1>
      <th>2.1</th> <th>First Name</th> <td>1</td> <td>1</td>
    </tr>
    <tr id=220 data-parent=200 class=level-1>
      <th>2.2</th> <th>Middle Name</th> <td>-</td> <td>-</td>
    </tr>
    <tr id=230 data-parent=200 class=level-1>
      <th>2.3</th> <th>Last Name</th> <td>1</td> <td>1</td>
    </tr>
    <tr id=300>
      <th>3</th> <th>Age</th> <td>-</td> <td>1</td>
    </tr>
  </tbody>
</table>

这里,父/子关系通过data-parent属性明确描述(如果需要,可以通过JavaScript访问该属性),而class=level-{n}属性提供了一个可以被CSS使用的钩子:

.level-1 > th {
  padding-left: 1em;
}

最终这是个人喜好和方便的问题,但我认为使用<table>的方法更好,因为它比上面两种嵌套列表的方法更符合“没有CSS也能看起来合理”的经验法则。


注意,id属性不能以数字开头。 - Matt Sach
8
在HTML5中,这已经不再是真实情况了。 - Zero Piraeus
1
哇塞,我完全不知道那件事有所变化。谢谢! - Matt Sach
不错的回答。至于“表格”解决方案,使用标题属性而不是自己编造一个(data-parent)会更好吧?据我所知,“headers”属性也可以被屏幕阅读器读取,但仅适用于单个单元格(例如seqitem name单元格),而不是整个行。 - JohnCand

8
我会通过使用表格并向标签添加自定义数据属性来展示它:自定义数据属性
<table id="myTable" class="table">
    <tr>
        <td data-indent="0">1</td>
        <td data-indent="0">Test</td>
        <td data-indent="0">Test 1</td>
    </tr>
    <tr>
        <td data-indent="1">1.1</td>
        <td data-indent="1">Another</td>
        <td data-indent="0">Test 1.1</td>
    </tr>
    <tr>
        <td data-indent="2">1.1.1</td>
        <td data-indent="3">Another</td>
        <td data-indent="0">Test 1.1.1</td>
    </tr>
        <tr>
        <td data-indent="2">1.1.2</td>
        <td data-indent="3">Another one</td>
        <td data-indent="0">Another test 1.1.2</td>
    </tr>
    <tr>
        <td data-indent="0">2</td>
        <td data-indent="0">Test</td>
        <td data-indent="0">Test 2</td>
    </tr>
</table>

然后,借助jQuery的帮助,设置表格中每个单元格数值的填充:
$("td")
    .css("padding-left", function (index) {
    return 10 * parseInt($(this).data("indent")) + "px";
});

jsFiddle上观看其运行。


2
为什么不通过CSS来进行填充? 您可以通过td:first-child [data-indent =“1”]或td:first-child [data-indent =“2”]匹配要填充的单元格,并应用所需的填充。 - Lars Ebert
2
就我所见,问题在于你失去了层次结构的语义。 - user764357

3
我认为有两种合理的方式可以在HTML中表示这个:
  • 使用table,并用自然语言明确表达关系/层次
  • 使用分块元素(如果您使用HTML 4.01,则使用标题)

table带有解释层级的列

如果没有标记来定义此关系,则应使用文本。如果可视化表示是明确无误的,则可以将此文本在视觉上隐藏,以便仅对屏幕阅读器/文本浏览器用户可访问。

在您的情况下,您可以添加一列,说明“子项”的行之间的关系:

<table>

  <tr>
    <th>Seq</th> 
    <th>Relation</th> <!-- or some clever better label -->
    <th>Item Name</th>
    <th>Min</th>
    <th>Max</th>
  </tr>

  <!-- … -->

  <tr id="2">
    <td>2</td>
    <td></td> <!-- "parent", "root", "container", empty -- whatever makes sense for your case -->
    <td>Name</td>
    <td>1</td>
    <td>1</td>
  </tr>

  <tr id="2.1">
    <td>2.1</td>
    <td>child of <a href="#2">Name</a></td>
    <td>First Name</td>
    <td>1</td>
    <td>1</td>
  </tr>

</table>

每行都可以得到一个id(值为Seq或项目名称),这样就可以链接到该行。注意,在HTML 4.01中不允许使用以数字开头的id值;在那里,你可以使用类似于id="seq-2.1"的东西。如果一个项目是另一个项目的子项,那么你可以链接到父项目的行。

这样做可以清晰地表达给人看,而机器仍然可以看到这些行是相互关联的,尽管这个关系的具体含义不能被机器读取。如果要明确关系的含义,你可以在此处使用链接类型(rel值)。在HTML 4.01中,你可以自己创建一个值,在HTML5中需要registered先注册。

区块元素/标题

不要使用table,可以利用HTML的大纲功能。{以下示例使用HTML5的分节元素。如果您使用HTML 4.01,则仅需将分节元素替换为div(或根本不使用元素),并仅使用标题。}

每个部分(由标题引入)表示一个项目。标题的大纲(或分节元素的嵌套)表示您项目的层次结构。

这是一个示例,以查看整个结构:

 <article>

   <h1><!-- heading for the whole data --></h1>

   <section class="item" id="1">
     <h2>Identifier</h2>
   </section>

   <section class="item" id="2">
     <h2>Name</h2>

     <section class="item" id="2.1">
       <h3>First Name</h3>
     </section>

     <section class="item" id="2.2">
       <h3>Middle Name</h3>
     </section>

     <section class="item" id="2.3">
       <h3>Last Name</h3>
     </section>

   </section>

   <section class="item" id="3">
     <h2>Age</h2>
   </section>

 </article>

每个 section 可以包含一个 dl 用于存放属性:
<section class="item" id="3">
  <h2>Age</h2>

  <dl>
    <dt>Min</dt>
    <dd>1</dd>
    <dt>Max</dt>
    <dd>1</dd>
  </dl>

</section>

根据您内容的实际含义,您可以使用 code 元素 表示项目名称(例如,如果它描述了标记语言的元素),和/或 dfn 元素,如果下一段内容是该项目的定义。

2
我的建议是使用嵌套有序列表来保留您的层次结构,然后使用描述列表来表示每一行的“列”。
例如,第一行的描述列表可能如下所示:
<dl>
  <dt>Item Name</dt>
  <dd>Identifier</dd>
  <dt>Min</dt>
  <dd>1</dd>
  <dt>Max</dt>
  <dd>1</dd>
  <dt>Notes</dt>
  <dd>(Required)</dd>
</dl>

您需要使用CSS来隐藏描述术语(因为您不希望它们出现在每一行中),并调整布局以使其更像一个表格。
这种格式的缺点是,您将在每一行上重复列标题。但从语义上讲,我认为这是最有意义的,而且对于屏幕阅读器来说,这样做也应该使内容更有意义。
我尝试了一下CSS,以便您可以了解它如何工作。这可能做得不太好,但至少它会给您一个开始的东西。 CodePen 链接

2

更新:我添加了使用ID和“headers”属性来语义化地标记层次结构。

这明显是表格数据(你应该真正推动“列表”的定义,以证明在这里使用UL或DL的合理性!)。

我认为一个好的方法是使用不同的tbodys将相关行分组在一起(类似于这里使用的内容http://www.w3.org/TR/2012/WD-html5-20120329/the-table-element.html#the-table-element看到“用于标记数独谜题的表格”)

<table>
  <thead>
    <tr>
      <th id="seq">Seq</th>
      <th>Item Name</th>
      <th>Min</th>
      <th>Max</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th id="s1" headers="seq">1</th>
      <td>Identifier</td>
      <td>1</td>
      <td>1</td>
    </tr>
  </tbody>
  <tbody>
    <tr>
      <th id="s2" headers="seq">2</th>
      <td>Name</td>
      <td>1</td>
      <td>1</td>
    </tr>
    <tr class="sub">
      <th id="s2_1" headers="seq s2">2.1</th>
      <td>First Name</td>
      <td>1</td>
      <td>1</td>
    </tr>
    <tr class="sub">
      <th id="s2_2" headers="seq s2">2.2</th>
      <td>Middle Name</td>
      <td>-</td>
      <td>-</td>
    </tr>
    <tr class="sub">
      <th id="s2_3" headers="seq s2">2.3</th>
      <td>Last Name</td>
      <td>1</td>
      <td>1</td>
    </tr>
    <tr class="sub sub">
      <th id="s2_3_1" headers="seq s2 s2_3">2.3.1</th>
      <td>Last Name</td>
      <td>1</td>
      <td>1</td>
    </tr>
    <tr class="sub sub">
      <th id="s2_3_2" headers="seq s2 s2_3">2.3.2</th>
      <td>Last Name</td>
      <td>1</td>
      <td>1</td>
    </tr>
  </tbody>
  <tbody>
    <tr>
      <th id="s3" headers="seq">3</th>
      <td>Age</td>
      <td>-</td>
      <td>1</td>
    </tr>
  </tbody>
</table>

那么您可以使用类似以下的内容:

然后您可以使用类似这样的内容:

table { border-collapse: collapse; }
tbody { border-bottom:1px solid #000; }
tbody .sub td { padding-left: 10px; }

事实上,我认为您只能使用tbody来将行分组,并单独留下单行。
<tr>
  <td>1</td>
  <td>Identifier</td>
  <td>1</td>
  <td>1</td>
</tr>
<tbody>
  <tr>
    <td>2</td>
    <td>Name</td>
    <td>1</td>
    <td>1</td>
  </tr>
  <tr class="sub">
    <td>2.1</td>
    <td>First Name</td>
    <td>1</td>
    <td>1</td>
  </tr>
  ...
</tbody>
<tr>
  <td>3</td>
  <td>Age</td>
  <td>-</td>
  <td>1</td>
</tr>

在多层次结构的情况下,使用 tbody 来分组元素是行不通的,正如 OP 在评论中所述。 - Zero Piraeus
1
有一个“headers”属性,您可以使用它来标记层次结构。我会举个例子,并在一分钟内编辑帖子。 - Coluccini
有趣的是,我不知道headers,这是一个很好的用例。但是,您将如何将其用作CSS钩子来在视觉上表示层次结构的深度,超出了tbody使用的两个级别? - Zero Piraeus
为了进行可视化表示,您可以在每个级别使用不同的类名(我在示例中使用了一个选项,但您也可以使用类似于“level_0”,“level_1”,“level_2”等的内容)。 - Coluccini

2

我认为你应该使用表格。虽然层次结构很重要,但也可以通过手动写下第一列来显示。但是在列表中,最小值、最大值和项目名称等属性无法像在表格中那样轻松显示。我的意见是:使用表格并手动提供seq列!


0

为什么不两者兼备呢?树状网格是一种很好的表示表格数据并符合层次结构的方式。

这是一个例子

然而,我在两个非常大的项目中使用了Extjs后,我的个人建议是,如果你要开发一个大型应用程序,最好远离它。虽然它可能适用于一次性网格,但我个人认为它实现得很差,在项目规模增加时可能会变得更加繁琐。然而,它非常功能丰富,所以示例面板可能会给您提供更多有关网格(表格)的想法,这些想法可能会对您有所帮助。

请记住,您应该始终问自己:“我的用户试图回答哪些问题?”然后,问自己:“哪些图形设备提供了回答该问题的最清晰和最简单的路径?”

有一本书对我在这些问题上帮助很大,那就是仪表盘设计书。即使你可能不会构建一个仪表盘,但它包含了许多用户体验技术和理论,这些对我在选择适当的UI元素(关于数据)时更加容易。

希望这能帮到你...


我不确定我会将<div id="tree-example"></div>(来自您链接的示例)描述为数据的语义表示...但它确实是一种非常Extjs的方法;-) - Zero Piraeus
我不确定在发布自己的答案后批评别人的答案是否是好的方式。他问关于在Web用户界面中表示数据。那是否意味着“仅限HTML”?因为我肯定不认为那是Web的当前状态。JavaScript是一种非常有价值的用户界面工具。 - Homer6
4
没有任何冒犯的意思……根据我的经验,在评论中指出答案的潜在缺点是很常见的,无论评论者是否也给出了答案(我自己也曾经遇到过这种情况)。关于你的第二点:无论是使用Javascript还是不使用Javascript,批评“根本没有表示数据”的方式来表示数据是合理的。提问者显然很在意语义化标记,而你提供的例子则完全不是。 - Zero Piraeus
如果有帮助的话,我刚刚在其他地方点赞了你的一个答案 :-) - Zero Piraeus
3
挑战的一部分是,我正在寻找一个“仅限HTML”的解决方案。屏幕阅读器和其他无障碍浏览器可能会限制CSS和JS的使用,因此更喜欢一个仅限HTML的解决方案。 - user764357

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