项目作者: aweffr

项目描述 :
演示bootstrap的验证css class用法
高级语言: JavaScript
项目地址: git://github.com/aweffr/bootstrap-form-validate-example.git
创建时间: 2018-11-13T05:54:00Z
项目社区:https://github.com/aweffr/bootstrap-form-validate-example

开源协议:

下载


Bootstrap4 定制验证(Custom Validation) 的简单实现方法


如果你只是想知道如何boostrap form validation, 请直接点
正文

官方文档

0. 关于Flask技术栈渲染网页上的一些碎碎念 a.k.a React定向安利

通常遇到一些小的页面需求,我们会用bootstrap搞定。
不过,作为最基础的表单,用户输入的合法验证也是需要的。

过去,我通常的做法是用flask-wtf + flask-bootstrap,配合
quick_form宏在服务器端生成表单。到目前为止这依然是一个可行
的选项,而且依旧很方便,主要问题在于flask-bootstrap并不支持
bootstrap4, 同时wtf的文档太丑, 一旦需要进一步customize, 你就要
开始看源码, 然后去试jinja宏里提供的各种参数, 最后
fight against it。。真是惨痛回忆,想起来就头疼。。

下图是我看flask-bootstrap
源码
时看到的@_@

make you cry

于此同时,当下python技术栈的同志们的javascript水平也逐渐开始6了起来,那么,直接撸前端是在正常不过的事情了。我因工作需求
搓了一个小React Native APP之后对React的技术栈就越发的喜爱起来,先不管前端各个框架的布道师们是如何定义React, Angular, Vue的,
就我这种玩票型选手而言React的JSX就是一个简洁灵活的界面描述语言,所有Jinja能干的事情都能干,而且能干的漂亮得多。
平常,我每次写html+css都是很蛋疼的,有了JSX我就可以All in JavaScript.
最重要的,python的灵活性和react的灵活性相近, 也让我有十足的亲近感。

1. Bootstrap Validation 的原理

首先, 表单的验证按官网分为”client side”和”server side”,
官网对”client side”的理解为通过游览器的Validation API去验证字段的合法性,
然后在form标签上加上”was-validated”来展示validation内容。

对于这种方法,一个简单的例子如下:

  1. <form> <!-- 请尝试更换为 <form class="was-validated"> -->
  2. <div class="form-group">
  3. <label>Name</label>
  4. <input type="text" class="form-control" required />
  5. <div class="invalid-feedback">这是一个invalid-feedback</div>
  6. </div>
  7. </form>

本例子中给input元素加上了required属性, 因此游览器会主动地验证这个地方有没有填值。

当form标签没加was-validated,那么就不会去触发:invalid:valid这两个css伪元素,
input外边框不会变成红色或者绿色, .invalid-feedback里的内容是隐藏的,如下:

inital

然后如果游览器加上was-validated,因为<input required>,没填游览器就会主动触发:invalid, ,如下:

invalid

填了就是:valid, 如下:

valid

如果你需要自己定义规则来触发:invalid和:valid元素, 那就需要很多
html5+js API的知识了,
不过,与其在html5里的框架里跳舞,我还是想概念越少越好;), 有兴趣的同学请点击上述链接扩展学习。

2. 本文的推荐实现方法

所以, 终于进入了本文的主要内容: server-rendering

长话短说, 直接在<input>标签上挂.is-invalid.is-valid, 不用在form上toggle.was-validated的class属性,就能显示
该input的验证状态, 并且控制其相邻.invalid-feedback元素的显示开关。

初始代码:

  1. <form>
  2. <div class="form-group">
  3. <label>Name</label>
  4. <input type="text" class="form-control" value="whatever"/>
  5. <div class="invalid-feedback">Server render invalid message</div>
  6. </div>
  7. </form>
  • <input type="text" class="form-control" value="whatever" /> 不显示验证状态, 如下:
    inital
  • <input type="text" class="form-control is-invalid" value="whatever" /> 显示invalid状态, 展示.invalid-feedback内容, 如下:
    invalid
  • <input type="text" class="form-control is-valid" value="whatever" /> 显示valid状态, 不展示.invalid-feedback内容, 如下:
    valid

OK, Get了这个知识点,那么恭喜诸位Flask同学已经知道如何server-rendering了。

接下来介绍一下我用react实现的一个简单实现的Demo:
Github Repo

本例采用的是controlled components, 不了解的这个概念的请看这里
同时,由于bootstrap里的标准form做法是把input包在.form-group里, 这部分可以抽象成一个Dumb组件:

  1. import classNames from 'classnames';
  2. function FormGroupText({label, name, type = 'text', onChange, placeholder, value, validation = {}}) {
  3. let id = `form-id-${name}`; // 用于label.for和input.id
  4. return (
  5. <div className="form-group">
  6. <label htmlFor={id}>{label}</label>
  7. <input
  8. id={id}
  9. type={type}
  10. className={classNames('form-control', {'is-invalid': validation.status === false}, {'is-valid': validation.status === true})}
  11. name={name}
  12. onChange={onChange}
  13. placeholder={placeholder}
  14. value={value}
  15. />
  16. {
  17. validation.msg &&
  18. <div className="invalid-feedback">
  19. {validation.msg}
  20. </div>
  21. }
  22. </div>
  23. );
  24. }

上述代码中:
对函数参数语法不了解的同学可以看这里
jsx中我推荐用classnames库来生成className代替手动拼接,链接

form表单的组件代码如下:

  1. import React, {Component} from 'react';
  2. class App extends Component {
  3. state = {
  4. email: "", username: "", password: "",
  5. validation: {}
  6. };
  7. onInputChange = (e) => {
  8. const {name, value} = e.target;
  9. this.setState({[name]: value});
  10. };
  11. checkValidation = () => {
  12. let validation = {};
  13. if (this.state.email === "" || !this.state.email.endsWith('foxmail.com')) {
  14. validation.email = {status: false, msg: '钦定必须是foxmail邮箱!'};
  15. return [false, validation];
  16. } else {
  17. validation.email = {status: true};
  18. }
  19. if (this.state.username === "" || this.state.username.length < 5) {
  20. validation.username = {status: false, msg: '用户名字符必须大于5位'};
  21. return [false, validation];
  22. } else {
  23. validation.username = {status: true};
  24. }
  25. if (this.state.password === "" || this.state.password.length < 6) {
  26. validation.password = {status: false, msg: '密码必须大于6位 '};
  27. return [false, validation];
  28. } else {
  29. validation.password = {status: true};
  30. }
  31. return [true, validation]
  32. };
  33. onSubmit = (e) => {
  34. e.preventDefault(); // 阻止默认的提交的页面跳转行为
  35. e.stopPropagation();
  36. const [isValid, validation] = this.checkValidation();
  37. this.setState({validation});
  38. if (isValid) {
  39. // Do ajax jobs
  40. }
  41. };
  42. render() {
  43. const {validation} = this.state;
  44. return (
  45. <div className="container">
  46. <h2>Form Validation Demo</h2>
  47. {/* form 加上 noValidate 来阻止默认的浏览器的验证tooltips*/}
  48. <form
  49. method='post'
  50. onSubmit={this.onSubmit}
  51. noValidate
  52. >
  53. <FormGroupText
  54. label="Email"
  55. type="email"
  56. name="email"
  57. value={this.state.email}
  58. placeholder="aweffr@foxmail.com"
  59. onChange={this.onInputChange}
  60. validation={validation.email}
  61. ></FormGroupText>
  62. <FormGroupText
  63. label="User Name"
  64. type="text"
  65. name="username"
  66. value={this.state.username}
  67. placeholder="aweffr"
  68. onChange={this.onInputChange}
  69. validation={validation.username}
  70. ></FormGroupText>
  71. <FormGroupText
  72. label="Password"
  73. type="password"
  74. name="password"
  75. value={this.state.password}
  76. placeholder="Please Input Password"
  77. onChange={this.onInputChange}
  78. validation={validation.password}
  79. ></FormGroupText>
  80. <button type="submit" className="btn btn-block btn-primary">
  81. Submit
  82. </button>
  83. </form>
  84. </div>
  85. );
  86. }
  87. }

上述demo的运行效果如下:

gif-demo

上述代码中:

  • checkValidation方法为具体的验证字段的逻辑
    • 在实现上,我在每次验证一个字段后,如果invalid就直接return,是因为觉得这样对用户比较友好,这样用户不会一提交就满屏幕的红色:)
    • 每个FormGroupText组件中的status来自于表单的state.validation, 如email字段的属性位于this.state.validation.email.status
      • status: undefined => 未验证, className='form-control'
      • status: true => 合法, className='form-control is-valid'
      • status: false => 不合法, className='form-control is-invalid'
    • onInputChange中{[name]: value}这个语法请参考这里
      Ctrl + F5 Computed property names (ES2015)

3. More

当然, 这个例子里的validation实现方法还是很粗糙的。更进一步,严肃的项目上我们会去验证某个字段是不是必须是数字,是不是只能含有中文,是不是不能包含特殊字符,以及输入长度验证和密码强度验证。
这就要求把validation rules配置化。这方面我用过的,体验非常好的是大哥级组件库antd。有兴趣的同学可以去antd表单
和它的具体实现rc-forms做进一步学习,
他们把验证的部分解耦到了async-validator上,用起来挺顺手的,
尤其他们开发时划分表单problem的方法和思路尤其值得学习。

不过,至于如何上手antd,以及其官方大佬所实现的脚手架umijs,乃至react状态管理摊开来讲就又是一篇博客了。To Be Continued.