=== modified file 'dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/components/dataentry/dataentry-controller.js' --- dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/components/dataentry/dataentry-controller.js 2015-06-10 13:45:11 +0000 +++ dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/components/dataentry/dataentry-controller.js 2015-06-14 13:07:10 +0000 @@ -57,36 +57,73 @@ //listen for rule effect changes $scope.$on('ruleeffectsupdated', function(event, args) { - angular.forEach($rootScope.ruleeffects, function(effect) { - if( effect.dataElement ) { - //in the data entry controller we only care about the "hidefield" actions - if(effect.action === "HIDEFIELD") { - if(effect.dataElement) { - //Hide the field if the hiding is in effect, and there is no data value in the field. - var hide = effect.ineffect && !$scope.currentEvent[effect.dataElement.id]; - $scope.hiddenFields[effect.dataElement.id] = hide; + if($rootScope.ruleeffects[args.event]) { + //Establish which event was affected: + var affectedEvent = $scope.currentEvent; + //In most cases the updated effects apply to the current event. In case the affected event is not the current event, fetch the correct event to affect: + if(args.event !== affectedEvent.event) { + angular.forEach($scope.currentStageEvents, function(searchedEvent) { + if(searchedEvent.event === args.event) { + affectedEvent = searchedEvent; } - else { - $log.warn("ProgramRuleAction " + effect.id + " is of type HIDEFIELD, bot does not have a dataelement defined"); + }); + } + + angular.forEach($rootScope.ruleeffects[args.event], function(effect) { + if( effect.dataElement ) { + //in the data entry controller we only care about the "hidefield" actions + if(effect.action === "HIDEFIELD") { + if(effect.dataElement) { + if(effect.ineffect && affectedEvent[effect.dataElement.id]) { + //If a field is going to be hidden, but contains a value, we need to take action; + if(effect.content) { + //TODO: Alerts is going to be replaced with a proper display mecanism. + alert(effect.content); + } + else { + //TODO: Alerts is going to be replaced with a proper display mecanism. + alert($scope.prStDes[effect.dataElement.id].dataElement.formName + "Was blanked out and hidden by your last action"); + } + + //Blank out the value: + affectedEvent[effect.dataElement.id] = ""; + $scope.saveDatavalueForEvent($scope.prStDes[effect.dataElement.id],null,affectedEvent); + } + + $scope.hiddenFields[effect.dataElement.id] = effect.ineffect; + } + else { + $log.warn("ProgramRuleAction " + effect.id + " is of type HIDEFIELD, bot does not have a dataelement defined"); + } } } - } - }); + }); + } }); + //check if field is hidden $scope.isHidden = function(id) { //In case the field contains a value, we cant hide it. //If we hid a field with a value, it would falsely seem the user was aware that the value was entered in the UI. - if($scope.currentEvent[id]) - { + if($scope.currentEvent[id]) { return false; } - else - { + else { return $scope.hiddenFields[id]; } }; + $scope.executeRules = function() { + //If the events is displayed in a table, it is necessary to run the rules for all visible events. + if($scope.currentStage.displayEventsInTable) { + angular.forEach($scope.currentStageEvents, function(event) { + TrackerRulesExecutionService.executeRules($scope.selectedProgramId,event,$scope.eventsByStage,$scope.prStDes,$scope.selectedTei); + }); + } else { + TrackerRulesExecutionService.executeRules($scope.selectedProgramId,$scope.currentEvent,$scope.eventsByStage,$scope.prStDes,$scope.selectedTei); + } + }; + //listen for the selected items $scope.$on('dashboardWidgets', function() { @@ -357,7 +394,7 @@ //Execute rules for the first time, to make the initial page appear correctly. //Subsequent calls will be made from the "saveDataValue" function. - TrackerRulesExecutionService.executeRules($scope); + $scope.executeRules(); }; function updateCurrentEventInStage(){ @@ -378,7 +415,7 @@ $scope.saveDatavalueForEvent(prStDe,field,$scope.currentEvent); }; - $scope.saveDatavalueForEvent = function(prStDe,field,eventToSave,object){ + $scope.saveDatavalueForEvent = function(prStDe,field,eventToSave){ //Blank out the input-saved class on the last saved due date: $scope.eventDateSaved = false; @@ -444,7 +481,8 @@ $scope.currentStageEventsOriginal = angular.copy($scope.currentStageEvents); - TrackerRulesExecutionService.executeRules($scope); + //Run rules on updated data: + $scope.executeRules(); }); } === modified file 'dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/components/rulebound/rulebound-controller.js' --- dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/components/rulebound/rulebound-controller.js 2015-06-02 05:55:14 +0000 +++ dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/components/rulebound/rulebound-controller.js 2015-06-14 13:07:10 +0000 @@ -20,11 +20,11 @@ var keyDataInEffect = false; //Bind non-bound rule effects, if any. - angular.forEach($rootScope.ruleeffects, function(effect) { - if(effect.location == $scope.widgetCode){ + angular.forEach($rootScope.ruleeffects[args.event], function(effect) { + if(effect.location === $scope.widgetCode){ //This effect is affecting the local widget - if(effect.action == "DISPLAYTEXT") { + if(effect.action === "DISPLAYTEXT") { //this action is display text. Make sure the displaytext is //added to the local list of displayed texts if(!angular.isObject($scope.displayTextEffects[effect.id])){ @@ -35,7 +35,7 @@ textInEffect = true; } } - else if(effect.action == "DISPLAYKEYVALUEPAIR") { + else if(effect.action === "DISPLAYKEYVALUEPAIR") { //this action is display text. Make sure the displaytext is //added to the local list of displayed texts if(!angular.isObject($scope.displayTextEffects[effect.id])){ @@ -46,7 +46,7 @@ keyDataInEffect = true; } } else { - $log.warn("action: '" + effect.action + "' not supported by rulebound-controller.js") + $log.warn("action: '" + effect.action + "' not supported by rulebound-controller.js"); } } }); === modified file 'dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/scripts/services.js' --- dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/scripts/services.js 2015-06-10 13:45:11 +0000 +++ dhis-2/dhis-web/dhis-web-apps/src/main/webapp/dhis-web-tracker-capture/scripts/services.js 2015-06-14 13:07:10 +0000 @@ -1778,66 +1778,66 @@ /* service for building variables based on the data in users fields */ .service('VariableService', function($rootScope,$q,TrackerRuleVariableFactory,$filter,orderByFilter,$log){ return { - getVariables: function($scope) { + getVariables: function(programid, executingEvent, allEventsByStage, allDataElements, selectedEntity) { var thePromisedVariables = $q.defer(); - var variables = []; + var variables = {}; - $scope.pushVariable = function(variablename, variablevalue, variabletype, variablefound) { + var pushVariable = function(variablename, variableValue, variableType, variablefound) { //First clean away single or double quotation marks at the start and end of the variable name. - variablevalue = $filter('trimquotes')(variablevalue); + variableValue = $filter('trimquotes')(variableValue); //Append single quotation marks in case the variable is of text type: - if(variabletype === 'string') { - variablevalue = "'" + variablevalue + "'"; - } - else if(variabletype === 'date') { - variablevalue = "'" + variablevalue + "'"; - } - else if(variabletype === 'bool' || variabletype === 'trueOnly') { - if(eval(variablevalue)) { - variablevalue = true; + if(variableType === 'string') { + variableValue = "'" + variableValue + "'"; + } + else if(variableType === 'date') { + variableValue = "'" + variableValue + "'"; + } + else if(variableType === 'bool' || variableType === 'trueOnly') { + if(eval(variableValue)) { + variableValue = true; } else { - variablevalue = false; + variableValue = false; } } - else if(variabletype === "int" || variabletype === "number") { - variablevalue = Number(variablevalue); + else if(variableType === "int" || variableType === "number") { + variableValue = Number(variableValue); } else{ - $log.warn("unknown datatype:" + variabletype); + $log.warn("unknown datatype:" + variableType); } - //Make sure that the variablevalue does not contain a dollar sign anywhere + //Make sure that the variableValue does not contain a dollar sign anywhere //- this would potentially mess up later use of the variable: -// if(angular.isDefined(variablevalue) -// && variablevalue !== null -// && variablevalue.indexOf("$") !== -1 ) { -// variablevalue = variablevalue.replace(/\\$/,""); +// if(angular.isDefined(variableValue) +// && variableValue !== null +// && variableValue.indexOf("$") !== -1 ) { +// variableValue = variableValue.replace(/\\$/,""); // } //TODO: //Also clean away instructions that might be erroneusly evalutated in javascript - variables.push({variablename:variablename, - variablevalue:variablevalue, - variabletype:variabletype, + variables[variablename] = { + variableValue:variableValue, + variableType:variableType, hasValue:variablefound - }); + }; }; - TrackerRuleVariableFactory.getProgramRuleVariables($scope.currentEvent.program).then(function(programVariables){ + TrackerRuleVariableFactory.getProgramRuleVariables(programid).then(function(programVariables){ // The following section will need a different implementation for event capture: var allEventsSorted = []; - var currentEvent = $scope.currentEvent; + var currentEvent = executingEvent; var eventsSortedPerProgramStage = []; - for(var key in $scope.eventsByStage){ - if($scope.eventsByStage.hasOwnProperty(key)){ + for(var key in allEventsByStage){ + if(allEventsByStage.hasOwnProperty(key)){ eventsSortedPerProgramStage[key] = []; - angular.forEach($scope.eventsByStage[key], function(event){ + angular.forEach(allEventsByStage[key], function(event){ allEventsSorted.push(event); eventsSortedPerProgramStage[key].push(event); }); @@ -1846,12 +1846,12 @@ } allEventsSorted = orderByFilter(allEventsSorted, '-sortingDate').reverse(); - var allDes = {}; - angular.forEach($scope.programStages, function(programStage){ - angular.forEach(programStage.programStageDataElements, function(dataElement) { - allDes[dataElement.dataElement.id] = dataElement; - }); - }); + var allDes = allDataElements; +// angular.forEach($scope.programStages, function(programStage){ +// angular.forEach(programStage.programStageDataElements, function(dataElement) { +// allDes[dataElement.dataElement.id] = dataElement; +// }); +// }); //End of region that neeeds specific implementation for event capture angular.forEach(programVariables, function(programVariable) { @@ -1862,7 +1862,7 @@ if(angular.isDefined(event[programVariable.dataElement.id]) && event[programVariable.dataElement.id] !== null ){ valueFound = true; - $scope.pushVariable(programVariable.name, event[programVariable.dataElement.id], allDes[programVariable.dataElement.id].dataElement.type, valueFound ); + pushVariable(programVariable.name, event[programVariable.dataElement.id], allDes[programVariable.dataElement.id].dataElement.type, valueFound ); } }); } else { @@ -1877,7 +1877,7 @@ if(angular.isDefined(event[programVariable.dataElement.id]) && event[programVariable.dataElement.id] !== null ){ valueFound = true; - $scope.pushVariable(programVariable.name, event[programVariable.dataElement.id], allDes[programVariable.dataElement.id].dataElement.type, valueFound ); + pushVariable(programVariable.name, event[programVariable.dataElement.id], allDes[programVariable.dataElement.id].dataElement.type, valueFound ); } }); } @@ -1885,7 +1885,7 @@ if(angular.isDefined(currentEvent[programVariable.dataElement.id]) && currentEvent[programVariable.dataElement.id] !== null ){ valueFound = true; - $scope.pushVariable(programVariable.name, currentEvent[programVariable.dataElement.id], allDes[programVariable.dataElement.id].dataElement.type, valueFound ); + pushVariable(programVariable.name, currentEvent[programVariable.dataElement.id], allDes[programVariable.dataElement.id].dataElement.type, valueFound ); } } else if(programVariable.programRuleVariableSourceType === "DATAELEMENT_PREVIOUS_EVENT"){ @@ -1904,21 +1904,20 @@ else if(allEventsSorted[i] === currentEvent) { //We have iterated to the newest event - store the last collected variable value - if any is found: if(valueFound) { - $scope.pushVariable(programVariable.name, previousvalue, allDes[programVariable.dataElement.id].dataElement.type, valueFound ); + pushVariable(programVariable.name, previousvalue, allDes[programVariable.dataElement.id].dataElement.type, valueFound ); } //Set currentEventPassed, ending the iteration: currentEventPassed = true; } - } } } else if(programVariable.programRuleVariableSourceType === "TEI_ATTRIBUTE"){ - angular.forEach($scope.selectedEntity.attributes , function(attribute) { + angular.forEach(selectedEntity.attributes , function(attribute) { if(!valueFound) { if(attribute.attribute === programVariable.trackedEntityAttribute.id) { valueFound = true; - $scope.pushVariable(programVariable.name, attribute.value, attribute.type, valueFound ); + pushVariable(programVariable.name, attribute.value, attribute.type, valueFound ); } } }); @@ -1932,7 +1931,7 @@ numberOfEvents = eventsSortedPerProgramStage[programVariable.programStage.id].length; } valueFound = true; - $scope.pushVariable(programVariable.name, numberOfEvents, 'int', valueFound ); + pushVariable(programVariable.name, numberOfEvents, 'int', valueFound ); } else { //Missing handing of ruletype @@ -1945,22 +1944,22 @@ if(programVariable.dataElement) { var dataElement = allDes[programVariable.dataElement.id]; if( dataElement ) { - $scope.pushVariable(programVariable.name, "", dataElement.dataElement.type ); + pushVariable(programVariable.name, "", dataElement.dataElement.type ); } else { $log.warn("Variable #{" + programVariable.name + "} is linked to a dataelement that is not part of the program"); - $scope.pushVariable(programVariable.name, "", "string" ); + pushVariable(programVariable.name, "", "string" ); } } else { - $scope.pushVariable(programVariable.name, "", "string" ); + pushVariable(programVariable.name, "", "string" ); } } }); //add context variables: //last parameter "valuefound" is always true for event date - $scope.pushVariable('eventdate', currentEvent.eventDate, 'date', true ); + pushVariable('eventdate', executingEvent.eventDate, 'date', true ); thePromisedVariables.resolve(variables); }); @@ -1975,16 +1974,15 @@ /* service for executing tracker rules and broadcasting results */ .service('TrackerRulesExecutionService', function(TrackerRulesFactory,VariableService, $rootScope, $log, $filter, orderByFilter){ return { - executeRules: function($scope) { + executeRules: function(programid, executingEvent, allEventsByStage, allDataElements, selectedEntity) { //When debugging rules, the caller should provide a variable for wether or not the rules is being debugged. //hard coding this for now: var debug = true; var verbose = true; var variablesHash = {}; - var variablesWithValueHash = {}; - $scope.replaceVariables = function(expression) { + var replaceVariables = function(expression) { //replaces the variables in an expression with actual variable values. //First check if the expression contains variables at all(any dollar signs): if(expression.indexOf('#') !== -1) { @@ -1998,7 +1996,7 @@ if(angular.isDefined(variablesHash[variablepresent])) { //Replace all occurrences of the variable name(hence using regex replacement): expression = expression.replace(new RegExp("#{" + variablepresent + "}", 'g'), - variablesHash[variablepresent]); + variablesHash[variablepresent].variableValue); } else { $log.warn("Expression " + expression + " conains variable " + variablepresent @@ -2010,7 +2008,7 @@ return expression; }; - $scope.runDhisFunctions = function(expression) { + var runDhisFunctions = function(expression) { //Called from "runExpression". Only proceed with this logic in case there seems to be dhis function calls: "dhis." is present. if(angular.isDefined(expression) && expression.indexOf("dhis.") !== -1){ var dhisFunctions = [{name:"dhis.daysbetween",parameters:2}, @@ -2042,7 +2040,7 @@ //In case the function call is nested, the parameter itself contains an expression, run the expression. if(angular.isDefined(parameters)) { for (var i = 0; i < parameters.length; i++) { - parameters[i] = $scope.runExpression(parameters[i],dhisFunction.name,"parameter:" + i); + parameters[i] = runExpression(parameters[i],dhisFunction.name,"parameter:" + i); } } @@ -2073,7 +2071,7 @@ else if(dhisFunction.name === "dhis.hasValue") { //"evaluate" hasvalue to true or false: - if(variablesWithValueHash[parameters[0]]){ + if(variablesHash[parameters[0]].hasValue){ expression = expression.replace(callToThisFunction, 'true'); } else { expression = expression.replace(callToThisFunction, 'false'); @@ -2095,14 +2093,14 @@ return expression; }; - $scope.runExpression = function(expression, beforereplacement, identifier ){ + var runExpression = function(expression, beforereplacement, identifier ){ //determine if expression is true, and actions should be effectuated //If DEBUG mode, use try catch and report errors. If not, omit the heavy try-catch loop.: var answer = false; if(debug) { try{ - var dhisfunctionsevaluated = $scope.runDhisFunctions(expression); + var dhisfunctionsevaluated = runDhisFunctions(expression); answer = eval(dhisfunctionsevaluated); if(verbose) @@ -2117,34 +2115,30 @@ } else { //Just run the expression. This is much faster than the debug route: http://jsperf.com/try-catch-block-loop-performance-comparison - var dhisfunctionsevaluated = $scope.runDhisFunctions(expression); + var dhisfunctionsevaluated = runDhisFunctions(expression); answer = eval(dhisfunctionsevaluated); } return answer; }; - VariableService.getVariables($scope).then(function(variables){ - TrackerRulesFactory.getProgramStageRules($scope.selectedProgram.id, $scope.currentStage.id).then(function(rules){ + VariableService.getVariables(programid, executingEvent, allEventsByStage, allDataElements, selectedEntity).then(function(variablesReceived){ + TrackerRulesFactory.getProgramStageRules(programid, executingEvent.programStage).then(function(rules){ //But run rules in priority - lowest number first(priority null is last) rules = orderByFilter(rules, 'priority'); - - //Make a variables hash to allow direct lookup: - angular.forEach(variables, function(variable) { - variablesHash[variable.variablename] = variable.variablevalue; - }); - //Make a variables-exists/hasvalue hash to allow direct lookup: - angular.forEach(variables, function(variable) { - variablesWithValueHash[variable.variablename] = variable.hasValue; - }); + variablesHash = variablesReceived; if(angular.isObject(rules) && angular.isArray(rules)){ //The program has rules, and we want to run them. //Prepare repository unless it is already prepared: - if(angular.isUndefined( $rootScope.ruleeffects )){ + if(angular.isUndefined( $rootScope.ruleeffects ) ) { $rootScope.ruleeffects = {}; } + + if(angular.isUndefined( $rootScope.ruleeffects[executingEvent.event] )){ + $rootScope.ruleeffects[executingEvent.event] = {}; + } var updatedEffectsExits = false; @@ -2155,18 +2149,18 @@ //Go through and populate variables with actual values, but only if there actually is any replacements to be made(one or more "$" is present) if(expression) { if(expression.indexOf('#') !== -1) { - expression = $scope.replaceVariables(expression); + expression = replaceVariables(expression); } //run expression: - ruleEffective = $scope.runExpression(expression, rule.condition, "rule:" + rule.id); + ruleEffective = runExpression(expression, rule.condition, "rule:" + rule.id); } else { $log.warn("Rule id:'" + rule.id + "'' and name:'" + rule.name + "' had no condition specified. Please check rule configuration."); } angular.forEach(rule.actions, function(action){ //In case the effect-hash is not populated, add entries - if(angular.isUndefined( $rootScope.ruleeffects[action.id] )){ - $rootScope.ruleeffects[action.id] = { + if(angular.isUndefined( $rootScope.ruleeffects[executingEvent.event][action.id] )){ + $rootScope.ruleeffects[executingEvent.event][action.id] = { id:action.id, location:action.location, action:action.programRuleActionType, @@ -2186,56 +2180,56 @@ //To make a lookup in variables hash, we must make a lookup without the dollar sign in the variable name //The first strategy is to make a direct lookup. In case the "data" expression is more complex, we have to do more replacement and evaluation. - var nameWithoutDollarSign = action.data.replace('#{','').replace('}',''); - if(angular.isDefined(variablesHash[nameWithoutDollarSign])) + var nameWithoutBrackets = action.data.replace('#{','').replace('}',''); + if(angular.isDefined(variablesHash[nameWithoutBrackets])) { //The variable exists, and is replaced with its corresponding value - $rootScope.ruleeffects[action.id].data = - variablesHash[nameWithoutDollarSign]; + $rootScope.ruleeffects[executingEvent.event][action.id].data = + variablesHash[nameWithoutBrackets].variableValue; } else if(action.data.indexOf('#') !== -1) { //Since the value couldnt be looked up directly, and contains a dollar sign, the expression was more complex //Now we will have to make a thorough replacement and separate evaluation to find the correct value: - $rootScope.ruleeffects[action.id].data = $scope.replaceVariables(action.data); + $rootScope.ruleeffects[executingEvent.event][action.id].data = replaceVariables(action.data); //In a scenario where the data contains a complex expression, evaluate the expression to compile(calculate) the result: - $rootScope.ruleeffects[action.id].data = $scope.runExpression($rootScope.ruleeffects[action.id].data, action.data, "action:" + action.id); + $rootScope.ruleeffects[executingEvent.event][action.id].data = runExpression($rootScope.ruleeffects[executingEvent.event][action.id].data, action.data, "action:" + action.id); } } //Update the rule effectiveness if it changed in this evaluation; - if($rootScope.ruleeffects[action.id].ineffect != ruleEffective) + if($rootScope.ruleeffects[executingEvent.event][action.id].ineffect !== ruleEffective) { //There is a change in the rule outcome, we need to update the effect object. updatedEffectsExits = true; - $rootScope.ruleeffects[action.id].ineffect = ruleEffective; + $rootScope.ruleeffects[executingEvent.event][action.id].ineffect = ruleEffective; } //In case the rule is of type "assign variable" and the rule is effective, //the variable data result needs to be applied to the correct variable: - if($rootScope.ruleeffects[action.id].action === "ASSIGNVARIABLE" && $rootScope.ruleeffects[action.id].ineffect){ + if($rootScope.ruleeffects[executingEvent.event][action.id].action === "ASSIGNVARIABLE" && $rootScope.ruleeffects[executingEvent.event][action.id].ineffect){ //from earlier evaluation, the data portion of the ruleeffect now contains the value of the variable to be assign. //the content portion of the ruleeffect defines the name for the variable, when dollar is removed: - var variabletoassign = $rootScope.ruleeffects[action.id].content.replace("#{","").replace("}",""); + var variabletoassign = $rootScope.ruleeffects[executingEvent.event][action.id].content.replace("#{","").replace("}",""); if(!angular.isDefined(variablesHash[variabletoassign])){ $log.warn("Variable " + variabletoassign + " was not defined."); } //Even if the variable is not defined: we assign it: - if(variablesHash[variabletoassign] !== $rootScope.ruleeffects[action.id].data){ + if(variablesHash[variabletoassign].variableValue !== $rootScope.ruleeffects[executingEvent.event][action.id].data){ //If the variable was actually updated, we assume that there is an updated ruleeffect somewhere: updatedEffectsExits = true; //Then we assign the new value: - variablesHash[variabletoassign] = $rootScope.ruleeffects[action.id].data; + variablesHash[variabletoassign].variableValue = $rootScope.ruleeffects[executingEvent.event][action.id].data; } } }); }); - //Broadcast rules finished if there was any actual changes. + //Broadcast rules finished if there was any actual changes to the event. if(updatedEffectsExits){ - $rootScope.$broadcast("ruleeffectsupdated"); + $rootScope.$broadcast("ruleeffectsupdated", { event: executingEvent.event }); } }