跳至主要內容

表格和表单

xmut-lby大约 17 分钟

表格和表单

表格

基本概念

表格可能会很多前端工程师要处理的内容。一般来说除了各种面向普通用户的需要精心布局的内容,以表格呈现的数据总是更加直观和精简的。

我们以一张学生成绩单为例:

学号姓名高等数学C语言网页设计
2300000001张三899488
2300000002李四778895
2300000003王五606055
2300000004赵六789793
2300000099庄体育40200

这样的数据显然适合用表格来呈现,一行就是一个学生的数据,非常直观。

我们先介绍一下表格的基本术语:

表格里的术语
表格里的术语

那么我们可以这样编写这个表格的代码:

    <table>
        <caption>成绩表</caption>
        <tr>
            <th>学号</th> <th>姓名</th> <th>高等数学</th> <th>C语言</th> <th>网页设计</th>
        </tr>
        <tr>
            <td>2300000001</td> <td>张三</td> <td>89</td> <td>94</td> <td>88</td>
        </tr>
        <tr>
            <td>2300000002</td> <td>李四</td> <td>77</td> <td>88</td> <td>95</td>
        </tr>
    </table>

所以:

  1. 一张表格就是一个table
  2. 一个表格可以有多个tr行(tr = table row)。
  3. 一个tr行里既可以包含th(table header),也可以包含td(table data)。

因此表格里有两层包含关系,三级对象。这里要注意一点的是并没有规定th必须处于第一行,实际上处于任何一个位置都是可以的。

风格化你的表格

如果按上面的代码,你此时会看到一个非常“朴素”的表格:

最“朴素”的表格
    <table>
        <caption>成绩表</caption>
        <tr>
            <th>学号</th> <th>姓名</th> <th>高等数学</th> <th>C语言</th> <th>网页设计</th>
        </tr>
        <tr>
            <td>2300000001</td> <td>张三</td> <td>89</td> <td>94</td> <td>88</td>
        </tr>
        <tr>
            <td>2300000002</td> <td>李四</td> <td>77</td> <td>88</td> <td>95</td>
        </tr>
    </table>

显然接下来就是css的工作了,要绘制我们中国人习惯的表格,可以为tdth加上边框:

加上边框
    <table>
        <caption>成绩表</caption>
        <tr>
            <th>学号</th> <th>姓名</th> <th>高等数学</th> <th>C语言</th> <th>网页设计</th>
        </tr>
        <tr>
            <td>2300000001</td> <td>张三</td> <td>89</td> <td>94</td> <td>88</td>
        </tr>
        <tr>
            <td>2300000002</td> <td>李四</td> <td>77</td> <td>88</td> <td>95</td>
        </tr>
    </table>
td, th{
    border: black thin solid;
}

这里边框都被分离开了,直觉上可能会觉得应该用margin来调整,实际上表格内部的格子是非常特殊的标记。如果你打开F12,会发现他的display属性是table-cell,既不是block也不是inline。在tdth上,margin属性是不会生效的。

控制边框分离的属性必须在table上设置,我们再给tdth加上padding,使他们不会挤在一起:

加上边框,美化一下:
    <table>
        <caption>成绩表</caption>
        <tr>
            <th>学号</th> <th>姓名</th> <th>高等数学</th> <th>C语言</th> <th>网页设计</th>
        </tr>
        <tr>
            <td>2300000001</td> <td>张三</td> <td>89</td> <td>94</td> <td>88</td>
        </tr>
        <tr>
            <td>2300000002</td> <td>李四</td> <td>77</td> <td>88</td> <td>95</td>
        </tr>
    </table>
td, th{
    border: black thin solid;
    padding: 5px 10px;
}

table{
    border-collapse: collapse;
}

怎么实现

我们常常会让表格里的元素居中对齐,你认为要怎么居中对齐?

除此之外,我们也经常会遇到下面这种只有横线没有竖线的表格(且慢点开,自己想想要如何实现,试试):

只有横线的表格
    <table>
        <caption>成绩表</caption>
        <tr>
            <th>学号</th> <th>姓名</th> <th>高等数学</th> <th>C语言</th> <th>网页设计</th>
        </tr>
        <tr>
            <td>2300000001</td> <td>张三</td> <td>89</td> <td>94</td> <td>88</td>
        </tr>
        <tr>
            <td>2300000002</td> <td>李四</td> <td>77</td> <td>88</td> <td>95</td>
        </tr>
    </table>
td, th{
    border-top: black thin solid; /* 你想对了吗 */
    border-bottom: black thin solid;
    text-align: center;
    padding: 5px 10px;
}

table{
    border-collapse: collapse;
}

 








还有一类在科技论文中非常常见的三线表(同样先别点开,自己想想如何实现):

三线表
    <table>
        <caption>成绩表</caption>
        <tr>
            <th>学号</th> <th>姓名</th> <th>高等数学</th> <th>C语言</th> <th>网页设计</th>
        </tr>
        <tr>
            <td>2300000001</td> <td>张三</td> <td>89</td> <td>94</td> <td>88</td>
        </tr>
        <tr>
            <td>2300000002</td> <td>李四</td> <td>77</td> <td>88</td> <td>95</td>
        </tr>
    </table>
/* 你想对了吗 */
th{
    border-bottom: black thin solid; 
}

td, th{
    text-align: center;
    padding: 5px 10px;
}

table{
    border-top: black 2px solid;
    border-bottom: black 2px solid;
    border-collapse: collapse;
}


 








 
 


最后表格标题的位置是可以通过caption-side来设置的,但只能选择上下两个位置(很鸡肋的功能)。

进一步美化

另一种非常常见的展现方式是所谓的斑马纹,即用不同的颜色隔行着色,这样用户就不容易混淆临近的两行:

斑马纹
    <table>
        <caption>成绩表</caption>
        <tr> <th>学号</th> <th>姓名</th> <th>高等数学</th> <th>C语言</th> <th>网页设计</th> </tr>
        <tr class="even"> <td>2300000001</td> <td>张三</td> <td>89</td> <td>94</td> <td>88</td> </tr>
        <tr> <td>2300000002</td> <td>李四</td> <td>77</td> <td>88</td> <td>95</td> </tr>
        <tr class="even"> <td>2300000003</td> <td>王五</td> <td>60</td> <td>60</td> <td>55</td> </tr>
        <tr> <td>2300000004</td> <td>赵六</td> <td>78</td> <td>97</td> <td>93</td> </tr>
        <tr class="even"> <td>2300000099</td> <td>庄体育</td> <td>40</td> <td>20</td> <td>0</td> </tr>
    </table>



 

 

 

th{
    border-bottom: black thin solid;
}

.even{
    background-color:antiquewhite;
}

td, th{
    padding: 5px 10px;
    text-align: center;
}

td{
    border-bottom: thin black dotted;
}

table{
    border-top: black 2px solid;
    border-bottom: black 2px solid;
    border-collapse: collapse;
}




 
 
 















上面这个例子里,我们为偶数行的tr都加上了even这个类,这样他们就可以呈现出与单数行不同的颜色了。 这种方法通常多用于兼容性比较差,或者用代码生成的页面(例如、php、jsp和js等)上。用人手来编写类,不仅繁琐而且很容易出错。

nth-child伪类选择器

css3时代比早期的css更进一步,提供了更多的效果和选择器,使我们可以轻易做到需要依靠代码才能实现的功能,比如下面这个伪类选择器:

:nth-child(2n){
}

这个伪类的意思是,根据括号里的内容,选择第几排行的子元素。如果放在我们上面的例子里,我们可以这样写:

用nth-child来选择
    <table>
        <caption>成绩表</caption>
        <tr> <th>学号</th> <th>姓名</th> <th>高等数学</th> <th>C语言</th> <th>网页设计</th> </tr>
        <tr> <td>2300000001</td> <td>张三</td> <td>89</td> <td>94</td> <td>88</td> </tr>
        <tr> <td>2300000002</td> <td>李四</td> <td>77</td> <td>88</td> <td>95</td> </tr>
        <tr> <td>2300000003</td> <td>王五</td> <td>60</td> <td>60</td> <td>55</td> </tr>
        <tr> <td>2300000004</td> <td>赵六</td> <td>78</td> <td>97</td> <td>93</td> </tr>
        <tr> <td>2300000099</td> <td>庄体育</td> <td>40</td> <td>20</td> <td>0</td> </tr>
    </table>
