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內容。

對於這種方法,一個簡單的例子如下:

<form> <!-- 請嘗試更換為 <form class="was-validated">  -->
    <div class="form-group">
        <label>Name</label>
        <input type="text" class="form-control" required />
        <div class="invalid-feedback">這是一個invalid-feedback</div>
    </div>
</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元素的顯示開關。

初始代碼:

<form>
  <div class="form-group">
    <label>Name</label>
    <input type="text" class="form-control" value="whatever"/>
    <div class="invalid-feedback">Server render invalid message</div>
  </div>
</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組件:

import classNames from 'classnames';

function FormGroupText({label, name, type = 'text', onChange, placeholder, value, validation = {}}) {
  let id = `form-id-${name}`; // 用於label.for和input.id

  return (
    <div className="form-group">
      <label htmlFor={id}>{label}</label>
      <input
        id={id}
        type={type}
        className={classNames('form-control', {'is-invalid': validation.status === false}, {'is-valid': validation.status === true})}
        name={name}
        onChange={onChange}
        placeholder={placeholder}
        value={value}
      />
      {
        validation.msg &&
        <div className="invalid-feedback">
          {validation.msg}
        </div>
      }
    </div>
  );
}

 

上述代碼中: 對函數參數語法不了解的同學可以看這里 jsx中我推薦用classnames庫來生成className代替手動拼接,鏈接

form表單的組件代碼如下:

import React, {Component} from 'react';

class App extends Component {

  state = {
    email: "", username: "", password: "",
    validation: {}
  };

  onInputChange = (e) => {
    const {name, value} = e.target;
    this.setState({[name]: value});
  };

  checkValidation = () => {
    let validation = {};

    if (this.state.email === "" || !this.state.email.endsWith('foxmail.com')) {
      validation.email = {status: false, msg: '欽定必須是foxmail郵箱!'};
      return [false, validation];
    } else {
      validation.email = {status: true};
    }

    if (this.state.username === "" || this.state.username.length < 5) {
      validation.username = {status: false, msg: '用戶名字符必須大於5位'};
      return [false, validation];
    } else {
      validation.username = {status: true};
    }


    if (this.state.password === "" || this.state.password.length < 6) {
      validation.password = {status: false, msg: '密碼必須大於6位 '};
      return [false, validation];
    } else {
      validation.password = {status: true};
    }

    return [true, validation]
  };

  onSubmit = (e) => {
    e.preventDefault(); // 阻止默認的提交的頁面跳轉行為
    e.stopPropagation();

    const [isValid, validation] = this.checkValidation();

    this.setState({validation});

    if (isValid) {
      // Do ajax jobs
    }
  };

  render() {
    const {validation} = this.state;
    return (
      <div className="container">
        <h2>Form Validation Demo</h2>
        {/* form 加上 noValidate 來阻止默認的瀏覽器的驗證tooltips*/}
        <form
          method='post'
          onSubmit={this.onSubmit}
          noValidate
        >
          <FormGroupText
            label="Email"
            type="email"
            name="email"
            value={this.state.email}
            placeholder="aweffr@foxmail.com"
            onChange={this.onInputChange}
            validation={validation.email}
          />
          <FormGroupText
            label="User Name"
            type="text"
            name="username"
            value={this.state.username}
            placeholder="aweffr"
            onChange={this.onInputChange}
            validation={validation.username}
          />
          <FormGroupText
            label="Password"
            type="password"
            name="password"
            value={this.state.password}
            placeholder="Please Input Password"
            onChange={this.onInputChange}
            validation={validation.password}
          />

          <button type="submit" className="btn btn-block btn-primary">
            Submit
          </button>
        </form>
      </div>
    );
  }
}

 

上述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.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM