← Back to team overview

dhis2-devs team mailing list archive

[Branch ~dhis2-devs-core/dhis2/trunk] Rev 16696: tracker capture - overdue and upcoming reports now allow for pdf printing and export to CSV, exce...

 

------------------------------------------------------------
revno: 16696
committer: Abyot Asalefew Gizaw <abyota@xxxxxxxxx>
branch nick: dhis2
timestamp: Thu 2014-09-11 14:35:41 +0200
message:
  tracker capture - overdue and upcoming reports now allow for pdf printing and export to CSV, excel is WIP
added:
  dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/scripts/ng-csv.js
  dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/angular/angular-sanitize.js
modified:
  dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/components/report/overdue-events-controller.js
  dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/components/report/overdue-events.html
  dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/components/report/upcoming-events-controller.js
  dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/components/report/upcoming-events.html
  dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/i18n/en.json
  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/app.js
  dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/scripts/directives.js
  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/styles/style.css
  dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/views/column-modal.html


--
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-tracker-capture/components/report/overdue-events-controller.js'
--- dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/components/report/overdue-events-controller.js	2014-09-10 07:13:29 +0000
+++ dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/components/report/overdue-events-controller.js	2014-09-11 12:35:41 +0000
@@ -2,9 +2,9 @@
          function($scope,
                 $modal,
                 $location,
+                $translate,
                 orderByFilter,
                 DateUtils,
-                EventUtils,
                 TEIService,
                 TEIGridService,
                 TranslationService,
@@ -17,7 +17,6 @@
     
     $scope.today = DateUtils.format(moment());
     
-    $scope.ouModes = [{name: 'SELECTED'}, {name: 'CHILDREN'}, {name: 'DESCENDANTS'}, {name: 'ACCESSIBLE'}];         
     $scope.selectedOuMode = 'SELECTED';
     $scope.report = {};
     $scope.displayMode = {};
@@ -79,7 +78,7 @@
         if (angular.isObject($scope.selectedProgram)){
             $scope.generateReport();
         }
-    });
+    });    
     
     $scope.generateReport = function(){
         
@@ -98,12 +97,11 @@
             AttributesFactory.getByProgram($scope.selectedProgram).then(function(atts){            
                 $scope.gridColumns = TEIGridService.generateGridColumns(atts, $scope.selectedOuMode);
 
-                $scope.gridColumns.push({name: 'event_name', id: 'event_name', type: 'string', displayInListNoProgram: false, showFilter: false, show: true});
-                $scope.filterTypes['event_name'] = 'string';                
-
-                $scope.gridColumns.push({name: 'due_date', id: 'due_date', type: 'date', displayInListNoProgram: false, showFilter: false, show: true});
-                $scope.filterTypes['due_date'] = 'date';
-                $scope.filterText['due_date']= {};                
+                $scope.gridColumns.push({name: $translate('event_name'), id: 'eventName', type: 'string', displayInListNoProgram: false, showFilter: false, show: true});
+                $scope.filterTypes['eventName'] = 'string';
+                $scope.gridColumns.push({name: $translate('due_date'), id: 'dueDate', type: 'date', displayInListNoProgram: false, showFilter: false, show: true});
+                $scope.filterTypes['dueDate'] = 'date';
+                $scope.filterText['dueDate']= {};                
             });  
 
             //fetch TEIs for the selected program and orgunit/mode
@@ -116,61 +114,36 @@
                                 false).then(function(data){                     
 
                 //process tei grid
-                var teis = TEIGridService.format(data,true);     
-                $scope.teiList = [];            
+                var teis = TEIGridService.format(data,true);
+                $scope.overdueEvents = [];
                 DHIS2EventFactory.getByOrgUnitAndProgram($scope.selectedOrgUnit.id, $scope.selectedOuMode, $scope.selectedProgram.id, null, null).then(function(eventList){
-                    $scope.dhis2Events = [];
+                    
                     angular.forEach(eventList, function(ev){
                         if(ev.dueDate){
                             ev.dueDate = DateUtils.format(ev.dueDate);
-
                             if( ev.trackedEntityInstance && 
                                 !ev.eventDate && 
                                 ev.dueDate < $scope.today){
-
-                                ev.name = $scope.programStages[ev.programStage].name;
-                                ev.programName = $scope.selectedProgram.name;
-                                ev.statusColor = EventUtils.getEventStatusColor(ev); 
-                                ev.dueDate = DateUtils.format(ev.dueDate);
-
-                                if($scope.dhis2Events[ev.trackedEntityInstance]){
-                                    if(teis.rows[ev.trackedEntityInstance]){
-                                        $scope.teiList.push(teis.rows[ev.trackedEntityInstance]);
-                                        delete teis.rows[ev.trackedEntityInstance];
-                                    }                     
-                                    $scope.dhis2Events[ev.trackedEntityInstance].push(ev);
-                                }
-                                else{
-                                    if(teis.rows[ev.trackedEntityInstance]){
-                                        $scope.teiList.push(teis.rows[ev.trackedEntityInstance]);
-                                        delete teis.rows[ev.trackedEntityInstance];
-                                    }  
-                                    $scope.dhis2Events[ev.trackedEntityInstance] = [ev];
-                                }
-                                ev = EventUtils.setEventOrgUnitName(ev);
-                            }                        
+                                
+                                var overDue = {};
+                                angular.copy(teis.rows[ev.trackedEntityInstance],overDue);
+                                angular.extend(overDue,{eventName: $scope.programStages[ev.programStage].name, dueDate: ev.dueDate, followup: ev.followup});
+                                
+                                $scope.overdueEvents.push(overDue);
+                            }
                         }
                     });
-
-                    //incase a TEI happens to have more than one overdue, sort using duedate
-                    for(var tei in $scope.dhis2Events){                    
-                        $scope.dhis2Events[tei] = orderByFilter($scope.dhis2Events[tei], '-dueDate');
-                        $scope.dhis2Events[tei].reverse();
-                    }
-
-                    //make upcoming event name and its due date part of the grid column
-                    for(var i=0; i<$scope.teiList.length; i++){
-                        $scope.teiList[i].event_name = $scope.dhis2Events[$scope.teiList[i].id][0].name;
-                        $scope.teiList[i].due_date = $scope.dhis2Events[$scope.teiList[i].id][0].dueDate;
-                        $scope.teiList[i].followup = $scope.dhis2Events[$scope.teiList[i].id][0].followup;
-                    }
+                    
+                    //sort overdue events by their due dates - this is default
+                    $scope.overdueEvents = orderByFilter($scope.overdueEvents, '-dueDate');
+                    $scope.overdueEvents.reverse();
 
                     $scope.reportFinished = true;
-                    $scope.reportStarted = false;                
+                    $scope.reportStarted = false;
                 });
-            });            
-        }        
-    };
+            });
+        }
+    };    
     
     $scope.showHideColumns = function(){
         
@@ -201,13 +174,19 @@
         });
     };
     
-    $scope.sortTEIGrid = function(gridHeader){
+    $scope.sortGrid = function(gridHeader){
         if ($scope.sortHeader === gridHeader.id){
             $scope.reverse = !$scope.reverse;
             return;
         }        
         $scope.sortHeader = gridHeader.id;
-        $scope.reverse = false;    
+        $scope.reverse = false;
+        
+        $scope.overdueEvents = orderByFilter($scope.overdueEvents, $scope.sortHeader);
+        
+        if($scope.reverse){
+            $scope.overdueEvents.reverse();
+        }
     };
     
     $scope.searchInGrid = function(gridColumn){
@@ -238,4 +217,12 @@
         $location.path('/dashboard').search({tei: tei.id,                                            
                                             program: $scope.selectedProgram ? $scope.selectedProgram.id: null});
     };
+    
+    $scope.generateReportData = function(){
+        return TEIGridService.getData($scope.overdueEvents, $scope.gridColumns);
+    };
+    
+    $scope.generateReportHeader = function(){
+        return TEIGridService.getHeader($scope.gridColumns);
+    };
 });
\ No newline at end of file

=== modified file 'dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/components/report/overdue-events.html'
--- dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/components/report/overdue-events.html	2014-09-10 07:13:29 +0000
+++ dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/components/report/overdue-events.html	2014-09-11 12:35:41 +0000
@@ -21,13 +21,13 @@
     <div class="row top-bar">        
         <div class="col-sm-12">            
             {{'overdue_events'| translate}}
-            <div class="pull-right">
+            <div class="pull-right not-printable">
                 <div class="btn-group" dropdown is-open="status.isopen">