th{
    border-bottom: black thin solid;
}

tr:nth-child(2n){
    background-color:antiquewhite;
}

td, th{
    padding: 5px 10px;
    text-align: center;
}

td{
    border-bottom: thin black dotted;
}

table{
    border-top: black 2px solid;
    border-bottom: black 2px solid;
    border-collapse: collapse;
}




 

















这里tr:nth-child(2n)的意思是,选中第2n个出现的tr标记,实际上也就是偶数的tr

试试

如果把tr:nth-child(2n)改为tr:nth-child(2n+1),会发生什么?验证一下你的想法。

你还可以试试以下的这几个表达式:

  1. tr:nth-child(3n)
  2. tr:nth-child(3n+1)
  3. tr:nth-child(2n+4)

浏览器的行为符合你的预期吗?

单元格的扩展和表格嵌套

td可以占据多行(或者多列),占据的行、列通过rowspancolspan属性设置:

跨行和跨列的td
    <table>
        <caption>成绩表</caption>
        <tr> <td colspan="2">跨越两列的单元格</td> <td>2</td> <td>3</td> <td>4</td> </tr>
        <tr> <td rowspan="3">跨越三行的单元格</td> <td>1</td> <td colspan="3" rowspan="2">跨越两行三列的单元格</td> </tr>
        <tr> <td>1</td> </tr>
        <tr> <td>1</td> <td>2</td> <td>3</td> <td>4</td> </tr>
        <tr> <td>只有我是正常的</td> <td>1</td> <td>2</td> <td>3</td> <td>4</td> </tr>
    </table>

td, th{
    padding: 5px 10px;
    text-align: center;
    border: thin black solid;
}

table{
    border-collapse: collapse;
}




 





rowspancolspan属性的效果用语言就不是很好表达,所以强烈建议你需要自己试一试。用比较概括的话来说就是“被扩充占据的单元格需要被去掉”。

可以看这里open in new window这里open in new window

以及权威的解释这里open in new window

表格可以嵌套,也就是说在tdth里再嵌套一个table,或者再嵌套1、2、3...n层都是可以的。

实际上td里可以嵌套其他一切标记,css3的flex布局应用没有那么广泛的时候,有一大部分前端工程师会用表格来进行复杂页面的排布。 用表格排页面的一大好处是自动对齐不会越界。当然现在已经不建议使用这种方式来排页面了。

表单

到目前为止,你应该意识到你只能单向浏览页面,而没法向页面发送参数。

例如在你上淘宝的时候,你会在页面上看到你的用户名,这当然是需要你告诉后端服务器的。

把前端的参数传递给后端有两种方式:表单和Ajax,静态页面通常使用表单。

为什么叫表单?

回想一下你去银行或者其他机构办事的时候。第一步要怎么做?

是不是根据你要办的业务拿一张表(单)先填?填完了再交给服务人员?

服务员就是后端服务器,所以让你填内容的部分,就是表单。

表单的实现

我们看一个最简单的表单,就登录所需要的用户名和密码:

    <form action="http://127.0.0.1:5000/app">
        用户名:<input type="text" name="username"><br>
        密码:<input type="text" name="password"><br>
        <input type="submit" value="登录">
    </form>

这里可以看到的有一个form标记,这就是表单的范围。form标记里有三个input标记,这种页面上供用户反馈的标记我们称为控件。 控件我们下面会详细讨论。

你会注意到form有个action属性,这个属性给出了你要向哪个服务提交这个表单。 这个地址可以分为两个部分,前面的http://127.0.0.1:5000/,这个称为主机名,也是服务器的地址。你可以将他类比你去的银行。 后面的/app,这部分称为服务。你可以类比为接待你的服务员,一个银行能提供的服务会有很多对吧,所以一个主机也可以有很多个服务。

