=== 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 @@
{{'overdue_events'| translate}} -
+ @@ -39,18 +39,18 @@
- - - -
{{'org_unit'| translate}} - + + + + - - - - + - - -
{{'org_unit'| translate}} +
+ {{'program'| translate}} +
{{'org_unit_scope'| translate}} + {{'org_unit_scope'| translate}}


@@ -69,21 +69,18 @@
+ {{'filter'| translate}} +
- - +
{{'no_program_exists_report'| translate}}
@@ -101,8 +98,7 @@
- -
+
{{'no_data_found'| translate}} @@ -115,22 +111,42 @@
-
+ +
+ + + +
+
- -
- - {{gridColumn.name| translate}} - - + + {{gridColumn.name}} + + - + @@ -141,7 +157,7 @@ - + @@ -168,17 +184,18 @@
- {{tei[gridColumn.id]}} + ng-repeat="gridColumn in gridColumns" ng-if='displayMode.onlyMarkedFollowup ? overdueEvent.followup:true'> + {{overdueEvent[gridColumn.id]}}
+
+
=== 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 @@
- - -
@@ -26,11 +23,11 @@ {{'upcoming_events'| translate}}
@@ -42,12 +39,18 @@
- - -
+ + + + + + + - - - + - - - + +
{{'org_unit'| translate}} + +
{{'program'| translate}} +
{{'org_unit'| translate}} -
-
-
- +
{{'org_unit_scope'| translate}} +
+
+
+
- {{'filter'| translate}} - - - + {{'filter'| translate}} + + +
@@ -103,7 +104,22 @@
- + + +
@@ -124,8 +140,7 @@
- -
+
{{'no_data_found'| translate}} @@ -145,7 +160,7 @@ - + {{gridColumn.name| translate}} @@ -191,12 +206,12 @@ - - {{tei[gridColumn.id]}} + ng-repeat="gridColumn in gridColumns" ng-if='displayMode.onlyMarkedFollowup ? upcomingEvent.followup:true'> + {{upcomingEvent[gridColumn.id]}} === 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 @@ - + + @@ -58,6 +59,7 @@ + === 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(''); + 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 @@
- {{gridColumn.name | translate}} + {{gridColumn.name}} === 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. + * + * + *
+ * + * See {@link ngSanitize.$sanitize `$sanitize`} for usage. + */ + +/* + * HTML Parser By Misko Hevery (misko@hevery.com) + * 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 + + + +
+ Snippet: + + + + + + + + + + + + + + + + + + + + + + + + + +
DirectiveHowSourceRendered
ng-bind-htmlAutomatically uses $sanitize
<div ng-bind-html="snippet">
</div>
ng-bind-htmlBypass $sanitize by explicitly trusting the dangerous value +
<div ng-bind-html="deliberatelyTrustDangerousSnippet()">
+</div>
+
ng-bindAutomatically escapes
<div ng-bind="snippet">
</div>
+
+
+ + it('should sanitize the html snippet by default', function() { + expect(element(by.css('#bind-html-with-sanitize div')).getInnerHtml()). + toBe('

an html\nclick here\nsnippet

'); + }); + + it('should inline raw snippet if bound to a trusted value', function() { + expect(element(by.css('#bind-html-with-trust div')).getInnerHtml()). + toBe("

an html\n" + + "click here\n" + + "snippet

"); + }); + + it('should escape snippet without any filter', function() { + expect(element(by.css('#bind-default div')).getInnerHtml()). + toBe("<p style=\"color:blue\">an html\n" + + "<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" + + "snippet</p>"); + }); + + it('should update', function() { + element(by.model('snippet')).clear(); + element(by.model('snippet')).sendKeys('new text'); + expect(element(by.css('#bind-html-with-sanitize div')).getInnerHtml()). + toBe('new text'); + expect(element(by.css('#bind-html-with-trust div')).getInnerHtml()).toBe( + 'new text'); + expect(element(by.css('#bind-default div')).getInnerHtml()).toBe( + "new <b onclick=\"alert(1)\">text</b>"); + }); +
+
+ */ +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 = /^/g, + DOCTYPE_REGEXP = /]*?)>/i, + CDATA_REGEXP = //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("", 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, '>'); +} + +/** + * 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(''); + } + 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 + + * + * @example + + + +
+ Snippet: + + + + + + + + + + + + + + + + + + + + + +
FilterSourceRendered
linky filter +
<div ng-bind-html="snippet | linky">
</div>
+
+
+
linky target +
<div ng-bind-html="snippetWithTarget | linky:'_blank'">
</div>
+
+
+
no filter
<div ng-bind="snippet">
</div>
+ + + 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@somewhere.org, ' + + 'another@somewhere.org, 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@somewhere.org, ' + + 'another@somewhere.org, 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'); + }); + + + */ +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(''); + addText(text); + html.push(''); + } + }; +}]); + + +})(window, window.angular);