-                    <button type="button" class="btn btn-default dropdown-toggle" ng-disabled="!teiList.length">
+                    <button type="button" class="btn btn-default dropdown-toggle" ng-disabled="!overdueEvents.length">
                         <i class="fa fa-cog" title="{{'settings'| translate}}"></i>
                     </button>
                     <ul class="dropdown-menu pull-right" role="menu">
-                        <li ng-show="teiList.length > 0"><a href ng-click="showHideColumns()">{{'show_hide_columns'| translate}}</a></li>
+                        <li ng-show="overdueEvents.length > 0"><a href ng-click="showHideColumns()">{{'show_hide_columns'| translate}}</a></li>
                     </ul>
                 </div>
             </div>            
@@ -39,18 +39,18 @@
     <form name="outerForm" novalidate>               
         <div class="row">
             <div class="col-sm-8 col-md-6">
-                <table class="table table-borderless table-striped">
-                    <tr ng-show='printMode'>
-                        <td class='col-sm-4 col-md-3 vertical-center'>{{'org_unit'| translate}}</td>
-                        <td class='col-sm-4 col-md-3'>                             
-                            <input type="text" selected-org-unit ng-model="selectedOrgUnit.name">                                                            
+                <table class="table-borderless table-striped">
+                    <tr>
+                        <td>{{'org_unit'| translate}}</td>
+                        <td>
+                            <input type="text" class="form-control" selected-org-unit ng-model="selectedOrgUnit.name" value="{{selectedOrgUnit.name || 'please_select'| translate}}" ng-disabled="true">                                                                                        
                         </td>
                     </tr>
                     <tr>
-                        <td class='col-sm-4 col-md-3'>
+                        <td>
                             {{'program'| translate}}
                         </td>
-                        <td class='col-sm-4 col-md-3'>
+                        <td>
                             <select ng-model="selectedProgram"
                                     class="form-control"
                                     ng-options="program as program.name for program in programs | orderBy: 'name'" 
@@ -60,8 +60,8 @@
                         </td>
                     </tr>
                     <tr>
-                        <td class='col-sm-4 col-md-3 vertical-center'>{{'org_unit_scope'| translate}}</td>
-                        <td class='col-sm-4 col-md-3'>                 
+                        <td>{{'org_unit_scope'| translate}}</td>
+                        <td>                 
                             <label><input type="radio" ng-model="selectedOuMode" name="selected" value="SELECTED"> {{'SELECTED'| translate}}</label><br/>
                             <label><input type="radio" ng-model="selectedOuMode" name="children" value="CHILDREN"> {{'CHILDREN'| translate}}</label><br/>
                             <label><input type="radio" ng-model="selectedOuMode" name="descendants" value="DESCENDANTS"> {{'DESCENDANTS'| translate}}</label><br/>
@@ -69,21 +69,18 @@
                         </td>
                     </tr>
                     <tr>
-                    <label>                    
-                        <td class='col-sm-4 col-md-3 vertical-center'>
+                        <td>
                             {{'filter'| translate}}
                         </td>
-                        <td class='col-sm-4 col-md-3'> 
+                        <td> 
                             <label>
                                 <input type="checkbox" ng-change="markForFollowup()" ng-model="displayMode.onlyMarkedFollowup"/>  {{'only_marked_for_followup'| translate}}
                             </label>                                
                         </td>
-                    </label>
                     </tr>
                 </table>
             </div>
-        </div>       
-
+        </div>
         <div class="row" ng-if="programs.length < 1">        
             <div class="col-sm-8 col-md-6 vertical-spacing">
                 <div class="alert alert-warning">{{'no_program_exists_report'| translate}}</div> 
@@ -101,8 +98,7 @@
 
     <!-- upcoming events list begins -->
     <div ng-if="reportFinished">
-
-        <div ng-switch="teiList.length">                    
+        <div ng-switch="overdueEvents.length">                    
             <div ng-switch-when="undefined">
                 <div class="alert alert-warning vertical-spacing">
                     {{'no_data_found'| translate}}
@@ -115,22 +111,42 @@
             </div>    
             <div ng-switch-default>
                 <!-- report begins -->
-                <div class="vertical-spacing">   
+
+                <div class='pull-right vertical-spacing not-printable'>
+                    <button type="button"
+                            class="btn btn-primary"
+                            onclick="javascript:window.print()">
+                        {{'print'| translate}}
+                    </button>
+                    <!--<button type="button" 
+                            class="btn btn-success small-horizonal-spacing" 
+                            ng-click="exportToExcel()">
+                        {{'excel_export'| translate}}
+                    </button>-->
+                    <button type="button" 
+                            class="btn btn-success small-horizonal-spacing" 
+                            ng-csv="generateReportData()"
+                            csv-header="generateReportHeader()"
+                            filename="overdueEvents.csv">
+                        {{'excel_export'| translate}}
+                    </button>
+                </div>
+                <div class="vertical-spacing">
                     <table class="listTable dhis2-table-striped-border dhis2-table-hover">               
                         <thead>                        
                             <tr>
                                 <th ng-show="gridColumn.show" ng-repeat="gridColumn in gridColumns">
 
                                     <!-- sort icon begins -->
-                                    <span ng-click="sortTEIGrid(gridColumn)">
-                                        {{gridColumn.name| translate}}
-                                        <i ng-if="sortHeader == gridColumn.id && reverse" class="fa fa-sort-desc"></i>
-                                        <i ng-if="sortHeader == gridColumn.id && !reverse" class="fa fa-sort-asc"></i>
+                                    <span ng-click="sortGrid(gridColumn)">
+                                        {{gridColumn.name}}
+                                        <i ng-if="sortHeader == gridColumn.id && reverse" class="fa fa-sort-desc not-printable"></i>
+                                        <i ng-if="sortHeader == gridColumn.id && !reverse" class="fa fa-sort-asc not-printable"></i>
                                     </span>
                                     <!-- sort icon ends -->
 
                                     <!-- filter icon begins -->
-                                    <span class='pull-right'>
+                                    <span class='pull-right not-printable'>
                                         <span ng-show="gridColumn.type != 'date' && gridColumn.type != 'int'">
                                             <a href ng-click="searchInGrid(gridColumn)" title="{{'search'| translate}}"><span ng-class="{true: 'filter - without - content', false: 'filter - with - content'} [filterText[gridColumn.id] == undefined || filterText[gridColumn.id] == '']"><i class="fa fa-search"></i></span></a>
                                         </span>
@@ -141,7 +157,7 @@
                                     <!-- filter icon ends -->
 
                                     <!-- filter input field begins -->
-                                    <span ng-show="gridColumn.showFilter">  
+                                    <span ng-show="gridColumn.showFilter" class="not-printable">  
                                         <span ng-switch="gridColumn.type">
                                             <span ng-switch-when="int">
                                                 <input style="width: 45%;" placeholder="{{'lower_limit'| translate}}" type="number" ng-model="filterText[gridColumn.id].start" ng-blur="searchInGrid(gridColumn)">
@@ -168,17 +184,18 @@
                             </tr>                        
                         </thead>
                         <tbody id="list">
-                            <tr ng-repeat="tei in teiList| orderBy:sortHeader:reverse | gridFilter:filterText:filterTypes"
-                                ng-click="showDashboard(tei)"
+                            <tr ng-repeat="overdueEvent in overdueEvents | orderBy:sortHeader:reverse | gridFilter:filterText:filterTypes"
+                                ng-click="showDashboard(overdueEvent)"
                                 title="{{'go_to_dashboard'| translate}}">
                                 <td ng-show="gridColumn.show"                                            
-                                    ng-repeat="gridColumn in gridColumns" ng-if='displayMode.onlyMarkedFollowup ? tei.followup:true'>                                                
-                                    {{tei[gridColumn.id]}}                                                
+                                    ng-repeat="gridColumn in gridColumns" ng-if='displayMode.onlyMarkedFollowup ? overdueEvent.followup:true'>                                                
+                                    {{overdueEvent[gridColumn.id]}}                                                
                                 </td>                                
                             </tr>
                         </tbody>        
-                    </table>                    
+                    </table>
                 </div>
+
                 <!-- report ends -->
             </div>
         </div> 