这个文件下载过去,这是一个python脚本,可以开启一个非常简单的服务,作用是把提交表单的参数依次列出来。

我不知道怎么跑,能不能帮帮我

你以后工作会遇到无数没遇到过的问题,实际上老板给你发工资就是为了让你来解决这些问题的。否则找个AI岂不美哉?

这里我只会给你一个针对只会用鼠标和键盘的计算机门外汉的建议:用命令行(控制台)!(或者在新的Windows上,终端)

如果一个程序猿不知道什么叫命令行,以及不知道怎么在命令行下工作,那么找个程序猿连计算机的门槛都没摸到。

另外还有一个针对刚入门的专业人士的建议:收集错误信息并善用搜索引擎来搜索

请合理利用搜索引擎,并且懂得根据找到的线索继续搜集你需要的知识。

多说一句

举例来说,如果你搜索windows跑python脚本,就比较专业,反而如果搜py文件怎么运行,结果就可能会比较杂。

以上是搜索的艺术。

然后你可能找到这个open in new window。这里的第一个问题给出了三个问题:

  1. python 安装了么?环境变量对不对(控制台输入 python 有没有反应)?
  2. xxx.py 脚本写好了么?
  3. 是不是 cd 到了脚本所在的目录位置?

然后你看不懂

  • 什么叫环境变量
  • 控制台是什么
  • 控制台又怎么输入python?
  • 什么是cd
  • 什么是cd到脚本所在目录? ......

于是你就放弃了,然后你啥都没学会。

实际上,你可以继续把你(可能)不理解的内容继续搜索。这样你将收获:

  1. 解决你手头的问题
  2. 理解了上面你不懂的东西,而这些东西在今后的工作中经常会遇到
  3. 提升了你的搜索的经验,搜索+114514。
  4. (极有可能)提升你的英语水平,因为你可能搜到英语的资料。实际上英语资料的准确性一般会更好。
  5. 自信。

必须再次强调,能力的提升必须经过实践!

启动这个后端,你可以通过浏览器打开http://127.0.0.1:5000/来查看后端是否正常启动了。你会看到一个hello world的页面。

然后在上面你编写的表单上输入用户名和密码,这类假设输入的是tom123,点击提交按钮,你会看到:

提交表单
提交表单

控件

我们看让我们输入的控件

    <input type="text" name="username">

这是一个text,也就是文本框控件。后面的name="username"给出了这个控件的名称,这个名称在提交表单的时候用来标识是哪个控件。

你可以这么理解:把http://127.0.0.1:5000/app看作是函数的话,username就是函数参数的名称。上面例子中,username参数的值就是tom

对于当前的这个表单,参数是编码在url上的,你可以浏览器地址栏的内容:

http://127.0.0.1:5000/app?username=tom&password=123

试试

把密码对应输入框的type改成password试试看,会发生什么?再提交一下表单,又会发生什么?

还有一些非常常用的控件:

常用控件
<div>
    <table>
        <tr> <td>单行文本输入框:</td> <td><input type="text" name="" id=""></td> </tr>
        <tr> <td>密码输入框:</td> <td><input type="password" name="" id=""></td> </tr>
        <tr> <td>单选框:</td> <td><input type="radio" name="radio-01">1<input type="radio" name="radio-01">2</td> </tr>
        <tr> <td>复选框:</td> <td><input type="checkbox" name="box-01">a<input type="checkbox" name="box-01">b</td> </tr>
        <tr> <td>按钮:</td> <td><input type="button" value="点我"></td></tr>
        <tr> <td>下拉框:</td> <td><select><option value="1">第一项</option><option value="1">第二项</option><option value="1">第三项</option></select></td></tr>
    </table>
    </table>
</div>
td:nth-child(1){
    text-align: right;
}

更多控件可以看这里open in new window

namevalue属性

控件里最重要的属性是namevalue,你可以理解成函数变量的名称和函数的值。

name告诉服务器是哪个控件(参数),value则说明这个控件(参数)的取值。 向服务器发送数据时,会以name=value的形式向服务器传递数据。

