Friday, April 8, 2016

The HTML5 JavaScript form validation API

The HTML5 JavaScript form validation API

password custom validation exampleThere is a JavaScript API for form validation. This API will let you use your own validation algorithm (i.e. check that you entered the same password in two different input fields), and customize error messages. Also, together with some HTML/CSS/JavaScript you will be able to make your own message bubbles.

TYPICAL USE

Example of password checking at JS Bin,  be careful to try this example in JS Bin standalone mode (click the small black arrow on the top right of the output tab).
Or you may try it here in your browser:
Example use of the validation API, enter different passwords and submit  
 
Extract from source code:
  1. <html>
  2. <head>
  3.    <title>Example of using the validation API</title>
  4.    <style>
  5.      .myForm input:invalid { background-color: lightPink;}
  6.      .myForm input:valid { background-color:lightGreen; }
  7.      .myForm input:required {border: 2px solid red;}
  8.      .myForm input:optional {border: 2px solid green;}
  9.      .myForm label { display: inline-block; width: 140px; text-align: right; }
  10.    </style>
  11. </head>
  12. <body>
  13. <form class="myForm">
  14.    <fieldset>
  15.      <legend>Example use of the validation API</legend>
  16.      <label for="password1" >Password:</label>
  17.      <input type="password" id="password1" oninput="checkPasswords();"required>
  18.      <p>
  19.      <label for="password2">Repeat password:</label>
  20.      <input type="password" id="password2" oninput="checkPasswords();"required>
  21.      <p>
  22.      <button>Submit</button>
  23.   </fieldset>
  24. </form>
  25.  
  26. <script>
  27.   function checkPasswords() {
  28.       var password1 = document.getElementById('password1');
  29.       var password2 = document.getElementById('password2');
  30.       if (password1.value != password2.value) {
  31.           password2.setCustomValidity('Passwords do not match!');
  32.       } else {
  33.          password2.setCustomValidity('');
  34.       }
  35.    }
  36. </script>
  37. </body>
  38. </html>
Explanations: the validity API proposes a setCustomValidity() method available on input DOM objects. This method allows you to customize error messages. It takes a string parameter. When this string is empty, the element is considered valid, when the string is not empty, the field is invalid and the validation error message displayed in the bubble will be equal to that string.
At lines 17 and 20 we added an input event listener: each time a key is typed, the checkPasswords()function is called.
Lines 28 and 29 get the input fields' values, and lines 31-35 check if the passwords are the same and set the validity of the field using the validation API's method setCustomValidity(error_message).

The validity property of input fields

The validity property of input fields helps to get error details when the field is invalid. This property tests the different validation types errors.
Here is how to get the validity property of an input field:
  1. var input = document.getElementById('IdOfField');
  2. var validityState_object = input.validity;
The different possible values for the validity property are: 
    • valueMissing 
    • typeMismatch
    • patternMismatch
    • tooLong
    • rangeUnderflow 
    • rangeOverflow
    • stepMismatch
    • valid 
    • customError
Here is an example at JS Bin that shows how to test the different types of validation errors, or you may try it here in your browser (enter bad values, too big, too small, enter invalid characters, etc.):
 
Note that testing it in Chrome/Opera/Firefox does not produce the same results. So far Opera has the most advanced implementations, however, entering "21" for example in the <input type="number" max="20"/>input field may yield some unexpected results depending on the browser. Test it yourself.
Source code:
  1. <!DOCTYPE html>
  2. <html>
  3. ...
  4. <body>
  5. <script>
  6. function validate() {
  7.      var input = document.getElementById('b');
  8.      var validityState_object = input.validity;
  9.  
  10.      if(validityState_object.valueMissing) {
  11.          input.setCustomValidity('Please set an age (required)');
  12.      } else if (validityState_object.rangeUnderflow) {
  13.          input.setCustomValidity('Your value is too low');
  14.      } else if (validityState_object.rangeOverflow) {
  15.          input.setCustomValidity('Your value is too high');
  16.      } else if (validityState_object.typeMismatch) {
  17.          input.setCustomValidity('Type mismatch');
  18.      } else if (validityState_object.tooLong) {
  19.          input.setCustomValidity('Too long');
  20.      } else if (validityState_object.stepMismatch) {
  21.          input.setCustomValidity('stepMismatch');
  22.      } else if (validityState_object.patternMismatch) {
  23.          input.setCustomValidity('patternMismatch');
  24.      } else {
  25.          input.setCustomValidity('');
  26.      }
  27. }
  28. </script>
  29. <form class="myForm">
  30. <label for="b">Enter a value between 10 and 20: </label>
  31.  
  32. <input type="number" name="text" id="b" min="10" max="20"
  33.         required oninput='validate();'/>
  34. <button>Submit</button>
  35. </form>
  36. </body>
  37. </html>

THE VALIDATIONMESSAGE PROPERTY

It is also possible to get the validation error message, using the validationMessage property of input fields.
  1. var input = document.getElementById('b');
  2.  
  3. console.log("Validation message = " + input.validationMessage);
This will be useful for making custom error messages. More about this topic in the next section of the course.

Custom validation: changing the default behavior, aggregating error messages, removing bubbles, etc.

Aggregating error messages

CRITICISM OF THE DEFAULT BEHAVIOR OF HTML5 BUILT-IN VALIDATION

The techniques we saw up to now for enhancing HTML forms are powerful and bring interesting features, but are also criticized by Web developers:
    • Browser support is still not 100% complete (Safari and Internet Explorer still lack several important features),
    • Aggregating error messages is not possible.  On submission, browsers show an error bubble next to the first invalid field, and there is no built-in way to display all error messages for all invalid fields at the same time,
    • You cannot style the bubbles.
However, the validation API gives enough power to make your own validation behavior, overriding the default when necessary.
We will give here an adaptation of work presented at the developer.telerik.com Web site.  This link is really worth reading, as it presents different approaches and gives external references for those who would like to go further.

EXAMPLE THAT SHOWS AGGREGATION OF ERROR MESSAGES + OVERRIDING DEFAULT BEHAVIOR

