AngularJS: show progress of a form by counting the number of disabled fields

Let say we have a survey with many questions. Some questions are enabled and some disabled at first and this keeps changing based on what is selected or entered. At any given time we want to know the count of all fields in a form, how many of them are disabled and how many still required. The number of all and required fields is easy as AngularJS keeps an array of all form elements. Disabled elements are something else.

Counting disabled fields can be a challenge if many radio buttons are used, as every radio button is counted as one element.

Lets take this code for example:

 <!doctype html>
 <html>
    <head>    
        <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.8.1/jquery.min.js"></script>
    </head>
    <body>
    
    <form>
     <input type="radio" name="typeComputer2" disabled> 1<br>
     <input type="radio" name="typeComputer2" disabled> 2<br>
     <input type="radio" name="typeComputer2" disabled> 3<br>
    </form>           
    <div id="res"></div>

    <script>
      var bla = $(':disabled').length;
      $('#res').text("Number of disabled elements: " + bla);
    </script>
    </body>
 </html>

Will produce this:

The number of disabled elements is 3. But in fact this is the same group of radio buttons and could be counted as one element (only one radio button can be selected at once).

Lets look at the solution using AngularJS.

We have this HTML document with 3 form elements.

  • First one is 3 radio buttons that are disabled and not required until the checkbox is selected
  • Second is a required radio button
  • Third is a required input field
<html ng-app>
  <head>
     <script src="http://code.angularjs.org/1.1.0/angular.min.js"></script>
     <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.8.1/jquery.min.js"></script>
     <script src="controller.js"></script>
  </head>
  <body>           
    <div ng-controller="ctrlRead">

      <form name="survey">
        <input type="radio" name="n1" value="1" ng-model="s.el1" 
                        ng-required="s.el2" ng-disabled="!s.el2"> 1<br>
        <input type="radio" name="n1" value="2" ng-model="s.el1" 
                        ng-required="s.el2" ng-disabled="!s.el2"> 2<br>
        <input type="radio" name="n1" value="3" ng-model="s.el1" 
                        ng-required="s.el2" ng-disabled="!s.el2"> 3<br><br>

        <input type="checkbox" name="n2" ng-model="s.el2" ng-required="true"> a<br><br>

        <input type="text" name="n3" ng-model="s.el3" ng-required="true">
      </form>           

      <div id="res"> 
        Disabled: {{eDisabled}} <br>
        All: {{eAll}}  <br>
        Required: {{eRequired}} <br>
        Done in %: {{eDonePercent}} <br>
        Valid: {{survey.$valid}}
      </div>

    </div>
  </body>
</html>

Now we need to write our controller.js

function ctrlRead($scope, $timeout) {

  //a variable to store our survey data
  var s = {}; 
  $scope.s = s;

  //watch for the sur variable to change
  $scope.$watch("s", function () {
     //timeout to fire events 100ms later 
     //for DOM to register the changes
     $timeout($scope.countPercentageDone, 100);
  }, true);

  $scope.countPercentageDone = function() {      
    var requiredTrue=0;
    var requiredFalse=0;
    var disabledElementNames = new Array();    
 
    if ($scope.survey) { 
      //this traverses the form elements stored in form name
      $.each($scope.survey, function(index, value) {
        //survey holds other elements ... 
        //only form elements have $error object
        if ($scope.survey[index].$error) { 
          if ($scope.survey[index].$error.required == true) {
            requiredTrue++;
          } else {
            requiredFalse++;
            //check if the form element is disabled
            if ($("[name="+$scope.survey[index].$name+"]:disabled")) {
              //if it is and it is not just element filled in 
              //element also become disabled once filled in!!
              if ($("[name="+$scope.survey[index].$name+"]:disabled").length != 0) { 
                disabledElementNames.push($scope.survey[index].$name);
              }
            }
          }
        }
      });
    }

    $scope.eDisabled = disabledElementNames.length;
    $scope.eAll = requiredTrue+requiredFalse;
    $scope.eRequired = requiredTrue;
    $scope.eDonePercent = 100-((requiredTrue*100)/(requiredTrue+requiredFalse-disabledElementNames.length));
  }

};

To summarise the code:

  • We traverse all form elements in the $scope.survey array.
  • Because this array contains other elements as well as form elements, we have to look only at those that have an $error object defined.
  • In all elements that are not required we look for those that have lenght > 0.
    We do this because elements that we fill in also become disabled by AngularJS.

This example is also available in jsfiddle.

For the progress bar we could use bootstrap:

<div class="progress">      <div class="bar" style="width: {{eDonePercent}}%"></div> </div>

or any of the solutions I wrote about here to support older browsers.

A big thanks to Tine for all the suggestions and help.

Trackbacks

Trackback specific URI for this entry

This link is not meant to be clicked. It contains the trackback URI for this entry. You can use this URI to send ping- & trackbacks from your own blog to this entry. To copy the link, right click and select "Copy Shortcut" in Internet Explorer or "Copy Link Location" in Mozilla.

No Trackbacks

Comments

Display comments as Linear | Threaded

No comments

Add Comment

Enclosing asterisks marks text as bold (*word*), underscore are made via _word_.
E-Mail addresses will not be displayed and will only be used for E-Mail notifications.

To prevent automated Bots from commentspamming, please enter the string you see in the image below in the appropriate input box. Your comment will only be submitted if the strings match. Please ensure that your browser supports and accepts cookies, or your comment cannot be verified correctly.
CAPTCHA

Can you please write (or copy/paste) this text in the field below: i h a t e s p a m