对于textpassword这些需要用户输入的控件,用户输入的值就是对应的value

但对于不需要用户输入(或者说,用户没法输入)的控件,比如checkboxradio等,你如果不显式给出value属性,那浏览器就不知道这个对应的控件(参数)的值是多少了。

试试

给文本输入框input加上value,有什么变化?

<input type="text" value="Hello World!">

再改改他的值,又发生了什么?

对于单选和复选框,name有另外一层含义:表明哪些控件属于同一组,这点对于单选框尤其重要。

我们知道单选框的作用是在某一组选项中选中某一项,所以你必须给出哪些是同一组的:

    <form action="http://127.0.0.1:5000/app">
        <input type="radio" name="option" value="option1"> 选项1 <br>
        <input type="radio" name="option" value="option2"> 选项2 <br>
        <input type="radio" name="option" value="option3"> 选项3 <br>
        <input type="radio" name="option" value="option4"> 选项4 <br>
        <input type="submit" value="登录">
    </form>

    <form action="http://127.0.0.1:5000/app">
        <input type="radio" name="option1" value="option1"> 选项1 <br>
        <input type="radio" name="option2" value="option2"> 选项2 <br>
        <input type="radio" name="option3" value="option3"> 选项3 <br>
        <input type="radio" name="option4" value="option4"> 选项4 <br>
        <input type="submit" value="登录">
    </form>

试试上面的代码,看看两组代码有什么区别,然后提交一下,看看是什么结果。

复选框呢

从应用上说,复选框是不是同一组其实关系并不大,毕竟都可以选嘛。所以设置成同一组的意义何在?

更进一步,你想过没有,如果存在一组复选框,说明这组复选框下所有控件的name都是一样的,但这些控件的value都是不同的。

换句话说,对于同一个name,存在多个value。那么这种情况下要如何将这么多个value放在同一个name里向服务器上传递呢?

如果你想到了这点,说明你认真思考过。那么对于这个问题,我们只有一个建议:试试

不过我可以给出为什么需要为复选框设置同一组这个问题的回答:为了更符合设计的逻辑,并且便于服务器端代码逻辑的实现。

GET和POST

我们之前已经看到了,当你点击提交并向服务器发送请求时,你的数据会被明文放在url里并发送出去。这显然是非常不安全的。

所以我们自然会问,有没有不把数据放在url里的方法?

这个回答比较复杂,简单说,有、也没有:

我们为form加上一个method="POST",你就会发现url里的参数编码消失了:

    <form action="http://127.0.0.1:5000/app" method="POST">
        用户名:<input type="text" name="username"><br>
        密码:<input type="text" name="password"><br>
        <input type="submit" value="登录">
    </form>
 




POST提交,参数不编码了
POST提交,参数不编码了

但实际上这种方式并不能帮你把参数加密,HTTP协议提出之初就是一种基于明文的协议,你的所有数据都是明文提交的,表单数据显然也是如此。

怎么找到数据?

有很多抓包的软件,如果你想从事网络安全、底层协议等方面的研究,就要学会使用他们。

此外F12菜单里的网络选项卡也可以查看浏览器发出的请求和服务器返回的数据包。这个功能在后续开发的排错中非常有用。也要学会。

那么这又有什么意义呢?实际上不同的method是HTTP协议的一部分,可以看这里open in new window

method的作用是告诉服务器这次请求我打算做什么。以默认条件下的GET为例,他的意思是,我这次请求只准备看,不准备改变服务器的状态(例如,登录)。 服务器可以根据这点对业务逻辑进行优化。所以GET请求还贴心地把参数编码到url上,这样用户可以复制这个url,以便下次再用,或者复制给其他人,例如你可以试试这个链接:https://www.baidu.com/s?wd=厦门理工学院open in new window

POST表示将实体提交到指定的资源,通常导致在服务器上的状态变化或副作用。所以实际上表单多数情况下应该以POST方式提交。

进阶阅读

你可以查一查HTTP2.0RESTFul API等资料。

对请求method的了解是后端开发必不可少的知识,请自行寻找相关资料。