=== modified file 'dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/components/report/upcoming-events-controller.js'
--- dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/components/report/upcoming-events-controller.js	2014-09-09 13:26:15 +0000
+++ dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/components/report/upcoming-events-controller.js	2014-09-11 12:35:41 +0000
@@ -2,9 +2,9 @@
          function($scope,
                 $modal,
                 $location,
+                $translate,
                 orderByFilter,
-                DateUtils,
-                EventUtils,
+                DateUtils,                
                 TEIService,
                 TEIGridService,
                 TranslationService,
@@ -17,10 +17,10 @@
     
     $scope.today = DateUtils.format(moment());
     
-    $scope.ouModes = [{name: 'SELECTED'}, {name: 'CHILDREN'}, {name: 'DESCENDANTS'}, {name: 'ACCESSIBLE'}];         
-    $scope.selectedOuMode = $scope.ouModes[0];
+    $scope.selectedOuMode = 'SELECTED';
     $scope.report = {};
     $scope.displayMode = {};
+    $scope.printMode = false;
     
     //watch for selection of org unit from tree
     $scope.$watch('selectedOrgUnit', function() {        
@@ -72,19 +72,18 @@
         });
             
         AttributesFactory.getByProgram($scope.selectedProgram).then(function(atts){            
-            $scope.gridColumns = TEIGridService.generateGridColumns(atts, $scope.selectedOuMode.name);
-
-            $scope.gridColumns.push({name: 'event_name', id: 'event_name', type: 'string', displayInListNoProgram: false, showFilter: false, show: true});
-            $scope.filterTypes['event_name'] = 'string';                
-
-            $scope.gridColumns.push({name: 'due_date', id: 'due_date', type: 'date', displayInListNoProgram: false, showFilter: false, show: true});
-            $scope.filterTypes['due_date'] = 'date';
-            $scope.filterText['due_date']= {};                
+            $scope.gridColumns = TEIGridService.generateGridColumns(atts, $scope.selectedOuMode);
+
+            $scope.gridColumns.push({name: $translate('event_name'), id: 'eventName', type: 'string', displayInListNoProgram: false, showFilter: false, show: true});
+            $scope.filterTypes['eventName'] = 'string';
+            $scope.gridColumns.push({name: $translate('due_date'), id: 'dueDate', type: 'date', displayInListNoProgram: false, showFilter: false, show: true});
+            $scope.filterTypes['dueDate'] = 'date';
+            $scope.filterText['dueDate']= {};                
         });  
         
         //fetch TEIs for the selected program and orgunit/mode
         TEIService.search($scope.selectedOrgUnit.id, 
-                            $scope.selectedOuMode.name,
+                            $scope.selectedOuMode,
                             null,
                             'program=' + $scope.selectedProgram.id,
                             null,
@@ -93,9 +92,8 @@
             
             //process tei grid
             var teis = TEIGridService.format(data,true);     
-            $scope.teiList = [];            
-            DHIS2EventFactory.getByOrgUnitAndProgram($scope.selectedOrgUnit.id, $scope.selectedOuMode.name, $scope.selectedProgram.id, null, null).then(function(eventList){
-                $scope.dhis2Events = [];
+            $scope.upcomingEvents = [];          
+            DHIS2EventFactory.getByOrgUnitAndProgram($scope.selectedOrgUnit.id, $scope.selectedOuMode, $scope.selectedProgram.id, null, null).then(function(eventList){
                 angular.forEach(eventList, function(ev){
                     if(ev.dueDate){
                         ev.dueDate = DateUtils.format(ev.dueDate);
@@ -105,45 +103,21 @@
                             ev.dueDate >= report.startDate && 
                             ev.dueDate <= report.endDate){
                         
-                            ev.name = $scope.programStages[ev.programStage].name;
-                            ev.programName = $scope.selectedProgram.name;
-                            ev.statusColor = EventUtils.getEventStatusColor(ev); 
-                            ev.dueDate = DateUtils.format(ev.dueDate);
+                            var upcomingEvent = {};
+                            angular.copy(teis.rows[ev.trackedEntityInstance],upcomingEvent);
+                            angular.extend(upcomingEvent,{eventName: $scope.programStages[ev.programStage].name, dueDate: ev.dueDate, followup: ev.followup});
 
-                            if($scope.dhis2Events[ev.trackedEntityInstance]){
-                                if(teis.rows[ev.trackedEntityInstance]){
-                                    $scope.teiList.push(teis.rows[ev.trackedEntityInstance]);
-                                    delete teis.rows[ev.trackedEntityInstance];
-                                }                     
-                                $scope.dhis2Events[ev.trackedEntityInstance].push(ev);
-                            }
-                            else{
-                                if(teis.rows[ev.trackedEntityInstance]){
-                                    $scope.teiList.push(teis.rows[ev.trackedEntityInstance]);
-                                    delete teis.rows[ev.trackedEntityInstance];
-                                }  
-                                $scope.dhis2Events[ev.trackedEntityInstance] = [ev];
-                            }
-                            ev = EventUtils.setEventOrgUnitName(ev);
+                            $scope.upcomingEvents.push(upcomingEvent);
                         }                        
                     }
                 });
-                
-                //incase a TEI happens to have more than one overdue, sort using duedate
-                for(var tei in $scope.dhis2Events){                    
-                    $scope.dhis2Events[tei] = orderByFilter($scope.dhis2Events[tei], '-dueDate');
-                    $scope.dhis2Events[tei].reverse();
-                }
-                
-                //make upcoming event name and its due date part of the grid column
-                for(var i=0; i<$scope.teiList.length; i++){
-                    $scope.teiList[i].event_name = $scope.dhis2Events[$scope.teiList[i].id][0].name;
-                    $scope.teiList[i].due_date = $scope.dhis2Events[$scope.teiList[i].id][0].dueDate;
-                    $scope.teiList[i].followup = $scope.dhis2Events[$scope.teiList[i].id][0].followup;
-                }
-               
+
+                //sort upcoming events by their due dates - this is default
+                $scope.upcomingEvents = orderByFilter($scope.upcomingEvents, '-dueDate');
+                $scope.upcomingEvents.reverse();
+
                 $scope.reportFinished = true;
-                $scope.reportStarted = false;                
+                $scope.reportStarted = false;
             });
         });
     };
@@ -177,13 +151,19 @@
         });
     };
     
-    $scope.sortTEIGrid = function(gridHeader){
+    $scope.sortGrid = function(gridHeader){
         if ($scope.sortHeader === gridHeader.id){
             $scope.reverse = !$scope.reverse;
             return;
         }        
         $scope.sortHeader = gridHeader.id;
-        $scope.reverse = false;    
+        $scope.reverse = false;
+        
+        $scope.upcomingEvents = orderByFilter($scope.upcomingEvents, $scope.sortHeader);
+        
+        if($scope.reverse){
+            $scope.upcomingEvents.reverse();
+        }
     };
     
     $scope.searchInGrid = function(gridColumn){
@@ -214,4 +194,12 @@
         $location.path('/dashboard').search({tei: tei.id,                                            
                                             program: $scope.selectedProgram ? $scope.selectedProgram.id: null});
     };
+    
+    $scope.generateReportData = function(){
+        return TEIGridService.getData($scope.upcomingEvents, $scope.gridColumns);
+    };
+    
+    $scope.generateReportHeader = function(){
+        return TEIGridService.getHeader($scope.gridColumns);
+    };
 });
\ No newline at end of file

=== modified file 'dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/components/report/upcoming-events.html'
--- dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/components/report/upcoming-events.html	2014-09-09 12:54:28 +0000
+++ dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/components/report/upcoming-events.html	2014-09-11 12:35:41 +0000
@@ -13,9 +13,6 @@
         </ul>
     </div>
     <img id="ouwt_loader" src="../images/ajax-loader-bar.gif"/>
-    <!--- selected org unit begins -->
-    <input type="text" selected-org-unit ng-model="selectedOrgUnit.name" ng-hide=true>
-    <!--- selected org unit ends  -->
 </div>
 
 <div id="mainPage" class="page">
@@ -26,11 +23,11 @@
             {{'upcoming_events'| translate}}
             <div class="pull-right">
                 <div class="btn-group" dropdown is-open="status.isopen">
-                    <button type="button" class="btn btn-default dropdown-toggle" ng-disabled="!teiList.length">
+                    <button type="button" class="btn btn-default dropdown-toggle" ng-disabled="!upcomingEvents.length">
                         <i class="fa fa-cog" title="{{'settings'| translate}}"></i>
                     </button>
                     <ul class="dropdown-menu pull-right" role="menu">
-                        <li ng-show="teiList.length > 0"><a href ng-click="showHideColumns()">{{'show_hide_columns'| translate}}</a></li>
+                        <li ng-show="upcomingEvents.length > 0"><a href ng-click="showHideColumns()">{{'show_hide_columns'| translate}}</a></li>
                     </ul>
                 </div>
             </div>            
@@ -42,12 +39,18 @@
     <form name="outerForm" novalidate>               
         <div class="row">
             <div class="col-sm-8 col-md-6">
-                <table class="table table-borderless table-striped">
-                    <tr>
-                        <td class='col-sm-4 col-md-3 vertical-center'>
+                <table class="table-borderless table-striped">
+                    <tr>
+                        <td>{{'org_unit'| translate}}</td>
+                        <td>
+                            <input type="text" class="form-control" selected-org-unit ng-model="selectedOrgUnit.name" value="{{selectedOrgUnit.name || 'please_select'| translate}}" ng-disabled="true">                                                                                        
+                        </td>
+                    </tr>
+                    <tr>
+                        <td>
                             {{'program'| translate}}
                         </td>
-                        <td class='col-sm-4 col-md-3'>
+                        <td>
                             <select ng-model="selectedProgram"
                                     class="form-control"
                                     ng-options="program as program.name for program in programs | orderBy: 'name'" 
@@ -57,25 +60,23 @@
                         </td>
                     </tr>
                     <tr>
-                        <td class='col-sm-4 col-md-3 vertical-center'>{{'org_unit'| translate}}</td>
-                        <td class='col-sm-4 col-md-3'>                 
-                            <label><input type="radio" ng-model="selectedOuMode.name" name="selected" value="SELECTED"> {{'SELECTED'| translate}}</label><br/>
-                            <label><input type="radio" ng-model="selectedOuMode.name" name="children" value="CHILDREN"> {{'CHILDREN'| translate}}</label><br/>
-                            <label><input type="radio" ng-model="selectedOuMode.name" name="descendants" value="DESCENDANTS"> {{'DESCENDANTS'| translate}}</label><br/>
-                            <label><input type="radio" ng-model="selectedOuMode.name" name="accessible" value="ACCESSIBLE"> {{'ACCESSIBLE'| translate}}</label>
+                        <td>{{'org_unit_scope'| translate}}</td>
+                        <td>                 
+                            <label><input type="radio" ng-model="selectedOuMode" name="selected" value="SELECTED"> {{'SELECTED'| translate}}</label><br/>
+                            <label><input type="radio" ng-model="selectedOuMode" name="children" value="CHILDREN"> {{'CHILDREN'| translate}}</label><br/>
+                            <label><input type="radio" ng-model="selectedOuMode" name="descendants" value="DESCENDANTS"> {{'DESCENDANTS'| translate}}</label><br/>
+                            <label><input type="radio" ng-model="selectedOuMode" name="accessible" value="ACCESSIBLE"> {{'ACCESSIBLE'| translate}}</label>
                         </td>
                     </tr>
                     <tr>
-                        <label>                    
-                            <td class='col-sm-4 col-md-3 vertical-center'>
-                                {{'filter'| translate}}
-                            </td>
-                            <td>
-                                <label>
-                                    <input type="checkbox" ng-change="markForFollowup()" ng-model="displayMode.onlyMarkedFollowup"/>  {{'only_marked_for_followup' | translate}}
-                                </label>                                
-                            </td>
-                        </label>
+                        <td>
+                            {{'filter'| translate}}
+                        </td>
+                        <td> 
+                            <label>
+                                <input type="checkbox" ng-change="markForFollowup()" ng-model="displayMode.onlyMarkedFollowup"/>  {{'only_marked_for_followup'| translate}}
+                            </label>                                
+                        </td>
                     </tr>
                 </table>
             </div>
@@ -103,7 +104,22 @@
                 </table>                
             </div>
             <div class="col-md-6 trim">
-                <button type="button" class="btn btn-primary" ng-click="generateReport(selectedProgram, report, selectedOuMode)" ng-disabled="!selectedProgram">{{'go'| translate}}</button>               
+                <button type="button" class="btn btn-primary" ng-click="generateReport(selectedProgram, report, selectedOuMode)" ng-disabled="!selectedProgram">{{'go'| translate}}</button>
+                <button type="button"
+                        class="btn btn-success small-horizonal-spacing"
+                        ng-if="upcomingEvents.length > 0"
+                        class="btn btn-primary"
+                        onclick="javascript:window.print()">
+                    {{'print'| translate}}
+                </button>
+                <button type="button" 
+                        class="btn btn-info small-horizonal-spacing"
+                        ng-if="upcomingEvents.length > 0"
+                        ng-csv="generateReportData()"
+                        csv-header="generateReportHeader()"
+                        filename="upcomingEvents.csv">
+                    {{'excel_export'| translate}}
+                </button>
             </div>
         </div>
 
@@ -124,8 +140,7 @@
 
     <!-- upcoming events list begins -->
     <div ng-if="reportFinished">
-
-        <div ng-switch="teiList.length">                    
+        <div ng-switch="upcomingEvents.length">                    
             <div ng-switch-when="undefined">
                 <div class="alert alert-warning vertical-spacing">
                     {{'no_data_found'| translate}}
@@ -145,7 +160,7 @@
                                 <th ng-show="gridColumn.show" ng-repeat="gridColumn in gridColumns">
 
                                     <!-- sort icon begins -->
-                                    <span ng-click="sortTEIGrid(gridColumn)">
+                                    <span ng-click="sortGrid(gridColumn)">
                                         {{gridColumn.name| translate}}
                                         <i ng-if="sortHeader == gridColumn.id && reverse" class="fa fa-sort-desc"></i>
                                         <i ng-if="sortHeader == gridColumn.id && !reverse" class="fa fa-sort-asc"></i>
@@ -191,12 +206,12 @@
                             </tr>                        
                         </thead>
                         <tbody id="list">
-                            <tr ng-repeat="tei in teiList| orderBy:sortHeader:reverse | gridFilter:filterText:filterTypes"
-                                ng-click="showDashboard(tei)"
+                            <tr ng-repeat="upcomingEvent in upcomingEvents| orderBy:sortHeader:reverse | gridFilter:filterText:filterTypes"
+                                ng-click="showDashboard(upcomingEvent)"
                                 title="{{'go_to_dashboard'| translate}}">
                                 <td ng-show="gridColumn.show"                                            
-                                    ng-repeat="gridColumn in gridColumns" ng-if='displayMode.onlyMarkedFollowup ? tei.followup:true'>                                                
-                                    {{tei[gridColumn.id]}}                                                
+                                    ng-repeat="gridColumn in gridColumns" ng-if='displayMode.onlyMarkedFollowup ? upcomingEvent.followup:true'>                                                
+                                    {{upcomingEvent[gridColumn.id]}}                                                
                                 </td>                                
                             </tr>
                         </tbody>        

=== modified file 'dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/i18n/en.json'
--- dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/i18n/en.json	2014-09-10 07:13:29 +0000
+++ dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/i18n/en.json	2014-09-11 12:35:41 +0000
@@ -163,6 +163,7 @@
     "close": "Close",
     "generate": "Generate",
     "print": "Print",
+    "excel_export": "Excel export",
     "list_programs": "List programs",
     "program_stage": "Program stage",
     "due_date": "Due date",

=== 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	2014-09-08 09:08:39 +0000
+++ dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/index.html	2014-09-11 12:35:41 +0000
@@ -23,7 +23,8 @@
         <script type="text/javascript" src="../dhis-web-commons/javascripts/angular/angular-resource.js"></script>
         <script type="text/javascript" src="../dhis-web-commons/javascripts/angular/angular-route.js"></script>
         <script type="text/javascript" src="../dhis-web-commons/javascripts/angular/angular-cookies.js"></script>
-        <script type="text/javascript" src="../dhis-web-commons/javascripts/angular/angular-animate.js"></script>        
+        <script type="text/javascript" src="../dhis-web-commons/javascripts/angular/angular-animate.js"></script>
+        <script type="text/javascript" src="../dhis-web-commons/javascripts/angular/angular-sanitize.js"></script>
         <script type="text/javascript" src="../dhis-web-commons/javascripts/angular/ui-bootstrap-tpls-0.10.0-draggable-modal.js"></script>
         
 
@@ -58,6 +59,7 @@
         <script type="text/javascript" src="../dhis-web-commons/javascripts/angular/plugins/angular-translate-loader-static-files.min.js"></script>
         <script type="text/javascript" src="../dhis-web-commons/javascripts/angular/plugins/angular-translate-loader-url.min.js"></script>
         <script type="text/javascript" src="../dhis-web-commons/javascripts/angular/plugins/select2.js"></script>
+        <script type="text/javascript" src="scripts/ng-csv.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-tracker-capture/scripts/app.js'
--- dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/scripts/app.js	2014-08-27 12:55:29 +0000
+++ dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/scripts/app.js	2014-09-11 12:35:41 +0000
@@ -5,7 +5,8 @@
 var trackerCapture = angular.module('trackerCapture',
         ['ui.bootstrap', 
          'ngRoute', 
-         'ngCookies',  
+         'ngCookies',
+         'ngSanitize',
          'trackerCaptureServices',
          'trackerCaptureFilters',
          'trackerCaptureDirectives', 
@@ -13,6 +14,7 @@
          'angularLocalStorage',
          'ui.select2',
          'd2Menu',
+         'ngCsv',
          'pascalprecht.translate'])
               
 .value('DHIS2URL', '..')

=== modified file 'dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/scripts/directives.js'
--- dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/scripts/directives.js	2014-09-09 12:06:50 +0000
+++ dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/scripts/directives.js	2014-09-11 12:35:41 +0000
@@ -17,8 +17,7 @@
     };   
 })
 
-.directive('selectedOrgUnit', function() {        
-
+.directive('selectedOrgUnit', function() {
     return {        
         restrict: 'A',        
         link: function(scope, element, attrs){  

=== added file 'dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/scripts/ng-csv.js'
--- dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/scripts/ng-csv.js	1970-01-01 00:00:00 +0000
+++ dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/scripts/ng-csv.js	2014-09-11 12:35:41 +0000
@@ -0,0 +1,236 @@
+(function(window, document) {
+
+// Create all modules and define dependencies to make sure they exist
+// and are loaded in the correct order to satisfy dependency injection
+// before all nested files are concatenated by Grunt
+
+// Config
+angular.module('ngCsv.config', []).
+  value('ngCsv.config', {
+      debug: true
+  }).
+  config(['$compileProvider', function($compileProvider){
+    if (angular.isDefined($compileProvider.urlSanitizationWhitelist)) {
+      $compileProvider.urlSanitizationWhitelist(/^\s*(https?|ftp|mailto|file|data):/);
+    } else {
+      $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|ftp|mailto|file|data):/);
+    }
+  }]);
+
+// Modules
+angular.module('ngCsv.directives', ['ngCsv.services']);
+angular.module('ngCsv.services', []);
+angular.module('ngCsv',
+    [
+        'ngCsv.config',
+        'ngCsv.services',
+        'ngCsv.directives',
+        'ngSanitize'
+    ]);
+/**
+ * Created by asafdav on 15/05/14.
+ */
+angular.module('ngCsv.services').
+  service('CSV', ['$q', function($q)  {
+
+    var EOL = encodeURIComponent('\r\n');
+    var DATA_URI_PREFIX = "data:text/csv;charset=utf-8,";
+
+    /**
+     * Stringify one field
+     * @param data
+     * @param delimier
+     * @returns {*}
+     */
+    this.stringifyField = function(data, delimier, quoteText) {
+      if (typeof data === 'string') {
+        data = data.replace(/"/g, '""'); // Escape double qoutes
+        if (quoteText || data.indexOf(',') > -1 || data.indexOf('\n') > -1 || data.indexOf('\r') > -1) data = delimier + data + delimier;
+        return encodeURIComponent(data);
+      }
+
+      if (typeof data === 'boolean') {
+        return data ? 'TRUE' : 'FALSE';
+      }
+
+      return data;
+    };
+
+    /**
+     * Creates a csv from a data array
+     * @param data
+     * @param options
+     *  * header - Provide the first row (optional)
+     *  * fieldSep - Field separator, default: ','
+     * @param callback
+     */
+    this.stringify = function (data, options)
+    {
+      var def = $q.defer();
+
+      var that = this;
+      var csv;
+      var csvContent = "";
+
+      var dataPromise = $q.when(data).then(function (responseData)
+      {
+        responseData = angular.copy(responseData);
+        // Check if there's a provided header array
+        if (angular.isDefined(options.header) && options.header)
+        {
+          var encodingArray, headerString;
+
+          encodingArray = [];
+          angular.forEach(options.header, function(title, key)
+          {
+            this.push(that.stringifyField(title, options.txtDelim, options.quoteStrings));
+          }, encodingArray);
+
+          headerString = encodingArray.join(options.fieldSep ? options.fieldSep : ",");
+          csvContent += headerString + EOL;
+        }
+
+        var arrData;
+
+        if (angular.isArray(responseData)) {
+          arrData = responseData;
+        }
+        else {
+          arrData = responseData();
+        }
+
+        angular.forEach(arrData, function(row, index)
+        {
+          var dataString, infoArray;
+
+          infoArray = [];
+
+          angular.forEach(row, function(field, key)
+          {
+            this.push(that.stringifyField(field, options.txtDelim, options.quoteStrings));
+          }, infoArray);
+
+          dataString = infoArray.join(options.fieldSep ? options.fieldSep : ",");
+          csvContent += index < arrData.length ? dataString + EOL : dataString;
+        });
+
+        if(window.navigator.msSaveOrOpenBlob) {
+          csv = csvContent;
+        }else{
+          csv = DATA_URI_PREFIX + csvContent;
+        }
+        def.resolve(csv);
+      });
+
+      if (typeof dataPromise.catch === 'function') {
+        dataPromise.catch(function(err) {
+          def.reject(err);
+        });
+      }
+
+      return def.promise;
+    };
+  }]);/**
+ * ng-csv module
+ * Export Javascript's arrays to csv files from the browser
+ *
+ * Author: asafdav - https://github.com/asafdav
+ */
+angular.module('ngCsv.directives').
+  directive('ngCsv', ['$parse', '$q', 'CSV', '$document', '$timeout', function ($parse, $q, CSV, $document, $timeout) {
+    return {
+      restrict: 'AC',
+      scope: {
+        data:'&ngCsv',
+        filename:'@filename',
+        header: '&csvHeader',
+        txtDelim: '@textDelimiter',
+        quoteStrings: '@quoteStrings',
+        fieldSep: '@fieldSeparator',
+        lazyLoad: '@lazyLoad',
+        ngClick: '&'
+      },
+      controller: [
+        '$scope',
+        '$element',
+        '$attrs',
+        '$transclude',
+        function ($scope, $element, $attrs, $transclude) {
+          $scope.csv = '';
+
+          if (!angular.isDefined($scope.lazyLoad) || $scope.lazyLoad != "true")
+          {
+            if (angular.isArray($scope.data))
+            {
+              $scope.$watch("data", function (newValue) {
+                $scope.buildCSV();
+              }, true);
+            }
+          }
+
+          $scope.getFilename = function ()
+          {
+            return $scope.filename || 'download.csv';
+          };
+
+          function getBuildCsvOptions() {
+            var options = {
+              txtDelim: $scope.txtDelim ? $scope.txtDelim : '"',
+              quoteStrings: $scope.quoteStrings
+            };
+            if (angular.isDefined($attrs.csvHeader)) options.header = $scope.$eval($scope.header);
+            options.fieldSep = $scope.fieldSep ? $scope.fieldSep : ",";
+
+            return options;
+          }
+
+          /**
+           * Creates the CSV and updates the scope
+           * @returns {*}
+           */
+          $scope.buildCSV = function() {
+            var deferred = $q.defer();
+
+            CSV.stringify($scope.data(), getBuildCsvOptions()).then(function(csv) {
+              $scope.csv = csv;
+              deferred.resolve(csv);
+            });
+            $scope.$apply(); // Old angular support
+
+            return deferred.promise;
+          };
+        }
+      ],
+      link: function (scope, element, attrs) {
+        function doClick() {
+          if(window.navigator.msSaveOrOpenBlob) {
+            var blob = new Blob([scope.csv],{
+                    type: "text/csv;charset=utf-8;"
+                });
+            navigator.msSaveBlob(blob, scope.getFilename());
+          } else {
+
+            var downloadLink = angular.element('<a></a>');
+            downloadLink.attr('href',scope.csv);
+            downloadLink.attr('download',scope.getFilename());
+
+            $document.find('body').append(downloadLink);
+            $timeout(function() {
+              downloadLink[0].click();
+              downloadLink.remove();
+            }, null);
+          }
+
+        }
+
+        element.bind('click', function (e)
+        {
+          scope.buildCSV().then(function(csv) {
+            doClick();
+          });
+          scope.$apply();
+        });
+      }
+    };
+  }]);
+})(window, document);
\ No newline at end of file

=== 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	2014-09-09 12:06:50 +0000
+++ dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/scripts/services.js	2014-09-11 12:35:41 +0000
@@ -1070,7 +1070,7 @@
             
 })
 
-.service('TEIGridService', function(OrgUnitService, DateUtils){
+.service('TEIGridService', function(OrgUnitService, DateUtils, $translate){
     
     return {
         format: function(grid, map){
@@ -1130,8 +1130,8 @@
             var columns = attributes ? angular.copy(attributes) : [];
        
             //also add extra columns which are not part of attributes (orgunit for example)
-            columns.push({id: 'orgUnitName', name: 'registering_unit', type: 'string', displayInListNoProgram: false});
-            columns.push({id: 'created', name: 'registration_date', type: 'date', displayInListNoProgram: false});
+            columns.push({id: 'orgUnitName', name: $translate('registering_unit'), type: 'string', displayInListNoProgram: false});
+            columns.push({id: 'created', name: $translate('registration_date'), type: 'date', displayInListNoProgram: false});
 
             //generate grid column for the selected program/attributes
             angular.forEach(columns, function(column){
@@ -1146,6 +1146,28 @@
             });     
             
             return columns;  
+        },
+        getData: function(rows, columns){
+            var data = [];
+            angular.forEach(rows, function(row){
+                var d = {};
+                angular.forEach(columns, function(col){
+                    if(col.show){
+                        d[col.name] = row[col.id];
+                    }                
+                });
+                data.push(d);            
+            });
+            return data;
+        },
+        getHeader: function(columns){
+            var header = []; 
+            angular.forEach(columns, function(col){
+                if(col.show){
+                    header.push($translate(col.name));
+                }
+            });        
+            return header;
         }
     };
 })
@@ -1230,7 +1252,7 @@
                 OrgUnitService.open().then(function(){
                     OrgUnitService.get(dhis2Event.orgUnit).then(function(ou){
                         if(ou){
-                            dhis2Event.orgUnitName = ou.n;
+                            dhis2Event.eventOrgUnitName = ou.n;
                             return dhis2Event;                            
                         }                                                       
                     });                            

=== modified file 'dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/styles/style.css'
--- dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/styles/style.css	2014-09-09 12:06:50 +0000
+++ dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/styles/style.css	2014-09-11 12:35:41 +0000
@@ -793,5 +793,4 @@
     #header, #leftBar, .not-printable {
         display: none;
     }
-
 }
\ No newline at end of file

=== modified file 'dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/views/column-modal.html'
--- dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/views/column-modal.html	2014-09-09 12:06:50 +0000
+++ dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/views/column-modal.html	2014-09-11 12:35:41 +0000
@@ -5,7 +5,7 @@
     <table class="listTable dhis2-table-striped-border">
         <tr ng-repeat="gridColumn in gridColumns">
             <td>
-                {{gridColumn.name | translate}}
+                {{gridColumn.name}}
             </td>
             <td>
                 <input type="checkbox" ng-model="gridColumn.show" ng-change="showHideColumns(gridColumn)" ng-disabled="hiddenGridColumns + 1 == gridColumns.length && gridColumn.show">

=== added file 'dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/angular/angular-sanitize.js'
--- dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/angular/angular-sanitize.js	1970-01-01 00:00:00 +0000
+++ dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/angular/angular-sanitize.js	2014-09-11 12:35:41 +0000
@@ -0,0 +1,624 @@
+/**
+ * @license AngularJS v1.2.14
+ * (c) 2010-2014 Google, Inc. http://angularjs.org
+ * License: MIT
+ */
+(function(window, angular, undefined) {'use strict';
+
+var $sanitizeMinErr = angular.$$minErr('$sanitize');
+
+/**
+ * @ngdoc module
+ * @name ngSanitize
+ * @description
+ *
+ * # ngSanitize
+ *
+ * The `ngSanitize` module provides functionality to sanitize HTML.
+ *
+ *
+ * <div doc-module-components="ngSanitize"></div>
+ *
+ * See {@link ngSanitize.$sanitize `$sanitize`} for usage.
+ */
+
+/*
+ * HTML Parser By Misko Hevery (misko@xxxxxxxxxx)
+ * based on:  HTML Parser By John Resig (ejohn.org)
+ * Original code by Erik Arvidsson, Mozilla Public License
+ * http://erik.eae.net/simplehtmlparser/simplehtmlparser.js
+ *
+ * // Use like so:
+ * htmlParser(htmlString, {
+ *     start: function(tag, attrs, unary) {},
+ *     end: function(tag) {},
+ *     chars: function(text) {},
+ *     comment: function(text) {}
+ * });
+ *
+ */
+
+
+/**
+ * @ngdoc service
+ * @name $sanitize
+ * @function
+ *
+ * @description
+ *   The input is sanitized by parsing the html into tokens. All safe tokens (from a whitelist) are
+ *   then serialized back to properly escaped html string. This means that no unsafe input can make
+ *   it into the returned string, however, since our parser is more strict than a typical browser
+ *   parser, it's possible that some obscure input, which would be recognized as valid HTML by a
+ *   browser, won't make it through the sanitizer.
+ *   The whitelist is configured using the functions `aHrefSanitizationWhitelist` and
+ *   `imgSrcSanitizationWhitelist` of {@link ng.$compileProvider `$compileProvider`}.
+ *
+ * @param {string} html Html input.
+ * @returns {string} Sanitized html.
+ *
+ * @example
+   <example module="ngSanitize" deps="angular-sanitize.js">
+   <file name="index.html">
+     <script>
+       function Ctrl($scope, $sce) {
+         $scope.snippet =
+           '<p style="color:blue">an html\n' +
+           '<em onmouseover="this.textContent=\'PWN3D!\'">click here</em>\n' +
+           'snippet</p>';
+         $scope.deliberatelyTrustDangerousSnippet = function() {
+           return $sce.trustAsHtml($scope.snippet);
+         };
+       }
+     </script>
+     <div ng-controller="Ctrl">
+        Snippet: <textarea ng-model="snippet" cols="60" rows="3"></textarea>
+       <table>
+         <tr>
+           <td>Directive</td>
+           <td>How</td>
+           <td>Source</td>
+           <td>Rendered</td>
+         </tr>
+         <tr id="bind-html-with-sanitize">
+           <td>ng-bind-html</td>
+           <td>Automatically uses $sanitize</td>
+           <td><pre>&lt;div ng-bind-html="snippet"&gt;<br/>&lt;/div&gt;</pre></td>
+           <td><div ng-bind-html="snippet"></div></td>
+         </tr>
+         <tr id="bind-html-with-trust">
+           <td>ng-bind-html</td>
+           <td>Bypass $sanitize by explicitly trusting the dangerous value</td>
+           <td>
+           <pre>&lt;div ng-bind-html="deliberatelyTrustDangerousSnippet()"&gt;
+&lt;/div&gt;</pre>
+           </td>
+           <td><div ng-bind-html="deliberatelyTrustDangerousSnippet()"></div></td>
+         </tr>
+         <tr id="bind-default">
+           <td>ng-bind</td>
+           <td>Automatically escapes</td>
+           <td><pre>&lt;div ng-bind="snippet"&gt;<br/>&lt;/div&gt;</pre></td>
+           <td><div ng-bind="snippet"></div></td>
+         </tr>
+       </table>
+       </div>
+   </file>
+   <file name="protractor.js" type="protractor">
+     it('should sanitize the html snippet by default', function() {
+       expect(element(by.css('#bind-html-with-sanitize div')).getInnerHtml()).
+         toBe('<p>an html\n<em>click here</em>\nsnippet</p>');
+     });
+
+     it('should inline raw snippet if bound to a trusted value', function() {
+       expect(element(by.css('#bind-html-with-trust div')).getInnerHtml()).
+         toBe("<p style=\"color:blue\">an html\n" +
+              "<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" +
+              "snippet</p>");
+     });
+
+     it('should escape snippet without any filter', function() {
+       expect(element(by.css('#bind-default div')).getInnerHtml()).
+         toBe("&lt;p style=\"color:blue\"&gt;an html\n" +
+              "&lt;em onmouseover=\"this.textContent='PWN3D!'\"&gt;click here&lt;/em&gt;\n" +
+              "snippet&lt;/p&gt;");
+     });
+
+     it('should update', function() {
+       element(by.model('snippet')).clear();
+       element(by.model('snippet')).sendKeys('new <b onclick="alert(1)">text</b>');
+       expect(element(by.css('#bind-html-with-sanitize div')).getInnerHtml()).
+         toBe('new <b>text</b>');
+       expect(element(by.css('#bind-html-with-trust div')).getInnerHtml()).toBe(
+         'new <b onclick="alert(1)">text</b>');
+       expect(element(by.css('#bind-default div')).getInnerHtml()).toBe(
+         "new &lt;b onclick=\"alert(1)\"&gt;text&lt;/b&gt;");
+     });
+   </file>
+   </example>
+ */
+function $SanitizeProvider() {
+  this.$get = ['$$sanitizeUri', function($$sanitizeUri) {
+    return function(html) {
+      var buf = [];
+      htmlParser(html, htmlSanitizeWriter(buf, function(uri, isImage) {
+        return !/^unsafe/.test($$sanitizeUri(uri, isImage));
+      }));
+      return buf.join('');
+    };
+  }];
+}
+
+function sanitizeText(chars) {
+  var buf = [];
+  var writer = htmlSanitizeWriter(buf, angular.noop);
+  writer.chars(chars);
+  return buf.join('');
+}
+
+
+// Regular Expressions for parsing tags and attributes
+var START_TAG_REGEXP =
+       /^<\s*([\w:-]+)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*>/,
+  END_TAG_REGEXP = /^<\s*\/\s*([\w:-]+)[^>]*>/,
+  ATTR_REGEXP = /([\w:-]+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g,
+  BEGIN_TAG_REGEXP = /^</,
+  BEGING_END_TAGE_REGEXP = /^<\s*\//,
+  COMMENT_REGEXP = /<!--(.*?)-->/g,
+  DOCTYPE_REGEXP = /<!DOCTYPE([^>]*?)>/i,
+  CDATA_REGEXP = /<!\[CDATA\[(.*?)]]>/g,
+  // Match everything outside of normal chars and " (quote character)
+  NON_ALPHANUMERIC_REGEXP = /([^\#-~| |!])/g;
+
+
+// Good source of info about elements and attributes
+// http://dev.w3.org/html5/spec/Overview.html#semantics
+// http://simon.html5.org/html-elements
+
+// Safe Void Elements - HTML5
+// http://dev.w3.org/html5/spec/Overview.html#void-elements
+var voidElements = makeMap("area,br,col,hr,img,wbr");
+
+// Elements that you can, intentionally, leave open (and which close themselves)
+// http://dev.w3.org/html5/spec/Overview.html#optional-tags
+var optionalEndTagBlockElements = makeMap("colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr"),
+    optionalEndTagInlineElements = makeMap("rp,rt"),
+    optionalEndTagElements = angular.extend({},
+                                            optionalEndTagInlineElements,
+                                            optionalEndTagBlockElements);
+
+// Safe Block Elements - HTML5
+var blockElements = angular.extend({}, optionalEndTagBlockElements, makeMap("address,article," +
+        "aside,blockquote,caption,center,del,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5," +
+        "h6,header,hgroup,hr,ins,map,menu,nav,ol,pre,script,section,table,ul"));
+
+// Inline Elements - HTML5
+var inlineElements = angular.extend({}, optionalEndTagInlineElements, makeMap("a,abbr,acronym,b," +
+        "bdi,bdo,big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s," +
+        "samp,small,span,strike,strong,sub,sup,time,tt,u,var"));
+
+
+// Special Elements (can contain anything)
+var specialElements = makeMap("script,style");
+
+var validElements = angular.extend({},
+                                   voidElements,
+                                   blockElements,
+                                   inlineElements,
+                                   optionalEndTagElements);
+
+//Attributes that have href and hence need to be sanitized
+var uriAttrs = makeMap("background,cite,href,longdesc,src,usemap");
+var validAttrs = angular.extend({}, uriAttrs, makeMap(
+    'abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,'+
+    'color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,'+
+    'ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,'+
+    'scope,scrolling,shape,size,span,start,summary,target,title,type,'+
+    'valign,value,vspace,width'));
+
+function makeMap(str) {
+  var obj = {}, items = str.split(','), i;
+  for (i = 0; i < items.length; i++) obj[items[i]] = true;
+  return obj;
+}
+
+
+/**
+ * @example
+ * htmlParser(htmlString, {
+ *     start: function(tag, attrs, unary) {},
+ *     end: function(tag) {},
+ *     chars: function(text) {},
+ *     comment: function(text) {}
+ * });
+ *
+ * @param {string} html string
+ * @param {object} handler
+ */
+function htmlParser( html, handler ) {
+  var index, chars, match, stack = [], last = html;
+  stack.last = function() { return stack[ stack.length - 1 ]; };
+
+  while ( html ) {
+    chars = true;
+
+    // Make sure we're not in a script or style element
+    if ( !stack.last() || !specialElements[ stack.last() ] ) {
+
+      // Comment
+      if ( html.indexOf("<!--") === 0 ) {
+        // comments containing -- are not allowed unless they terminate the comment
+        index = html.indexOf("--", 4);
+
+        if ( index >= 0 && html.lastIndexOf("-->", index) === index) {
+          if (handler.comment) handler.comment( html.substring( 4, index ) );
+          html = html.substring( index + 3 );
+          chars = false;
+        }
+      // DOCTYPE
+      } else if ( DOCTYPE_REGEXP.test(html) ) {
+        match = html.match( DOCTYPE_REGEXP );
+
+        if ( match ) {
+          html = html.replace( match[0] , '');
+          chars = false;
+        }
+      // end tag
+      } else if ( BEGING_END_TAGE_REGEXP.test(html) ) {
+        match = html.match( END_TAG_REGEXP );
+
+        if ( match ) {
+          html = html.substring( match[0].length );
+          match[0].replace( END_TAG_REGEXP, parseEndTag );
+          chars = false;
+        }
+
+      // start tag
+      } else if ( BEGIN_TAG_REGEXP.test(html) ) {
+        match = html.match( START_TAG_REGEXP );
+
+        if ( match ) {
+          html = html.substring( match[0].length );
+          match[0].replace( START_TAG_REGEXP, parseStartTag );
+          chars = false;
+        }
+      }
+
+      if ( chars ) {
+        index = html.indexOf("<");
+
+        var text = index < 0 ? html : html.substring( 0, index );
+        html = index < 0 ? "" : html.substring( index );
+
+        if (handler.chars) handler.chars( decodeEntities(text) );
+      }
+
+    } else {
+      html = html.replace(new RegExp("(.*)<\\s*\\/\\s*" + stack.last() + "[^>]*>", 'i'),
+        function(all, text){
+          text = text.replace(COMMENT_REGEXP, "$1").replace(CDATA_REGEXP, "$1");
+
+          if (handler.chars) handler.chars( decodeEntities(text) );
+
+          return "";
+      });
+
+      parseEndTag( "", stack.last() );
+    }
+
+    if ( html == last ) {
+      throw $sanitizeMinErr('badparse', "The sanitizer was unable to parse the following block " +
+                                        "of html: {0}", html);
+    }
+    last = html;
+  }
+
+  // Clean up any remaining tags
+  parseEndTag();
+
+  function parseStartTag( tag, tagName, rest, unary ) {
+    tagName = angular.lowercase(tagName);
+    if ( blockElements[ tagName ] ) {
+      while ( stack.last() && inlineElements[ stack.last() ] ) {
+        parseEndTag( "", stack.last() );
+      }
+    }
+
+    if ( optionalEndTagElements[ tagName ] && stack.last() == tagName ) {
+      parseEndTag( "", tagName );
+    }
+
+    unary = voidElements[ tagName ] || !!unary;
+
+    if ( !unary )
+      stack.push( tagName );
+
+    var attrs = {};
+
+    rest.replace(ATTR_REGEXP,
+      function(match, name, doubleQuotedValue, singleQuotedValue, unquotedValue) {
+        var value = doubleQuotedValue
+          || singleQuotedValue
+          || unquotedValue
+          || '';
+
+        attrs[name] = decodeEntities(value);
+    });
+    if (handler.start) handler.start( tagName, attrs, unary );
+  }
+
+  function parseEndTag( tag, tagName ) {
+    var pos = 0, i;
+    tagName = angular.lowercase(tagName);
+    if ( tagName )
+      // Find the closest opened tag of the same type
+      for ( pos = stack.length - 1; pos >= 0; pos-- )
+        if ( stack[ pos ] == tagName )
+          break;
+
+    if ( pos >= 0 ) {
+      // Close all the open elements, up the stack
+      for ( i = stack.length - 1; i >= pos; i-- )
+        if (handler.end) handler.end( stack[ i ] );
+
+      // Remove the open elements from the stack
+      stack.length = pos;
+    }
+  }
+}
+
+var hiddenPre=document.createElement("pre");
+var spaceRe = /^(\s*)([\s\S]*?)(\s*)$/;
+/**
+ * decodes all entities into regular string
+ * @param value
+ * @returns {string} A string with decoded entities.
+ */
+function decodeEntities(value) {
+  if (!value) { return ''; }
+
+  // Note: IE8 does not preserve spaces at the start/end of innerHTML
+  // so we must capture them and reattach them afterward
+  var parts = spaceRe.exec(value);
+  var spaceBefore = parts[1];
+  var spaceAfter = parts[3];
+  var content = parts[2];
+  if (content) {
+    hiddenPre.innerHTML=content.replace(/</g,"&lt;");
+    // innerText depends on styling as it doesn't display hidden elements.
+    // Therefore, it's better to use textContent not to cause unnecessary
+    // reflows. However, IE<9 don't support textContent so the innerText
+    // fallback is necessary.
+    content = 'textContent' in hiddenPre ?
+      hiddenPre.textContent : hiddenPre.innerText;
+  }
+  return spaceBefore + content + spaceAfter;
+}
+
+/**
+ * Escapes all potentially dangerous characters, so that the
+ * resulting string can be safely inserted into attribute or
+ * element text.
+ * @param value
+ * @returns {string} escaped text
+ */
+function encodeEntities(value) {
+  return value.
+    replace(/&/g, '&amp;').
+    replace(NON_ALPHANUMERIC_REGEXP, function(value){
+      return '&#' + value.charCodeAt(0) + ';';
+    }).
+    replace(/</g, '&lt;').
+    replace(/>/g, '&gt;');
+}
+
+/**
+ * create an HTML/XML writer which writes to buffer
+ * @param {Array} buf use buf.jain('') to get out sanitized html string
+ * @returns {object} in the form of {
+ *     start: function(tag, attrs, unary) {},
+ *     end: function(tag) {},
+ *     chars: function(text) {},
+ *     comment: function(text) {}
+ * }
+ */
+function htmlSanitizeWriter(buf, uriValidator){
+  var ignore = false;
+  var out = angular.bind(buf, buf.push);
+  return {
+    start: function(tag, attrs, unary){
+      tag = angular.lowercase(tag);
+      if (!ignore && specialElements[tag]) {
+        ignore = tag;
+      }
+      if (!ignore && validElements[tag] === true) {
+        out('<');
+        out(tag);
+        angular.forEach(attrs, function(value, key){
+          var lkey=angular.lowercase(key);
+          var isImage = (tag === 'img' && lkey === 'src') || (lkey === 'background');
+          if (validAttrs[lkey] === true &&
+            (uriAttrs[lkey] !== true || uriValidator(value, isImage))) {
+            out(' ');
+            out(key);
+            out('="');
+            out(encodeEntities(value));
+            out('"');
+          }
+        });
+        out(unary ? '/>' : '>');
+      }
+    },
+    end: function(tag){
+        tag = angular.lowercase(tag);
+        if (!ignore && validElements[tag] === true) {
+          out('</');
+          out(tag);
+          out('>');
+        }
+        if (tag == ignore) {
+          ignore = false;
+        }
+      },
+    chars: function(chars){
+        if (!ignore) {
+          out(encodeEntities(chars));
+        }
+      }
+  };
+}
+
+
+// define ngSanitize module and register $sanitize service
+angular.module('ngSanitize', []).provider('$sanitize', $SanitizeProvider);
+
+/* global sanitizeText: false */
+
+/**
+ * @ngdoc filter
+ * @name linky
+ * @function
+ *
+ * @description
+ * Finds links in text input and turns them into html links. Supports http/https/ftp/mailto and
+ * plain email address links.
+ *
+ * Requires the {@link ngSanitize `ngSanitize`} module to be installed.
+ *
+ * @param {string} text Input text.
+ * @param {string} target Window (_blank|_self|_parent|_top) or named frame to open links in.
+ * @returns {string} Html-linkified text.
+ *
+ * @usage
+   <span ng-bind-html="linky_expression | linky"></span>
+ *
+ * @example
+   <example module="ngSanitize" deps="angular-sanitize.js">
+     <file name="index.html">
+       <script>
+         function Ctrl($scope) {
+           $scope.snippet =
+             'Pretty text with some links:\n'+
+             'http://angularjs.org/,\n'+
+             'mailto:us@xxxxxxxxxxxxx,\n'+
+             'another@xxxxxxxxxxxxx,\n'+
+             'and one more: ftp://127.0.0.1/.';
+           $scope.snippetWithTarget = 'http://angularjs.org/';
+         }
+       </script>
+       <div ng-controller="Ctrl">
+       Snippet: <textarea ng-model="snippet" cols="60" rows="3"></textarea>
+       <table>
+         <tr>
+           <td>Filter</td>
+           <td>Source</td>
+           <td>Rendered</td>
+         </tr>
+         <tr id="linky-filter">
+           <td>linky filter</td>
+           <td>
+             <pre>&lt;div ng-bind-html="snippet | linky"&gt;<br>&lt;/div&gt;</pre>
+           </td>
+           <td>
+             <div ng-bind-html="snippet | linky"></div>
+           </td>
+         </tr>
+         <tr id="linky-target">
+          <td>linky target</td>
+          <td>
+            <pre>&lt;div ng-bind-html="snippetWithTarget | linky:'_blank'"&gt;<br>&lt;/div&gt;</pre>
+          </td>
+          <td>
+            <div ng-bind-html="snippetWithTarget | linky:'_blank'"></div>
+          </td>
+         </tr>
+         <tr id="escaped-html">
+           <td>no filter</td>
+           <td><pre>&lt;div ng-bind="snippet"&gt;<br>&lt;/div&gt;</pre></td>
+           <td><div ng-bind="snippet"></div></td>
+         </tr>
+       </table>
+     </file>
+     <file name="protractor.js" type="protractor">
+       it('should linkify the snippet with urls', function() {
+         expect(element(by.id('linky-filter')).element(by.binding('snippet | linky')).getText()).
+             toBe('Pretty text with some links: http://angularjs.org/, us@xxxxxxxxxxxxx, ' +
+                  'another@xxxxxxxxxxxxx, and one more: ftp://127.0.0.1/.');
+         expect(element.all(by.css('#linky-filter a')).count()).toEqual(4);
+       });
+
+       it('should not linkify snippet without the linky filter', function() {
+         expect(element(by.id('escaped-html')).element(by.binding('snippet')).getText()).
+             toBe('Pretty text with some links: http://angularjs.org/, mailto:us@xxxxxxxxxxxxx, ' +
+                  'another@xxxxxxxxxxxxx, and one more: ftp://127.0.0.1/.');
+         expect(element.all(by.css('#escaped-html a')).count()).toEqual(0);
+       });
+
+       it('should update', function() {
+         element(by.model('snippet')).clear();
+         element(by.model('snippet')).sendKeys('new http://link.');
+         expect(element(by.id('linky-filter')).element(by.binding('snippet | linky')).getText()).
+             toBe('new http://link.');
+         expect(element.all(by.css('#linky-filter a')).count()).toEqual(1);
+         expect(element(by.id('escaped-html')).element(by.binding('snippet')).getText())
+             .toBe('new http://link.');
+       });
+
+       it('should work with the target property', function() {
+        expect(element(by.id('linky-target')).
+            element(by.binding("snippetWithTarget | linky:'_blank'")).getText()).
+            toBe('http://angularjs.org/');
+        expect(element(by.css('#linky-target a')).getAttribute('target')).toEqual('_blank');
+       });
+     </file>
+   </example>
+ */
+angular.module('ngSanitize').filter('linky', ['$sanitize', function($sanitize) {
+  var LINKY_URL_REGEXP =
+        /((ftp|https?):\/\/|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s.;,(){}<>]/,
+      MAILTO_REGEXP = /^mailto:/;
+
+  return function(text, target) {
+    if (!text) return text;
+    var match;
+    var raw = text;
+    var html = [];
+    var url;
+    var i;
+    while ((match = raw.match(LINKY_URL_REGEXP))) {
+      // We can not end in these as they are sometimes found at the end of the sentence
+      url = match[0];
+      // if we did not match ftp/http/mailto then assume mailto
+      if (match[2] == match[3]) url = 'mailto:' + url;
+      i = match.index;
+      addText(raw.substr(0, i));
+      addLink(url, match[0].replace(MAILTO_REGEXP, ''));
+      raw = raw.substring(i + match[0].length);
+    }
+    addText(raw);
+    return $sanitize(html.join(''));
+
+    function addText(text) {
+      if (!text) {
+        return;
+      }
+      html.push(sanitizeText(text));
+    }
+
+    function addLink(url, text) {
+      html.push('<a ');
+      if (angular.isDefined(target)) {
+        html.push('target="');
+        html.push(target);
+        html.push('" ');
+      }
+      html.push('href="');
+      html.push(url);
+      html.push('">');
+      addText(text);
+      html.push('</a>');
+    }
+  };
+}]);
+
+
+})(window, window.angular);