Try the online example at JS Bin, or try it here in your browser: enter invalid values and submit with one or two invalid fields.
 
 
Complete source code:
  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4.    <meta charset="utf-8">
  5.    <title>Aggregating error messages</title>
  6.    <style>
  7.        input:invalid { background-color: lightPink;}
  8.        input:valid { background-color:lightGreen; }
  9.        input:required {border: 2px solid red;}
  10.        input:optional {border: 2px solid green;}
  11.  
  12.        .error-messages {
  13.            display: none;
  14.            margin: 0 10px 15px 10px;
  15.            padding: 8px 35px 8px 30px;
  16.            color: #B94A48;
  17.            background-color: #F2DEDE;
  18.            border: 2px solid #EED3D7;
  19.            border-radius: 4px;
  20.        }
  21.        fieldset {
  22.           border:1px solid;
  23.           padding:20px;
  24.        }
  25.     </style>
  26. </head>
  27. <body>
  28. <form>
  29.      <fieldset>
  30.          <legend>Submit with one or two invalid fields</legend>
  31.          <ul class="error-messages"></ul>
  32.          <label for="name">Name:</label>
  33.          <input id="name" name="name" required>
  34.          <p>
  35.          <label for="email">Email:</label>
  36.          <input id="email" name="email" type="email" required>
  37.          <p>
  38.          <button>Submit</button>
  39.      </fieldset>
  40. </form>
  41.  
  42. <script>
  43.     function replaceValidationUI(form) {
  44.        // Suppress the default bubbles
  45.           form.addEventListener("invalid", function (event) {
  46.           event.preventDefault();
  47.        }, true);
  48.  
  49.        // Support Safari, iOS Safari, and the Android browser—each of which
  50.        // do not prevent form submissions by default
  51.        form.addEventListener("submit", function (event) {
  52.           if (!this.checkValidity()) {
  53.              event.preventDefault();
  54.           }
  55.        });
  56.  
  57.        // Container that holds error messages. By default it has a CSS
  58.        // display:none property
  59.        var errorMessages = form.querySelector(".error-messages");
  60.  
  61.        var submitButton = form.querySelector("button:not([type=button]),
  62.                                               input[type=submit]");
  63.  
  64.        submitButton.addEventListener("click", function (event) {
  65.            var invalidFields = form.querySelectorAll("input:invalid");
  66.            var listHtml = "";
  67.            var errorMessagesContainer = form.querySelector(".error-messages");
  68.            var label;
  69.  
  70.            // Get the labels' values of their name attributes + the validation error
  71.            // message of the corresponding input field using the validationMessage
  72.            // property of input fields
  73.            // We build a list of <li>...</li> that we add to the error message container
  74.            for (var i = 0; i < invalidFields.length; i++) {
  75.                label = form.querySelector("label[for=" + invalidFields[ i ].id+ "]");
  76.                listHtml += "<li>" +
  77.                            label.innerHTML +
  78.                            " " +
  79.                            invalidFields[ i ].validationMessage +
  80.                            "</li>";
  81.            }
  82.  
  83.            // Update the list with the new error messages
  84.            errorMessagesContainer.innerHTML = listHtml;
  85.  
  86.            // If there are errors, give focus to the first invalid field and show
  87.            // the error messages container by setting its CSS property display=block
  88.            if (invalidFields.length > 0) {
  89.               invalidFields[ 0 ].focus();
  90.               errorMessagesContainer.style.display = "block";
  91.            }
  92.        });
  93.    }
  94.  
  95.    // Replace the validation UI for all forms
  96.    var forms = document.querySelectorAll("form");
  97.    for (var i = 0; i < forms.length; i++) {
  98.        replaceValidationUI(forms[ i ]);
  99.    }
  100. </script>
  101. </body>
  102. </html>
Explanations:
    • Line 32: we added an empty unnumbered list (<ul>..</ul>) to the form, with the CSS class="error-messages". We will use this class attribute for styling and hiding by default the error messages using CSS (see lines 12-20, line 13 hides the messages by default).
    • Lines 97-102 look at all forms in the document and call a function that will replace the default validation behavior for all of them: the replaceValidationUI(form) function.
    • This function first disables all default behavior (no more display of bubbles in case of form submission), this is done at lines 45-57.
    • Line 66: we add a click listener to the submit button of the current form.
    • Line 67 gets all invalid input fields for that form,
    • Lines 76-83For each invalid field, we get the value of the name attribute of the corresponding label, we also get the validation error message, and we build a list item(<li>...</li>).
    • Line 86: Then we add this list element (a formatted error message corresponding to an invalid input field) to the error message container.
    • Lines 90-93The focus is given to the first invalid field that shows the error messages.
  • In order to stop the webcam and make the hardware "unlock it", you need to call the stop() method of the video stream.
    Modified version of the previous example:
    1. <html>
    2. <head>
    3. <meta charset="utf-8">
    4. <title>JS Bin</title>
    5. <script>
    6.     navigator.getUserMedia = ( navigator.getUserMedia ||
    7.                                navigator.webkitGetUserMedia ||
    8.                                navigator.mozGetUserMedia ||
    9.                                navigator.msGetUserMedia);
    10. var webcamStream;
    11.  
    12.  function startWebCam() {
    13.     if (navigator.getUserMedia) {
    14.         navigator.getUserMedia (
    15.             // constraints
    16.             {
    17.                 video: true,
    18.                 audio: false
    19.             },
    20.  
    21.             // successCallback
    22.             function(localMediaStream) {
    23.                 var video = document.querySelector('video');
    24.                 video.src = window.URL.createObjectURL(localMediaStream);
    25.                 webcamStream = localmediaStream;
    26.             },
    27.  
    28.             // errorCallback
    29.             function(err) {
    30.                 console.log("The following error occurred: " + err);
    31.             }
    32.         );
    33.     } else {
    34.         console.log("getUserMedia not supported");
    35.     }
    36. }
    37. function stopWebcam() {
    38.     webcamStream.stop();
    39. }
    40. </script>
    41. </head>
    42. <body >
    43.      <video width=200 height=200 id="video" controls autoplay></video>
    44.      <button onclick="startWebcam();">Start Webcam</button>
    45.      <button onclick="stopWebcam();">Stop Webcam</button>
    46. </body>
    47. </html>

    Explanation:

    Lines 6-9: set the navigator.getUserMedia method with the name that works on the current browser. As some browsers only have experimental implementations, this method needs to be prefixed by -webkit or -moz etc. Like that, in the rest of the code, we can just use navigator.getUserMedia without worrying about prefixes. In case the browser does not support this API at all, it will receive null.
    Line 13: we test if navigator.getUserMedia is not null (aka supported by the current browser).
    Lines 14-32: we call navigator.getUserMedia. We defined here (line 21 and 28)  the callback functions directly between the parenthesis of the method call. This is possible with JavaScript, and might confuse beginners:
      • Lines 15-19 define the first parameter of the call: a JavaScript object that defines the "constraints", here we only want to capture the video. 
      • Lines 21-26 define the function called in case of success, instead of having just the function name here like in previous examples, we directly put the code of the function. Lines 23-25 are executed when the webcam stream could be opened. Line 25 we store the stream in a global variable so that we can use from another function (for stopping/starting the webcam...)
      • Lines 28-31 define a function called in case of error (the webcam cannot be accessed).
    Line 32: closing parenthesis of the navigator.getUserMedia(...) method call.
    Lines 37-39: a function for stopping the webcam. We use the global variable webcamStream here, that has been initialized when we started using the webcam, line 25.

    OTHER EXAMPLES THAT MIX IN WHAT WE'VE SEEN IN PREVIOUS CHAPTERS, BUT THIS TIME WITH A LIVE VIDEO STREAM

    Applying CSS effects on a video element with a live webcam

    Try this example that shows how to use the getUserMedia API - note the CSS effects (click on the video to cycle from one effect to another). Works in Chrome/Firefox/Opera: Online version at JS Bin

Taking a snapshot from the live webcam stream

The trick is to copy and paste the current image from the video stream into a <canvas> element:
Online version at JS Bin
We will get into more details about this example next week when we will look at the <canvas> element. For the time being, just play with the example.
take screenshot of live video stream

IMPRESSIVE DEMONSTRATIONS AVAILABLE ON THE WEB

Working with the microphone

Instead of using the getUserMedia API with: navigator.getUserMedia({video:true}, onSuccess, onError), it is also possible to use {audio:true} for the first parameter. In that case, only the microphone input will be captured. Notice that {video:true, audio:true} is also accepted, if you write a video conferencing system and need to capture both the audio and the video (this is often the case when writing WebRTC applications). The W3C WebRTC is another W3C specification, under development, for P2P audio/video/data Real Time Communication.
Apart from videoconferencing, microphone input will be used for music Web apps, from the WebAudio API. This API focuses on real time sound processing and music synthesis. This API will be covered in the advanced W3Cx HTML5 course.
Do try some nice WebRTC applications like Appear.in audio and video conferencing tool. Also check out theWebAudio demonstrations written by Chris Wilson, in particular the one called "Input effects".
Below is an example of real time audio processing of the microphone input using getUserMedia and WebAudio APIs
webaudio live processing
appear.in: a free WebRTC video conferencing tool. It uses the getUserMedia API for video and audio.

How to set the webcam resolution

Note that as of June 2015, this only works with Google Chrome, but should come soon
on the other browsers that support the getUserMedia API.
It is possible to set "hints" for the preferred resolution during video capture. This is done by using a "constraint" object that is passed as a parameter to the getUserMedia(...) method. It's just the same object we passed in the basic example: navigator.getUserMedia({video:true}, success, error) except that this time this object is a little more complex.
For more information, this article on HTML5rocks.com about the getUserMedia API gives extra examples on how to set the camera resolution.  Check also this good article that tested systematically a set of "preferred resolutions" and compared them to the actual resolutions returned by the browser. Remember that the requested resolution is a hint, and there is no real guarantee that your configuration will allow it.
Typical use:
  1. var constraint = {
  2.     video: {
  3.         mandatory: {
  4.             maxWidth: 320,
  5.             maxHeight: 200
  6.         }
  7.     }
  8. };
  9.  
  10. navigator.getUserMedia(constraint, success, error);
  11.  
  12. function sucess(stream) {
  13.     video.src = window.URL.createObjectURL(stream);
  14. }
  15.  
  16. function error(error) {
  17.     console.log('navigator.getUserMedia error: ', error);
  18. }

COMPLETE EXAMPLE: CHOOSE BETWEEN 3 DIFFERENT RESOLUTIONS



HTML code:
  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4.   <title>getUserMedia constraints for choosing resolution</title>
  5. </head>
  6. <body onload="init();">
  7. <h1>Set the camera resolution</h1>
  8.  Example adapted from:
  9.    <a href="http://www.simpl.info/getusermedia/constraints/">
  10.       http://www.simpl.info/getusermedia/constraints/
  11.    </a>
  12.    <br>
  13.    <p>Click a button to call <code>getUserMedia()</code> with appropriate
  14.       resolution.</p>
  15.  
  16.    <div id="buttons">
  17.        <button id="qvga">QVGA</button>
  18.        <button id="vga">VGA</button>
  19.        <button id="hd">HD</button>
  20.    </div>
  21.  
  22.    <p id="dimensions"></p>
  23.  
  24.    <video autoplay></video>
  25. </body>
  26. </html>
  27.  
JavaScript code:
  1. var vgaButton, qvgaButton, hdButton, dimensions, video, stream;
  2.  
  3. function init() {
  4.     vgaButton = document.querySelector('button#vga');
  5.     qvgaButton = document.querySelector('button#qvga');
  6.     hdButton = document.querySelector('button#hd');
  7.     dimensions = document.querySelector('p#dimensions');
  8.     video = document.querySelector('video');
  9.     navigator.getUserMedia = navigator.getUserMedia ||
  10.                       navigator.webkitGetUserMedia ||navigator.mozGetUserMedia;
  11.     // Defines event listeners for the buttons that set the resolution
  12.     qvgaButton.onclick = function() {
  13.         getMedia(qvgaConstraints);
  14.     };
  15.     vgaButton.onclick = function() {
  16.         getMedia(vgaConstraints);
  17.     };
  18.     hdButton.onclick = function() {
  19.         getMedia(hdConstraints);
  20.     };
  21.  
  22.     // Trick: check regularly the size of the video element and display it
  23.     // When getUserMedia is called the video element changes it sizes but for
  24.     // a while its size is zero pixels... o we check every half a second
  25.    video.addEventListener('play', function() {
  26.        setTimeout(function() {
  27.            displayVideoDimensions();
  28.        }, 500);
  29.     });
  30. }
  31.  
  32.  
  33. // The different values for the constraints on resolution
  34. var qvgaConstraints = {
  35.    video: {
  36.       mandatory: {
  37.          maxWidth: 320,
  38.          maxHeight: 180
  39.       }
  40.    }
  41. };
  42.  
  43. var vgaConstraints = {
  44.    video: {
  45.       mandatory: {
  46.          maxWidth: 640,
  47.          maxHeight: 360
  48.       }
  49.    }
  50. };
  51.  
  52. var hdConstraints = {
  53.    video: {
  54.       mandatory: {
  55.          minWidth: 1280,
  56.          minHeight: 720
  57.       }
  58.    }
  59. };
  60.  
  61. // The function that is called when a button has been clicked: starts the video
  62. // with the preferred resolution
  63. function getMedia(constraints) {
  64.    if (!!stream) {
  65.       video.src = null;
  66.       stream.stop();
  67.    }
  68.    navigator.getUserMedia(constraints, successCallback, errorCallback);
  69. }
  70.  
  71. // callback if the capture is a sucess or an error
  72. function successCallback(stream) {
  73.    window.stream = stream; // For resetting it later if we change the resolution
  74.    video.src = window.URL.createObjectURL(stream);
  75. }
  76.  
  77. function errorCallback(error) {
  78.    console.log('navigator.getUserMedia error: ', error);
  79. }
  80.  
  81. // util function that is called by the setInterval(...) every 0.5s, for
  82. // displaying the video dimensions
  83. function displayVideoDimensions() {
  84.    dimensions.innerHTML = 'Actual video dimensions: ' + video.videoWidth +
  85.                           'x' + video.videoHeight + 'px.';
  86. }

3 comments:

  1. Hi, Great.. Tutorial is just awesome..It is really helpful for a newbie like me.. I am a regular follower of your blog. Really very informative post you shared here. Kindly keep blogging. If anyone wants to become a Front end developer learn from Javascript Online Training from India . or learn thru ES6 Training in Chennai. Nowadays JavaScript has tons of job opportunities on various vertical industry. HTML5 CSS3 Javascript Online Training from India

    ReplyDelete
  2. Wonderful blog you have shared over here, i like the way you presented it. Thanks and keep it up.

    WebRTC application developer in India

    ReplyDelete
  3. I am happy to find this post Very useful for me, as it contains lot of information. I Always prefer to read The Quality and glad I found this thing in you post. Thanks uv04 form

    ReplyDelete