The Art of Form Validation

Aff0a25540addff750cbbe6aa884e429?s=47 Caneco
August 29, 2019

The Art of Form Validation

A form could be simple or exhaustive, can have simple or dynamic values… Either way, all payloads between client and server should be validated. And when errors happen (because they will happen), the user needs help and guidance to solve the problem. How can you achieve the best combination of User Interface (UI), User Experience (UX), and Developer Experience (DX)? Join me and learn all the good tips and tricks using the community's favorite stack: Laravel, Vue.

↓↓↓
Live recording of this talk at Laracon.EU
https://youtube.com/watch?v=rkdlJeHTeCY

Aff0a25540addff750cbbe6aa884e429?s=128

Caneco

August 29, 2019
Tweet

Transcript

  1. FORM validation #TheLaravelKid BEGINS LARACON.EU The Art of

  2. So, validtaion…

  3. So, validation… It's required, right?

  4. I'm Caneco Full-Stack Developer ! MEDICARE caneco.xyz

  5. I'm Caneco .PHP .JS .HTML .CSS .JSON .SQL .GIT .SH

    .SVG .PNG .PSD .AI
  6. I'm Caneco @enunomaduro's "Schoger"

  7. I'm Caneco from Lisbon, Portugal Netherlands here Spain here Portugal

    here
  8. WARNING ALL THE FOLLOWING SLIDES CONTAIN VALUABLE INFORMATION FOR YOUR

    DAILY LIFE… ALL GIFS ARE FROM THE BADASS TV SHOW COBRA KAI THAT YOU ALL MUST WATCH… AND ALL THE SLIDES, AND CONTENT ARE 100% MADE ON KEYNOTE ̈
  9. LET'S BEGIN

  10. Why validate?

  11. On a regular day in 2019

  12. 500 million tweets source: weforum.org

  13. 294 billion emails source: weforum.org

  14. 5 billion searches source: weforum.org

  15. ~300 new posts on laracasts.com/discuss source: the Jeffrey Way

  16. ~64.5K downloads of Spatie packages source: spatie.be/open-source

  17. It's a LOT of data

  18. It's a LOT of data

  19. through APIs [GET] skyscanner-example.dev/v1/flights [PATCH] zomato-example.dev/place/123/book [GET] openweather-example.dev/fatima [GET] musixmatch-example.dev/top-10

    [GET] nasa-example.dev/v9999/planet-x [GET] numbers-example.dev/random-number [GET] urbandictionary-example.dev/tldr [GET] tempmail-example.dev/fresh [GET] moviedatabase-example.dev/v1/top/horror [GET] ip-example.dev/127.0.0.1
  20. or in forms

  21. Validation… do we really need it?

  22. Can't we trust our users?

  23. MOST DEF

  24. It's purely just for fun

  25. Validation 101

  26. SIGN IN Sign in Email address Password SENDING

  27. SIGN IN Sign in These credentials do not match our

    records. Email address Password
  28. 1 2 3 4 ▶ 9 10 11 12 <input

    name="username" type="text" "# ""$ <input name="password" type="password" "#
  29. 1 2 3 4 5 ▶ 8 9 10 11

    12 <input name="username" type="text" required "# ""$ <input name="password" type="password" required "#
  30. SIGN IN Sign in Email address Password Please fill in

    this field. Please fill in this field.
  31. Sign in Email address example Password •••••• SIGN IN SENDING

  32. SIGN IN Sign in These credentials do not match our

    records. Email address example Password
  33. 1 2 3 4 5 ▶ 8 9 10 11

    12 <input name="username" type="text" required "# ""$ <input name="password" type="password" required "#
  34. 1 2 3 4 5 ▶ 8 9 10 11

    12 <input name="username" type="email" required "# ""$ <input name="password" type="password" required "#
  35. SIGN IN Sign in Email address example Password •••••• Please

    include an '@' in the email address.
  36. SIGN IN Sign in Email address example@ Password •••••• Please

    enter a part following '@'.
  37. SIGN IN Sign in Email address example@ Password •••••• SENDING

    email
  38. SIGN IN Sign in These credentials do not match our

    records. Email address example@email Password ••••••
  39. 1 2 3 4 5 ▶ 8 9 10 11

    12 <input name="username" type="email" required "# ""$ <input name="password" type="password" required "#
  40. 1 2 3 4 5 6 ▶ 8 9 10

    11 12 <input name="username" type="email" pattern=".+@.+\"%+" required "# ""$ <input name="password" type="password" required "#
  41. 1 2 3 4 5 6 ▶ 8 9 10

    11 12 13 <input name="username" type="email" pattern=".+@.+\"%+" required "# ""$ <input name="password" type="password" minlength="6" required "#
  42. 1 2 3 4 5 6 ▶ 8 9 10

    11 12 13 14 <input name="username" type="email" pattern=".+@.+\"%+" required "# ""$ <input name="password" type="password" minlength="6" pattern="^(?=.\d)(?=.[a-z])(?=.*[A-Z]).{8,}$" required "#
  43. 1 2 3 4 5 6 ▶ 8 9 10

    11 12 13 14 <input name="username" type="email" pattern=".+@.+\"%+" required "# ""$ <input name="password" type="password" minlenght="6" pattern="^(?=.\d)(?=.[a-z])(?=.*[A-Z]).{8,}$" required "#
  44. Thank you

  45. Not exactly…

  46. HTML5 validation depends on the browser

  47. And it's not that simple nor pretty

  48. 1 2 3 4 5 6 ▶ 8 9 10

    11 12 13 <input name="username" type="email" pattern=".+@.+\"%+" required "# ""$ <input name="password" type="password" minlenght="6" pattern="^(?=.\d)(?=.[a-z])(?=.*[A-Z]).{8,}$" required "#
  49. 1 2 3 4 5 6 ▶ 20 function "'invoke(Request

    $request) { $request"(validate([ 'username' ") 'required|email', 'password' ") 'required|min:6|strong_password', ]); ""$ }
  50. JavaScript

  51. 1 ▶ 42 <form action="/" method="post"> ""$ "*form>

  52. 1 ▶ 42 <form action="/" method="post" novalidate> ""$ "*form>

  53. jQueryValidate.js

  54. 1 2 3 4 5 6 7 8 9 10

    var rules = { username: { required: true, email: true, }, password: { required: true, minlength: 6, }, }
  55. 1 2 3 4 5 6 7 8 9 10

    11 12 var rules = { username: { required: true, email: true, }, password: { required: true, minlength: 6, }, } $("#login").validate(rules)
  56. validate.js

  57. 1 2 3 4 let input = { username: document.getElementById('username').value(),

    password: document.getElementById('password').value(), }
  58. 1 2 3 4 5 6 7 8 9 10

    11 12 13 14 15 let input = { username: document.getElementById('username').value(), password: document.getElementById('password').value(), } let rules = { username: { presence: true, email: true }, password: { presence: true, length: { minimum: 6 }, }, }
  59. 1 ▶ 4 7 6 ▶ 15 16 17 18

    19 let input = { !!" } let rules = { ""$ } if (validate(input, rules)) { "+ }
  60. VeeValidate

  61. > npm install vee-validate

  62. 1 2 import Vue from 'vue' import VeeValidate from 'vee-validate'

  63. 1 2 3 4 import Vue from 'vue' import VeeValidate

    from 'vee-validate' Vue.use(VeeValidate)
  64. 1 2 3 4 5 <input name="username" type="email" required "#

  65. 1 2 3 4 5 6 7 <input name="username" v-model="username"

    v-validate type="email" required "#
  66. 1 2 3 4 5 6 7 <input name="username" v-model="username"

    v—validate type="email" required "#
  67. 1 2 3 4 import Vue from 'vue' import VeeValidate

    from 'vee-validate' Vue.use(VeeValidate)
  68. 1 2 3 4 5 6 import Vue from 'vue'

    import VeeValidate from 'vee-validate' Vue.use(VeeValidate, { useConstraintAttrs: false, })
  69. 1 2 3 4 5 6 7 <input name="username" v-model="username"

    v-validate type="email" required "#
  70. 1 2 3 4 5 6 <input name="username" v-model="username" v-validate="'required|email'"

    type="text" "#
  71. 1 2 3 4 5 6 7 8 9 10

    11 alpha alpha_dash alpha_num alpha_spaces before between confirmed credit_card date_between date_format decimal digits dimensions email image included integer ip is is_not length max_value mimes min max min_value excluded numeric regex required required_if size url
  72. Add custom rules

  73. 1 import { Validator } from 'vee-validate'

  74. 1 2 3 4 5 6 7 import { Validator

    } from 'vee-validate' import { STRONG_PASS_REGEX } from '@/helpers/regex_rules' const strongPassword = { getMessage: field ") `${field} is weak.`, validate: value ") STRONG_PASS_REGEX.test(value) }
  75. 1 2 3 4 5 6 7 import { Validator

    } from 'vee-validate' import { STRONG_PASS_REGEX } from '@/helpers/regex_rules' const strongPassword = { getMessage: field ") `${field} is weak.`, validate: value ") STRONG_PASS_REGEX.test(value) }
  76. 1 2 3 4 5 6 7 import { Validator

    } from 'vee-validate' import { STRONG_PASS_REGEX } from '@/helpers/regex_rules' const strongPassword = { getMessage: field ") `${field} is weak.`, validate: value ") STRONG_PASS_REGEX.test(value) }
  77. 1 2 3 4 5 6 7 8 9 import

    { Validator } from 'vee-validate' import { STRONG_PASS_REGEX } from '@/helpers/regex-rules' const strongPassword = { getMessage: field ") `${field} is weak.`, validate: value ") STRONG_PASS_REGEX.test(value) } Validator.extend('strong_password', strongPassword)
  78. 1 2 3 4 5 6 <input name="password" v-model="password" v—validate="'required|strong_password'"

    type="text” "#
  79. Using ErrorBag

  80. 1 2 3 4 5 6 7 <input class="field-input" name="username"

    v-model="username" v-validate="'required|email'" type="text" "#
  81. 1 2 3 4 5 6 7 8 9 10

    <label class="field-label"> Email address "*label> <input class="field-input" name="username" v-model="username" v-validate="'required|email'" type="text" "#
  82. 1 2 3 4 5 6 7 8 9 10

    11 12 13 14 <label class="field-input" :class="{ 'has-error': errors.has('username') }" > Email address "*label> <input class="field-input" :class="{ 'has-error': errors.has('username') }" name="username" v-model="username" v-validate="'required|email'" type="text" "#
  83. 1 ▶ 6 7 ▶ 14 15 16 17 <label>

    ""$ "*label> <input ""$ "# <p v-if="errors.has('username')" class="field-help"> The username must be a valid email. "*p>
  84. 1 ▶ 6 7 ▶ 14 15 16 17 <label>

    ""$ "*label> <input ""$ "# <p v-if="errors.has('username')" class="field-help"> {{ errors.first('username') }} "*p>
  85. SIGN IN Sign in The username must be a valid

    email. Email address example Password •••••• Email address example
  86. 1 2 3 4 {{ errors.any() }} {{ errors.all() }}

    {{ errors.count() }} {{ errors.clear() }}
  87. WAIT?! ALL OF THAT?

  88. Why validate on the client side?

  89. Assure the data is good

  90. Quick feedback

  91. Real-time feedback

  92. Reduce server validation

  93. Now in the server side

  94. None
  95. Why validate on the server side?

  96. Reassure the data is good

  97. Server-side checking

  98. Domain rules

  99. 1 2 3 4 5 6 ▶ 20 function "'invoke(Request

    $request) { $request"(validate([ 'username' ") 'required|email', 'password' ") 'required|min:6|strong_password', ]); ""$ }
  100. 1 2 3 4 5 6 7 8 9 HTTP/1.1

    422 Unprocessable Entity Content-Type: application/json; charset=utf-8 ""$ "data": { "errors": { "email": [ "The email must be a valid email address." ], ""$
  101. 1 2 3 4 5 6 7 8 9 HTTP/1.1

    422 Unprocessable Entity Content-Type: application/json; charset=utf-8 ""$ "data": { "errors": { "email": [ "The email must be a valid email address." ], ""$
  102. 1 2 3 4 5 6 7 8 9 10

    11 12 13 HTTP/1.1 422 Unprocessable Entity Content-Type: application/json; charset=utf-8 ""$ "data": { "errors": { "email": [ "The email must be a valid email address." ], "password": [ "The password must be at least 6 characters", "The password must have 2 uppercase letters 1 special letter 2 digi ] ""$
  103. 1 2 ▶ 8 ▶ 12 13 if (response.data.errors.email) {

    if (/required/i.test(response.data.errors)) { ""$ } else if (/invalid/i.test(response.data.errors)) { ""$ } }
  104. How to simplify the conversation?

  105. > art make:request SubmitRequest

  106. 1 2 3 4 5 6 7 public function rules()

    { return [ 'username' ") 'required|email', 'password' ") 'required|min:6|strong_password', ]; }
  107. 1 2 3 4 5 6 7 8 9 public

    function messages() { return [ 'required' ") ':attribute.required', 'email' ") 'email.invalid', 'password.min' ") 'password:min', 'strong_password' ") 'password_not_strong', ]; }
  108. 1 2 3 4 5 6 7 8 9 public

    function failedValidation(Validator $validator) { throw (new HttpResponseException( response()"(json([ 'message' ") 'The given data was invalid.', 'errors' ") $validator"(errors()"(all(), ], 422) )); }
  109. 1 2 3 4 5 6 7 8 9 public

    function failedValidation(Validator $validator) { throw (new HttpResponseException( response()"(json([ 'message' ") 'The given data was invalid.', 'errors' ") $validator"(errors()"(all(), ], 418) )); }
  110. 1 2 3 4 5 6 7 8 9 HTTP/1.1

    418 I'm a teapot Content-Type: application/json; charset=utf-8 ""$ { "message": "The given data was invalid.", "errors": [ "email.invalid" ] }
  111. None
  112. Validation examples

  113. email fields

  114. Simple form Email address SUBMIT The email field is required.

  115. Simple form Email address SUBMIT The email must be a

    valid email address. example
  116. 1 2 3 4 5 6 <input name="email" v-model="email" v—validate="'required|email'"

    type="text" "#
  117. " prevent the errors

  118. Simple form Email address SUBMIT Did you mean example@hotmail.com? example@hotmale.com

  119. Simple form Email address SUBMIT example@gmail.com example@gmail.com example@hotmail.com example@outlook.com example@me.com

  120. 1 2 3 4 5 6 7 8 9 function

    "'invoke(Request $request) { $request"(validate([ 'email' ") [ 'required', 'email', ] ]); }
  121. 1 2 3 4 5 6 7 8 9 function

    "'invoke(Request $request) { $request"(validate([ 'email' ") [ 'required', 'email', "+ taylor@laravel.ceo ] ]); }
  122. Simple form Email address SUBMIT taylor@laravel.ceo

  123. 1 2 3 4 5 6 7 8 9 function

    "'invoke(Request $request) { $request"(validate([ 'email' ") [ 'required', 'email', ] ]); }
  124. 1 2 3 4 5 6 7 8 9 function

    "'invoke(Request $request) { $request"(validate([ 'email' ") [ 'required', 'email:rfc,dns', "+ new in Laravel 5.8.33 ] ]); }
  125. 1 2 3 4 5 6 7 8 9 10

    function "'invoke(Request $request) { $request"(validate([ 'email' ") [ 'required', 'email:rfc,dns', 'unique:users.email', ] ]); }
  126. 1 2 3 4 5 6 7 8 9 10

    function "'invoke(Request $request) { $request"(validate([ 'email' ") [ 'required', 'email:rfc,dns', "+ ", ATTENTION 'unique:users.email', "+ ", ATTENTION ] ]); }
  127. 1 2 3 4 5 6 7 8 9 10

    11 function "'invoke(Request $request) { $request"(validate([ 'email' ") [ 'bail', 'required', 'email:rfc,dns', 'unique:users.email', ] ]); }
  128. Remember SubmitRequest?

  129. 1 2 3 4 5 6 7 8 HTTP/1.1 418

    I'm a teapot Content-Type: application/json; charset=utf-8 ""$ "data": { "errors": [ "email.invalid", ] ""$
  130. 1 2 3 4 5 6 7 this.$http.post('/submit’) .then(response ")

    { "+ }) .catch({ response } ") { "+ })
  131. 1 ▶ 5 6 7 8 9 this.$http.post('/submit’) ""$ .catch({

    response } ") { if (response.status ""- 418) { "+ } })
  132. 1 ▶ 5 6 7 8 9 10 11 this.$http.post('/submit’)

    ""$ .catch({ response } ") { if (response.status ""- 418) { if (response.data.errors.includes('email.invalid')) { "+ } } })
  133. 1 ▶ 5 6 7 8 9 10 11 12

    13 14 15 this.$http.post('/submit’) ""$ .catch({ response } ") { if (response.status ""- 418) { let errors = response.data.errors if (response.data.errors.includes('email.invalid')) { this.$validator.errors.add({ field: 'email', msg: 'The email must be a valid email address.', }) } } })
  134. 1 ▶ 5 6 7 8 9 10 11 12

    13 14 15 16 this.$http.post('/submit’) ""$ .catch({ response } ") { if (response.status ""- 418) { let errors = response.data.errors if (errors.includes('email.invalid')) { this.$validator.errors.add({ field: 'email', msg: 'The email must be a valid email address.', }) this.$el.username.focus() } } })
  135. phone fields

  136. 1 2 3 4 5 6 <input name="phone" v-model="phone" v-validate="'required'"

    type="text" "#
  137. 1 2 3 4 5 6 <input name="phone" v-model="phone" v-validate="'required|phone'"

    type="text" "#
  138. 1 2 3 4 5 6 7 8 import {

    Validator } from 'vee-validate' const phone = { getMessage: field ") `The ${field} must be a valid phone number.`, validate: value ") { return /^[0-9]{9}$/.test(value) } }
  139. 1 2 3 4 5 6 7 8 import {

    Validator } from 'vee-validate' const phone = { getMessage: field ") `The ${field} must be a valid phone number.`, validate: value ") { return /^2[0-9]{8}|9[1236][0-9]{7}$/.test(value) } }
  140. Simple form Mobile number SUBMIT The phone must be a

    valid phone number. 201000123
  141. " prevent the errors

  142. Simple form Mobile number SUBMIT 912367125637512123987

  143. Simple form Mobile number SUBMIT asdasdasd

  144. 1 2 3 4 5 6 <input name="phone" v-model="phone" v-validate="'required|phone'"

    type="text" "#
  145. 1 2 3 4 5 6 7 <input name="phone" v-model="phone"

    v-validate="'required|phone'" type="text" maxlength="9" "#
  146. > npm install vee-mask

  147. 1 2 import Vue from 'vue' import VeeMask from 'vee-mask'

  148. 1 2 3 4 import Vue from 'vue' import VeeMask

    from 'vee-mask' Vue.use(VeeMask)
  149. 1 2 3 4 5 6 7 8 <input name="phone"

    v-model="phone" v-validate="'required|phone'" v-mask="'#########'" type="text" maxlength="9" "#
  150. 1 2 3 4 5 6 7 8 <input name="phone"

    v-model="phone" v-validate="'required|phone'" v-mask="'"". "". "".'" type="text" maxlength="11" "#
  151. Simple form Mobile number SUBMIT 213 123 123

  152. zip, iban, address, weight…

  153. They follow the same idea

  154. WAX ON, WAX OFF

  155. Extra tricks

  156. autofocus

  157. 1 <input name="fullname" type="text" "#

  158. 1 <input name="fullname" type="text" autofocus "#

  159. Sign up Full name Email address Phone CONTINUE |

  160. " attention to mobile

  161. Sign up Full name Email address Phone CONTINUE | Done

    Q W E R T Y U I O P space return 123 A S D F G H J K L ⌫ ⇧ Z X C V B N M
  162. 1 <input name="fullname" type="text" autofocus "#

  163. 1 <input name="fullname" type="text" v-autofocus "#

  164. 1 2 3 4 5 6 7 8 Vue.directive('autofocus', {

    inserted: (el, binding) ") { if (! isMobile) { el.setAttribute('autofocus', 'autofocus') el.focus() "+ Justin Case } }, })
  165. input types

  166. 1 <input name="email" type="email" "#

  167. SUBMIT The Form Email field | Done Q W E

    R T Y U I O P space return 123 A S D F G H J K L ⌫ ⇧ Z X C V B N M Done Q W E R T Y U I O P space return 123 A S D F G H J K L ⌫ ⇧ Z X C V B N M @ .
  168. 1 <input name="field" type="tel" "#

  169. The Form Phone field SUBMIT | Done ⌫ + ❋

    # 2 1 3 5 4 6 8 7 9 0 ABC GHI JKL MNO DEF TUV PQRS WXYZ
  170. 1 <input name="field" type="number" "#

  171. The Form Number field SUBMIT | Done 1 2 3

    4 5 6 7 8 9 0 space return ABC ⌫ #+= . , ? ! ´ - / : ; ( ) $ & @ "
  172. 1 <input name="field" type="number" maxlength="9" "#

  173. 1 <input name="field" type="number" maxlength="9" "#

  174. inputmode

  175. 66 20 76 Nope Chrome Firefox Edge Safari Desktop Mobile

    / Tablet 75 Nope 67 12.2-12.3 Android Chrome Android Firefox Android iOS Safari
  176. 1 <input name="field" type="number" maxlength="9" "#

  177. 1 2 3 4 5 6 <input name="field" type="text" inputmode="number"

    maxlength="9" "#
  178. 1 2 3 4 5 6 <input name="field" type="text" inputmode="number"

    "+ tel, url, email, … maxlength="9" "#
  179. 1 2 3 4 5 6 <input name="field" type="text" inputmode="decimal"

    maxlength="9" "#
  180. The Form Decimal field SUBMIT | Done ⌫ . 2

    1 3 5 4 6 8 7 9 0 ABC GHI JKL MNO DEF TUV PQRS WXYZ
  181. placeholder

  182. 1 2 3 4 5 <input name="name" type="text" placeholder="Your Fullname"

    "#
  183. REGISTER Sign up Your email Caneco

  184. Prevent the Dory Syndrome

  185. REGISTER Sign up Your email Caneco Your full name

  186. REGISTER Sign up Your full name Your email Caneco The

    full name must be a valid name. Caneco2
  187. REGISTER Sign up example@email.com Your full name Caneco Your email

  188. Now you're ready

  189. READY TO STRIKE

  190. But first

  191. VALIDATE FIRST VALIDATE BACK NO MERCY

  192. Thank you

  193. FORM validation #TheLaravelKid The Art of