Wednesday, April 22, 2015

Using jQuery, Bootstrap and Form Validation in a UI development

jQuery:  "jQuery is a fast, small, and feature-rich JavaScript library. It makes things like HTML document traversal and manipulation, event handling, animation, and Ajax much simpler with an easy-to-use API that works across a multitude of browsers. With a combination of versatility and extensibility, jQuery has changed the way that millions of people write JavaScript. " (From https://jquery.com/)

Bootstrap: "Bootstrap is the most popular HTML, CSS, and JS framework for developing responsive, mobile first projects on the web. Bootstrap makes front-end web development faster and easier. It's made for folks of all skill levels, devices of all shapes, and projects of all sizes. Millions of amazing sites across the web are being built with Bootstrap." (From http://getbootstrap.com/)

FormValidation : "Best jQuery plugin to validate form fields."  (From http://formvalidation.io/ )

In one of my project, I am using jQuery, Bootstrap and a jQuery plug in to do the form validation.
For the detail of each one above, you can just click the links for detail.

I've used jQuery before, this is my first time to use Bootstrap and Form Validation. But this is a very good experience, it makes the UI development job easier and fun.

Below is the form I am building. In the previous blog, I introduced how to use jQuery to build drag and drop sorting table, and how to add a new row or remove a row.

Here, I use Bootstrap form validation to validate the form.


Features for this form:
1, Each row can be drag and drop to the any place. 
2. User can add a new row and remove an existing row.
3. Internal name, display name, Search weight columns are required for each row.
4. If there is a current image, the new image is not required.
5. If there is not a current image, the new image is required.

HTML code as below:

     <form id="classconfigform">  
     <button type="button" class="btn btn-default btn-xs" id="addbutton">Add New Class</button>  
         <table id="classtable" class="table table-striped">  
             <thead>  
                 <tr>  
                     <th class="hidden-xs hidden-sm hidden-md hidden-lg">Order</th>  
                     <th>Class Internal Name</th>  
                     <th>Display Name</th>  
                     <th>Enabled</th>  
                     <th>Search Weight</th>  
                     <th>Current Image</th>  
                     <th>New Image</th>  
                     <th>Delete class</th>  
                 </tr>  
             </thead>  
             <tbody id="sortable">  
                 <c:forEach items="${classes}" var="class">  
                     <tr>  
                         <td class="hidden-xs hidden-sm hidden-md hidden-lg ui-state-default">  
                             <input type="hidden" id="displayOrder" name="displayOrder[]" class="form-control" value='<c:out value="${class.displayOrder}"/>'></input>  
                         </td>  
                         <td><input type="text" id="internalClassName" name="internalClassName[]" class="form-control" placeholder="Class Internal Name" value='<c:out value="${class.internalClassName}"/>'></td>  
                         <td><input type="text" id="displayName" name="displayName[]" class="form-control" placeholder="Display Name" value='<c:out value="${class.displayName}"/>'></td>  
                         <td><input type="checkbox" name="enabled[]" class="form-control" checked ></td>  
                         <td><input type="text" id="searchWeight[]" name="searchWeight[]" class="form-control" value='<c:out value="${class.searchWeight}"/>'></td>  
                         <td><input type="hidden" id="fileid"     name="fileid[]" class="form-control" value='<c:out value="${class.fileStore.fileId}"/>'><img src="${pageContext.request.contextPath}/download/${class.fileStore.fileId}"></td>  
                         <td><input type="file" id="uploadimage"     name="uploadimage[]" class="form-control" value=''>  
                         </td>  
                         <td><button type="button" class="removebutton" title="Remove this class"><span class="glyphicon glyphicon-remove "></span></button> </td>  
                     </tr>  
                 </c:forEach>  
                  <!-- The new row template containing -->  
                 <tr class="hide" id="newClassRowTemplate">  
                         <td class="hidden-xs hidden-sm hidden-md hidden-lg ui-state-default">  
                             <input type="hidden" id="displayOrder" name="displayOrder[]" class="form-control" value='<c:out value="${class.displayOrder}"/>'>  
                         </td>  
                         <td><input type="text" id="internalClassName" name="internalClassName[]" class="form-control" placeholder="Class Internal Name"></td>  
                         <td><input type="text" id="displayName" name="displayName[]" class="form-control" placeholder="Display Name"></td>  
                         <td><input type="checkbox" name="enabled[]" class="form-control" checked ></td>  
                         <td><input type="text" id="searchWeight[]" name="searchWeight[]" class="form-control"></td>  
                         <td><input type="hidden" id="fileid"     name="fileid[]" class="form-control"/> </td>  
                         <td><input type="file" id="uploadimage"     name="uploadimage[]" class="form-control" value=''>  
                      </td>  
                         <td><button type="button" class="removebutton" title="Remove this class"><span class="glyphicon glyphicon-remove "></span></button> </td>  
                 </tr>   
             </tbody>  
         </table>  
     <button type="submit" id="saveClasses" class="btn btn-primary">Save Classes</button>  
     </form>  




Javascript code as below:

 <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->  
 <script src="${pageContext.request.contextPath}/resources/scripts/jquery.js"     type="text/javascript"></script>  
 <script src="${pageContext.request.contextPath}/resources/scripts/jquery-ui.min.js" type="text/javascript"></script>  
 <script    src="${pageContext.request.contextPath}/resources/scripts/bootstrap-3.3.2-dist/js/bootstrap.min.js"    type="text/javascript"></script>  
 <!-- FormValidation CSS file -->  
 <link rel="stylesheet" href="${pageContext.request.contextPath}/resources/scripts/formvalidation-0.6.2/dist/css/formValidation.min.css">  
 <!-- FormValidation plugin and the class supports validating Bootstrap form -->  
 <script src="${pageContext.request.contextPath}/resources/scripts/formvalidation-0.6.2/dist/js/formValidation.min.js"></script>  
 <script src="${pageContext.request.contextPath}/resources/scripts/formvalidation-0.6.2/dist/js/framework/bootstrap.min.js"></script>  
 <script>  
 $(document).ready(function() {  
         $("#sortable").sortable();  
          $('#classconfigform').formValidation({  
             framework: 'bootstrap',  
             err: {  
               container: 'tooltip'  
             },  
             row: {  
               selector: 'td'  
             },  
             icon: {  
               valid: 'glyphicon glyphicon-ok',  
               invalid: 'glyphicon glyphicon-remove',  
               validating: 'glyphicon glyphicon-refresh'  
             },  
             fields: {  
               'internalClassName[]': {  
                 validators: {  
                   notEmpty: {  
                     message: 'Internal class name is required'  
                   }  
                 }  
               },  
               'displayName[]': {  
                 validators: {  
                   notEmpty: {  
                     message: 'Display name is required'  
                   }  
                 }  
               },  
               'searchWeight[]': {  
                 validators: {  
                   notEmpty: {  
                     message: 'Search Weight is required'  
                   }  
                 }  
               },  
         'uploadimage[]': {  
               validators: {  
                                    callback: {  
                     message: 'An image is required',  
                     callback: function (value, validator, $field) {  
                     var $fileid='';  
                     var $classimg =  $field.closest('tr');  
                     $fileid = $classimg.find('#fileid').val();                                          
                     if (($fileid==null||$fileid=='')&&(value==null ||value==''))  
                            return false;  
                     else  
                             return true;      
                     }  
                     }//end of callback                                 
                                }  
                             }               
             }  
           })//end of validation  
 .on("click","#addbutton",function (){  
             var $template = $('#newClassRowTemplate'),  
                         $clone = $template  
                                      .clone()  
                                      .removeClass('hide')  
                                      .removeAttr('id')  
                                      .insertBefore($template)  
                                      .find('input')  
                                      .each(function(){  
                                      $('#classconfigform').formValidation('addField',$(this) );  
                                          });                         
         })  
 .on("click",".removebutton",function () {  
            if (confirm("Do you want to delete the class?")){  
                         var $removeclass =  $(this).closest('tr');  
         $removeclass.remove()  
                .find('input')  
                .each(function(){  
                     $('#classconfigform').formValidation('removeField',$(this) );                                                                       
                    });                  
            }  
     });  
 });  
 </script>  

Challenges in this development:
1. After add/remove  a new row, how to add/remove it from the validation?
There is a simple example (adding dynamic field) from form validation web site. But the form above is more complicated because this one has multiple rows, and each row has multiple fields.

2. Validation on the images.
 (If there is a current image, the new image is not required.
 If there is not a current image, the new image is required.)
This validation need to use a callback function.
The callback function examples can be found here. Since the form above has more rows, so it makes this callback more challenge.

5 comments: