← Back to team overview

dhis2-devs team mailing list archive

[Branch ~dhis2-devs-core/dhis2/trunk] Rev 18151: minor refactoring to adjust conventional naming; custom registration form - WIP

 

------------------------------------------------------------
revno: 18151
committer: Abyot Asalefew Gizaw <abyota@xxxxxxxxx>
branch nick: dhis2
timestamp: Mon 2015-02-02 14:42:37 +0100
message:
  minor refactoring to adjust conventional naming; custom registration form - WIP
removed:
  dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/angular/plugins/dhis2/
  dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/angular/plugins/dhis2/controllers.js
  dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/angular/plugins/dhis2/directives.js
  dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/angular/plugins/dhis2/filters.js
  dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/angular/plugins/dhis2/services.js
added:
  dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/components/registration/custom-form.html
  dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/components/registration/default-form.html
  dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/scripts/d2Directives.js
  dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/scripts/d2Services.js
  dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/dhis2/dhis2.angular.controllers.js
  dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/dhis2/dhis2.angular.directives.js
  dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/dhis2/dhis2.angular.filters.js
  dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/dhis2/dhis2.angular.services.js
modified:
  dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-cache-cleaner/index.html
  dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-event-capture/event-capture.appcache
  dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-event-capture/index.html
  dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-event-capture/scripts/controllers.js
  dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/components/profile/profile-controller.js
  dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/components/registration/registration-controller.js
  dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/components/registration/registration.html
  dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/index.html
  dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/scripts/services.js
  dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/tracker-capture.appcache
  dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/css/green/green.css
  dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/css/india/india.css
  dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/css/light_blue/light_blue.css
  dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/css/myanmar/myanmar.css
  dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/css/vietnam/vietnam.css


--
lp:dhis2
https://code.launchpad.net/~dhis2-devs-core/dhis2/trunk

Your team DHIS 2 developers is subscribed to branch lp:dhis2.
To unsubscribe from this branch go to https://code.launchpad.net/~dhis2-devs-core/dhis2/trunk/+edit-subscription
=== modified file 'dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-cache-cleaner/index.html'
--- dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-cache-cleaner/index.html	2015-01-28 19:03:29 +0000
+++ dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-cache-cleaner/index.html	2015-02-02 13:42:37 +0000
@@ -49,8 +49,8 @@
 
         <script type="text/javascript" src="../dhis-web-commons/javascripts/angular/plugins/angularLocalStorage.js"></script>
         <script type="text/javascript" src="../dhis-web-commons/javascripts/angular/plugins/angular-translate.min.js"></script>
-        <script type="text/javascript" src="../dhis-web-commons/javascripts/angular/plugins/dhis2/services.js"></script>
-        <script type="text/javascript" src="../dhis-web-commons/javascripts/angular/plugins/dhis2/controllers.js"></script>
+        <script type="text/javascript" src="../dhis-web-commons/javascripts/dhis2/dhis2.angular.services.js"></script>
+        <script type="text/javascript" src="../dhis-web-commons/javascripts/dhis2/dhis2.angular.controllers.js"></script>
 
         <script type="text/javascript" src="scripts/app.js"></script>        
         <script type="text/javascript" src="scripts/services.js"></script>

=== modified file 'dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-event-capture/event-capture.appcache'
--- dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-event-capture/event-capture.appcache	2015-01-23 14:07:46 +0000
+++ dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-event-capture/event-capture.appcache	2015-02-02 13:42:37 +0000
@@ -56,16 +56,14 @@
 ../dhis-web-commons/javascripts/angular/angular-animate.js        
 ../dhis-web-commons/javascripts/angular/ui-bootstrap-tpls-0.10.0-draggable-modal.js
 
-
-#scripts/angular-translate.min.js
 ../dhis-web-commons/javascripts/angular/plugins/angularLocalStorage.js
 ../dhis-web-commons/javascripts/angular/plugins/angular-translate.min.js
 ../dhis-web-commons/javascripts/angular/plugins/angular-translate-loader-static-files.min.js
 ../dhis-web-commons/javascripts/angular/plugins/angular-translate-loader-url.min.js
-../dhis-web-commons/javascripts/angular/plugins/dhis2/directives.js
-../dhis-web-commons/javascripts/angular/plugins/dhis2/filters.js
-../dhis-web-commons/javascripts/angular/plugins/dhis2/services.js
-../dhis-web-commons/javascripts/angular/plugins/dhis2/controllers.js
+../dhis-web-commons/javascripts/dhis2/angular.directives.js
+../dhis-web-commons/javascripts/dhis2/angular.filters.js
+../dhis-web-commons/javascripts/dhis2/angular.services.js
+../dhis-web-commons/javascripts/dhis2/angular.controllers.js
 
 ../dhis-web-commons/javascripts/moment/moment-with-langs.min.js       
 

=== modified file 'dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-event-capture/index.html'
--- dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-event-capture/index.html	2015-01-29 17:13:36 +0000
+++ dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-event-capture/index.html	2015-02-02 13:42:37 +0000
@@ -66,10 +66,10 @@
         
         <script type="text/javascript" src="../dhis-web-commons/javascripts/angular/plugins/angularLocalStorage.js"></script>
         <script type="text/javascript" src="../dhis-web-commons/javascripts/angular/plugins/angular-translate.min.js"></script>
-        <script type="text/javascript" src="../dhis-web-commons/javascripts/angular/plugins/dhis2/directives.js"></script>
-        <script type="text/javascript" src="../dhis-web-commons/javascripts/angular/plugins/dhis2/filters.js"></script>
-        <script type="text/javascript" src="../dhis-web-commons/javascripts/angular/plugins/dhis2/services.js"></script>
-        <script type="text/javascript" src="../dhis-web-commons/javascripts/angular/plugins/dhis2/controllers.js"></script>
+        <script type="text/javascript" src="../dhis-web-commons/javascripts/dhis2/dhis2.angular.directives.js"></script>
+        <script type="text/javascript" src="../dhis-web-commons/javascripts/dhis2/dhis2.angular.filters.js"></script>
+        <script type="text/javascript" src="../dhis-web-commons/javascripts/dhis2/dhis2.angular.services.js"></script>
+        <script type="text/javascript" src="../dhis-web-commons/javascripts/dhis2/dhis2.angular.controllers.js"></script>
         
         <script type="text/javascript" src="scripts/app.js"></script>        
         <script type="text/javascript" src="scripts/services.js"></script>

=== modified file 'dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-event-capture/scripts/controllers.js'
--- dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-event-capture/scripts/controllers.js	2015-01-29 17:13:36 +0000
+++ dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-event-capture/scripts/controllers.js	2015-02-02 13:42:37 +0000
@@ -196,11 +196,7 @@
                 ErrorMessageService.setErrorMessages(errorMessages);
 
                 ProgramValidationService.getByProgram($scope.selectedProgram.id).then(function(pvs){
-
                     $scope.programValidations = pvs;
-
-                    console.log('the validations:  ', $scope.programValidations);
-
                     $scope.loadEvents();
                 });
             });

=== modified file 'dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/components/profile/profile-controller.js'
--- dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/components/profile/profile-controller.js	2015-01-14 11:21:15 +0000
+++ dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/components/profile/profile-controller.js	2015-02-02 13:42:37 +0000
@@ -2,7 +2,8 @@
         function($rootScope,
                 $scope,     
                 CurrentSelection,
-                DateUtils,
+                CustomFormService,
+                TEFormService,
                 TEIService,
                 DialogService,
                 AttributesFactory) {
@@ -31,6 +32,24 @@
         //if no program, display attributesInNoProgram
         TEIService.processAttributes($scope.selectedTei, $scope.selectedProgram, $scope.selectedEnrollment).then(function(tei){
             $scope.selectedTei = tei;
+            if($scope.selectedProgram && $scope.selectedProgram.id){            
+                AttributesFactory.getByProgram($scope.selectedProgram).then(function(atts){
+                    $scope.attributesById = [];
+                    angular.forEach(atts, function(att){
+                        $scope.attributesById[att.id] = att;
+                    });
+
+                    $scope.selectedProgram.hasCustomForm = false;               
+                    TEFormService.getByProgram($scope.selectedProgram, atts).then(function(teForm){                    
+                        if(angular.isObject(teForm)){                        
+                            $scope.selectedProgram.hasCustomForm = true;
+                            $scope.selectedProgram.displayCustomForm = $scope.selectedProgram.hasCustomForm ? true:false;
+                            $scope.trackedEntityForm = teForm;
+                            $scope.customForm = CustomFormService.getForTrackedEntity($scope.trackedEntityForm, 'PROFILE');
+                        }                    
+                    });  
+                });                
+            }
         });
     });
     

=== added file 'dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/components/registration/custom-form.html'
=== added file 'dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/components/registration/default-form.html'
--- dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/components/registration/default-form.html	1970-01-01 00:00:00 +0000
+++ dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/components/registration/default-form.html	2015-02-02 13:42:37 +0000
@@ -0,0 +1,138 @@
+<div ng-if='!selectedProgram'>
+    <h3>{{'category'| translate}}</h3>
+    <table class="table-borderless table-striped">
+        <tr>
+            <td>
+                {{'entity_type'| translate}}
+            </td>
+            <td>
+                <select class="form-control" ng-model="trackedEntities.selected" ng-options="trackedEntity.name for trackedEntity in trackedEntities.available | orderBy: 'name'">                                
+                </select>                            
+            </td>
+        </tr>                   
+    </table>
+    <hr>
+</div>
+<h3>{{'profile'| translate}}</h3>
+<table class="table-borderless table-striped">
+    <tr ng-repeat="attribute in attributes">
+        <td>
+            {{attribute.name}}
+        </td>
+        <td>
+            <ng-form name="innerForm">     
+                <div ng-switch="attribute.valueType">
+                    <div ng-switch-when="date">
+                        <input type="text" placeholder="{{dhis2CalendarFormat.keyDateFormat}}" name="foo" class="form-control" d2-date ng-model="attribute.value" ng-required="attribute.mandatory" />
+                        <span ng-show="outerForm.submitted && innerForm.foo.$invalid" class="error">{{'required'| translate}}</span>
+                    </div>
+                    <div ng-switch-when="trueOnly">
+                        <input type="checkbox" name="foo" class="form-control" ng-model="attribute.value" ng-required="attribute.mandatory" />
+                        <span ng-show="outerForm.submitted && innerForm.foo.$invalid" class="error">{{'required'| translate}}</span>
+                    </div>
+                    <div ng-switch-when="bool">
+                        <select name="foo" ng-model="attribute.value" class="form-control" ng-required="attribute.mandatory">
+                            <option value="">{{'please_select'| translate}}</option>                        
+                            <option value="false">{{'no'| translate}}</option>
+                            <option value="true">{{'yes'| translate}}</option>
+                        </select>
+                        <span ng-show="outerForm.submitted && innerForm.foo.$invalid" class="error">{{'required'| translate}}</span>
+                    </div>                            
+                    <div ng-switch-when="optionSet">
+                        <div ng-if="!selectedProgram">
+                            <input type="text"
+                                   name="foo"
+                                   class="form-control"
+                                   ng-model="attribute.value"                                                 
+                                   typeahead="option.name as option.name for option in optionSets[attribute.optionSet.id].options | filter:$viewValue | limitTo:20" 
+                                   typeahead-open-on-focus
+                                   ng-required="attribute.mandatory"/>
+                        </div>
+                        <div ng-if="selectedProgram">
+                            <div ng-if="!selectedProgram.dataEntryMethod || optionSets[attribute.optionSet.id].options.length >= 7">
+                                <input type="text" 
+                                       name="foo"
+                                       class="form-control"
+                                       ng-model="attribute.value"                                                 
+                                       typeahead="option.name as option.name for option in optionSets[attribute.optionSet.id].options | filter:$viewValue | limitTo:20" 
+                                       typeahead-open-on-focus
+                                       typeahead-editable=false
+                                       ng-required="attribute.mandatory"/>
+                            </div>
+                            <div ng-if="selectedProgram.dataEntryMethod && optionSets.optionSet[attribute.optionSet.id].options.length < 7">
+                                <label>                                        
+                                    <input type="radio"
+                                           name={{attribute.id}}
+                                           ng-required="attribute.mandatory"
+                                           ng-model="attribute.value"
+                                           value=""> {{'no_value'| translate}}<br>                                       
+                                </label><br>
+                                <span ng-repeat="option in optionSets[attribute.optionSet.id].options">
+                                    <label>
+                                        <input type="radio"
+                                               name={{attribute.id}}
+                                               ng-required="attribute.mandatory"
+                                               ng-model="attribute.value" 
+                                               value={{option.name}}> {{option.name}}
+                                    </label><br>
+                                </span>                                            
+                            </div>
+                        </div>                                
+                        <span ng-show="outerForm.submitted && innerForm.foo.$invalid" class="error">{{'required'| translate}}</span>
+                    </div>
+                    <div ng-switch-when="number">
+                        <input type="number" name="foo" class="form-control" ng-model="attribute.value" ng-required="attribute.mandatory"/>
+                        <span ng-show="outerForm.submitted && innerForm.foo.$invalid" class="error">{{'required'| translate}}</span>
+                    </div>
+                    <div ng-switch-when="email">
+                        <input type="email" name="foo" class="form-control" ng-model="attribute.value" ng-required="attribute.mandatory"/>
+                        <span ng-show="outerForm.submitted && innerForm.foo.$invalid" class="error">{{'required'| translate}}</span>
+                    </div>
+                    <div ng-switch-default>
+                        <input type="text" name="foo" class="form-control" ng-model="attribute.value" ng-required="attribute.mandatory"/>
+                        <span ng-show="outerForm.submitted && innerForm.foo.$invalid" class="error">{{'required'| translate}}</span>
+                    </div>
+                </div>
+            </ng-form>
+        </td>
+    </tr>                        
+</table>
+
+<div ng-if='selectedProgram'>
+    <hr>
+    <h3>{{'enrollment'| translate}}</h3>
+    <table class="dhis2-list-table-striped dhis2-table-hover">
+        <tr>
+            <td>
+                {{selectedProgram.dateOfEnrollmentDescription}}
+            </td>
+            <td>
+                <input type="text" 
+                       placeholder="{{dhis2CalendarFormat.keyDateFormat}}" 
+                       name="dateOfEnrollment" 
+                       class="form-control" 
+                       d2-date 
+                       ng-model="enrollment.dateOfEnrollment" 
+                       max-date="selectedProgram.selectEnrollmentDatesInFuture ? '' : 0"
+                       min-date=""
+                       ng-required="true"/>
+                <span ng-show="outerForm.submitted && outerForm.dateOfEnrollment.$invalid" class="error">{{'required'| translate}}</span>
+            </td>
+        </tr>     
+        <tr ng-if="selectedProgram.displayIncidentDate">
+            <td>
+                {{selectedProgram.dateOfIncidentDescription}}
+            </td>
+            <td>
+                <input type="text" 
+                       placeholder="{{dhis2CalendarFormat.keyDateFormat}}"
+                       name="dateOfIncident"
+                       class="form-control" 
+                       d2-date 
+                       max-date="selectedProgram.selectIncidentDatesInFuture ? '' : 0"
+                       min-date=""
+                       ng-model="enrollment.dateOfIncident"/>
+            </td>
+        </tr>
+    </table>
+</div>
\ No newline at end of file

=== modified file 'dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/components/registration/registration-controller.js'
--- dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/components/registration/registration-controller.js	2015-01-05 15:31:59 +0000
+++ dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/components/registration/registration-controller.js	2015-02-02 13:42:37 +0000
@@ -7,6 +7,8 @@
                 DHIS2EventFactory,
                 TEService,
                 TEIService,
+                TEFormService,
+                CustomFormService,
                 EnrollmentService,
                 DialogService,
                 CurrentSelection,
@@ -16,7 +18,8 @@
                 storage) {
     
     $scope.today = DateUtils.getToday();
-    
+    $scope.trackedEntityForm = null;
+    $scope.customForm = null;
     $scope.optionSets = CurrentSelection.getOptionSets();
             
     if(!$scope.optionSets){
@@ -45,19 +48,30 @@
     
     //watch for selection of program
     $scope.$watch('selectedProgram', function() {        
+        $scope.trackedEntityForm = null;
+        $scope.customForm = null;
         $scope.getAttributes();
     });    
         
     $scope.getAttributes = function(){
-
-        if($scope.selectedProgram){
+        if($scope.selectedProgram && $scope.selectedProgram.id){            
             AttributesFactory.getByProgram($scope.selectedProgram).then(function(atts){
                 $scope.attributes = atts;
                 $scope.attributesById = [];
                 angular.forEach(atts, function(att){
                     $scope.attributesById[att.id] = att;
                 });
-            });           
+
+                $scope.selectedProgram.hasCustomForm = false;               
+                TEFormService.getByProgram($scope.selectedProgram, $scope.attributes).then(function(teForm){                    
+                    if(angular.isObject(teForm)){                        
+                        $scope.selectedProgram.hasCustomForm = true;
+                        $scope.selectedProgram.displayCustomForm = $scope.selectedProgram.hasCustomForm ? true:false;                        
+                        $scope.trackedEntityForm = teForm;                      
+                        $scope.customForm = CustomFormService.getForTrackedEntity($scope.trackedEntityForm, 'ENROLLMENT');
+                    }                    
+                });  
+            });                
         }
         else{            
             AttributesFactory.getWithoutProgram().then(function(atts){
@@ -70,8 +84,7 @@
         }
     };
     
-    $scope.registerEntity = function(destination){
-        
+    $scope.registerEntity = function(destination){        
         //check for form validity
         $scope.outerForm.submitted = true;        
         if( $scope.outerForm.$invalid ){

=== modified file 'dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/components/registration/registration.html'
--- dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/components/registration/registration.html	2015-01-05 15:31:59 +0000
+++ dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/components/registration/registration.html	2015-02-02 13:42:37 +0000
@@ -1,147 +1,50 @@
 <div class="bordered-div col-md-12" ng-controller="RegistrationController">
-    <form name="outerForm" novalidate>
-
-        <div ng-if='!selectedProgram'>
-            <h3>{{'category' | translate}}</h3>
-             <table class="table-borderless table-striped">
-                <tr>
-                    <td>
-                        {{'entity_type' | translate}}
-                    </td>
-                    <td>
-                        <select class="form-control" ng-model="trackedEntities.selected" ng-options="trackedEntity.name for trackedEntity in trackedEntities.available | orderBy: 'name'">                                
-                        </select>                            
-                    </td>
-                </tr>                   
-            </table>
-            <hr>
-        </div>
-
-        <h3>{{'profile' | translate}}</h3>
-        <table class="table-borderless table-striped">
-            <tr ng-repeat="attribute in attributes">
-                <td>
-                    {{attribute.name}}
-                </td>
-                <td>
-                    <ng-form name="innerForm">     
-                        <div ng-switch="attribute.valueType">
-                            <div ng-switch-when="date">
-                                <input type="text" placeholder="{{dhis2CalendarFormat.keyDateFormat}}" name="foo" class="form-control" d2-date ng-model="attribute.value" ng-required="attribute.mandatory" />
-                                <span ng-show="outerForm.submitted && innerForm.foo.$invalid" class="error">{{'required'| translate}}</span>
-                            </div>
-                            <div ng-switch-when="trueOnly">
-                                <input type="checkbox" name="foo" class="form-control" ng-model="attribute.value" ng-required="attribute.mandatory" />
-                                <span ng-show="outerForm.submitted && innerForm.foo.$invalid" class="error">{{'required'| translate}}</span>
-                            </div>
-                            <div ng-switch-when="bool">
-                                <select name="foo" ng-model="attribute.value" class="form-control" ng-required="attribute.mandatory">
-                                    <option value="">{{'please_select'| translate}}</option>                        
-                                    <option value="false">{{'no'| translate}}</option>
-                                    <option value="true">{{'yes'| translate}}</option>
-                                </select>
-                                <span ng-show="outerForm.submitted && innerForm.foo.$invalid" class="error">{{'required'| translate}}</span>
-                            </div>                            
-                            <div ng-switch-when="optionSet">
-                                <div ng-if="!selectedProgram">
-                                    <input type="text"
-                                       name="foo"
-                                       class="form-control"
-                                       ng-model="attribute.value"                                                 
-                                       typeahead="option.name as option.name for option in optionSets[attribute.optionSet.id].options | filter:$viewValue | limitTo:20" 
-                                       typeahead-open-on-focus
-                                       ng-required="attribute.mandatory"/>
-                                </div>
-                                <div ng-if="selectedProgram">
-                                    <div ng-if="!selectedProgram.dataEntryMethod || optionSets[attribute.optionSet.id].options.length >= 7">
-                                        <input type="text" 
-                                           name="foo"
-                                           class="form-control"
-                                           ng-model="attribute.value"                                                 
-                                           typeahead="option.name as option.name for option in optionSets[attribute.optionSet.id].options | filter:$viewValue | limitTo:20" 
-                                           typeahead-open-on-focus
-                                           typeahead-editable=false
-                                           ng-required="attribute.mandatory"/>
-                                    </div>
-                                    <div ng-if="selectedProgram.dataEntryMethod && optionSets.optionSet[attribute.optionSet.id].options.length < 7">
-                                        <label>                                        
-                                            <input type="radio"
-                                                name={{attribute.id}}
-                                                ng-required="attribute.mandatory"
-                                                ng-model="attribute.value"
-                                                value=""> {{'no_value' | translate}}<br>                                       
-                                        </label><br>
-                                        <span ng-repeat="option in optionSets[attribute.optionSet.id].options">
-                                            <label>
-                                                <input type="radio"
-                                                    name={{attribute.id}}
-                                                    ng-required="attribute.mandatory"
-                                                    ng-model="attribute.value" 
-                                                    value={{option.name}}> {{option.name}}
-                                            </label><br>
-                                        </span>                                            
-                                    </div>
-                                </div>                                
-                                <span ng-show="outerForm.submitted && innerForm.foo.$invalid" class="error">{{'required'| translate}}</span>
-                            </div>
-                            <div ng-switch-when="number">
-                                <input type="number" name="foo" class="form-control" ng-model="attribute.value" ng-required="attribute.mandatory"/>
-                                <span ng-show="outerForm.submitted && innerForm.foo.$invalid" class="error">{{'required'| translate}}</span>
-                            </div>
-                            <div ng-switch-when="email">
-                                <input type="email" name="foo" class="form-control" ng-model="attribute.value" ng-required="attribute.mandatory"/>
-                                <span ng-show="outerForm.submitted && innerForm.foo.$invalid" class="error">{{'required'| translate}}</span>
-                            </div>
-                            <div ng-switch-default>
-                                <input type="text" name="foo" class="form-control" ng-model="attribute.value" ng-required="attribute.mandatory"/>
-                                <span ng-show="outerForm.submitted && innerForm.foo.$invalid" class="error">{{'required'| translate}}</span>
-                            </div>
-                        </div>
-                    </ng-form>
-                </td>
-            </tr>                        
-        </table>
-
-        <div ng-if='selectedProgram'>
-            <hr>
-            <h3>{{'enrollment' | translate}}</h3>
-            <table class="dhis2-list-table-striped dhis2-table-hover">
-                <tr>
-                    <td>
-                        {{selectedProgram.dateOfEnrollmentDescription}}
-                    </td>
-                    <td>
-                        <input type="text" 
-                               placeholder="{{dhis2CalendarFormat.keyDateFormat}}" 
-                               name="dateOfEnrollment" 
-                               class="form-control" 
-                               d2-date 
-                               ng-model="enrollment.dateOfEnrollment" 
-                               max-date="selectedProgram.selectEnrollmentDatesInFuture ? '' : 0"
-                               min-date=""
-                               ng-required="true"/>
-                        <span ng-show="outerForm.submitted && outerForm.dateOfEnrollment.$invalid" class="error">{{'required'| translate}}</span>
-                    </td>
-                </tr>     
-                <tr ng-if="selectedProgram.displayIncidentDate">
-                    <td>
-                        {{selectedProgram.dateOfIncidentDescription}}
-                    </td>
-                    <td>
-                        <input type="text" 
-                               placeholder="{{dhis2CalendarFormat.keyDateFormat}}"
-                               name="dateOfIncident"
-                               class="form-control" 
-                               d2-date 
-                               max-date="selectedProgram.selectIncidentDatesInFuture ? '' : 0"
-                               min-date=""
-                               ng-model="enrollment.dateOfIncident"/>
-                    </td>
-                </tr>
-            </table>
-        </div>
-
-        <div ng-if="formEmpty  && outerForm.submitted">
+    <form name="outerForm" novalidate>        
+        <div class="row col-sm-12 vertical-spacing" ng-if="selectedProgram && selectedProgram.displayCustomForm">
+            <div ng-if="!customForm.hasProgramDate">
+                <table>
+                    <table class="dhis2-list-table-striped dhis2-table-hover">
+                        <tr>
+                            <td>
+                                {{selectedProgram.dateOfEnrollmentDescription}}
+                            </td>
+                            <td>
+                                <input type="text" 
+                                       placeholder="{{dhis2CalendarFormat.keyDateFormat}}" 
+                                       name="dateOfEnrollment" 
+                                       class="form-control" 
+                                       d2-date 
+                                       ng-model="enrollment.dateOfEnrollment" 
+                                       max-date="selectedProgram.selectEnrollmentDatesInFuture ? '' : 0"
+                                       min-date=""
+                                       ng-required="true"/>
+                                <span ng-show="outerForm.submitted && outerForm.dateOfEnrollment.$invalid" class="error">{{'required'| translate}}</span>
+                            </td>
+                        </tr>     
+                        <tr ng-if="selectedProgram.displayIncidentDate">
+                            <td>
+                                {{selectedProgram.dateOfIncidentDescription}}
+                            </td>
+                            <td>
+                                <input type="text" 
+                                       placeholder="{{dhis2CalendarFormat.keyDateFormat}}"
+                                       name="dateOfIncident"
+                                       class="form-control" 
+                                       d2-date 
+                                       max-date="selectedProgram.selectIncidentDatesInFuture ? '' : 0"
+                                       min-date=""
+                                       ng-model="enrollment.dateOfIncident"/>
+                            </td>
+                        </tr>
+                    </table>
+            </div>
+            <div ng-include="'../dhis-web-commons/customform/custom-form.html'"></div>
+        </div>
+        <div class="row col-sm-12 vertical-spacing" ng-if="!selectedProgram.displayCustomForm">            
+            <div ng-include="'components/registration/default-form.html'"></div>
+        </div>
+
+        <div ng-if="formEmpty && outerForm.submitted">
             <div class="alert alert-warning">{{'form_is_empty_fill_at_least_one'| translate}}</div> 
         </div>
 

=== modified file 'dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/index.html'
--- dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/index.html	2015-01-28 19:03:29 +0000
+++ dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/index.html	2015-02-02 13:42:37 +0000
@@ -79,10 +79,12 @@
         <script type="text/javascript" src="../dhis-web-commons/javascripts/angular/plugins/angularjs-nvd3-directives.min.js"></script>
         <script type="text/javascript" src="../dhis-web-commons/javascripts/angular/plugins/angularLocalStorage.js"></script>
         <script type="text/javascript" src="../dhis-web-commons/javascripts/angular/plugins/angular-translate.min.js"></script>
-        <script type="text/javascript" src="../dhis-web-commons/javascripts/angular/plugins/dhis2/directives.js"></script>
-        <script type="text/javascript" src="../dhis-web-commons/javascripts/angular/plugins/dhis2/filters.js"></script>
-        <script type="text/javascript" src="../dhis-web-commons/javascripts/angular/plugins/dhis2/services.js"></script>
-        <script type="text/javascript" src="../dhis-web-commons/javascripts/angular/plugins/dhis2/controllers.js"></script>
+        <!--<script type="text/javascript" src="../dhis-web-commons/javascripts/dhis2/dhis2.angular.directives.js"></script>-->
+        <script type="text/javascript" src="../dhis-web-commons/javascripts/dhis2/dhis2.angular.filters.js"></script>
+        <!--<script type="text/javascript" src="../dhis-web-commons/javascripts/dhis2/dhis2.angular.services.js"></script>-->
+        <script type="text/javascript" src="../dhis-web-commons/javascripts/dhis2/dhis2.angular.controllers.js"></script>
+        <script type="text/javascript" src="scripts/d2Services.js"></script>
+        <script type="text/javascript" src="scripts/d2Directives.js"></script>
 
         <script type="text/javascript" src="scripts/app.js"></script>
         <script type="text/javascript" src="scripts/services.js"></script>

=== added file 'dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/scripts/d2Directives.js'
--- dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/scripts/d2Directives.js	1970-01-01 00:00:00 +0000
+++ dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/scripts/d2Directives.js	2015-02-02 13:42:37 +0000
@@ -0,0 +1,582 @@
+'use strict';
+
+/* Directives */
+
+var d2Directives = angular.module('d2Directives', [])
+
+
+.directive('d2OuSearch', function() {
+    
+    return {
+        restrict: 'E',
+        template: '<div style="margin-top:20px">\n\
+                    <img id="searchIcon" src="../images/search.png" style="cursor: pointer" title="{{ \'locate_organisation_unit_by_name\' | translate}}">\n\
+                    <span id="searchSpan" style="width:100%;display:none;">\n\
+                        <input type="text" id="searchField" name="key"/>\n\
+                        <input type="button" value="{{\'find\' | translate}}" onclick="selection.findByName()"/>\n\
+                    </span>\n\
+                  </div>',
+        link: function (scope, element, attrs) {
+            
+            $("#searchIcon").click(function() {
+                $("#searchSpan").toggle();
+                $("#searchField").focus();
+            });
+
+            $("#searchField").autocomplete({
+                source: "../dhis-web-commons/ouwt/getOrganisationUnitsByName.action",
+                select: function(event, ui) {
+                    $("#searchField").val(ui.item.value);
+                    selection.findByName();
+                }
+            });
+        }
+    };
+})
+
+.directive('inputValidator', function() {
+    
+    return {
+        require: 'ngModel',
+        link: function (scope, element, attrs, ctrl) {  
+
+            ctrl.$parsers.push(function (value) {
+                return parseFloat(value || '');
+            });
+        }
+    };
+})
+
+.directive('selectedOrgUnit', function($timeout, storage) {        
+
+    return {        
+        restrict: 'A',        
+        link: function(scope, element, attrs){
+            
+            //once ou tree is loaded, start meta-data download
+            $(function() {
+                dhis2.ou.store.open().done( function() {
+                    selection.load();
+                    $( "#orgUnitTree" ).one( "ouwtLoaded", function(event, ids, names) {
+                        console.log('Finished loading orgunit tree');
+                        
+                        //Disable ou selection until meta-data has downloaded
+                        $( "#orgUnitTree" ).addClass( "disable-clicks" );
+                        
+                        $timeout(function() {
+                            scope.treeLoaded = true;
+                            scope.$apply();
+                        });
+                        
+                        downloadMetaData();
+                    });
+                });
+            });
+            
+            //listen to user selection, and inform angular         
+            selection.setListenerFunction( setSelectedOu, true );
+            
+            function setSelectedOu( ids, names ) {
+                var ou = {id: ids[0], name: names[0]};
+                $timeout(function() {
+                    scope.selectedOrgUnit = ou;
+                    scope.$apply();
+                });
+            }
+        }  
+    };
+})
+
+.directive('blurOrChange', function() {
+    
+    return function( scope, elem, attrs) {
+        elem.calendarsPicker({
+            onSelect: function() {
+                scope.$apply(attrs.blurOrChange);
+                $(this).change();                                        
+            }
+        }).change(function() {
+            scope.$apply(attrs.blurOrChange);
+        });
+    };
+})
+
+.directive('d2Enter', function () {
+    return function (scope, element, attrs) {
+        element.bind("keydown keypress", function (event) {
+            if(event.which === 13) {
+                scope.$apply(function (){
+                    scope.$eval(attrs.d2Enter);
+                });
+                event.preventDefault();
+            }
+        });
+    };
+})
+
+.directive('d2NumberValidation', function(ErrorMessageService, $translate) {
+    
+    return {
+        require: 'ngModel',
+        restrict: 'A',
+        link: function (scope, element, attrs, ctrl) {
+            
+            function checkValidity(numberType, value){
+                var isValid = false;
+                switch(numberType){
+                    case "number":
+                        isValid = dhis2.validation.isNumber(value);
+                        break;
+                    case "posInt":
+                        isValid = dhis2.validation.isPositiveInt(value);
+                        break;
+                    case "negInt":
+                        isValid = dhis2.validation.isNegativeInt(value);
+                        break;
+                    case "zeroPositiveInt":
+                        isValid = dhis2.validation.isZeroOrPositiveInt(value);
+                        break;
+                    case "int":
+                        isValid = dhis2.validation.isInt(value);
+                        break;
+                    default:
+                        isValid = true;
+                }
+                return isValid;
+            }
+            
+            var errorMessages = ErrorMessageService.getErrorMessages();
+            var fieldName = attrs.inputFieldId;
+            var numberType = attrs.numberType;
+            var isRequired = attrs.ngRequired === 'true';
+            var msg = $translate(numberType)+ ' ' + $translate('required');
+           
+            ctrl.$parsers.unshift(function(value) {
+            	if(value){
+                    var isValid = checkValidity(numberType, value);                    
+                    if(!isValid){
+                        errorMessages[fieldName] = $translate('value_must_be_' + numberType);
+                    }
+                    else{
+                        if(isRequired){
+                            errorMessages[fieldName] = msg;
+                        }
+                        else{
+                            errorMessages[fieldName] = "";
+                        }
+                    }
+                    
+                    ErrorMessageService.setErrorMessages(errorMessages);
+                	ctrl.$setValidity(fieldName, isValid);
+                    return value;
+                }
+                
+                if(value === ''){
+                    if(isRequired){
+                        errorMessages[fieldName] = msg;
+                    }
+                    else{
+                        ctrl.$setValidity(fieldName, true);
+                        errorMessages[fieldName] = "";
+                    }
+                    
+                    ErrorMessageService.setErrorMessages(errorMessages);
+                    return undefined;
+                }              
+            });
+           
+            ctrl.$formatters.unshift(function(value) {                
+                if(value){
+                    var isValid = checkValidity(numberType, value);
+                    ctrl.$setValidity(fieldName, isValid);
+                    return value;
+                }
+            });
+        }
+    };
+})
+
+.directive('typeaheadOpenOnFocus', function () {
+  	
+  	return {
+        require: ['typeahead', 'ngModel'],
+        link: function (scope, element, attr, ctrls) {
+            element.bind('focus', function () {
+                ctrls[0].getMatchesAsync(ctrls[1].$viewValue);                
+                scope.$watch(attr.ngModel, function(value) {
+                    if(value === '' || angular.isUndefined(value)){
+                        ctrls[0].getMatchesAsync(ctrls[1].$viewValue);
+                    }                
+                });
+            });
+        }
+    };
+})
+
+.directive('d2TypeaheadValidation', function() {
+    
+    return {
+        require: ['typeahead', 'ngModel'],
+        restrict: 'A',
+        link: function (scope, element, attrs, ctrls) {
+            element.bind('blur', function () {                
+                if(ctrls[1].$viewValue && !ctrls[1].$modelValue && ctrls[0].active === -1){
+                    ctrls[1].$setViewValue();
+                    ctrls[1].$render();
+                }                
+            });
+        }
+    };
+})
+
+.directive('d2PopOver', function($compile, $templateCache){
+    
+    return {        
+        restrict: 'EA',
+        link: function(scope, element, attrs){
+            var content = $templateCache.get("popover.html");
+            content = $compile(content)(scope);
+            var options = {
+                    content: content,
+                    placement: 'bottom',
+                    trigger: 'hover',
+                    html: true,
+                    title: scope.title               
+                };            
+            $(element).popover(options);
+        },
+        scope: {
+            content: '=',
+            title: '@details',
+            template: "@template"
+        }
+    };
+})
+
+.directive('sortable', function() {        
+
+    return {        
+        restrict: 'A',        
+        link: function(scope, element, attrs){
+            element.sortable({
+                connectWith: ".connectedSortable",
+                placeholder: "ui-state-highlight",
+                tolerance: "pointer",
+                handle: '.handle'
+            });
+        }  
+    };
+})
+
+.directive('serversidePaginator', function factory() {
+    
+    return {
+        restrict: 'E',
+        controller: function ($scope, Paginator) {
+            $scope.paginator = Paginator;
+        },
+        templateUrl: '../dhis-web-commons/paging/serverside-pagination.html'
+    };
+})
+
+.directive('draggableModal', function(){
+    
+    return {
+      	restrict: 'EA',
+      	link: function(scope, element) {
+        	element.draggable();
+      	}
+    };  
+})
+
+.directive('d2GoogleMap', function ($parse, $compile, storage) {
+    return {
+        restrict: 'E',
+        replace: true,
+        template: '<div></div>',
+        link: function(scope, element, attrs){
+            
+            //remove angular bootstrap ui modal draggable
+            $(".modal-content").draggable({ disabled: true });
+            
+            //get a default center
+            var latCenter = 12.31, lngCenter = 51.48;            
+            
+            //if there is any marker already - use it as center
+            if(angular.isObject(scope.location)){
+                if(scope.location.lat && scope.location.lng){
+                    latCenter = scope.location.lat;
+                    lngCenter = scope.location.lng;
+                }                
+            }
+            
+            //default map configurations 
+            var mapOptions = {
+                zoom: 3,
+                center: new google.maps.LatLng(latCenter, lngCenter),
+                mapTypeId: google.maps.MapTypeId.ROADMAP
+            },featureStyle = {
+                strokeWeight: 2,
+                strokeOpacity: 0.4,
+                fillOpacity: 0.4,
+                fillColor: 'green'
+            };
+            
+            var geojsons = $parse(attrs.geojsons)(scope);
+            var currentLayer = 0, currentGeojson = geojsons[0]; 
+            
+            var map = new google.maps.Map(document.getElementById(attrs.id), mapOptions);            
+            var currentGeojsonFeatures = map.data.addGeoJson(currentGeojson);
+            
+            var marker = new google.maps.Marker({
+                map: map
+            });
+            
+            if(angular.isObject(scope.location)){
+                if(scope.location.lat && scope.location.lng){                    
+                    addMarker({lat: scope.location.lat, lng: scope.location.lng});                    
+                }                
+            }
+            
+            function addMarker(loc){
+                var latLng = new google.maps.LatLng(loc.lat, loc.lng);
+                marker.setPosition(latLng);
+            }
+            
+            function centerMap(){
+                
+                if(currentGeojson && currentGeojson.features){
+                    var latLngBounds = new google.maps.LatLngBounds();
+                    angular.forEach(currentGeojson.features, function(feature){
+                        if(feature.geometry.type === 'MultiPolygon'){
+                            angular.forEach(feature.geometry.coordinates[0][0], function(coordinate){
+                                latLngBounds.extend(new google.maps.LatLng(coordinate[1],coordinate[0]));
+                            });
+                        }
+                        else if(feature.geometry.type === 'Point'){                        
+                            latLngBounds.extend(new google.maps.LatLng(feature.geometry.coordinates[1],feature.geometry.coordinates[0]));
+                        }
+                    });
+                    
+                    map.fitBounds(latLngBounds);
+                    map.panToBounds(latLngBounds);
+                }                
+            }
+            
+            function initializeMap(){                
+                google.maps.event.addListenerOnce(map, 'idle', function(){
+                    google.maps.event.trigger(map, 'resize');
+                    map.data.setStyle(featureStyle);
+                    centerMap();
+                });
+            }
+            
+            map.data.addListener('mouseover', function(e) {                
+                $("#polygon-label").text( e.feature.k.name );
+                map.data.revertStyle();
+                map.data.overrideStyle(e.feature, {fillOpacity: 0.8});
+            });
+            
+            map.data.addListener('mouseout', function() {                
+                $("#polygon-label").text( '' );
+                map.data.revertStyle();
+            });
+            
+            //drill-down based on polygons assigned to orgunits
+            map.data.addListener('rightclick', function(e){                
+                for (var i = 0; i < currentGeojsonFeatures.length; i++){
+                    map.data.remove(currentGeojsonFeatures[i]);
+                }
+                                
+                if(currentLayer >= geojsons.length-1){
+                    currentLayer = 0;
+                    currentGeojson = angular.copy(geojsons[currentLayer]);                    
+                }
+                else{
+                    currentLayer++;
+                    currentGeojson = angular.copy(geojsons[currentLayer]);
+                    currentGeojson.features = [];
+                    var selectedFeatures = [];
+                    angular.forEach(geojsons[currentLayer].features, function(feature){                    
+                        if(feature.properties.parent === e.feature.B){
+                            selectedFeatures.push(feature);
+                        }
+                    });
+                    
+                    if(selectedFeatures.length){
+                        currentGeojson.features = selectedFeatures;
+                    }                   
+                }                
+                currentGeojsonFeatures = map.data.addGeoJson(currentGeojson);
+                centerMap();         
+            });            
+            
+            //capturing coordinate from defined polygons
+            map.data.addListener('click', function(e) {                
+                scope.$apply(function(){
+                    addMarker({
+                       lat: e.latLng.lat(),
+                       lng: e.latLng.lng()
+                    });
+                    $parse(attrs.location).assign(scope.$parent, {lat: e.latLng.lat(), lng: e.latLng.lng()});                    
+                });                
+            });
+            
+            //capturing coordinate from anywhere in the map - incase no polygons are defined
+            google.maps.event.addListener(map, 'click', function(e){                
+                scope.$apply(function(){
+                    addMarker({
+                       lat: e.latLng.lat(),
+                       lng: e.latLng.lng()
+                    });
+                    $parse(attrs.location).assign(scope.$parent, {lat: e.latLng.lat(), lng: e.latLng.lng()});                    
+                });                
+            });
+            
+            initializeMap();
+        }
+    };
+})
+
+.directive('d2CustomForm', function($compile) {
+    return{ 
+        restrict: 'E',
+        link: function(scope, elm, attrs){
+            scope.$watch('customForm', function(){
+                elm.html(scope.customForm.htmlCode);
+                $compile(elm.contents())(scope);
+            });
+        }
+    };
+})
+
+.directive('d2ContextMenu', function(ContextMenuSelectedItem) {
+        
+    return {        
+        restrict: 'A',
+        link: function(scope, element, attrs){
+            var contextMenu = $("#contextMenu");                   
+            
+            element.click(function (e) {
+                var selectedItem = $.parseJSON(attrs.selectedItem);
+                ContextMenuSelectedItem.setSelectedItem(selectedItem);
+                
+                var menuHeight = contextMenu.height();
+                var menuWidth = contextMenu.width();
+                var winHeight = $(window).height();
+                var winWidth = $(window).width();
+
+                var pageX = e.pageX;
+                var pageY = e.pageY;
+
+                contextMenu.show();
+
+                if( (menuWidth + pageX) > winWidth ) {
+                  pageX -= menuWidth;
+                }
+
+                if( (menuHeight + pageY) > winHeight ) {
+                  pageY -= menuHeight;
+
+                  if( pageY < 0 ) {
+                      pageY = e.pageY;
+                  }
+                }
+                
+                contextMenu.css({
+                    left: pageX,
+                    top: pageY
+                });
+
+                return false;
+            });
+            
+            contextMenu.on("click", "a", function () {                    
+                contextMenu.hide();
+            });
+
+            $(document).click(function () {                                        
+                contextMenu.hide();
+            });
+        }     
+    };
+})
+
+.directive('d2Date', function(DateUtils, CalendarService, ErrorMessageService, $translate, $parse) {
+    return {
+        restrict: 'A',
+        require: 'ngModel',        
+        link: function(scope, element, attrs, ctrl) {    
+            
+            var errorMessages = ErrorMessageService.getErrorMessages();
+            var fieldName = attrs.inputFieldId;
+            var isRequired = attrs.ngRequired === 'true';
+            var calendarSetting = CalendarService.getSetting();            
+            var dateFormat = 'yyyy-mm-dd';
+            if(calendarSetting.keyDateFormat === 'dd-MM-yyyy'){
+                dateFormat = 'dd-mm-yyyy';
+            }            
+            
+            var minDate = $parse(attrs.minDate)(scope), 
+                maxDate = $parse(attrs.maxDate)(scope),
+                calendar = $.calendars.instance(calendarSetting.keyCalendar);
+            
+            element.calendarsPicker({
+                changeMonth: true,
+                dateFormat: dateFormat,
+                yearRange: '-120:+30',
+                minDate: minDate,
+                maxDate: maxDate,
+                calendar: calendar,
+                duration: "fast",
+                showAnim: "",
+                renderer: $.calendars.picker.themeRollerRenderer,
+                onSelect: function(date) {
+                    $(this).change();
+                }
+            })
+            .change(function() {                
+                if(this.value){                    
+                    var rawDate = this.value;
+                    var convertedDate = DateUtils.format(this.value);
+
+                    var isValid = rawDate == convertedDate;
+                    
+                    if(!isValid){
+                        errorMessages[fieldName] = $translate('date_required');
+                    }
+                    else{
+                        if(isRequired){
+                            errorMessages[fieldName] = $translate('required');
+                        }
+                        else{
+                            errorMessages[fieldName] = "";
+                        }
+                        if(maxDate === 0){                    
+                            isValid = !moment(convertedDate, calendarSetting.momentFormat).isAfter(DateUtils.getToday());
+                            if(!isValid){
+                                errorMessages[fieldName] = $translate('future_date_not_allowed');                            
+                            }                           
+                        }
+                    }                                        
+                    ctrl.$setViewValue(this.value);
+                    ctrl.$setValidity(fieldName, isValid);
+                }
+                else{
+                    if(!isRequired){
+                        ctrl.$setViewValue(this.value);
+                        ctrl.$setValidity(fieldName, !isRequired);
+                        errorMessages[fieldName] = "";
+                    }
+                    else{
+                        errorMessages[fieldName] = $translate('required');                        
+                    }
+                }
+                
+                ErrorMessageService.setErrorMessages(errorMessages);
+                this.focus();
+                scope.$apply();
+            });    
+        }      
+    };   
+});
\ No newline at end of file

=== added file 'dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/scripts/d2Services.js'
--- dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/scripts/d2Services.js	1970-01-01 00:00:00 +0000
+++ dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/scripts/d2Services.js	2015-02-02 13:42:37 +0000
@@ -0,0 +1,743 @@
+/* Pagination service */
+var d2Services = angular.module('d2Services', ['ngResource'])
+
+/* Factory for loading translation strings */
+.factory('i18nLoader', function ($q, $http, storage, DialogService) {
+ 
+    var getTranslationStrings = function(locale){
+        var defaultUrl = 'i18n/i18n_app.properties';
+        var url = '';
+        if(locale === 'en' || !locale){
+            url = defaultUrl;
+        }
+        else{
+            url = 'i18n/i18n_app_' + locale + '.properties';
+        }
+
+        var tx = {locale: locale};
+
+        var promise = $http.get(url).then(function(response){
+            tx= {locale: locale, keys: dhis2.util.parseJavaProperties(response.data)};
+            return tx;
+        }, function(){
+            var dialogOptions = {
+                headerText: 'missing_translation_file',
+                bodyText: 'missing_translation_using_default'
+            };
+
+            DialogService.showDialog({}, dialogOptions);
+            var p = $http.get(defaultUrl).then(function(response){
+                tx= {locale: locale, keys: dhis2.util.parseJavaProperties(response.data)};
+                return tx;
+            });
+            return p;
+        });
+        return promise;
+    };
+
+    var getLocale = function(){
+        var locale = 'en';
+
+        var promise = $http.get('../api/me/profile.json').then(function(response){
+            storage.set('USER_PROFILE', response.data);
+            if(response.data && response.data.settings && response.data.settings.keyUiLocale){
+                locale = response.data.settings.keyUiLocale;
+            }
+            return locale;
+        }, function(){
+            return locale;
+        });
+
+        return promise;
+    };
+    return function () {
+        var deferred = $q.defer(), translations;    
+        var userProfile = storage.get('USER_PROFILE');
+        if(userProfile && userProfile.settings && userProfile.settings.keyUiLocale){                
+            getTranslationStrings(userProfile.settings.keyUiLocale).then(function(response){
+                translations = response.keys;
+                deferred.resolve(translations);
+            });
+            return deferred.promise;
+        }
+        else{
+            getLocale().then(function(locale){
+                getTranslationStrings(locale).then(function(response){
+                    translations = response.keys;
+                    deferred.resolve(translations);
+                });
+            });
+            return deferred.promise;
+        }
+    };
+})
+
+/* Factory for loading external data */
+.factory('ExternalDataFactory', function($http) {
+
+    return {        
+        get: function(fileName) {
+            var promise = $http.get( fileName ).then(function(response){
+                return response.data;
+            });            
+            return promise;
+        }
+    };
+})
+
+/* service for getting calendar setting */
+.service('CalendarService', function(storage, $rootScope){    
+
+    return {
+        getSetting: function() {
+            
+            var dhis2CalendarFormat = {keyDateFormat: 'yyyy-MM-dd', keyCalendar: 'gregorian', momentFormat: 'YYYY-MM-DD'};                
+            var storedFormat = storage.get('CALENDAR_SETTING');
+            if(angular.isObject(storedFormat) && storedFormat.keyDateFormat && storedFormat.keyCalendar){
+                if(storedFormat.keyCalendar === 'iso8601'){
+                    storedFormat.keyCalendar = 'gregorian';
+                }
+
+                if(storedFormat.keyDateFormat === 'dd-MM-yyyy'){
+                    dhis2CalendarFormat.momentFormat = 'DD-MM-YYYY';
+                }
+                
+                dhis2CalendarFormat.keyCalendar = storedFormat.keyCalendar;
+                dhis2CalendarFormat.keyDateFormat = storedFormat.keyDateFormat;
+            }
+            $rootScope.dhis2CalendarFormat = dhis2CalendarFormat;
+            return dhis2CalendarFormat;
+        }
+    };            
+})
+
+/* service for dealing with dates */
+.service('DateUtils', function($filter, CalendarService){
+    
+    return {        
+        getDate: function(dateValue){
+            if(!dateValue){
+                return;
+            }            
+            var calendarSetting = CalendarService.getSetting();
+            dateValue = moment(dateValue, calendarSetting.momentFormat)._d;
+            return Date.parse(dateValue);
+        },
+        format: function(dateValue) {            
+            if(!dateValue){
+                return;
+            }
+
+            var calendarSetting = CalendarService.getSetting();            
+            dateValue = moment(dateValue, calendarSetting.momentFormat)._d;
+            dateValue = $filter('date')(dateValue, calendarSetting.keyDateFormat);            
+            return dateValue;
+        },
+        formatToHrsMins: function(dateValue) {
+            var calendarSetting = CalendarService.getSetting();
+            var dateFormat = 'YYYY-MM-DD @ hh:mm A';
+            if(calendarSetting.keyDateFormat === 'dd-MM-yyyy'){
+                dateFormat = 'DD-MM-YYYY @ hh:mm A';
+            }            
+            return moment(dateValue).format(dateFormat);
+        },
+        getToday: function(){  
+            var calendarSetting = CalendarService.getSetting();
+            var tdy = $.calendars.instance(calendarSetting.keyCalendar).newDate();            
+            var today = moment(tdy._year + '-' + tdy._month + '-' + tdy._day, 'YYYY-MM-DD')._d;            
+            today = Date.parse(today);     
+            today = $filter('date')(today,  calendarSetting.keyDateFormat);
+            return today;
+        },
+        formatFromUserToApi: function(dateValue){            
+            if(!dateValue){
+                return;
+            }
+            var calendarSetting = CalendarService.getSetting();
+            dateValue = moment(dateValue, calendarSetting.momentFormat)._d;
+            dateValue = Date.parse(dateValue);     
+            dateValue = $filter('date')(dateValue, 'yyyy-MM-dd'); 
+            return dateValue;            
+        },
+        formatFromApiToUser: function(dateValue){            
+            if(!dateValue){
+                return;
+            }            
+            var calendarSetting = CalendarService.getSetting();
+            dateValue = moment(dateValue, 'YYYY-MM-DD')._d;
+            return $filter('date')(dateValue, calendarSetting.keyDateFormat); 
+        }
+    };
+})
+
+/* service for dealing with custom form */
+.service('CustomFormService', function(){
+    
+    return {
+        getForProgramStage: function(programStage){
+            
+            var htmlCode = programStage.dataEntryForm ? programStage.dataEntryForm.htmlCode : null;  
+            
+            if(htmlCode){                
+            
+                var programStageDataElements = [];
+
+                angular.forEach(programStage.programStageDataElements, function(prStDe){
+                    programStageDataElements[prStDe.dataElement.id] = prStDe;
+                });
+
+                var inputRegex = /<input.*?\/>/g,
+                    match,
+                    inputFields = [],
+                    hasEventDate = false;                
+
+                while (match = inputRegex.exec(htmlCode)) {                
+                    inputFields.push(match[0]);
+                }
+                
+                for(var i=0; i<inputFields.length; i++){                    
+                    var inputField = inputFields[i];                    
+                    var inputElement = $.parseHTML( inputField );
+                    var attributes = {};
+                                       
+                    $(inputElement[0].attributes).each(function() {
+                        attributes[this.nodeName] = this.value;                       
+                    });
+                    
+                    var fieldId = '', errorMessageId = '', newInputField;   
+                    if(attributes.hasOwnProperty('id')){
+                        
+                        if(attributes['id'] === 'executionDate'){
+                            fieldId = 'eventDate';
+                            errorMessageId = '"' + 'eventDate' + '"';
+                            hasEventDate = true;
+                            
+                            //name needs to be unique so that it can be used for validation in angularjs
+                            if(attributes.hasOwnProperty('name')){
+                                attributes['name'] = fieldId;
+                            }
+                            
+                            newInputField = '<input type="text" ' +
+                                                this.getAttributesAsString(attributes) +
+                                                ' d2-validation ' +
+                                                ' ng-model="currentEvent.' + fieldId + '"' +
+                                                ' input-field-id="' + fieldId + '"' +
+                                                ' d2-date ' +
+                                                ' max-date="' + 0 + '"' + 
+                                                ' placeholder="{{dhis2CalendarFormat.keyDateFormat}}" ' +
+                                                ' ng-class="getInputNotifcationClass(prStDes.' + fieldId + '.dataElement.id,true)"' +
+                                                ' blur-or-change="saveDatavalue(prStDes.'+ fieldId + ')"' + 
+                                                ' ng-required="{{true}}">';
+                        }
+                        else{
+                            fieldId = attributes['id'].substring(4, attributes['id'].length-1).split("-")[1]; 
+                            errorMessageId = 'prStDes.' + fieldId + '.dataElement.id';
+                            
+                            //name needs to be unique so that it can be used for validation in angularjs
+                            if(attributes.hasOwnProperty('name')){
+                                attributes['name'] = fieldId;
+                            }
+
+                            //check data element type and generate corresponding angular input field
+                            if(programStageDataElements[fieldId].dataElement.type === "int"){
+                                newInputField = '<input type="text" ' +
+                                                this.getAttributesAsString(attributes) +
+                                                ' d2-validation ' +
+                                                ' d2-number-validation ' +
+                                                ' number-type="' + programStageDataElements[fieldId].dataElement.numberType + '" ' +
+                                                ' ng-model="currentEvent.' + fieldId + '"' +
+                                                ' input-field-id="' + fieldId + '"' +
+                                                ' ng-class="getInputNotifcationClass(prStDes.' + fieldId + '.dataElement.id,true)"' +
+                                                ' ng-disabled="selectedEnrollment.status===\'CANCELLED\' || selectedEnrollment.status===\'COMPLETED\'"' +
+                                                ' ng-blur="saveDatavalue(prStDes.'+ fieldId + ')"' + 
+                                                ' ng-required="{{prStDes.' + fieldId + '.compulsory}}">';
+                            }
+                            if(programStageDataElements[fieldId].dataElement.type === "string"){
+                                if(programStageDataElements[fieldId].dataElement.optionSet){
+                                    var optionSetId = programStageDataElements[fieldId].dataElement.optionSet.id;
+                                    newInputField = '<input type="text" ' +
+                                                this.getAttributesAsString(attributes) +
+                                                ' d2-validation ' +
+                                                ' ng-model="currentEvent.' + fieldId + '" ' +
+                                                ' input-field-id="' + fieldId + '"' +
+                                                ' ng-disabled="selectedEnrollment.status===\'CANCELLED\' || selectedEnrollment.status===\'COMPLETED\' || currentEvent[uid]==\'uid\'"' +
+                                                ' ng-required="{{prStDes.' + fieldId + '.compulsory}}"' +
+                                                ' typeahead="option.name as option.name for option in optionSets.'+optionSetId+'.options | filter:$viewValue | limitTo:20"' +
+                                                ' typeahead-editable="false" ' +
+                                                ' d2-typeahead-validation ' +
+                                                ' class="typeahead" ' +
+                                                ' placeholder="&#xf0d7;&nbsp;&nbsp;" ' +
+                                                ' ng-class="getInputNotifcationClass(prStDes.' + fieldId + '.dataElement.id,true)"' +                                            
+                                                ' ng-blur="saveDatavalue(prStDes.'+ fieldId + ')"' +
+                                                ' typeahead-open-on-focus ng-required="prStDes.'+fieldId+'.compulsory"> ';
+                                }
+                                else{
+                                    newInputField = '<input type="text" ' +
+                                                this.getAttributesAsString(attributes) +
+                                                ' d2-validation ' +
+                                                ' ng-model="currentEvent.' + fieldId + '" ' +
+                                                ' input-field-id="' + fieldId + '"' +
+                                                ' ng-disabled="selectedEnrollment.status===\'CANCELLED\' || selectedEnrollment.status===\'COMPLETED\' || currentEvent[uid]==\'uid\'"' +
+                                                ' ng-class="getInputNotifcationClass(prStDes.' + fieldId + '.dataElement.id,true)"' +
+                                                ' ng-blur="saveDatavalue(prStDes.'+ fieldId + ')"' +
+                                                ' ng-required="prStDes.' + fieldId + '.compulsory"> ';                                     
+                                }
+                            }
+                            if(programStageDataElements[fieldId].dataElement.type === "bool"){
+                                newInputField = '<select ' +
+                                                this.getAttributesAsString(attributes) +
+                                                ' d2-validation ' +
+                                                ' ng-model="currentEvent.' + fieldId + '" ' +
+                                                ' input-field-id="' + fieldId + '"' +
+                                                ' ng-class="getInputNotifcationClass(prStDes.' + fieldId + '.dataElement.id,true)"' +
+                                                ' ng-disabled="selectedEnrollment.status===\'CANCELLED\' || selectedEnrollment.status===\'COMPLETED\'"' +
+                                                ' ng-change="saveDatavalue(prStDes.'+ fieldId + ')"' + 
+                                                ' ng-required="{{prStDes.' + fieldId + '.compulsory}}">' + 
+                                                '<option value="">{{\'please_select\'| translate}}</option>' +
+                                                '<option value="false">{{\'no\'| translate}}</option>' + 
+                                                '<option value="true">{{\'yes\'| translate}}</option>' +
+                                                '</select> ';                                     
+                            }
+                            if(programStageDataElements[fieldId].dataElement.type === "date"){
+                                var maxDate = programStageDataElements[fieldId].allowFutureDate ? '' : 0;
+                                newInputField = '<input type="text" ' +
+                                                this.getAttributesAsString(attributes) +
+                                                ' d2-validation ' +
+                                                ' ng-model="currentEvent.' + fieldId + '"' +
+                                                ' input-field-id="' + fieldId + '"' +                                                
+                                                ' placeholder="{{dhis2CalendarFormat.keyDateFormat}}" ' +
+                                                ' d2-date ' +
+                                                ' max-date="' + maxDate + '"' +
+                                                ' ng-class="getInputNotifcationClass(prStDes.' + fieldId + '.dataElement.id,true)"' +
+                                                ' ng-disabled="selectedEnrollment.status===\'CANCELLED\' || selectedEnrollment.status===\'COMPLETED\'"' +
+                                                ' blur-or-change="saveDatavalue(prStDes.'+ fieldId + ')"' + 
+                                                ' ng-required="{{prStDes.' + fieldId + '.compulsory}}"> '; 
+                            }
+                            if(programStageDataElements[fieldId].dataElement.type === "trueOnly"){
+                                newInputField = '<input type="checkbox" ' +
+                                                this.getAttributesAsString(attributes) +
+                                                ' d2-validation ' +
+                                                ' ng-model="currentEvent.' + fieldId + '"' +
+                                                ' input-field-id="' + fieldId + '"' +
+                                                ' ng-class="getInputNotifcationClass(prStDes.' + fieldId + '.dataElement.id,true)"' +
+                                                ' ng-disabled="selectedEnrollment.status===\'CANCELLED\' || selectedEnrollment.status===\'COMPLETED\'"' +
+                                                ' ng-change="saveDatavalue(prStDes.'+ fieldId + ')"' +
+                                                ' ng-required="{{prStDes.' + fieldId + '.compulsory}}"> ';
+                            }                            
+                        }
+						
+                        newInputField = newInputField + ' <span ng-show="(outerForm.'+ fieldId +'.$dirty && outerForm.'+ fieldId +'.$invalid) || (outerForm.submitted && outerForm.'+ fieldId +'.$invalid) || (currentEvent.' + fieldId + ' && outerForm.' + fieldId + '.$invalid)" class="required">{{getErrorMessage(' + errorMessageId + ')}}</span> ';
+                        
+                        htmlCode = htmlCode.replace(inputField, newInputField);
+                    }
+                }
+                return {htmlCode: htmlCode, hasEventDate: hasEventDate};
+            }
+            return null;
+        },
+        getForTrackedEntity: function(trackedEntity, target){
+            if(!trackedEntity ){
+                return null;
+            }
+
+            var htmlCode = trackedEntity.dataEntryForm ? trackedEntity.dataEntryForm.htmlCode : null;
+            if(htmlCode){                
+            
+                var trackedEntityFormAttributes = [];
+                angular.forEach(trackedEntity.attributes, function(att){                    
+                    trackedEntityFormAttributes[att.id] = att;
+                });
+
+                
+                var inputRegex = /<input.*?\/>/g, match, inputFields = [];
+                var hasProgramDate = false;
+                while (match = inputRegex.exec(htmlCode)) {                
+                    inputFields.push(match[0]);                    
+                }
+
+                for(var i=0; i<inputFields.length; i++){                    
+                    var inputField = inputFields[i];                    
+                    var inputElement = $.parseHTML( inputField );
+                    var attributes = {};
+                                       
+                    $(inputElement[0].attributes).each(function() {
+                        attributes[this.nodeName] = this.value;                       
+                    });
+                    
+                    var attId = '', newInputField, programId;     
+                    if(attributes.hasOwnProperty('attributeid')){
+                        attId = attributes['attributeid'];
+
+                        var fieldName = attId;                        
+                        var attMaxDate = trackedEntityFormAttributes[attId].allowFutureDate ? '' : 0;
+                        
+                        //check attribute type and generate corresponding angular input field
+                        if(trackedEntityFormAttributes[attId].valueType === "number"){
+                            newInputField = '<input type="text" ' +
+                                            ' name="' + fieldName + '"' +                          
+                                            ' element-id="' + i + '"' +
+                                            ' element-code="' + trackedEntityFormAttributes[attId].code + '"' +
+                                            this.getAttributesAsString(attributes) +
+                                            ' d2-validation ' +
+                                            ' d2-number-validation ' +
+                                            ' d2-focus-next-on-enter' + 
+                                            ' ng-model="registeredTei.' + attId + '" ' +                                             
+                                            ' ng-blur="validationAndSkipLogic(registeredTei,\'' + attId + '\')" ' +
+                                            ' ng-required=" ' + trackedEntityFormAttributes[attId].mandatory + '"> ';
+                        }                                               
+                        else if(trackedEntityFormAttributes[attId].valueType === "optionSet"){
+                            var optionSetId = trackedEntityFormAttributes[attId].optionSet.id;                            
+                            newInputField = '<input type="text" ' +
+                                            ' name="' + fieldName + '"' +
+                                            ' element-id="' + i + '"' +
+                                            ' element-code="' + trackedEntityFormAttributes[attId].code + '"' +                                            
+                                            this.getAttributesAsString(attributes) +
+                                            ' d2-focus-next-on-enter' + 
+                                            ' ng-model="registeredTei.' + attId + '" ' +   
+                                            ' d2-validation ' +
+                                            ' d2-typeahead-validation ' +
+                                            ' class="typeahead" ' +
+                                            ' placeholder="&#xf0d7;&nbsp;&nbsp;" ' +
+                                            ' typeahead-editable="false" ' + 
+                                            ' typeahead="option.name as option.name for option in optionSets.' + optionSetId + '.options | filter:$viewValue | limitTo:50"' +
+                                            ' typeahead-open-on-focus ' +
+                                            ' ng-blur="validationAndSkipLogic(registeredTei,\'' + attId + '\')" ' +
+                                            ' ng-required=" ' + trackedEntityFormAttributes[attId].mandatory + '"> ';                            
+                        }
+                        else if(trackedEntityFormAttributes[attId].valueType === "bool"){
+                            newInputField = '<select ' +
+                                            ' name="' + fieldName + '"' +
+                                            ' element-id="' + i + '"' + 
+                                            ' element-code="' + trackedEntityFormAttributes[attId].code + '"' +                                            
+                                            this.getAttributesAsString(attributes) +
+                                            ' d2-focus-next-on-enter' + 
+                                            ' ng-model="registeredTei.' + attId + '" ' +                                            
+                                            ' ng-change="validationAndSkipLogic(registeredTei,\'' + attId + '\')" ' +
+                                            ' ng-required=" ' + trackedEntityFormAttributes[attId].mandatory + '"> ' +
+                                            ' <option value="">{{\'please_select\'| translate}}</option>' +
+                                            ' <option value="false">{{\'no\'| translate}}</option>' + 
+                                            ' <option value="true">{{\'yes\'| translate}}</option>' +
+                                            '</select> ';
+                        }
+                        else if(trackedEntityFormAttributes[attId].valueType === "date"){
+                            newInputField = '<input type="text" ' +
+                                            ' name="' + fieldName + '"' +
+                                            ' element-id="' + i + '"' + 
+                                            ' element-code="' + trackedEntityFormAttributes[attId].code + '"' +
+                                            this.getAttributesAsString(attributes) +
+                                            ' d2-focus-next-on-enter' + 
+                                            ' placeholder="{{dhis2CalendarFormat.keyDateFormat}}" ' +
+                                            ' ng-model="registeredTei.' + attId + '" ' +                                            
+                                            ' max-date="' + attMaxDate + '"' + '\'' +
+                                            ' d2-date' +
+                                            ' d2-validation ' +
+                                            ' blur-or-change="validationAndSkipLogic(registeredTei,\'' + attId + '\')" ' +
+                                            ' ng-required=" ' + trackedEntityFormAttributes[attId].mandatory + '"> ';
+                        }
+                        else if(trackedEntityFormAttributes[attId].valueType === "trueOnly"){
+                            newInputField = '<input type="checkbox" ' +  
+                                            ' name="' + fieldName + '"' +
+                                            ' element-id="' + i + '"' + 
+                                            ' element-code="' + trackedEntityFormAttributes[attId].code + '"' +
+                                            this.getAttributesAsString(attributes) + 
+                                            ' d2-validation ' +
+                                            ' d2-focus-next-on-enter' + 
+                                            ' ng-model="registeredTei.' + attId + '" ' +                                            
+                                            ' ng-change="validationAndSkipLogic(registeredTei,\'' + attId + '\')" ' +
+                                            ' ng-required=" ' + trackedEntityFormAttributes[attId].mandatory + '"> ';
+                        }
+                        else if(trackedEntityFormAttributes[attId].valueType === "email"){
+                            newInputField = '<input type="email" ' +    
+                                            ' name="' + fieldName + '"' +                                              
+                                            ' element-id="' + i + '"' + 
+                                            ' element-code="' + trackedEntityFormAttributes[attId].code + '"' +
+                                            this.getAttributesAsString(attributes) +
+                                            ' d2-validation ' +
+                                            ' d2-focus-next-on-enter' + 
+                                            ' ng-model="registeredTei.' + attId + '" ' +                                            
+                                            ' ng-blur="validationAndSkipLogic(registeredTei,\'' + attId + '\')" ' +
+                                            ' ng-required=" ' + trackedEntityFormAttributes[attId].mandatory + '"> ';
+                        }
+                        else {
+                            newInputField = '<input type="text" ' +
+                                            ' name="' + fieldName + '"' +
+                                            ' element-id="' + i + '"' + 
+                                            ' element-code="' + trackedEntityFormAttributes[attId].code + '"' +                                            
+                                            this.getAttributesAsString(attributes) +
+                                            ' d2-validation ' +
+                                            ' d2-focus-next-on-enter' + 
+                                            ' ng-model="registeredTei.' + attId + '" ' +                                            
+                                            ' ng-blur="validationAndSkipLogic(registeredTei,\'' + attId + '\')" ' +
+                                            ' ng-required=" ' + trackedEntityFormAttributes[attId].mandatory + '"> ';
+                        } 
+                    }                        
+                 
+                    if(target === 'ENROLLMENT' && attributes.hasOwnProperty('programid')){
+                        hasProgramDate = true;
+                        programId = attributes['programid'];                        
+                        if(programId === 'enrollmentDate'){
+                            fieldName = 'dateOfEnrollment';
+                            var enMaxDate = trackedEntity.selectEnrollmentDatesInFuture ? '' : 0;
+                            newInputField = '<input type="text" ' +
+                                            ' name="' + fieldName + '"' +
+                                            ' element-id="' + i + '"' +       
+                                            this.getAttributesAsString(attributes) +
+                                            ' d2-focus-next-on-enter' + 
+                                            ' placeholder="{{dhis2CalendarFormat.keyDateFormat}}" ' +
+                                            ' ng-model="enrollment.dateOfEnrollment" ' +
+                                            ' d2-date' +
+                                            ' max-date="' + enMaxDate + '"' + '\'' +
+                                            ' ng-required="true"> ';
+                        }
+                        if(programId === 'dateOfIncident'){
+                            fieldName = 'dateOfEnrollment';
+                            var inMaxDate = trackedEntity.selectIncidentDatesInFuture ? '' : 0;                            
+                            newInputField = '<input type="text" ' +
+                                            ' name="' + fieldName + '"' +
+                                            ' element-id="' + i + '"' +    
+                                            this.getAttributesAsString(attributes) +
+                                            ' d2-focus-next-on-enter' + 
+                                            ' placeholder="{{dhis2CalendarFormat.keyDateFormat}}" ' +
+                                            ' ng-model="enrollment.dateOfIncident" ' +
+                                            ' d2-date ' +
+                                            ' max-date="' + inMaxDate + '"' + + '\'' +
+                                            ' ng-required="true"> ';
+                        }
+                    }
+                    
+                    newInputField = //'<ng-form name="innerForm">' + 
+                                        newInputField + 
+                                        ' <span ng-show="outerForm.submitted && outerForm.'+ fieldName +'.$invalid" class="required">{{required}}</span> ' + 
+                                        ' <span ng-show="invalidInputs.' + fieldName + '"" class="required">{{invalidInputLabel}}</span> ';
+                                    //'</ng-form>';
+                                    
+                    htmlCode = htmlCode.replace(inputField, newInputField);                    
+                }                
+                return {htmlCode: htmlCode, hasProgramDate: hasProgramDate};
+            }            
+            return null;
+        },
+        getAttributesAsString: function(attributes){
+            if(attributes){
+                var attributesAsString = '';                
+                for(var prop in attributes){
+                    if(prop !== 'value'){
+                        attributesAsString += prop + '="' + attributes[prop] + '" ';
+                    }
+                }
+                return attributesAsString;
+            }
+            return null;
+        }
+    };            
+})
+
+/* Context menu for grid*/
+.service('ContextMenuSelectedItem', function(){
+    this.selectedItem = '';
+    
+    this.setSelectedItem = function(selectedItem){  
+        this.selectedItem = selectedItem;        
+    };
+    
+    this.getSelectedItem = function(){
+        return this.selectedItem;
+    };
+})
+
+/* Error messages*/
+.service('ErrorMessageService', function(){
+    this.errorMessages = {};
+    
+    this.setErrorMessages = function(errorMessages){  
+        this.errorMessages = errorMessages;        
+    };
+    
+    this.getErrorMessages = function(){
+        return this.errorMessages;
+    };
+    
+    this.get = function(id){
+        return this.errorMessages[id];
+    };
+})
+
+/* Modal service for user interaction */
+.service('ModalService', ['$modal', function($modal) {
+
+    var modalDefaults = {
+        backdrop: true,
+        keyboard: true,
+        modalFade: true,
+        templateUrl: 'views/modal.html'
+    };
+
+    var modalOptions = {
+        closeButtonText: 'Close',
+        actionButtonText: 'OK',
+        headerText: 'Proceed?',
+        bodyText: 'Perform this action?'
+    };
+
+    this.showModal = function(customModalDefaults, customModalOptions) {
+        if (!customModalDefaults)
+            customModalDefaults = {};
+        customModalDefaults.backdrop = 'static';
+        return this.show(customModalDefaults, customModalOptions);
+    };
+
+    this.show = function(customModalDefaults, customModalOptions) {
+        //Create temp objects to work with since we're in a singleton service
+        var tempModalDefaults = {};
+        var tempModalOptions = {};
+
+        //Map angular-ui modal custom defaults to modal defaults defined in service
+        angular.extend(tempModalDefaults, modalDefaults, customModalDefaults);
+
+        //Map modal.html $scope custom properties to defaults defined in service
+        angular.extend(tempModalOptions, modalOptions, customModalOptions);
+
+        if (!tempModalDefaults.controller) {
+            tempModalDefaults.controller = function($scope, $modalInstance) {
+                $scope.modalOptions = tempModalOptions;
+                $scope.modalOptions.ok = function(result) {
+                    $modalInstance.close(result);
+                };
+                $scope.modalOptions.close = function(result) {
+                    $modalInstance.dismiss('cancel');
+                };
+            };
+        }
+
+        return $modal.open(tempModalDefaults).result;
+    };
+
+}])
+
+/* Dialog service for user interaction */
+.service('DialogService', ['$modal', function($modal) {
+
+    var dialogDefaults = {
+        backdrop: true,
+        keyboard: true,
+        backdropClick: true,
+        modalFade: true,            
+        templateUrl: 'views/dialog.html'
+    };
+
+    var dialogOptions = {
+        closeButtonText: 'close',
+        actionButtonText: 'ok',
+        headerText: 'dhis2_tracker',
+        bodyText: 'Perform this action?'
+    };
+
+    this.showDialog = function(customDialogDefaults, customDialogOptions) {
+        if (!customDialogDefaults)
+            customDialogDefaults = {};
+        customDialogDefaults.backdropClick = false;
+        return this.show(customDialogDefaults, customDialogOptions);
+    };
+
+    this.show = function(customDialogDefaults, customDialogOptions) {
+        //Create temp objects to work with since we're in a singleton service
+        var tempDialogDefaults = {};
+        var tempDialogOptions = {};
+
+        //Map angular-ui modal custom defaults to modal defaults defined in service
+        angular.extend(tempDialogDefaults, dialogDefaults, customDialogDefaults);
+
+        //Map modal.html $scope custom properties to defaults defined in service
+        angular.extend(tempDialogOptions, dialogOptions, customDialogOptions);
+
+        if (!tempDialogDefaults.controller) {
+            tempDialogDefaults.controller = function($scope, $modalInstance) {
+                $scope.dialogOptions = tempDialogOptions;
+                $scope.dialogOptions.ok = function(result) {
+                    $modalInstance.close(result);
+                };                           
+            };
+        }
+
+        return $modal.open(tempDialogDefaults).result;
+    };
+
+}])
+
+.service('Paginator', function () {
+    this.page = 1;
+    this.pageSize = 50;
+    this.itemCount = 0;
+    this.pageCount = 0;
+    this.toolBarDisplay = 5;
+
+    this.setPage = function (page) {
+        if (page > this.getPageCount()) {
+            return;
+        }
+
+        this.page = page;
+    };
+    
+    this.getPage = function(){
+        return this.page;
+    };
+    
+    this.setPageSize = function(pageSize){
+      this.pageSize = pageSize;
+    };
+    
+    this.getPageSize = function(){
+        return this.pageSize;
+    };
+    
+    this.setItemCount = function(itemCount){
+      this.itemCount = itemCount;
+    };
+    
+    this.getItemCount = function(){
+        return this.itemCount;
+    };
+    
+    this.setPageCount = function(pageCount){
+        this.pageCount = pageCount;
+    };
+
+    this.getPageCount = function () {
+        return this.pageCount;
+    };
+
+    this.lowerLimit = function() { 
+        var pageCountLimitPerPageDiff = this.getPageCount() - this.toolBarDisplay;
+
+        if (pageCountLimitPerPageDiff < 0) { 
+            return 0; 
+        }
+
+        if (this.getPage() > pageCountLimitPerPageDiff + 1) { 
+            return pageCountLimitPerPageDiff; 
+        } 
+
+        var low = this.getPage() - (Math.ceil(this.toolBarDisplay/2) - 1); 
+
+        return Math.max(low, 0);
+    };
+})
+
+.service('GridColumnService', function(){
+    return {        
+        columnExists: function(cols, id) {
+            var colExists = false;
+            if(!angular.isObject(cols) || !id || angular.isObject(cols) && !cols.length){
+                return colExists;
+            }
+            
+            for(var i=0; i<cols.length && !colExists; i++){
+                if(cols[i].id === id){
+                    colExists = true;
+                }
+            }
+            return colExists;
+        }
+    };
+});

=== modified file 'dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/scripts/services.js'
--- dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/scripts/services.js	2015-01-29 17:13:36 +0000
+++ dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/scripts/services.js	2015-02-02 13:42:37 +0000
@@ -390,13 +390,17 @@
 .factory('TEFormService', function(TCStorageService, $q, $rootScope) {
 
     return {
-        getByProgram: function(programUid){            
+        getByProgram: function(program, attributes){            
             var def = $q.defer();
             
             TCStorageService.currentStore.open().done(function(){
-                TCStorageService.currentStore.get('trackedEntityForms', programUid).done(function(te){                    
+                TCStorageService.currentStore.get('trackedEntityForms', program.id).done(function(teForm){                    
                     $rootScope.$apply(function(){
-                        def.resolve(te);
+                        var trackedEntityForm = teForm;
+                        trackedEntityForm.attributes = attributes;
+                        trackedEntityForm.selectIncidentDatesInFuture = program.selectIncidentDatesInFuture;
+                        trackedEntityForm.selectEnrollmentDatesInFuture = program.selectEnrollmentDatesInFuture;                        
+                        def.resolve(trackedEntityForm);
                     });
                 });
             });                        

=== modified file 'dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/tracker-capture.appcache'
--- dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/tracker-capture.appcache	2015-01-23 14:07:46 +0000
+++ dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/tracker-capture.appcache	2015-02-02 13:42:37 +0000
@@ -39,6 +39,10 @@
 ../dhis-web-commons/javascripts/angular/plugins/angular-translate.min.js
 ../dhis-web-commons/javascripts/angular/plugins/angular-translate-loader-static-files.min.js
 ../dhis-web-commons/javascripts/angular/plugins/angular-translate-loader-url.min.js
+../dhis-web-commons/javascripts/dhis2/angular.directives.js
+../dhis-web-commons/javascripts/dhis2/angular.filters.js
+../dhis-web-commons/javascripts/dhis2/angular.services.js
+../dhis-web-commons/javascripts/dhis2/angular.controllers.js
 
 ../dhis-web-commons/javascripts/moment/moment-with-langs.min.js       
 

=== modified file 'dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/css/green/green.css'
--- dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/css/green/green.css	2014-11-26 13:33:14 +0000
+++ dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/css/green/green.css	2015-02-02 13:42:37 +0000
@@ -42,7 +42,7 @@
   text-decoration: underline;
 }
 
-input[type=text],input[type=password],textarea
+input[type=text],input[type=password],input[type=email],textarea
 {
   border: 1px solid #aaa;
   padding: 4px 1px;

=== modified file 'dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/css/india/india.css'
--- dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/css/india/india.css	2014-07-23 06:59:30 +0000
+++ dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/css/india/india.css	2015-02-02 13:42:37 +0000
@@ -42,7 +42,7 @@
   text-decoration: underline;
 }
 
-input[type=text],input[type=password],textarea
+input[type=text],input[type=password],input[type=email],textarea
 {
   border: 1px solid #aaa;
   padding: 4px 1px;

=== modified file 'dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/css/light_blue/light_blue.css'
--- dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/css/light_blue/light_blue.css	2014-11-26 13:33:14 +0000
+++ dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/css/light_blue/light_blue.css	2015-02-02 13:42:37 +0000
@@ -42,7 +42,7 @@
   text-decoration: underline;
 }
 
-input[type=text],input[type=password],textarea
+input[type=text],input[type=password],input[type=email],textarea
 {
   border: 1px solid #aaa;
   padding: 4px 1px;

=== modified file 'dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/css/myanmar/myanmar.css'
--- dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/css/myanmar/myanmar.css	2014-11-26 13:33:14 +0000
+++ dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/css/myanmar/myanmar.css	2015-02-02 13:42:37 +0000
@@ -42,7 +42,7 @@
   text-decoration: underline;
 }
 
-input[type=text],input[type=password],textarea
+input[type=text],input[type=password],input[type=email],textarea
 {
   border: 1px solid #aaa;
   padding: 4px 1px;

=== modified file 'dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/css/vietnam/vietnam.css'
--- dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/css/vietnam/vietnam.css	2014-11-26 13:33:14 +0000
+++ dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/css/vietnam/vietnam.css	2015-02-02 13:42:37 +0000
@@ -42,7 +42,7 @@
   text-decoration: underline;
 }
 
-input[type=text],input[type=password],textarea
+input[type=text],input[type=password],input[type=email],textarea
 {
   border: 1px solid #aaa;
   padding: 4px 1px;

=== removed directory 'dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/angular/plugins/dhis2'
=== removed file 'dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/angular/plugins/dhis2/controllers.js'
--- dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/angular/plugins/dhis2/controllers.js	2015-01-29 17:13:36 +0000
+++ dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/angular/plugins/dhis2/controllers.js	1970-01-01 00:00:00 +0000
@@ -1,53 +0,0 @@
-'use strict';
-
-/* Controllers */
-var d2Controllers = angular.module('d2Controllers', [])
-
-//Controller for column show/hide
-.controller('ColumnDisplayController', 
-    function($scope, 
-            $modalInstance, 
-            hiddenGridColumns,
-            gridColumns){
-    
-    $scope.gridColumns = gridColumns;
-    $scope.hiddenGridColumns = hiddenGridColumns;
-    
-    $scope.close = function () {
-      $modalInstance.close($scope.gridColumns);
-    };
-    
-    $scope.showHideColumns = function(gridColumn){
-       
-        if(gridColumn.show){                
-            $scope.hiddenGridColumns--;            
-        }
-        else{
-            $scope.hiddenGridColumns++;            
-        }      
-    };    
-})
-
-//controller for dealing with google map
-.controller('MapController',
-        function($scope, 
-                $modalInstance,
-                DHIS2URL,
-                geoJsons,
-                location) {
-    
-    $scope.home = function(){        
-        window.location = DHIS2URL;
-    };
-    
-    $scope.location = location;
-    $scope.geoJsons = geoJsons;
-    
-    $scope.close = function () {
-        $modalInstance.close();
-    };
-    
-    $scope.captureCoordinate = function(){        
-        $modalInstance.close($scope.location);
-    };
-});
\ No newline at end of file

=== removed file 'dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/angular/plugins/dhis2/directives.js'
--- dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/angular/plugins/dhis2/directives.js	2015-01-27 22:51:17 +0000
+++ dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/angular/plugins/dhis2/directives.js	1970-01-01 00:00:00 +0000
@@ -1,582 +0,0 @@
-'use strict';
-
-/* Directives */
-
-var d2Directives = angular.module('d2Directives', [])
-
-
-.directive('d2OuSearch', function() {
-    
-    return {
-        restrict: 'E',
-        template: '<div style="margin-top:20px">\n\
-                    <img id="searchIcon" src="../images/search.png" style="cursor: pointer" title="{{ \'locate_organisation_unit_by_name\' | translate}}">\n\
-                    <span id="searchSpan" style="width:100%;display:none;">\n\
-                        <input type="text" id="searchField" name="key"/>\n\
-                        <input type="button" value="{{\'find\' | translate}}" onclick="selection.findByName()"/>\n\
-                    </span>\n\
-                  </div>',
-        link: function (scope, element, attrs) {
-            
-            $("#searchIcon").click(function() {
-                $("#searchSpan").toggle();
-                $("#searchField").focus();
-            });
-
-            $("#searchField").autocomplete({
-                source: "../dhis-web-commons/ouwt/getOrganisationUnitsByName.action",
-                select: function(event, ui) {
-                    $("#searchField").val(ui.item.value);
-                    selection.findByName();
-                }
-            });
-        }
-    };
-})
-
-.directive('inputValidator', function() {
-    
-    return {
-        require: 'ngModel',
-        link: function (scope, element, attrs, ctrl) {  
-
-            ctrl.$parsers.push(function (value) {
-                return parseFloat(value || '');
-            });
-        }
-    };
-})
-
-.directive('selectedOrgUnit', function($timeout, storage) {        
-
-    return {        
-        restrict: 'A',        
-        link: function(scope, element, attrs){
-            
-            //once ou tree is loaded, start meta-data download
-            $(function() {
-                dhis2.ou.store.open().done( function() {
-                    selection.load();
-                    $( "#orgUnitTree" ).one( "ouwtLoaded", function(event, ids, names) {
-                        console.log('Finished loading orgunit tree');
-                        
-                        //Disable ou selection until meta-data has downloaded
-                        $( "#orgUnitTree" ).addClass( "disable-clicks" );
-                        
-                        $timeout(function() {
-                            scope.treeLoaded = true;
-                            scope.$apply();
-                        });
-                        
-                        downloadMetaData();
-                    });
-                });
-            });
-            
-            //listen to user selection, and inform angular         
-            selection.setListenerFunction( setSelectedOu, true );
-            
-            function setSelectedOu( ids, names ) {
-                var ou = {id: ids[0], name: names[0]};
-                $timeout(function() {
-                    scope.selectedOrgUnit = ou;
-                    scope.$apply();
-                });
-            }
-        }  
-    };
-})
-
-.directive('blurOrChange', function() {
-    
-    return function( scope, elem, attrs) {
-        elem.calendarsPicker({
-            onSelect: function() {
-                scope.$apply(attrs.blurOrChange);
-                $(this).change();                                        
-            }
-        }).change(function() {
-            scope.$apply(attrs.blurOrChange);
-        });
-    };
-})
-
-.directive('d2Enter', function () {
-    return function (scope, element, attrs) {
-        element.bind("keydown keypress", function (event) {
-            if(event.which === 13) {
-                scope.$apply(function (){
-                    scope.$eval(attrs.d2Enter);
-                });
-                event.preventDefault();
-            }
-        });
-    };
-})
-
-.directive('d2NumberValidation', function(ErrorMessageService, $translate) {
-    
-    return {
-        require: 'ngModel',
-        restrict: 'A',
-        link: function (scope, element, attrs, ctrl) {
-            
-            function checkValidity(numberType, value){
-                var isValid = false;
-                switch(numberType){
-                    case "number":
-                        isValid = dhis2.validation.isNumber(value);
-                        break;
-                    case "posInt":
-                        isValid = dhis2.validation.isPositiveInt(value);
-                        break;
-                    case "negInt":
-                        isValid = dhis2.validation.isNegativeInt(value);
-                        break;
-                    case "zeroPositiveInt":
-                        isValid = dhis2.validation.isZeroOrPositiveInt(value);
-                        break;
-                    case "int":
-                        isValid = dhis2.validation.isInt(value);
-                        break;
-                    default:
-                        isValid = true;
-                }
-                return isValid;
-            }
-            
-            var errorMessages = ErrorMessageService.getErrorMessages();
-            var fieldName = attrs.inputFieldId;
-            var numberType = attrs.numberType;
-            var isRequired = attrs.ngRequired === 'true';
-            var msg = $translate(numberType)+ ' ' + $translate('required');
-           
-            ctrl.$parsers.unshift(function(value) {
-            	if(value){
-                    var isValid = checkValidity(numberType, value);                    
-                    if(!isValid){
-                        errorMessages[fieldName] = $translate('value_must_be_' + numberType);
-                    }
-                    else{
-                        if(isRequired){
-                            errorMessages[fieldName] = msg;
-                        }
-                        else{
-                            errorMessages[fieldName] = "";
-                        }
-                    }
-                    
-                    ErrorMessageService.setErrorMessages(errorMessages);
-                	ctrl.$setValidity(fieldName, isValid);
-                    return value;
-                }
-                
-                if(value === ''){
-                    if(isRequired){
-                        errorMessages[fieldName] = msg;
-                    }
-                    else{
-                        ctrl.$setValidity(fieldName, true);
-                        errorMessages[fieldName] = "";
-                    }
-                    
-                    ErrorMessageService.setErrorMessages(errorMessages);
-                    return undefined;
-                }              
-            });
-           
-            ctrl.$formatters.unshift(function(value) {                
-                if(value){
-                    var isValid = checkValidity(numberType, value);
-                    ctrl.$setValidity(fieldName, isValid);
-                    return value;
-                }
-            });
-        }
-    };
-})
-
-.directive('typeaheadOpenOnFocus', function () {
-  	
-  	return {
-        require: ['typeahead', 'ngModel'],
-        link: function (scope, element, attr, ctrls) {
-            element.bind('focus', function () {
-                ctrls[0].getMatchesAsync(ctrls[1].$viewValue);                
-                scope.$watch(attr.ngModel, function(value) {
-                    if(value === '' || angular.isUndefined(value)){
-                        ctrls[0].getMatchesAsync(ctrls[1].$viewValue);
-                    }                
-                });
-            });
-        }
-    };
-})
-
-.directive('d2TypeaheadValidation', function() {
-    
-    return {
-        require: ['typeahead', 'ngModel'],
-        restrict: 'A',
-        link: function (scope, element, attrs, ctrls) {
-            element.bind('blur', function () {                
-                if(ctrls[1].$viewValue && !ctrls[1].$modelValue && ctrls[0].active === -1){
-                    ctrls[1].$setViewValue();
-                    ctrls[1].$render();
-                }                
-            });
-        }
-    };
-})
-
-.directive('d2PopOver', function($compile, $templateCache){
-    
-    return {        
-        restrict: 'EA',
-        link: function(scope, element, attrs){
-            var content = $templateCache.get("popover.html");
-            content = $compile(content)(scope);
-            var options = {
-                    content: content,
-                    placement: 'bottom',
-                    trigger: 'hover',
-                    html: true,
-                    title: scope.title               
-                };            
-            $(element).popover(options);
-        },
-        scope: {
-            content: '=',
-            title: '@details',
-            template: "@template"
-        }
-    };
-})
-
-.directive('sortable', function() {        
-
-    return {        
-        restrict: 'A',        
-        link: function(scope, element, attrs){
-            element.sortable({
-                connectWith: ".connectedSortable",
-                placeholder: "ui-state-highlight",
-                tolerance: "pointer",
-                handle: '.handle'
-            });
-        }  
-    };
-})
-
-.directive('serversidePaginator', function factory() {
-    
-    return {
-        restrict: 'E',
-        controller: function ($scope, Paginator) {
-            $scope.paginator = Paginator;
-        },
-        templateUrl: '../dhis-web-commons/paging/serverside-pagination.html'
-    };
-})
-
-.directive('draggableModal', function(){
-    
-    return {
-      	restrict: 'EA',
-      	link: function(scope, element) {
-        	element.draggable();
-      	}
-    };  
-})
-
-.directive('d2GoogleMap', function ($parse, $compile, storage) {
-    return {
-        restrict: 'E',
-        replace: true,
-        template: '<div></div>',
-        link: function(scope, element, attrs){
-            
-            //remove angular bootstrap ui modal draggable
-            $(".modal-content").draggable({ disabled: true });
-            
-            //get a default center
-            var latCenter = 12.31, lngCenter = 51.48;            
-            
-            //if there is any marker already - use it as center
-            if(angular.isObject(scope.location)){
-                if(scope.location.lat && scope.location.lng){
-                    latCenter = scope.location.lat;
-                    lngCenter = scope.location.lng;
-                }                
-            }
-            
-            //default map configurations 
-            var mapOptions = {
-                zoom: 3,
-                center: new google.maps.LatLng(latCenter, lngCenter),
-                mapTypeId: google.maps.MapTypeId.ROADMAP
-            },featureStyle = {
-                strokeWeight: 2,
-                strokeOpacity: 0.4,
-                fillOpacity: 0.4,
-                fillColor: 'green'
-            };
-            
-            var geojsons = $parse(attrs.geojsons)(scope);
-            var currentLayer = 0, currentGeojson = geojsons[0]; 
-            
-            var map = new google.maps.Map(document.getElementById(attrs.id), mapOptions);            
-            var currentGeojsonFeatures = map.data.addGeoJson(currentGeojson);
-            
-            var marker = new google.maps.Marker({
-                map: map
-            });
-            
-            if(angular.isObject(scope.location)){
-                if(scope.location.lat && scope.location.lng){                    
-                    addMarker({lat: scope.location.lat, lng: scope.location.lng});                    
-                }                
-            }
-            
-            function addMarker(loc){
-                var latLng = new google.maps.LatLng(loc.lat, loc.lng);
-                marker.setPosition(latLng);
-            }
-            
-            function centerMap(){
-                
-                if(currentGeojson && currentGeojson.features){
-                    var latLngBounds = new google.maps.LatLngBounds();
-                    angular.forEach(currentGeojson.features, function(feature){
-                        if(feature.geometry.type === 'MultiPolygon'){
-                            angular.forEach(feature.geometry.coordinates[0][0], function(coordinate){
-                                latLngBounds.extend(new google.maps.LatLng(coordinate[1],coordinate[0]));
-                            });
-                        }
-                        else if(feature.geometry.type === 'Point'){                        
-                            latLngBounds.extend(new google.maps.LatLng(feature.geometry.coordinates[1],feature.geometry.coordinates[0]));
-                        }
-                    });
-                    
-                    map.fitBounds(latLngBounds);
-                    map.panToBounds(latLngBounds);
-                }                
-            }
-            
-            function initializeMap(){                
-                google.maps.event.addListenerOnce(map, 'idle', function(){
-                    google.maps.event.trigger(map, 'resize');
-                    map.data.setStyle(featureStyle);
-                    centerMap();
-                });
-            }
-            
-            map.data.addListener('mouseover', function(e) {                
-                $("#polygon-label").text( e.feature.k.name );
-                map.data.revertStyle();
-                map.data.overrideStyle(e.feature, {fillOpacity: 0.8});
-            });
-            
-            map.data.addListener('mouseout', function() {                
-                $("#polygon-label").text( '' );
-                map.data.revertStyle();
-            });
-            
-            //drill-down based on polygons assigned to orgunits
-            map.data.addListener('rightclick', function(e){                
-                for (var i = 0; i < currentGeojsonFeatures.length; i++){
-                    map.data.remove(currentGeojsonFeatures[i]);
-                }
-                                
-                if(currentLayer >= geojsons.length-1){
-                    currentLayer = 0;
-                    currentGeojson = angular.copy(geojsons[currentLayer]);                    
-                }
-                else{
-                    currentLayer++;
-                    currentGeojson = angular.copy(geojsons[currentLayer]);
-                    currentGeojson.features = [];
-                    var selectedFeatures = [];
-                    angular.forEach(geojsons[currentLayer].features, function(feature){                    
-                        if(feature.properties.parent === e.feature.B){
-                            selectedFeatures.push(feature);
-                        }
-                    });
-                    
-                    if(selectedFeatures.length){
-                        currentGeojson.features = selectedFeatures;
-                    }                   
-                }                
-                currentGeojsonFeatures = map.data.addGeoJson(currentGeojson);
-                centerMap();         
-            });            
-            
-            //capturing coordinate from defined polygons
-            map.data.addListener('click', function(e) {                
-                scope.$apply(function(){
-                    addMarker({
-                       lat: e.latLng.lat(),
-                       lng: e.latLng.lng()
-                    });
-                    $parse(attrs.location).assign(scope.$parent, {lat: e.latLng.lat(), lng: e.latLng.lng()});                    
-                });                
-            });
-            
-            //capturing coordinate from anywhere in the map - incase no polygons are defined
-            google.maps.event.addListener(map, 'click', function(e){                
-                scope.$apply(function(){
-                    addMarker({
-                       lat: e.latLng.lat(),
-                       lng: e.latLng.lng()
-                    });
-                    $parse(attrs.location).assign(scope.$parent, {lat: e.latLng.lat(), lng: e.latLng.lng()});                    
-                });                
-            });
-            
-            initializeMap();
-        }
-    };
-})
-
-.directive('d2CustomForm', function($compile, $parse, CustomFormService) {
-    return{ 
-        restrict: 'E',
-        link: function(scope, elm, attrs){            
-             scope.$watch('customForm', function(){
-                 elm.html(scope.customForm.htmlCode);
-                 $compile(elm.contents())(scope);
-             });
-        }
-    };
-})
-
-.directive('d2ContextMenu', function(ContextMenuSelectedItem) {
-        
-    return {        
-        restrict: 'A',
-        link: function(scope, element, attrs){
-            var contextMenu = $("#contextMenu");                   
-            
-            element.click(function (e) {
-                var selectedItem = $.parseJSON(attrs.selectedItem);
-                ContextMenuSelectedItem.setSelectedItem(selectedItem);
-                
-                var menuHeight = contextMenu.height();
-                var menuWidth = contextMenu.width();
-                var winHeight = $(window).height();
-                var winWidth = $(window).width();
-
-                var pageX = e.pageX;
-                var pageY = e.pageY;
-
-                contextMenu.show();
-
-                if( (menuWidth + pageX) > winWidth ) {
-                  pageX -= menuWidth;
-                }
-
-                if( (menuHeight + pageY) > winHeight ) {
-                  pageY -= menuHeight;
-
-                  if( pageY < 0 ) {
-                      pageY = e.pageY;
-                  }
-                }
-                
-                contextMenu.css({
-                    left: pageX,
-                    top: pageY
-                });
-
-                return false;
-            });
-            
-            contextMenu.on("click", "a", function () {                    
-                contextMenu.hide();
-            });
-
-            $(document).click(function () {                                        
-                contextMenu.hide();
-            });
-        }     
-    };
-})
-
-.directive('d2Date', function(DateUtils, CalendarService, ErrorMessageService, $translate, $parse) {
-    return {
-        restrict: 'A',
-        require: 'ngModel',        
-        link: function(scope, element, attrs, ctrl) {    
-            
-            var errorMessages = ErrorMessageService.getErrorMessages();
-            var fieldName = attrs.inputFieldId;
-            var isRequired = attrs.ngRequired === 'true';
-            var calendarSetting = CalendarService.getSetting();            
-            var dateFormat = 'yyyy-mm-dd';
-            if(calendarSetting.keyDateFormat === 'dd-MM-yyyy'){
-                dateFormat = 'dd-mm-yyyy';
-            }            
-            
-            var minDate = $parse(attrs.minDate)(scope), 
-                maxDate = $parse(attrs.maxDate)(scope),
-                calendar = $.calendars.instance(calendarSetting.keyCalendar);
-            
-            element.calendarsPicker({
-                changeMonth: true,
-                dateFormat: dateFormat,
-                yearRange: '-120:+30',
-                minDate: minDate,
-                maxDate: maxDate,
-                calendar: calendar,
-                duration: "fast",
-                showAnim: "",
-                renderer: $.calendars.picker.themeRollerRenderer,
-                onSelect: function(date) {
-                    $(this).change();
-                }
-            })
-            .change(function() {                
-                if(this.value){                    
-                    var rawDate = this.value;
-                    var convertedDate = DateUtils.format(this.value);
-
-                    var isValid = rawDate == convertedDate;
-                    
-                    if(!isValid){
-                        errorMessages[fieldName] = $translate('date_required');
-                    }
-                    else{
-                        if(isRequired){
-                            errorMessages[fieldName] = $translate('required');
-                        }
-                        else{
-                            errorMessages[fieldName] = "";
-                        }
-                        if(maxDate === 0){                    
-                            isValid = !moment(convertedDate, calendarSetting.momentFormat).isAfter(DateUtils.getToday());
-                            if(!isValid){
-                                errorMessages[fieldName] = $translate('future_date_not_allowed');                            
-                            }                           
-                        }
-                    }                                        
-                    ctrl.$setViewValue(this.value);
-                    ctrl.$setValidity(fieldName, isValid);
-                }
-                else{
-                    if(!isRequired){
-                        ctrl.$setViewValue(this.value);
-                        ctrl.$setValidity(fieldName, !isRequired);
-                        errorMessages[fieldName] = "";
-                    }
-                    else{
-                        errorMessages[fieldName] = $translate('required');                        
-                    }
-                }
-                
-                ErrorMessageService.setErrorMessages(errorMessages);
-                this.focus();
-                scope.$apply();
-            });    
-        }      
-    };   
-});
\ No newline at end of file

=== removed file 'dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/angular/plugins/dhis2/filters.js'
--- dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/angular/plugins/dhis2/filters.js	2014-12-04 13:04:31 +0000
+++ dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/angular/plugins/dhis2/filters.js	1970-01-01 00:00:00 +0000
@@ -1,97 +0,0 @@
-'use strict';
-
-/* Filters */
-
-var d2Filters = angular.module('d2Filters', [])
-
-.filter('gridFilter', function($filter, CalendarService){    
-    
-    return function(data, filters, filterTypes){
-
-        if(!data ){
-            return;
-        }
-        
-        if(!filters){
-            return data;
-        }        
-        else{            
-            
-            var dateFilter = {}, 
-                textFilter = {}, 
-                numberFilter = {},
-                filteredData = data;
-            
-            for(var key in filters){
-                
-                if(filterTypes[key] === 'date'){
-                    if(filters[key].start || filters[key].end){
-                        dateFilter[key] = filters[key];
-                    }
-                }
-                else if(filterTypes[key] === 'int'){
-                    if(filters[key].start || filters[key].end){
-                        numberFilter[key] = filters[key];
-                    }
-                }
-                else{
-                    textFilter[key] = filters[key];
-                }
-            }
-            
-            filteredData = $filter('filter')(filteredData, textFilter); 
-            filteredData = $filter('filter')(filteredData, dateFilter, dateComparator);            
-            filteredData = $filter('filter')(filteredData, numberFilter, numberComparator);
-                        
-            return filteredData;
-        } 
-    }; 
-    
-    function dateComparator(data,filter){
-    	var calendarSetting = CalendarService.getSetting(); 
-        var start = moment(filter.start, calendarSetting.momentFormat);
-        var end = moment(filter.end, calendarSetting.momentFormat);  
-        var date = moment(data, calendarSetting.momentFormat); 
-        
-        if(filter.start && filter.end){
-            return ( Date.parse(date) <= Date.parse(end) ) && (Date.parse(date) >= Date.parse(start));
-        }        
-        return ( Date.parse(date) <= Date.parse(end) ) || (Date.parse(date) >= Date.parse(start));
-    }
-    
-    function numberComparator(data,filter){
-        var start = filter.start;
-        var end = filter.end;
-        
-        if(filter.start && filter.end){
-            return ( data <= end ) && ( data >= start );
-        }        
-        return ( data <= end ) || ( data >= start );
-    }
-})
-
-.filter('paginate', function(Paginator) {
-    return function(input, rowsPerPage) {
-        if (!input) {
-            return input;
-        }
-
-        if (rowsPerPage) {
-            Paginator.rowsPerPage = rowsPerPage;
-        }       
-        
-        Paginator.itemCount = input.length;
-
-        return input.slice(parseInt(Paginator.page * Paginator.rowsPerPage), parseInt((Paginator.page + 1) * Paginator.rowsPerPage + 1) - 1);
-    };
-})
-
-.filter('forLoop', function() {
-    return function(input, start, end) {
-        input = new Array(end - start);
-        for (var i = 0; start < end; start++, i++) {
-            input[i] = start;
-        }
-        return input;
-    };
-});
\ No newline at end of file

=== removed file 'dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/angular/plugins/dhis2/services.js'
--- dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/angular/plugins/dhis2/services.js	2015-01-26 17:47:09 +0000
+++ dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/angular/plugins/dhis2/services.js	1970-01-01 00:00:00 +0000
@@ -1,560 +0,0 @@
-/* Pagination service */
-var d2Services = angular.module('d2Services', ['ngResource'])
-
-/* Factory for loading translation strings */
-.factory('i18nLoader', function ($q, $http, storage, DialogService) {
- 
-    var getTranslationStrings = function(locale){
-        var defaultUrl = 'i18n/i18n_app.properties';
-        var url = '';
-        if(locale === 'en' || !locale){
-            url = defaultUrl;
-        }
-        else{
-            url = 'i18n/i18n_app_' + locale + '.properties';
-        }
-
-        var tx = {locale: locale};
-
-        var promise = $http.get(url).then(function(response){
-            tx= {locale: locale, keys: dhis2.util.parseJavaProperties(response.data)};
-            return tx;
-        }, function(){
-            var dialogOptions = {
-                headerText: 'missing_translation_file',
-                bodyText: 'missing_translation_using_default'
-            };
-
-            DialogService.showDialog({}, dialogOptions);
-            var p = $http.get(defaultUrl).then(function(response){
-                tx= {locale: locale, keys: dhis2.util.parseJavaProperties(response.data)};
-                return tx;
-            });
-            return p;
-        });
-        return promise;
-    };
-
-    var getLocale = function(){
-        var locale = 'en';
-
-        var promise = $http.get('../api/me/profile.json').then(function(response){
-            storage.set('USER_PROFILE', response.data);
-            if(response.data && response.data.settings && response.data.settings.keyUiLocale){
-                locale = response.data.settings.keyUiLocale;
-            }
-            return locale;
-        }, function(){
-            return locale;
-        });
-
-        return promise;
-    };
-    return function () {
-        var deferred = $q.defer(), translations;    
-        var userProfile = storage.get('USER_PROFILE');
-        if(userProfile && userProfile.settings && userProfile.settings.keyUiLocale){                
-            getTranslationStrings(userProfile.settings.keyUiLocale).then(function(response){
-                translations = response.keys;
-                deferred.resolve(translations);
-            });
-            return deferred.promise;
-        }
-        else{
-            getLocale().then(function(locale){
-                getTranslationStrings(locale).then(function(response){
-                    translations = response.keys;
-                    deferred.resolve(translations);
-                });
-            });
-            return deferred.promise;
-        }
-    };
-})
-
-/* Factory for loading external data */
-.factory('ExternalDataFactory', function($http) {
-
-    return {        
-        get: function(fileName) {
-            var promise = $http.get( fileName ).then(function(response){
-                return response.data;
-            });            
-            return promise;
-        }
-    };
-})
-
-/* service for getting calendar setting */
-.service('CalendarService', function(storage, $rootScope){    
-
-    return {
-        getSetting: function() {
-            
-            var dhis2CalendarFormat = {keyDateFormat: 'yyyy-MM-dd', keyCalendar: 'gregorian', momentFormat: 'YYYY-MM-DD'};                
-            var storedFormat = storage.get('CALENDAR_SETTING');
-            if(angular.isObject(storedFormat) && storedFormat.keyDateFormat && storedFormat.keyCalendar){
-                if(storedFormat.keyCalendar === 'iso8601'){
-                    storedFormat.keyCalendar = 'gregorian';
-                }
-
-                if(storedFormat.keyDateFormat === 'dd-MM-yyyy'){
-                    dhis2CalendarFormat.momentFormat = 'DD-MM-YYYY';
-                }
-                
-                dhis2CalendarFormat.keyCalendar = storedFormat.keyCalendar;
-                dhis2CalendarFormat.keyDateFormat = storedFormat.keyDateFormat;
-            }
-            $rootScope.dhis2CalendarFormat = dhis2CalendarFormat;
-            return dhis2CalendarFormat;
-        }
-    };            
-})
-
-/* service for dealing with dates */
-.service('DateUtils', function($filter, CalendarService){
-    
-    return {        
-        getDate: function(dateValue){
-            if(!dateValue){
-                return;
-            }            
-            var calendarSetting = CalendarService.getSetting();
-            dateValue = moment(dateValue, calendarSetting.momentFormat)._d;
-            return Date.parse(dateValue);
-        },
-        format: function(dateValue) {            
-            if(!dateValue){
-                return;
-            }
-
-            var calendarSetting = CalendarService.getSetting();            
-            dateValue = moment(dateValue, calendarSetting.momentFormat)._d;
-            dateValue = $filter('date')(dateValue, calendarSetting.keyDateFormat);            
-            return dateValue;
-        },
-        formatToHrsMins: function(dateValue) {
-            var calendarSetting = CalendarService.getSetting();
-            var dateFormat = 'YYYY-MM-DD @ hh:mm A';
-            if(calendarSetting.keyDateFormat === 'dd-MM-yyyy'){
-                dateFormat = 'DD-MM-YYYY @ hh:mm A';
-            }            
-            return moment(dateValue).format(dateFormat);
-        },
-        getToday: function(){  
-            var calendarSetting = CalendarService.getSetting();
-            var tdy = $.calendars.instance(calendarSetting.keyCalendar).newDate();            
-            var today = moment(tdy._year + '-' + tdy._month + '-' + tdy._day, 'YYYY-MM-DD')._d;            
-            today = Date.parse(today);     
-            today = $filter('date')(today,  calendarSetting.keyDateFormat);
-            return today;
-        },
-        formatFromUserToApi: function(dateValue){            
-            if(!dateValue){
-                return;
-            }
-            var calendarSetting = CalendarService.getSetting();
-            dateValue = moment(dateValue, calendarSetting.momentFormat)._d;
-            dateValue = Date.parse(dateValue);     
-            dateValue = $filter('date')(dateValue, 'yyyy-MM-dd'); 
-            return dateValue;            
-        },
-        formatFromApiToUser: function(dateValue){            
-            if(!dateValue){
-                return;
-            }            
-            var calendarSetting = CalendarService.getSetting();
-            dateValue = moment(dateValue, 'YYYY-MM-DD')._d;
-            return $filter('date')(dateValue, calendarSetting.keyDateFormat); 
-        }
-    };
-})
-
-/* service for dealing with custom form */
-.service('CustomFormService', function(){
-    
-    return {
-        getForProgramStage: function(programStage){
-            
-            var htmlCode = programStage.dataEntryForm ? programStage.dataEntryForm.htmlCode : null;  
-            
-            if(htmlCode){                
-            
-                var programStageDataElements = [];
-
-                angular.forEach(programStage.programStageDataElements, function(prStDe){
-                    programStageDataElements[prStDe.dataElement.id] = prStDe;
-                });
-
-                var inputRegex = /<input.*?\/>/g,
-                    match,
-                    inputFields = [];                
-
-                while (match = inputRegex.exec(htmlCode)) {                
-                    inputFields.push(match[0]);
-                }
-                
-                for(var i=0; i<inputFields.length; i++){                    
-                    var inputField = inputFields[i];                    
-                    var inputElement = $.parseHTML( inputField );
-                    var attributes = {};
-                                       
-                    $(inputElement[0].attributes).each(function() {
-                        attributes[this.nodeName] = this.value;                       
-                    });
-                    
-                    var fieldId = '', errorMessageId = '', newInputField, hasEventDate = false;     
-                    if(attributes.hasOwnProperty('id')){
-                        
-                        if(attributes['id'] === 'executionDate'){
-                            fieldId = 'eventDate';
-                            errorMessageId = '"' + 'eventDate' + '"';
-                            hasEventDate = true;
-                            
-                            //name needs to be unique so that it can be used for validation in angularjs
-                            if(attributes.hasOwnProperty('name')){
-                                attributes['name'] = fieldId;
-                            }
-                            
-                            newInputField = '<input type="text" ' +
-                                                this.getAttributesAsString(attributes) +
-                                                ' d2-validation ' +
-                                                ' ng-model="currentEvent.' + fieldId + '"' +
-                                                ' input-field-id="' + fieldId + '"' +
-                                                ' d2-date ' +
-                                                ' max-date="' + 0 + '"' + 
-                                                ' placeholder="{{dhis2CalendarFormat.keyDateFormat}}" ' +
-                                                ' ng-class="getInputNotifcationClass(prStDes.' + fieldId + '.dataElement.id,true)"' +
-                                                ' blur-or-change="saveDatavalue(prStDes.'+ fieldId + ')"' + 
-                                                ' ng-required="{{true}}">';
-                        }
-                        else{
-                            fieldId = attributes['id'].substring(4, attributes['id'].length-1).split("-")[1]; 
-                            errorMessageId = 'prStDes.' + fieldId + '.dataElement.id';
-                            
-                            //name needs to be unique so that it can be used for validation in angularjs
-                            if(attributes.hasOwnProperty('name')){
-                                attributes['name'] = fieldId;
-                            }
-
-                            //check data element type and generate corresponding angular input field
-                            if(programStageDataElements[fieldId].dataElement.type === "int"){
-                                newInputField = '<input type="text" ' +
-                                                this.getAttributesAsString(attributes) +
-                                                ' d2-validation ' +
-                                                ' d2-number-validation ' +
-                                                ' number-type="' + programStageDataElements[fieldId].dataElement.numberType + '" ' +
-                                                ' ng-model="currentEvent.' + fieldId + '"' +
-                                                ' input-field-id="' + fieldId + '"' +
-                                                ' ng-class="getInputNotifcationClass(prStDes.' + fieldId + '.dataElement.id,true)"' +
-                                                ' ng-disabled="selectedEnrollment.status===\'CANCELLED\' || selectedEnrollment.status===\'COMPLETED\'"' +
-                                                ' ng-blur="saveDatavalue(prStDes.'+ fieldId + ')"' + 
-                                                ' ng-required="{{prStDes.' + fieldId + '.compulsory}}">';
-                            }
-                            if(programStageDataElements[fieldId].dataElement.type === "string"){
-                                if(programStageDataElements[fieldId].dataElement.optionSet){
-                                    var optionSetId = programStageDataElements[fieldId].dataElement.optionSet.id;
-                                    newInputField = '<input type="text" ' +
-                                                this.getAttributesAsString(attributes) +
-                                                ' d2-validation ' +
-                                                ' ng-model="currentEvent.' + fieldId + '" ' +
-                                                ' input-field-id="' + fieldId + '"' +
-                                                ' ng-disabled="selectedEnrollment.status===\'CANCELLED\' || selectedEnrollment.status===\'COMPLETED\' || currentEvent[uid]==\'uid\'"' +
-                                                ' ng-required="{{prStDes.' + fieldId + '.compulsory}}"' +
-                                                ' typeahead="option.name as option.name for option in optionSets.'+optionSetId+'.options | filter:$viewValue | limitTo:20"' +
-                                                ' typeahead-editable="false" ' +
-                                                ' d2-typeahead-validation ' +
-                                                ' class="typeahead" ' +
-                                                ' placeholder="&#xf0d7;&nbsp;&nbsp;" ' +
-                                                ' ng-class="getInputNotifcationClass(prStDes.' + fieldId + '.dataElement.id,true)"' +                                            
-                                                ' ng-blur="saveDatavalue(prStDes.'+ fieldId + ')"' +
-                                                ' typeahead-open-on-focus ng-required="prStDes.'+fieldId+'.compulsory"> ';
-                                }
-                                else{
-                                    newInputField = '<input type="text" ' +
-                                                this.getAttributesAsString(attributes) +
-                                                ' d2-validation ' +
-                                                ' ng-model="currentEvent.' + fieldId + '" ' +
-                                                ' input-field-id="' + fieldId + '"' +
-                                                ' ng-disabled="selectedEnrollment.status===\'CANCELLED\' || selectedEnrollment.status===\'COMPLETED\' || currentEvent[uid]==\'uid\'"' +
-                                                ' ng-class="getInputNotifcationClass(prStDes.' + fieldId + '.dataElement.id,true)"' +
-                                                ' ng-blur="saveDatavalue(prStDes.'+ fieldId + ')"' +
-                                                ' ng-required="prStDes.' + fieldId + '.compulsory"> ';                                     
-                                }
-                            }
-                            if(programStageDataElements[fieldId].dataElement.type === "bool"){
-                                newInputField = '<select ' +
-                                                this.getAttributesAsString(attributes) +
-                                                ' d2-validation ' +
-                                                ' ng-model="currentEvent.' + fieldId + '" ' +
-                                                ' input-field-id="' + fieldId + '"' +
-                                                ' ng-class="getInputNotifcationClass(prStDes.' + fieldId + '.dataElement.id,true)"' +
-                                                ' ng-disabled="selectedEnrollment.status===\'CANCELLED\' || selectedEnrollment.status===\'COMPLETED\'"' +
-                                                ' ng-change="saveDatavalue(prStDes.'+ fieldId + ')"' + 
-                                                ' ng-required="{{prStDes.' + fieldId + '.compulsory}}">' + 
-                                                '<option value="">{{\'please_select\'| translate}}</option>' +
-                                                '<option value="false">{{\'no\'| translate}}</option>' + 
-                                                '<option value="true">{{\'yes\'| translate}}</option>' +
-                                                '</select> ';                                     
-                            }
-                            if(programStageDataElements[fieldId].dataElement.type === "date"){
-                                var maxDate = programStageDataElements[fieldId].allowFutureDate ? '' : 0;
-                                newInputField = '<input type="text" ' +
-                                                this.getAttributesAsString(attributes) +
-                                                ' d2-validation ' +
-                                                ' ng-model="currentEvent.' + fieldId + '"' +
-                                                ' input-field-id="' + fieldId + '"' +                                                
-                                                ' placeholder="{{dhis2CalendarFormat.keyDateFormat}}" ' +
-                                                ' d2-date ' +
-                                                ' max-date="' + maxDate + '"' +
-                                                ' ng-class="getInputNotifcationClass(prStDes.' + fieldId + '.dataElement.id,true)"' +
-                                                ' ng-disabled="selectedEnrollment.status===\'CANCELLED\' || selectedEnrollment.status===\'COMPLETED\'"' +
-                                                ' blur-or-change="saveDatavalue(prStDes.'+ fieldId + ')"' + 
-                                                ' ng-required="{{prStDes.' + fieldId + '.compulsory}}"> '; 
-                            }
-                            if(programStageDataElements[fieldId].dataElement.type === "trueOnly"){
-                                newInputField = '<input type="checkbox" ' +
-                                                this.getAttributesAsString(attributes) +
-                                                ' d2-validation ' +
-                                                ' ng-model="currentEvent.' + fieldId + '"' +
-                                                ' input-field-id="' + fieldId + '"' +
-                                                ' ng-class="getInputNotifcationClass(prStDes.' + fieldId + '.dataElement.id,true)"' +
-                                                ' ng-disabled="selectedEnrollment.status===\'CANCELLED\' || selectedEnrollment.status===\'COMPLETED\'"' +
-                                                ' ng-change="saveDatavalue(prStDes.'+ fieldId + ')"' +
-                                                ' ng-required="{{prStDes.' + fieldId + '.compulsory}}"> ';
-                            }                            
-                        }
-						
-                        newInputField = newInputField + ' <span ng-show="(outerForm.'+ fieldId +'.$dirty && outerForm.'+ fieldId +'.$invalid) || (outerForm.submitted && outerForm.'+ fieldId +'.$invalid) || (currentEvent.' + fieldId + ' && outerForm.' + fieldId + '.$invalid)" class="required">{{getErrorMessage(' + errorMessageId + ')}}</span> ';
-                        
-                        htmlCode = htmlCode.replace(inputField, newInputField);
-                    }
-                }
-                return {htmlCode: htmlCode, hasEventDate: hasEventDate};
-            }
-            return null;
-        },
-        getAttributesAsString: function(attributes){
-            if(attributes){
-                var attributesAsString = '';                
-                for(var prop in attributes){
-                    if(prop !== 'value'){
-                        attributesAsString += prop + '="' + attributes[prop] + '" ';
-                    }
-                }
-                return attributesAsString;
-            }
-            return null;
-        }
-    };            
-})
-
-/* Context menu for grid*/
-.service('ContextMenuSelectedItem', function(){
-    this.selectedItem = '';
-    
-    this.setSelectedItem = function(selectedItem){  
-        this.selectedItem = selectedItem;        
-    };
-    
-    this.getSelectedItem = function(){
-        return this.selectedItem;
-    };
-})
-
-/* Error messages*/
-.service('ErrorMessageService', function(){
-    this.errorMessages = {};
-    
-    this.setErrorMessages = function(errorMessages){  
-        this.errorMessages = errorMessages;        
-    };
-    
-    this.getErrorMessages = function(){
-        return this.errorMessages;
-    };
-    
-    this.get = function(id){
-        return this.errorMessages[id];
-    };
-})
-
-/* Modal service for user interaction */
-.service('ModalService', ['$modal', function($modal) {
-
-    var modalDefaults = {
-        backdrop: true,
-        keyboard: true,
-        modalFade: true,
-        templateUrl: 'views/modal.html'
-    };
-
-    var modalOptions = {
-        closeButtonText: 'Close',
-        actionButtonText: 'OK',
-        headerText: 'Proceed?',
-        bodyText: 'Perform this action?'
-    };
-
-    this.showModal = function(customModalDefaults, customModalOptions) {
-        if (!customModalDefaults)
-            customModalDefaults = {};
-        customModalDefaults.backdrop = 'static';
-        return this.show(customModalDefaults, customModalOptions);
-    };
-
-    this.show = function(customModalDefaults, customModalOptions) {
-        //Create temp objects to work with since we're in a singleton service
-        var tempModalDefaults = {};
-        var tempModalOptions = {};
-
-        //Map angular-ui modal custom defaults to modal defaults defined in service
-        angular.extend(tempModalDefaults, modalDefaults, customModalDefaults);
-
-        //Map modal.html $scope custom properties to defaults defined in service
-        angular.extend(tempModalOptions, modalOptions, customModalOptions);
-
-        if (!tempModalDefaults.controller) {
-            tempModalDefaults.controller = function($scope, $modalInstance) {
-                $scope.modalOptions = tempModalOptions;
-                $scope.modalOptions.ok = function(result) {
-                    $modalInstance.close(result);
-                };
-                $scope.modalOptions.close = function(result) {
-                    $modalInstance.dismiss('cancel');
-                };
-            };
-        }
-
-        return $modal.open(tempModalDefaults).result;
-    };
-
-}])
-
-/* Dialog service for user interaction */
-.service('DialogService', ['$modal', function($modal) {
-
-    var dialogDefaults = {
-        backdrop: true,
-        keyboard: true,
-        backdropClick: true,
-        modalFade: true,            
-        templateUrl: 'views/dialog.html'
-    };
-
-    var dialogOptions = {
-        closeButtonText: 'close',
-        actionButtonText: 'ok',
-        headerText: 'dhis2_tracker',
-        bodyText: 'Perform this action?'
-    };
-
-    this.showDialog = function(customDialogDefaults, customDialogOptions) {
-        if (!customDialogDefaults)
-            customDialogDefaults = {};
-        customDialogDefaults.backdropClick = false;
-        return this.show(customDialogDefaults, customDialogOptions);
-    };
-
-    this.show = function(customDialogDefaults, customDialogOptions) {
-        //Create temp objects to work with since we're in a singleton service
-        var tempDialogDefaults = {};
-        var tempDialogOptions = {};
-
-        //Map angular-ui modal custom defaults to modal defaults defined in service
-        angular.extend(tempDialogDefaults, dialogDefaults, customDialogDefaults);
-
-        //Map modal.html $scope custom properties to defaults defined in service
-        angular.extend(tempDialogOptions, dialogOptions, customDialogOptions);
-
-        if (!tempDialogDefaults.controller) {
-            tempDialogDefaults.controller = function($scope, $modalInstance) {
-                $scope.dialogOptions = tempDialogOptions;
-                $scope.dialogOptions.ok = function(result) {
-                    $modalInstance.close(result);
-                };                           
-            };
-        }
-
-        return $modal.open(tempDialogDefaults).result;
-    };
-
-}])
-
-.service('Paginator', function () {
-    this.page = 1;
-    this.pageSize = 50;
-    this.itemCount = 0;
-    this.pageCount = 0;
-    this.toolBarDisplay = 5;
-
-    this.setPage = function (page) {
-        if (page > this.getPageCount()) {
-            return;
-        }
-
-        this.page = page;
-    };
-    
-    this.getPage = function(){
-        return this.page;
-    };
-    
-    this.setPageSize = function(pageSize){
-      this.pageSize = pageSize;
-    };
-    
-    this.getPageSize = function(){
-        return this.pageSize;
-    };
-    
-    this.setItemCount = function(itemCount){
-      this.itemCount = itemCount;
-    };
-    
-    this.getItemCount = function(){
-        return this.itemCount;
-    };
-    
-    this.setPageCount = function(pageCount){
-        this.pageCount = pageCount;
-    };
-
-    this.getPageCount = function () {
-        return this.pageCount;
-    };
-
-    this.lowerLimit = function() { 
-        var pageCountLimitPerPageDiff = this.getPageCount() - this.toolBarDisplay;
-
-        if (pageCountLimitPerPageDiff < 0) { 
-            return 0; 
-        }
-
-        if (this.getPage() > pageCountLimitPerPageDiff + 1) { 
-            return pageCountLimitPerPageDiff; 
-        } 
-
-        var low = this.getPage() - (Math.ceil(this.toolBarDisplay/2) - 1); 
-
-        return Math.max(low, 0);
-    };
-})
-
-.service('GridColumnService', function(){
-    return {        
-        columnExists: function(cols, id) {
-            var colExists = false;
-            if(!angular.isObject(cols) || !id || angular.isObject(cols) && !cols.length){
-                return colExists;
-            }
-            
-            for(var i=0; i<cols.length && !colExists; i++){
-                if(cols[i].id === id){
-                    colExists = true;
-                }
-            }
-            return colExists;
-        }
-    };
-});

=== added file 'dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/dhis2/dhis2.angular.controllers.js'
--- dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/dhis2/dhis2.angular.controllers.js	1970-01-01 00:00:00 +0000
+++ dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/dhis2/dhis2.angular.controllers.js	2015-02-02 13:42:37 +0000
@@ -0,0 +1,53 @@
+'use strict';
+
+/* Controllers */
+var d2Controllers = angular.module('d2Controllers', [])
+
+//Controller for column show/hide
+.controller('ColumnDisplayController', 
+    function($scope, 
+            $modalInstance, 
+            hiddenGridColumns,
+            gridColumns){
+    
+    $scope.gridColumns = gridColumns;
+    $scope.hiddenGridColumns = hiddenGridColumns;
+    
+    $scope.close = function () {
+      $modalInstance.close($scope.gridColumns);
+    };
+    
+    $scope.showHideColumns = function(gridColumn){
+       
+        if(gridColumn.show){                
+            $scope.hiddenGridColumns--;            
+        }
+        else{
+            $scope.hiddenGridColumns++;            
+        }      
+    };    
+})
+
+//controller for dealing with google map
+.controller('MapController',
+        function($scope, 
+                $modalInstance,
+                DHIS2URL,
+                geoJsons,
+                location) {
+    
+    $scope.home = function(){        
+        window.location = DHIS2URL;
+    };
+    
+    $scope.location = location;
+    $scope.geoJsons = geoJsons;
+    
+    $scope.close = function () {
+        $modalInstance.close();
+    };
+    
+    $scope.captureCoordinate = function(){        
+        $modalInstance.close($scope.location);
+    };
+});
\ No newline at end of file

=== added file 'dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/dhis2/dhis2.angular.directives.js'
--- dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/dhis2/dhis2.angular.directives.js	1970-01-01 00:00:00 +0000
+++ dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/dhis2/dhis2.angular.directives.js	2015-02-02 13:42:37 +0000
@@ -0,0 +1,582 @@
+'use strict';
+
+/* Directives */
+
+var d2Directives = angular.module('d2Directives', [])
+
+
+.directive('d2OuSearch', function() {
+    
+    return {
+        restrict: 'E',
+        template: '<div style="margin-top:20px">\n\
+                    <img id="searchIcon" src="../images/search.png" style="cursor: pointer" title="{{ \'locate_organisation_unit_by_name\' | translate}}">\n\
+                    <span id="searchSpan" style="width:100%;display:none;">\n\
+                        <input type="text" id="searchField" name="key"/>\n\
+                        <input type="button" value="{{\'find\' | translate}}" onclick="selection.findByName()"/>\n\
+                    </span>\n\
+                  </div>',
+        link: function (scope, element, attrs) {
+            
+            $("#searchIcon").click(function() {
+                $("#searchSpan").toggle();
+                $("#searchField").focus();
+            });
+
+            $("#searchField").autocomplete({
+                source: "../dhis-web-commons/ouwt/getOrganisationUnitsByName.action",
+                select: function(event, ui) {
+                    $("#searchField").val(ui.item.value);
+                    selection.findByName();
+                }
+            });
+        }
+    };
+})
+
+.directive('inputValidator', function() {
+    
+    return {
+        require: 'ngModel',
+        link: function (scope, element, attrs, ctrl) {  
+
+            ctrl.$parsers.push(function (value) {
+                return parseFloat(value || '');
+            });
+        }
+    };
+})
+
+.directive('selectedOrgUnit', function($timeout, storage) {        
+
+    return {        
+        restrict: 'A',        
+        link: function(scope, element, attrs){
+            
+            //once ou tree is loaded, start meta-data download
+            $(function() {
+                dhis2.ou.store.open().done( function() {
+                    selection.load();
+                    $( "#orgUnitTree" ).one( "ouwtLoaded", function(event, ids, names) {
+                        console.log('Finished loading orgunit tree');
+                        
+                        //Disable ou selection until meta-data has downloaded
+                        $( "#orgUnitTree" ).addClass( "disable-clicks" );
+                        
+                        $timeout(function() {
+                            scope.treeLoaded = true;
+                            scope.$apply();
+                        });
+                        
+                        downloadMetaData();
+                    });
+                });
+            });
+            
+            //listen to user selection, and inform angular         
+            selection.setListenerFunction( setSelectedOu, true );
+            
+            function setSelectedOu( ids, names ) {
+                var ou = {id: ids[0], name: names[0]};
+                $timeout(function() {
+                    scope.selectedOrgUnit = ou;
+                    scope.$apply();
+                });
+            }
+        }  
+    };
+})
+
+.directive('blurOrChange', function() {
+    
+    return function( scope, elem, attrs) {
+        elem.calendarsPicker({
+            onSelect: function() {
+                scope.$apply(attrs.blurOrChange);
+                $(this).change();                                        
+            }
+        }).change(function() {
+            scope.$apply(attrs.blurOrChange);
+        });
+    };
+})
+
+.directive('d2Enter', function () {
+    return function (scope, element, attrs) {
+        element.bind("keydown keypress", function (event) {
+            if(event.which === 13) {
+                scope.$apply(function (){
+                    scope.$eval(attrs.d2Enter);
+                });
+                event.preventDefault();
+            }
+        });
+    };
+})
+
+.directive('d2NumberValidation', function(ErrorMessageService, $translate) {
+    
+    return {
+        require: 'ngModel',
+        restrict: 'A',
+        link: function (scope, element, attrs, ctrl) {
+            
+            function checkValidity(numberType, value){
+                var isValid = false;
+                switch(numberType){
+                    case "number":
+                        isValid = dhis2.validation.isNumber(value);
+                        break;
+                    case "posInt":
+                        isValid = dhis2.validation.isPositiveInt(value);
+                        break;
+                    case "negInt":
+                        isValid = dhis2.validation.isNegativeInt(value);
+                        break;
+                    case "zeroPositiveInt":
+                        isValid = dhis2.validation.isZeroOrPositiveInt(value);
+                        break;
+                    case "int":
+                        isValid = dhis2.validation.isInt(value);
+                        break;
+                    default:
+                        isValid = true;
+                }
+                return isValid;
+            }
+            
+            var errorMessages = ErrorMessageService.getErrorMessages();
+            var fieldName = attrs.inputFieldId;
+            var numberType = attrs.numberType;
+            var isRequired = attrs.ngRequired === 'true';
+            var msg = $translate(numberType)+ ' ' + $translate('required');
+           
+            ctrl.$parsers.unshift(function(value) {
+            	if(value){
+                    var isValid = checkValidity(numberType, value);                    
+                    if(!isValid){
+                        errorMessages[fieldName] = $translate('value_must_be_' + numberType);
+                    }
+                    else{
+                        if(isRequired){
+                            errorMessages[fieldName] = msg;
+                        }
+                        else{
+                            errorMessages[fieldName] = "";
+                        }
+                    }
+                    
+                    ErrorMessageService.setErrorMessages(errorMessages);
+                	ctrl.$setValidity(fieldName, isValid);
+                    return value;
+                }
+                
+                if(value === ''){
+                    if(isRequired){
+                        errorMessages[fieldName] = msg;
+                    }
+                    else{
+                        ctrl.$setValidity(fieldName, true);
+                        errorMessages[fieldName] = "";
+                    }
+                    
+                    ErrorMessageService.setErrorMessages(errorMessages);
+                    return undefined;
+                }              
+            });
+           
+            ctrl.$formatters.unshift(function(value) {                
+                if(value){
+                    var isValid = checkValidity(numberType, value);
+                    ctrl.$setValidity(fieldName, isValid);
+                    return value;
+                }
+            });
+        }
+    };
+})
+
+.directive('typeaheadOpenOnFocus', function () {
+  	
+  	return {
+        require: ['typeahead', 'ngModel'],
+        link: function (scope, element, attr, ctrls) {
+            element.bind('focus', function () {
+                ctrls[0].getMatchesAsync(ctrls[1].$viewValue);                
+                scope.$watch(attr.ngModel, function(value) {
+                    if(value === '' || angular.isUndefined(value)){
+                        ctrls[0].getMatchesAsync(ctrls[1].$viewValue);
+                    }                
+                });
+            });
+        }
+    };
+})
+
+.directive('d2TypeaheadValidation', function() {
+    
+    return {
+        require: ['typeahead', 'ngModel'],
+        restrict: 'A',
+        link: function (scope, element, attrs, ctrls) {
+            element.bind('blur', function () {                
+                if(ctrls[1].$viewValue && !ctrls[1].$modelValue && ctrls[0].active === -1){
+                    ctrls[1].$setViewValue();
+                    ctrls[1].$render();
+                }                
+            });
+        }
+    };
+})
+
+.directive('d2PopOver', function($compile, $templateCache){
+    
+    return {        
+        restrict: 'EA',
+        link: function(scope, element, attrs){
+            var content = $templateCache.get("popover.html");
+            content = $compile(content)(scope);
+            var options = {
+                    content: content,
+                    placement: 'bottom',
+                    trigger: 'hover',
+                    html: true,
+                    title: scope.title               
+                };            
+            $(element).popover(options);
+        },
+        scope: {
+            content: '=',
+            title: '@details',
+            template: "@template"
+        }
+    };
+})
+
+.directive('sortable', function() {        
+
+    return {        
+        restrict: 'A',        
+        link: function(scope, element, attrs){
+            element.sortable({
+                connectWith: ".connectedSortable",
+                placeholder: "ui-state-highlight",
+                tolerance: "pointer",
+                handle: '.handle'
+            });
+        }  
+    };
+})
+
+.directive('serversidePaginator', function factory() {
+    
+    return {
+        restrict: 'E',
+        controller: function ($scope, Paginator) {
+            $scope.paginator = Paginator;
+        },
+        templateUrl: '../dhis-web-commons/paging/serverside-pagination.html'
+    };
+})
+
+.directive('draggableModal', function(){
+    
+    return {
+      	restrict: 'EA',
+      	link: function(scope, element) {
+        	element.draggable();
+      	}
+    };  
+})
+
+.directive('d2GoogleMap', function ($parse, $compile, storage) {
+    return {
+        restrict: 'E',
+        replace: true,
+        template: '<div></div>',
+        link: function(scope, element, attrs){
+            
+            //remove angular bootstrap ui modal draggable
+            $(".modal-content").draggable({ disabled: true });
+            
+            //get a default center
+            var latCenter = 12.31, lngCenter = 51.48;            
+            
+            //if there is any marker already - use it as center
+            if(angular.isObject(scope.location)){
+                if(scope.location.lat && scope.location.lng){
+                    latCenter = scope.location.lat;
+                    lngCenter = scope.location.lng;
+                }                
+            }
+            
+            //default map configurations 
+            var mapOptions = {
+                zoom: 3,
+                center: new google.maps.LatLng(latCenter, lngCenter),
+                mapTypeId: google.maps.MapTypeId.ROADMAP
+            },featureStyle = {
+                strokeWeight: 2,
+                strokeOpacity: 0.4,
+                fillOpacity: 0.4,
+                fillColor: 'green'
+            };
+            
+            var geojsons = $parse(attrs.geojsons)(scope);
+            var currentLayer = 0, currentGeojson = geojsons[0]; 
+            
+            var map = new google.maps.Map(document.getElementById(attrs.id), mapOptions);            
+            var currentGeojsonFeatures = map.data.addGeoJson(currentGeojson);
+            
+            var marker = new google.maps.Marker({
+                map: map
+            });
+            
+            if(angular.isObject(scope.location)){
+                if(scope.location.lat && scope.location.lng){                    
+                    addMarker({lat: scope.location.lat, lng: scope.location.lng});                    
+                }                
+            }
+            
+            function addMarker(loc){
+                var latLng = new google.maps.LatLng(loc.lat, loc.lng);
+                marker.setPosition(latLng);
+            }
+            
+            function centerMap(){
+                
+                if(currentGeojson && currentGeojson.features){
+                    var latLngBounds = new google.maps.LatLngBounds();
+                    angular.forEach(currentGeojson.features, function(feature){
+                        if(feature.geometry.type === 'MultiPolygon'){
+                            angular.forEach(feature.geometry.coordinates[0][0], function(coordinate){
+                                latLngBounds.extend(new google.maps.LatLng(coordinate[1],coordinate[0]));
+                            });
+                        }
+                        else if(feature.geometry.type === 'Point'){                        
+                            latLngBounds.extend(new google.maps.LatLng(feature.geometry.coordinates[1],feature.geometry.coordinates[0]));
+                        }
+                    });
+                    
+                    map.fitBounds(latLngBounds);
+                    map.panToBounds(latLngBounds);
+                }                
+            }
+            
+            function initializeMap(){                
+                google.maps.event.addListenerOnce(map, 'idle', function(){
+                    google.maps.event.trigger(map, 'resize');
+                    map.data.setStyle(featureStyle);
+                    centerMap();
+                });
+            }
+            
+            map.data.addListener('mouseover', function(e) {                
+                $("#polygon-label").text( e.feature.k.name );
+                map.data.revertStyle();
+                map.data.overrideStyle(e.feature, {fillOpacity: 0.8});
+            });
+            
+            map.data.addListener('mouseout', function() {                
+                $("#polygon-label").text( '' );
+                map.data.revertStyle();
+            });
+            
+            //drill-down based on polygons assigned to orgunits
+            map.data.addListener('rightclick', function(e){                
+                for (var i = 0; i < currentGeojsonFeatures.length; i++){
+                    map.data.remove(currentGeojsonFeatures[i]);
+                }
+                                
+                if(currentLayer >= geojsons.length-1){
+                    currentLayer = 0;
+                    currentGeojson = angular.copy(geojsons[currentLayer]);                    
+                }
+                else{
+                    currentLayer++;
+                    currentGeojson = angular.copy(geojsons[currentLayer]);
+                    currentGeojson.features = [];
+                    var selectedFeatures = [];
+                    angular.forEach(geojsons[currentLayer].features, function(feature){                    
+                        if(feature.properties.parent === e.feature.B){
+                            selectedFeatures.push(feature);
+                        }
+                    });
+                    
+                    if(selectedFeatures.length){
+                        currentGeojson.features = selectedFeatures;
+                    }                   
+                }                
+                currentGeojsonFeatures = map.data.addGeoJson(currentGeojson);
+                centerMap();         
+            });            
+            
+            //capturing coordinate from defined polygons
+            map.data.addListener('click', function(e) {                
+                scope.$apply(function(){
+                    addMarker({
+                       lat: e.latLng.lat(),
+                       lng: e.latLng.lng()
+                    });
+                    $parse(attrs.location).assign(scope.$parent, {lat: e.latLng.lat(), lng: e.latLng.lng()});                    
+                });                
+            });
+            
+            //capturing coordinate from anywhere in the map - incase no polygons are defined
+            google.maps.event.addListener(map, 'click', function(e){                
+                scope.$apply(function(){
+                    addMarker({
+                       lat: e.latLng.lat(),
+                       lng: e.latLng.lng()
+                    });
+                    $parse(attrs.location).assign(scope.$parent, {lat: e.latLng.lat(), lng: e.latLng.lng()});                    
+                });                
+            });
+            
+            initializeMap();
+        }
+    };
+})
+
+.directive('d2CustomForm', function($compile, $parse, CustomFormService) {
+    return{ 
+        restrict: 'E',
+        link: function(scope, elm, attrs){            
+             scope.$watch('customForm', function(){
+                 elm.html(scope.customForm.htmlCode);
+                 $compile(elm.contents())(scope);
+             });
+        }
+    };
+})
+
+.directive('d2ContextMenu', function(ContextMenuSelectedItem) {
+        
+    return {        
+        restrict: 'A',
+        link: function(scope, element, attrs){
+            var contextMenu = $("#contextMenu");                   
+            
+            element.click(function (e) {
+                var selectedItem = $.parseJSON(attrs.selectedItem);
+                ContextMenuSelectedItem.setSelectedItem(selectedItem);
+                
+                var menuHeight = contextMenu.height();
+                var menuWidth = contextMenu.width();
+                var winHeight = $(window).height();
+                var winWidth = $(window).width();
+
+                var pageX = e.pageX;
+                var pageY = e.pageY;
+
+                contextMenu.show();
+
+                if( (menuWidth + pageX) > winWidth ) {
+                  pageX -= menuWidth;
+                }
+
+                if( (menuHeight + pageY) > winHeight ) {
+                  pageY -= menuHeight;
+
+                  if( pageY < 0 ) {
+                      pageY = e.pageY;
+                  }
+                }
+                
+                contextMenu.css({
+                    left: pageX,
+                    top: pageY
+                });
+
+                return false;
+            });
+            
+            contextMenu.on("click", "a", function () {                    
+                contextMenu.hide();
+            });
+
+            $(document).click(function () {                                        
+                contextMenu.hide();
+            });
+        }     
+    };
+})
+
+.directive('d2Date', function(DateUtils, CalendarService, ErrorMessageService, $translate, $parse) {
+    return {
+        restrict: 'A',
+        require: 'ngModel',        
+        link: function(scope, element, attrs, ctrl) {    
+            
+            var errorMessages = ErrorMessageService.getErrorMessages();
+            var fieldName = attrs.inputFieldId;
+            var isRequired = attrs.ngRequired === 'true';
+            var calendarSetting = CalendarService.getSetting();            
+            var dateFormat = 'yyyy-mm-dd';
+            if(calendarSetting.keyDateFormat === 'dd-MM-yyyy'){
+                dateFormat = 'dd-mm-yyyy';
+            }            
+            
+            var minDate = $parse(attrs.minDate)(scope), 
+                maxDate = $parse(attrs.maxDate)(scope),
+                calendar = $.calendars.instance(calendarSetting.keyCalendar);
+            
+            element.calendarsPicker({
+                changeMonth: true,
+                dateFormat: dateFormat,
+                yearRange: '-120:+30',
+                minDate: minDate,
+                maxDate: maxDate,
+                calendar: calendar,
+                duration: "fast",
+                showAnim: "",
+                renderer: $.calendars.picker.themeRollerRenderer,
+                onSelect: function(date) {
+                    $(this).change();
+                }
+            })
+            .change(function() {                
+                if(this.value){                    
+                    var rawDate = this.value;
+                    var convertedDate = DateUtils.format(this.value);
+
+                    var isValid = rawDate == convertedDate;
+                    
+                    if(!isValid){
+                        errorMessages[fieldName] = $translate('date_required');
+                    }
+                    else{
+                        if(isRequired){
+                            errorMessages[fieldName] = $translate('required');
+                        }
+                        else{
+                            errorMessages[fieldName] = "";
+                        }
+                        if(maxDate === 0){                    
+                            isValid = !moment(convertedDate, calendarSetting.momentFormat).isAfter(DateUtils.getToday());
+                            if(!isValid){
+                                errorMessages[fieldName] = $translate('future_date_not_allowed');                            
+                            }                           
+                        }
+                    }                                        
+                    ctrl.$setViewValue(this.value);
+                    ctrl.$setValidity(fieldName, isValid);
+                }
+                else{
+                    if(!isRequired){
+                        ctrl.$setViewValue(this.value);
+                        ctrl.$setValidity(fieldName, !isRequired);
+                        errorMessages[fieldName] = "";
+                    }
+                    else{
+                        errorMessages[fieldName] = $translate('required');                        
+                    }
+                }
+                
+                ErrorMessageService.setErrorMessages(errorMessages);
+                this.focus();
+                scope.$apply();
+            });    
+        }      
+    };   
+});
\ No newline at end of file

=== added file 'dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/dhis2/dhis2.angular.filters.js'
--- dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/dhis2/dhis2.angular.filters.js	1970-01-01 00:00:00 +0000
+++ dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/dhis2/dhis2.angular.filters.js	2015-02-02 13:42:37 +0000
@@ -0,0 +1,97 @@
+'use strict';
+
+/* Filters */
+
+var d2Filters = angular.module('d2Filters', [])
+
+.filter('gridFilter', function($filter, CalendarService){    
+    
+    return function(data, filters, filterTypes){
+
+        if(!data ){
+            return;
+        }
+        
+        if(!filters){
+            return data;
+        }        
+        else{            
+            
+            var dateFilter = {}, 
+                textFilter = {}, 
+                numberFilter = {},
+                filteredData = data;
+            
+            for(var key in filters){
+                
+                if(filterTypes[key] === 'date'){
+                    if(filters[key].start || filters[key].end){
+                        dateFilter[key] = filters[key];
+                    }
+                }
+                else if(filterTypes[key] === 'int'){
+                    if(filters[key].start || filters[key].end){
+                        numberFilter[key] = filters[key];
+                    }
+                }
+                else{
+                    textFilter[key] = filters[key];
+                }
+            }
+            
+            filteredData = $filter('filter')(filteredData, textFilter); 
+            filteredData = $filter('filter')(filteredData, dateFilter, dateComparator);            
+            filteredData = $filter('filter')(filteredData, numberFilter, numberComparator);
+                        
+            return filteredData;
+        } 
+    }; 
+    
+    function dateComparator(data,filter){
+    	var calendarSetting = CalendarService.getSetting(); 
+        var start = moment(filter.start, calendarSetting.momentFormat);
+        var end = moment(filter.end, calendarSetting.momentFormat);  
+        var date = moment(data, calendarSetting.momentFormat); 
+        
+        if(filter.start && filter.end){
+            return ( Date.parse(date) <= Date.parse(end) ) && (Date.parse(date) >= Date.parse(start));
+        }        
+        return ( Date.parse(date) <= Date.parse(end) ) || (Date.parse(date) >= Date.parse(start));
+    }
+    
+    function numberComparator(data,filter){
+        var start = filter.start;
+        var end = filter.end;
+        
+        if(filter.start && filter.end){
+            return ( data <= end ) && ( data >= start );
+        }        
+        return ( data <= end ) || ( data >= start );
+    }
+})
+
+.filter('paginate', function(Paginator) {
+    return function(input, rowsPerPage) {
+        if (!input) {
+            return input;
+        }
+
+        if (rowsPerPage) {
+            Paginator.rowsPerPage = rowsPerPage;
+        }       
+        
+        Paginator.itemCount = input.length;
+
+        return input.slice(parseInt(Paginator.page * Paginator.rowsPerPage), parseInt((Paginator.page + 1) * Paginator.rowsPerPage + 1) - 1);
+    };
+})
+
+.filter('forLoop', function() {
+    return function(input, start, end) {
+        input = new Array(end - start);
+        for (var i = 0; start < end; start++, i++) {
+            input[i] = start;
+        }
+        return input;
+    };
+});
\ No newline at end of file

=== added file 'dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/dhis2/dhis2.angular.services.js'
--- dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/dhis2/dhis2.angular.services.js	1970-01-01 00:00:00 +0000
+++ dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/dhis2/dhis2.angular.services.js	2015-02-02 13:42:37 +0000
@@ -0,0 +1,560 @@
+/* Pagination service */
+var d2Services = angular.module('d2Services', ['ngResource'])
+
+/* Factory for loading translation strings */
+.factory('i18nLoader', function ($q, $http, storage, DialogService) {
+ 
+    var getTranslationStrings = function(locale){
+        var defaultUrl = 'i18n/i18n_app.properties';
+        var url = '';
+        if(locale === 'en' || !locale){
+            url = defaultUrl;
+        }
+        else{
+            url = 'i18n/i18n_app_' + locale + '.properties';
+        }
+
+        var tx = {locale: locale};
+
+        var promise = $http.get(url).then(function(response){
+            tx= {locale: locale, keys: dhis2.util.parseJavaProperties(response.data)};
+            return tx;
+        }, function(){
+            var dialogOptions = {
+                headerText: 'missing_translation_file',
+                bodyText: 'missing_translation_using_default'
+            };
+
+            DialogService.showDialog({}, dialogOptions);
+            var p = $http.get(defaultUrl).then(function(response){
+                tx= {locale: locale, keys: dhis2.util.parseJavaProperties(response.data)};
+                return tx;
+            });
+            return p;
+        });
+        return promise;
+    };
+
+    var getLocale = function(){
+        var locale = 'en';
+
+        var promise = $http.get('../api/me/profile.json').then(function(response){
+            storage.set('USER_PROFILE', response.data);
+            if(response.data && response.data.settings && response.data.settings.keyUiLocale){
+                locale = response.data.settings.keyUiLocale;
+            }
+            return locale;
+        }, function(){
+            return locale;
+        });
+
+        return promise;
+    };
+    return function () {
+        var deferred = $q.defer(), translations;    
+        var userProfile = storage.get('USER_PROFILE');
+        if(userProfile && userProfile.settings && userProfile.settings.keyUiLocale){                
+            getTranslationStrings(userProfile.settings.keyUiLocale).then(function(response){
+                translations = response.keys;
+                deferred.resolve(translations);
+            });
+            return deferred.promise;
+        }
+        else{
+            getLocale().then(function(locale){
+                getTranslationStrings(locale).then(function(response){
+                    translations = response.keys;
+                    deferred.resolve(translations);
+                });
+            });
+            return deferred.promise;
+        }
+    };
+})
+
+/* Factory for loading external data */
+.factory('ExternalDataFactory', function($http) {
+
+    return {        
+        get: function(fileName) {
+            var promise = $http.get( fileName ).then(function(response){
+                return response.data;
+            });            
+            return promise;
+        }
+    };
+})
+
+/* service for getting calendar setting */
+.service('CalendarService', function(storage, $rootScope){    
+
+    return {
+        getSetting: function() {
+            
+            var dhis2CalendarFormat = {keyDateFormat: 'yyyy-MM-dd', keyCalendar: 'gregorian', momentFormat: 'YYYY-MM-DD'};                
+            var storedFormat = storage.get('CALENDAR_SETTING');
+            if(angular.isObject(storedFormat) && storedFormat.keyDateFormat && storedFormat.keyCalendar){
+                if(storedFormat.keyCalendar === 'iso8601'){
+                    storedFormat.keyCalendar = 'gregorian';
+                }
+
+                if(storedFormat.keyDateFormat === 'dd-MM-yyyy'){
+                    dhis2CalendarFormat.momentFormat = 'DD-MM-YYYY';
+                }
+                
+                dhis2CalendarFormat.keyCalendar = storedFormat.keyCalendar;
+                dhis2CalendarFormat.keyDateFormat = storedFormat.keyDateFormat;
+            }
+            $rootScope.dhis2CalendarFormat = dhis2CalendarFormat;
+            return dhis2CalendarFormat;
+        }
+    };            
+})
+
+/* service for dealing with dates */
+.service('DateUtils', function($filter, CalendarService){
+    
+    return {        
+        getDate: function(dateValue){
+            if(!dateValue){
+                return;
+            }            
+            var calendarSetting = CalendarService.getSetting();
+            dateValue = moment(dateValue, calendarSetting.momentFormat)._d;
+            return Date.parse(dateValue);
+        },
+        format: function(dateValue) {            
+            if(!dateValue){
+                return;
+            }
+
+            var calendarSetting = CalendarService.getSetting();            
+            dateValue = moment(dateValue, calendarSetting.momentFormat)._d;
+            dateValue = $filter('date')(dateValue, calendarSetting.keyDateFormat);            
+            return dateValue;
+        },
+        formatToHrsMins: function(dateValue) {
+            var calendarSetting = CalendarService.getSetting();
+            var dateFormat = 'YYYY-MM-DD @ hh:mm A';
+            if(calendarSetting.keyDateFormat === 'dd-MM-yyyy'){
+                dateFormat = 'DD-MM-YYYY @ hh:mm A';
+            }            
+            return moment(dateValue).format(dateFormat);
+        },
+        getToday: function(){  
+            var calendarSetting = CalendarService.getSetting();
+            var tdy = $.calendars.instance(calendarSetting.keyCalendar).newDate();            
+            var today = moment(tdy._year + '-' + tdy._month + '-' + tdy._day, 'YYYY-MM-DD')._d;            
+            today = Date.parse(today);     
+            today = $filter('date')(today,  calendarSetting.keyDateFormat);
+            return today;
+        },
+        formatFromUserToApi: function(dateValue){            
+            if(!dateValue){
+                return;
+            }
+            var calendarSetting = CalendarService.getSetting();
+            dateValue = moment(dateValue, calendarSetting.momentFormat)._d;
+            dateValue = Date.parse(dateValue);     
+            dateValue = $filter('date')(dateValue, 'yyyy-MM-dd'); 
+            return dateValue;            
+        },
+        formatFromApiToUser: function(dateValue){            
+            if(!dateValue){
+                return;
+            }            
+            var calendarSetting = CalendarService.getSetting();
+            dateValue = moment(dateValue, 'YYYY-MM-DD')._d;
+            return $filter('date')(dateValue, calendarSetting.keyDateFormat); 
+        }
+    };
+})
+
+/* service for dealing with custom form */
+.service('CustomFormService', function(){
+    
+    return {
+        getForProgramStage: function(programStage){
+            
+            var htmlCode = programStage.dataEntryForm ? programStage.dataEntryForm.htmlCode : null;  
+            
+            if(htmlCode){                
+            
+                var programStageDataElements = [];
+
+                angular.forEach(programStage.programStageDataElements, function(prStDe){
+                    programStageDataElements[prStDe.dataElement.id] = prStDe;
+                });
+
+                var inputRegex = /<input.*?\/>/g,
+                    match,
+                    inputFields = [];                
+
+                while (match = inputRegex.exec(htmlCode)) {                
+                    inputFields.push(match[0]);
+                }
+                
+                for(var i=0; i<inputFields.length; i++){                    
+                    var inputField = inputFields[i];                    
+                    var inputElement = $.parseHTML( inputField );
+                    var attributes = {};
+                                       
+                    $(inputElement[0].attributes).each(function() {
+                        attributes[this.nodeName] = this.value;                       
+                    });
+                    
+                    var fieldId = '', errorMessageId = '', newInputField, hasEventDate = false;     
+                    if(attributes.hasOwnProperty('id')){
+                        
+                        if(attributes['id'] === 'executionDate'){
+                            fieldId = 'eventDate';
+                            errorMessageId = '"' + 'eventDate' + '"';
+                            hasEventDate = true;
+                            
+                            //name needs to be unique so that it can be used for validation in angularjs
+                            if(attributes.hasOwnProperty('name')){
+                                attributes['name'] = fieldId;
+                            }
+                            
+                            newInputField = '<input type="text" ' +
+                                                this.getAttributesAsString(attributes) +
+                                                ' d2-validation ' +
+                                                ' ng-model="currentEvent.' + fieldId + '"' +
+                                                ' input-field-id="' + fieldId + '"' +
+                                                ' d2-date ' +
+                                                ' max-date="' + 0 + '"' + 
+                                                ' placeholder="{{dhis2CalendarFormat.keyDateFormat}}" ' +
+                                                ' ng-class="getInputNotifcationClass(prStDes.' + fieldId + '.dataElement.id,true)"' +
+                                                ' blur-or-change="saveDatavalue(prStDes.'+ fieldId + ')"' + 
+                                                ' ng-required="{{true}}">';
+                        }
+                        else{
+                            fieldId = attributes['id'].substring(4, attributes['id'].length-1).split("-")[1]; 
+                            errorMessageId = 'prStDes.' + fieldId + '.dataElement.id';
+                            
+                            //name needs to be unique so that it can be used for validation in angularjs
+                            if(attributes.hasOwnProperty('name')){
+                                attributes['name'] = fieldId;
+                            }
+
+                            //check data element type and generate corresponding angular input field
+                            if(programStageDataElements[fieldId].dataElement.type === "int"){
+                                newInputField = '<input type="text" ' +
+                                                this.getAttributesAsString(attributes) +
+                                                ' d2-validation ' +
+                                                ' d2-number-validation ' +
+                                                ' number-type="' + programStageDataElements[fieldId].dataElement.numberType + '" ' +
+                                                ' ng-model="currentEvent.' + fieldId + '"' +
+                                                ' input-field-id="' + fieldId + '"' +
+                                                ' ng-class="getInputNotifcationClass(prStDes.' + fieldId + '.dataElement.id,true)"' +
+                                                ' ng-disabled="selectedEnrollment.status===\'CANCELLED\' || selectedEnrollment.status===\'COMPLETED\'"' +
+                                                ' ng-blur="saveDatavalue(prStDes.'+ fieldId + ')"' + 
+                                                ' ng-required="{{prStDes.' + fieldId + '.compulsory}}">';
+                            }
+                            if(programStageDataElements[fieldId].dataElement.type === "string"){
+                                if(programStageDataElements[fieldId].dataElement.optionSet){
+                                    var optionSetId = programStageDataElements[fieldId].dataElement.optionSet.id;
+                                    newInputField = '<input type="text" ' +
+                                                this.getAttributesAsString(attributes) +
+                                                ' d2-validation ' +
+                                                ' ng-model="currentEvent.' + fieldId + '" ' +
+                                                ' input-field-id="' + fieldId + '"' +
+                                                ' ng-disabled="selectedEnrollment.status===\'CANCELLED\' || selectedEnrollment.status===\'COMPLETED\' || currentEvent[uid]==\'uid\'"' +
+                                                ' ng-required="{{prStDes.' + fieldId + '.compulsory}}"' +
+                                                ' typeahead="option.name as option.name for option in optionSets.'+optionSetId+'.options | filter:$viewValue | limitTo:20"' +
+                                                ' typeahead-editable="false" ' +
+                                                ' d2-typeahead-validation ' +
+                                                ' class="typeahead" ' +
+                                                ' placeholder="&#xf0d7;&nbsp;&nbsp;" ' +
+                                                ' ng-class="getInputNotifcationClass(prStDes.' + fieldId + '.dataElement.id,true)"' +                                            
+                                                ' ng-blur="saveDatavalue(prStDes.'+ fieldId + ')"' +
+                                                ' typeahead-open-on-focus ng-required="prStDes.'+fieldId+'.compulsory"> ';
+                                }
+                                else{
+                                    newInputField = '<input type="text" ' +
+                                                this.getAttributesAsString(attributes) +
+                                                ' d2-validation ' +
+                                                ' ng-model="currentEvent.' + fieldId + '" ' +
+                                                ' input-field-id="' + fieldId + '"' +
+                                                ' ng-disabled="selectedEnrollment.status===\'CANCELLED\' || selectedEnrollment.status===\'COMPLETED\' || currentEvent[uid]==\'uid\'"' +
+                                                ' ng-class="getInputNotifcationClass(prStDes.' + fieldId + '.dataElement.id,true)"' +
+                                                ' ng-blur="saveDatavalue(prStDes.'+ fieldId + ')"' +
+                                                ' ng-required="prStDes.' + fieldId + '.compulsory"> ';                                     
+                                }
+                            }
+                            if(programStageDataElements[fieldId].dataElement.type === "bool"){
+                                newInputField = '<select ' +
+                                                this.getAttributesAsString(attributes) +
+                                                ' d2-validation ' +
+                                                ' ng-model="currentEvent.' + fieldId + '" ' +
+                                                ' input-field-id="' + fieldId + '"' +
+                                                ' ng-class="getInputNotifcationClass(prStDes.' + fieldId + '.dataElement.id,true)"' +
+                                                ' ng-disabled="selectedEnrollment.status===\'CANCELLED\' || selectedEnrollment.status===\'COMPLETED\'"' +
+                                                ' ng-change="saveDatavalue(prStDes.'+ fieldId + ')"' + 
+                                                ' ng-required="{{prStDes.' + fieldId + '.compulsory}}">' + 
+                                                '<option value="">{{\'please_select\'| translate}}</option>' +
+                                                '<option value="false">{{\'no\'| translate}}</option>' + 
+                                                '<option value="true">{{\'yes\'| translate}}</option>' +
+                                                '</select> ';                                     
+                            }
+                            if(programStageDataElements[fieldId].dataElement.type === "date"){
+                                var maxDate = programStageDataElements[fieldId].allowFutureDate ? '' : 0;
+                                newInputField = '<input type="text" ' +
+                                                this.getAttributesAsString(attributes) +
+                                                ' d2-validation ' +
+                                                ' ng-model="currentEvent.' + fieldId + '"' +
+                                                ' input-field-id="' + fieldId + '"' +                                                
+                                                ' placeholder="{{dhis2CalendarFormat.keyDateFormat}}" ' +
+                                                ' d2-date ' +
+                                                ' max-date="' + maxDate + '"' +
+                                                ' ng-class="getInputNotifcationClass(prStDes.' + fieldId + '.dataElement.id,true)"' +
+                                                ' ng-disabled="selectedEnrollment.status===\'CANCELLED\' || selectedEnrollment.status===\'COMPLETED\'"' +
+                                                ' blur-or-change="saveDatavalue(prStDes.'+ fieldId + ')"' + 
+                                                ' ng-required="{{prStDes.' + fieldId + '.compulsory}}"> '; 
+                            }
+                            if(programStageDataElements[fieldId].dataElement.type === "trueOnly"){
+                                newInputField = '<input type="checkbox" ' +
+                                                this.getAttributesAsString(attributes) +
+                                                ' d2-validation ' +
+                                                ' ng-model="currentEvent.' + fieldId + '"' +
+                                                ' input-field-id="' + fieldId + '"' +
+                                                ' ng-class="getInputNotifcationClass(prStDes.' + fieldId + '.dataElement.id,true)"' +
+                                                ' ng-disabled="selectedEnrollment.status===\'CANCELLED\' || selectedEnrollment.status===\'COMPLETED\'"' +
+                                                ' ng-change="saveDatavalue(prStDes.'+ fieldId + ')"' +
+                                                ' ng-required="{{prStDes.' + fieldId + '.compulsory}}"> ';
+                            }                            
+                        }
+						
+                        newInputField = newInputField + ' <span ng-show="(outerForm.'+ fieldId +'.$dirty && outerForm.'+ fieldId +'.$invalid) || (outerForm.submitted && outerForm.'+ fieldId +'.$invalid) || (currentEvent.' + fieldId + ' && outerForm.' + fieldId + '.$invalid)" class="required">{{getErrorMessage(' + errorMessageId + ')}}</span> ';
+                        
+                        htmlCode = htmlCode.replace(inputField, newInputField);
+                    }
+                }
+                return {htmlCode: htmlCode, hasEventDate: hasEventDate};
+            }
+            return null;
+        },
+        getAttributesAsString: function(attributes){
+            if(attributes){
+                var attributesAsString = '';                
+                for(var prop in attributes){
+                    if(prop !== 'value'){
+                        attributesAsString += prop + '="' + attributes[prop] + '" ';
+                    }
+                }
+                return attributesAsString;
+            }
+            return null;
+        }
+    };            
+})
+
+/* Context menu for grid*/
+.service('ContextMenuSelectedItem', function(){
+    this.selectedItem = '';
+    
+    this.setSelectedItem = function(selectedItem){  
+        this.selectedItem = selectedItem;        
+    };
+    
+    this.getSelectedItem = function(){
+        return this.selectedItem;
+    };
+})
+
+/* Error messages*/
+.service('ErrorMessageService', function(){
+    this.errorMessages = {};
+    
+    this.setErrorMessages = function(errorMessages){  
+        this.errorMessages = errorMessages;        
+    };
+    
+    this.getErrorMessages = function(){
+        return this.errorMessages;
+    };
+    
+    this.get = function(id){
+        return this.errorMessages[id];
+    };
+})
+
+/* Modal service for user interaction */
+.service('ModalService', ['$modal', function($modal) {
+
+    var modalDefaults = {
+        backdrop: true,
+        keyboard: true,
+        modalFade: true,
+        templateUrl: 'views/modal.html'
+    };
+
+    var modalOptions = {
+        closeButtonText: 'Close',
+        actionButtonText: 'OK',
+        headerText: 'Proceed?',
+        bodyText: 'Perform this action?'
+    };
+
+    this.showModal = function(customModalDefaults, customModalOptions) {
+        if (!customModalDefaults)
+            customModalDefaults = {};
+        customModalDefaults.backdrop = 'static';
+        return this.show(customModalDefaults, customModalOptions);
+    };
+
+    this.show = function(customModalDefaults, customModalOptions) {
+        //Create temp objects to work with since we're in a singleton service
+        var tempModalDefaults = {};
+        var tempModalOptions = {};
+
+        //Map angular-ui modal custom defaults to modal defaults defined in service
+        angular.extend(tempModalDefaults, modalDefaults, customModalDefaults);
+
+        //Map modal.html $scope custom properties to defaults defined in service
+        angular.extend(tempModalOptions, modalOptions, customModalOptions);
+
+        if (!tempModalDefaults.controller) {
+            tempModalDefaults.controller = function($scope, $modalInstance) {
+                $scope.modalOptions = tempModalOptions;
+                $scope.modalOptions.ok = function(result) {
+                    $modalInstance.close(result);
+                };
+                $scope.modalOptions.close = function(result) {
+                    $modalInstance.dismiss('cancel');
+                };
+            };
+        }
+
+        return $modal.open(tempModalDefaults).result;
+    };
+
+}])
+
+/* Dialog service for user interaction */
+.service('DialogService', ['$modal', function($modal) {
+
+    var dialogDefaults = {
+        backdrop: true,
+        keyboard: true,
+        backdropClick: true,
+        modalFade: true,            
+        templateUrl: 'views/dialog.html'
+    };
+
+    var dialogOptions = {
+        closeButtonText: 'close',
+        actionButtonText: 'ok',
+        headerText: 'dhis2_tracker',
+        bodyText: 'Perform this action?'
+    };
+
+    this.showDialog = function(customDialogDefaults, customDialogOptions) {
+        if (!customDialogDefaults)
+            customDialogDefaults = {};
+        customDialogDefaults.backdropClick = false;
+        return this.show(customDialogDefaults, customDialogOptions);
+    };
+
+    this.show = function(customDialogDefaults, customDialogOptions) {
+        //Create temp objects to work with since we're in a singleton service
+        var tempDialogDefaults = {};
+        var tempDialogOptions = {};
+
+        //Map angular-ui modal custom defaults to modal defaults defined in service
+        angular.extend(tempDialogDefaults, dialogDefaults, customDialogDefaults);
+
+        //Map modal.html $scope custom properties to defaults defined in service
+        angular.extend(tempDialogOptions, dialogOptions, customDialogOptions);
+
+        if (!tempDialogDefaults.controller) {
+            tempDialogDefaults.controller = function($scope, $modalInstance) {
+                $scope.dialogOptions = tempDialogOptions;
+                $scope.dialogOptions.ok = function(result) {
+                    $modalInstance.close(result);
+                };                           
+            };
+        }
+
+        return $modal.open(tempDialogDefaults).result;
+    };
+
+}])
+
+.service('Paginator', function () {
+    this.page = 1;
+    this.pageSize = 50;
+    this.itemCount = 0;
+    this.pageCount = 0;
+    this.toolBarDisplay = 5;
+
+    this.setPage = function (page) {
+        if (page > this.getPageCount()) {
+            return;
+        }
+
+        this.page = page;
+    };
+    
+    this.getPage = function(){
+        return this.page;
+    };
+    
+    this.setPageSize = function(pageSize){
+      this.pageSize = pageSize;
+    };
+    
+    this.getPageSize = function(){
+        return this.pageSize;
+    };
+    
+    this.setItemCount = function(itemCount){
+      this.itemCount = itemCount;
+    };
+    
+    this.getItemCount = function(){
+        return this.itemCount;
+    };
+    
+    this.setPageCount = function(pageCount){
+        this.pageCount = pageCount;
+    };
+
+    this.getPageCount = function () {
+        return this.pageCount;
+    };
+
+    this.lowerLimit = function() { 
+        var pageCountLimitPerPageDiff = this.getPageCount() - this.toolBarDisplay;
+
+        if (pageCountLimitPerPageDiff < 0) { 
+            return 0; 
+        }
+
+        if (this.getPage() > pageCountLimitPerPageDiff + 1) { 
+            return pageCountLimitPerPageDiff; 
+        } 
+
+        var low = this.getPage() - (Math.ceil(this.toolBarDisplay/2) - 1); 
+
+        return Math.max(low, 0);
+    };
+})
+
+.service('GridColumnService', function(){
+    return {        
+        columnExists: function(cols, id) {
+            var colExists = false;
+            if(!angular.isObject(cols) || !id || angular.isObject(cols) && !cols.length){
+                return colExists;
+            }
+            
+            for(var i=0; i<cols.length && !colExists; i++){
+                if(cols[i].id === id){
+                    colExists = true;
+                }
+            }
+            return colExists;
+        }
+    };
+});