=== added file 'dhis-2/dhis-web/dhis-web-dataentry/src/main/java/org/hisp/dhis/de/action/CheckValidationRulesAction.java' --- dhis-2/dhis-web/dhis-web-dataentry/src/main/java/org/hisp/dhis/de/action/CheckValidationRulesAction.java 1970-01-01 00:00:00 +0000 +++ dhis-2/dhis-web/dhis-web-dataentry/src/main/java/org/hisp/dhis/de/action/CheckValidationRulesAction.java 2013-09-24 08:26:24 +0000 @@ -0,0 +1,124 @@ +package org.hisp.dhis.de.action; + +import com.opensymphony.xwork2.Action; + +import org.hisp.dhis.constant.Constant; +import org.hisp.dhis.constant.ConstantService; +import org.hisp.dhis.validation.ValidationRule; +import org.hisp.dhis.validation.ValidationRuleService; + +import java.util.Collection; +import java.util.Date; + +/** + * @author Stefan Börjesson + */ + +public class CheckValidationRulesAction implements Action { + + // ------------------------------------------------------------------------- + // Dependencies + // ------------------------------------------------------------------------- + + private ValidationRuleService validationRuleService; + + public void setValidationRuleService( + ValidationRuleService validationRuleService) { + this.validationRuleService = validationRuleService; + } + + private ConstantService constantService; + + public void setConstantService(ConstantService constantService) { + this.constantService = constantService; + } + + // ------------------------------------------------------------------------- + // Output + // ------------------------------------------------------------------------- + + private long validationRulesTs; + + public long getValidationRulesTs() { + return this.validationRulesTs; + } + + private int numberOfValidationRules; + + public int getNumberOfValidationRules() { + return this.numberOfValidationRules; + } + + private long constantsTs; + + public long getConstantsTs() { + return constantsTs; + } + + private int numberOfConstants; + + public int getNumberOfConstants() { + return this.numberOfConstants; + } + + // ------------------------------------------------------------------------- + // Action implementation + // ------------------------------------------------------------------------- + + public String execute() { + this.validationRulesTs = 0; + this.constantsTs = 0; + Date lastUpdatedDate = null; + + // Validation Rules + Collection rules = this.validationRuleService + .getAllValidationRules(); + + // Loop to get the largest timestamp from the existing validation rules + for (ValidationRule rule : rules) { + if (lastUpdatedDate != null) { + if (rule.getLastUpdated() != null) { + if (rule.getLastUpdated().after(lastUpdatedDate)) { + lastUpdatedDate = rule.getLastUpdated(); + } + } + } else { + lastUpdatedDate = rule.getLastUpdated(); + } + + } + // If date is null set TS to Long.MAX_VALUE to force update as rules + // might have been deleted. + this.validationRulesTs = lastUpdatedDate == null ? Long.MAX_VALUE + : lastUpdatedDate.getTime(); + + // Setting the number of validation rules to the size of the collection + // returned. + this.numberOfValidationRules = rules.size(); + + // Constants + lastUpdatedDate = null; + Collection constants = constantService.getAllConstants(); + for (Constant constant : constants) { + if (lastUpdatedDate != null) { + if (constant.getLastUpdated() != null) { + if (constant.getLastUpdated().after(lastUpdatedDate)) { + lastUpdatedDate = constant.getLastUpdated(); + } + } + } else { + lastUpdatedDate = constant.getLastUpdated(); + } + + } + + this.constantsTs = lastUpdatedDate == null ? Long.MAX_VALUE + : lastUpdatedDate.getTime(); + + // Setting the number of constants to the size of the collection + // returned. + this.numberOfConstants = constants.size(); + + return SUCCESS; + } +} === added file 'dhis-2/dhis-web/dhis-web-dataentry/src/main/java/org/hisp/dhis/de/action/GetValidationRulesAction.java' --- dhis-2/dhis-web/dhis-web-dataentry/src/main/java/org/hisp/dhis/de/action/GetValidationRulesAction.java 1970-01-01 00:00:00 +0000 +++ dhis-2/dhis-web/dhis-web-dataentry/src/main/java/org/hisp/dhis/de/action/GetValidationRulesAction.java 2013-09-24 08:26:24 +0000 @@ -0,0 +1,98 @@ +package org.hisp.dhis.de.action; + +import java.text.DecimalFormat; +import java.util.Collection; + +import org.hisp.dhis.constant.Constant; +import org.hisp.dhis.constant.ConstantService; +import org.hisp.dhis.expression.ExpressionService; +import org.hisp.dhis.validation.ValidationRule; +import org.hisp.dhis.validation.ValidationRuleService; + +import com.opensymphony.xwork2.Action; + +/** + * @author Stefan Börjesson + */ +public class GetValidationRulesAction implements Action { + + private ValidationRuleService validationRuleService; + + public void setValidationRuleService( + ValidationRuleService validationRuleService) { + this.validationRuleService = validationRuleService; + } + + + + private ConstantService constantService; + + public void setConstantService( ConstantService constantService ) + { + this.constantService = constantService; + } + + private ExpressionService expressionService; + + public void setExpressionService( ExpressionService expressionService) + { + this.expressionService = expressionService; + } + + public ExpressionService getExpressionService() + { + return this.expressionService; + } + + private Collection validationRules; + + public void setValidationRules(Collection validationRules) { + this.validationRules = validationRules; + } + + public Collection getValidationRules() { + return this.validationRules; + } + + private Collection constants; + + public Collection getConstants() { + return constants; + } + + public void setConstants(Collection constants) { + this.constants = constants; + } + + private DecimalFormat df; + + public DecimalFormat getDf(){ + return this.df; + } + + @Override + public String execute() throws Exception { + df = new DecimalFormat(); + df.setMaximumFractionDigits(12); + df.setMinimumFractionDigits(0); + df.setMaximumFractionDigits(1); + df.setGroupingUsed(false); + + this.validationRules = this.validationRuleService + .getAllValidationRules(); + /*for(ValidationRule rule: this.validationRules){ + System.out.println("rule.getPeriodType().getName() = " + rule.getPeriodType().getName()); + System.out.println("rule.getType() = " + rule.getType()); + System.out.println("rule.getOperator().getMathematicalOperator() = " + rule.getOperator().getMathematicalOperator()); + System.out.println("rule.getLeftSide().getDescription() = " + rule.getLeftSide().getDescription()); + System.out.println("rule.getLeftSide().getExpression() = " + rule.getLeftSide().getExpression()); + System.out.println("rule.getRightSide().getDescription() = " + rule.getRightSide().getDescription()); + System.out.println("rule.getRightSide().getExpression() = " + rule.getRightSide().getExpression()); + System.out.println("rule.getRightSide().isNullIfBlank() = " + rule.getRightSide().isNullIfBlank()); + }*/ + + this.constants = constantService.getAllConstants(); + + return SUCCESS; + } +} === modified file 'dhis-2/dhis-web/dhis-web-dataentry/src/main/resources/META-INF/dhis/beans.xml' --- dhis-2/dhis-web/dhis-web-dataentry/src/main/resources/META-INF/dhis/beans.xml 2012-12-14 13:46:47 +0000 +++ dhis-2/dhis-web/dhis-web-dataentry/src/main/resources/META-INF/dhis/beans.xml 2013-09-24 08:26:24 +0000 @@ -129,4 +129,17 @@ + + + + + + + + + + + === modified file 'dhis-2/dhis-web/dhis-web-dataentry/src/main/resources/struts.xml' --- dhis-2/dhis-web/dhis-web-dataentry/src/main/resources/struts.xml 2013-07-25 09:37:43 +0000 +++ dhis-2/dhis-web/dhis-web-dataentry/src/main/resources/struts.xml 2013-09-24 08:26:24 +0000 @@ -13,7 +13,7 @@ /main.vm /dhis-web-dataentry/select.vm /dhis-web-dataentry/menu.vm - ../dhis-web-commons/ouwt/ouwt.js,javascript/form.js,javascript/entry.js,javascript/history.js + ../dhis-web-commons/ouwt/ouwt.js,javascript/form.js,javascript/entry.js,javascript/history.js,javascript/ruleValidation.js style/dhis-web-dataentry.css ../dhis-web-commons/cacheManifest.action @@ -97,5 +97,13 @@ + + /dhis-web-dataentry/checkValidationRules.vm + + + + /dhis-web-dataentry/getValidationRules.vm + + === added file 'dhis-2/dhis-web/dhis-web-dataentry/src/main/webapp/dhis-web-dataentry/checkValidationRules.vm' --- dhis-2/dhis-web/dhis-web-dataentry/src/main/webapp/dhis-web-dataentry/checkValidationRules.vm 1970-01-01 00:00:00 +0000 +++ dhis-2/dhis-web/dhis-web-dataentry/src/main/webapp/dhis-web-dataentry/checkValidationRules.vm 2013-09-24 08:26:24 +0000 @@ -0,0 +1,6 @@ +{ + "validationRulesTs":"${validationRulesTs}", + "numberOfValidationRules":"${numberOfValidationRules}", + "constantsTs":"${constantsTs}", + "numberOfConstants":"${numberOfConstants}" +} \ No newline at end of file === added file 'dhis-2/dhis-web/dhis-web-dataentry/src/main/webapp/dhis-web-dataentry/getValidationRules.vm' --- dhis-2/dhis-web/dhis-web-dataentry/src/main/webapp/dhis-web-dataentry/getValidationRules.vm 1970-01-01 00:00:00 +0000 +++ dhis-2/dhis-web/dhis-web-dataentry/src/main/webapp/dhis-web-dataentry/getValidationRules.vm 2013-09-24 08:26:24 +0000 @@ -0,0 +1,31 @@ +#set( $size = $validationRules.size() ) +#set( $size2 = $constants.size() ) +{ "validationRules": [ +#foreach( $value in $validationRules ) +{ + "id":"${value.id}", + "description":"${encoder.jsonEncode( $value.description)}", + "leftSide":{ + "expression":"${encoder.jsonEncode( $value.leftSide.expression)}", + "isNullIfBlank":${value.leftSide.nullIfBlank}, + "description":"${encoder.jsonEncode($expressionService.getExpressionDescription($value.leftSide.expression))}" + }, + "rightSide":{ + "expression":"${encoder.jsonEncode( $value.rightSide.expression)}", + "isNullIfBlank":${value.rightSide.nullIfBlank}, + "description":"${encoder.jsonEncode($expressionService.getExpressionDescription($value.rightSide.expression))}" + }, + "operator":"${value.operator.mathematicalOperator}", + "validationResult": null +}#if( $velocityCount < $size ),#end +#end ], +"constants": [ +#foreach( $constant in $constants) +{ + "id":"${constant.id}", + "description":"${encoder.jsonEncode($constant.displayName)}", + "value":"$df.format($constant.value)", + "uid":"${constant.uid}" +}#if( $velocityCount < $size2 ),#end +#end ] +} === modified file 'dhis-2/dhis-web/dhis-web-dataentry/src/main/webapp/dhis-web-dataentry/javascript/form.js' --- dhis-2/dhis-web/dhis-web-dataentry/src/main/webapp/dhis-web-dataentry/javascript/form.js 2013-09-23 09:57:48 +0000 +++ dhis-2/dhis-web/dhis-web-dataentry/src/main/webapp/dhis-web-dataentry/javascript/form.js 2013-09-24 08:26:24 +0000 @@ -222,6 +222,7 @@ log( 'Meta-data loaded' ); updateForms(); + updateValidationRules(); } } ); } @@ -1432,6 +1433,30 @@ } ); } + +function validate_offline( ignoreSuccessfulValidation, successCallback) +{ + var success = true; + var response = runOfflineValidations(ignoreSuccessfulValidation); + + if(response == ''){ + var successHtml = '

' + i18n_validation_result + '  

' + + '

' + i18n_successful_validation + '

'; + if(!ignoreSuccessfulValidation){ + displayValidationDialog( successHtml, 200 ); + } + } else{ + success = false; + displayValidationDialog( response, 500 ); + } + + + if ( success && $.isFunction( successCallback ) ) + { + successCallback.call(); + } +} + function validate( ignoreSuccessfulValidation, successCallback ) { var compulsoryCombinationsValid = validateCompulsoryCombinations(); @@ -1459,34 +1484,46 @@ params['organisationUnitId'] = getCurrentOrganisationUnit(); params['multiOrganisationUnit'] = multiOrganisationUnit; - $( '#validationDiv' ).load( 'validate.action', params, function( response, status, xhr ) { - var success = null; - - if ( status == 'error' && !ignoreSuccessfulValidation ) - { - window.alert( i18n_operation_not_available_offline ); - success = true; // Accept if offline - } - else - { - var hasViolations = isDefined( response ) && $.trim( response ).length > 0; - var success = !( hasViolations && validCompleteOnly ); - - if ( hasViolations ) - { - displayValidationDialog( response, 500 ); - } - else if ( !ignoreSuccessfulValidation ) - { - displayValidationDialog( successHtml, 200 ); - } - } - - if ( success && $.isFunction( successCallback ) ) - { - successCallback.call(); - } - } ); + if(!dhis2.availability._isAvailable) + { + success = validate_offline( ignoreSuccessfulValidation, successCallback); + } + else + { + $( '#validationDiv' ).load( 'validate.action', params, function( response, status, xhr ) { + var success = null; + + if ( status == 'error' && !ignoreSuccessfulValidation ) + { + //window.alert( i18n_operation_not_available_offline ); + //success = true; // Accept if offline + success = validate_offline( ignoreSuccessfulValidation, successCallback); + if ( success && $.isFunction( successCallback ) ) + { + successCallback.call(); + } + } + else + { + var hasViolations = isDefined( response ) && $.trim( response ).length > 0; + var success = !( hasViolations && validCompleteOnly ); + + if ( hasViolations ) + { + displayValidationDialog( response, 500 ); + } + else if ( !ignoreSuccessfulValidation ) + { + displayValidationDialog( successHtml, 200 ); + } + } + + if ( success && $.isFunction( successCallback ) ) + { + successCallback.call(); + } + } ); + } } function validateCompulsoryCombinations() @@ -1642,6 +1679,49 @@ } ); } +//----------------------------------------------------------------------------- +//Local storage of Validation Rules +//----------------------------------------------------------------------------- + +function updateValidationRules() +{ + log("Before checkValidationRules.action"); + $.ajax( { + url: 'checkValidationRules.action', + dataType: 'json', + success: function( data, textStatus, jqXHR ) + { + var validationRulesControl = data; + var localValidationRulesControl = storageManager.getValidationRulesControl(); + + if(localValidationRulesControl.validationRulesTs != validationRulesControl.validationRulesTs || + localValidationRulesControl.numberOfValidationRules != validationRulesControl.numberOfValidationRules || + localValidationRulesControl.constantsTs != validationRulesControl.constantsTs || + localValidationRulesControl.numberOfConstants != validationRulesControl.numberOfConstants){ + + storageManager.purgeValidationRules(); + storageManager.purgeConstants(); + + $.ajax( { + url: 'getValidationRules.action', + dataType: 'json', + success: function( data, textStatus, jqXHR ) + { + storageManager.saveValidationRules(data); + storageManager.saveConstants(data); + storageManager.saveValidationRulesControl(validationRulesControl); + } + } ); + } + else + { + log("Validation rules in localstorage are up to date."); + } + } + } ); +} + + // TODO break if local storage is full // ----------------------------------------------------------------------------- @@ -1661,6 +1741,9 @@ var KEY_FORM_VERSIONS = 'formversions'; var KEY_DATAVALUES = 'datavalues'; var KEY_COMPLETEDATASETS = 'completedatasets'; + var KEY_VALIDATIONRULESCONTROL = 'validationrulescontrol'; + var KEY_VALIDATIONRULE = 'validationrule-'; + var KEY_CONSTANT = 'validationrulesconstants'; /** * Returns the total number of characters currently in the local storage. @@ -2150,6 +2233,179 @@ return true; }; + + + /** + * Method to retrive the timestamp of the validation rules in localstorage. + * + * @return The timestamp of the validation rules in localstorage. + */ + + + this.getValidationRulesControl = function() + { + var validationRuleControl = localStorage[KEY_VALIDATIONRULESCONTROL]; + if(validationRuleControl == null) + { + validationRuleControl = new Object(); + validationRuleControl.validationRulesTs = 0; + validationRuleControl.numberOfValidationRules = 0; + validationRuleControl.constantsTs = 0; + validationRuleControl.numberOfValidationConstants = 0; + return 0; + } + else + { + return JSON.parse(validationRuleControl); + } + + }; + + /** + * Method to save a new timestamp of the validation rules in localstorage. + * + * @param The timestamp to be saved. + */ + + this.saveValidationRulesControl = function(newValidationRulesControl) + { + try + { + localStorage[KEY_VALIDATIONRULESCONTROL] = JSON.stringify(newValidationRulesControl); + } + catch ( e ) + { + log( 'Max local storage quota reached, validation rule timestamp not saved locally.'); + return false; + } + return true; + }; + + + /** + * Method that purges the validation rules stored in localstorage. + * + */ + + this.purgeValidationRules = function() + { + for ( var i = 0; i < localStorage.length; i++ ) + { + var key = localStorage.key( i ); + + if ( key.substring( 0, KEY_VALIDATIONRULE.length) == KEY_VALIDATIONRULE) + { + localStorage.removeItem(key); + } + } + }; + + + /** + * Method that purges the validation rules stored in localstorage. + * @param json The json object containing the new validation rules + */ + + this.saveValidationRules = function(json) + { + validationRules = json.validationRules; + for(var i = 0; i < validationRules.length; i++) + { + try + { + localStorage[KEY_VALIDATIONRULE + validationRules[i].id] = JSON.stringify(validationRules[i]); + } + catch ( e ) + { + log( 'Max local storage quota reached, ignored validation rule: ' + validationRules[i].id ); + return false; + } + } + return true; + }; + + /** + * Method that returns all validation rules from localstorage. + * @return An object contining all validation rules. + */ + + this.getValidationRules = function() + { + var validationRules = []; + + var validationRuleIndex = 0; + + for ( var i = 0; i < localStorage.length; i++ ) + { + var key = localStorage.key( i ); + + if ( key.substring( 0, KEY_VALIDATIONRULE.length) == KEY_VALIDATIONRULE) + { + validationRules[validationRuleIndex++] = JSON.parse(localStorage[key]); + } + } + return validationRules; + }; + + + + /** + * Method that purges the constants stored in localstorage. + * + */ + + this.purgeConstants = function() + { + for ( var i = 0; i < localStorage.length; i++ ) + { + var key = localStorage.key( i ); + + if ( key.substring( 0, KEY_CONSTANT.length) == KEY_CONSTANT) + { + localStorage.removeItem(key); + } + } + }; + + + /** + * Method that saves constants in localstorage. + * @param json The json object containing the new constnats + */ + + this.saveConstants = function(json) + { + constants = json.constants; + if(constants != null){ + try + { + localStorage[KEY_CONSTANT] = JSON.stringify(constants); + } + catch ( e ) + { + log( 'Max local storage quota reached, ignored constants: ' + constants); + return false; + } + } + return true; + }; + + /** + * Method that returns all constants localstorage. + * @return An object contining all constants. + */ + + this.getConstants = function() + { + var constants = []; + if (localStorage[KEY_CONSTANT] != null){ + constants = JSON.parse(localStorage[KEY_CONSTANT]); + } + + return constants; + }; + + } // ----------------------------------------------------------------------------- === added file 'dhis-2/dhis-web/dhis-web-dataentry/src/main/webapp/dhis-web-dataentry/javascript/ruleValidation.js' --- dhis-2/dhis-web/dhis-web-dataentry/src/main/webapp/dhis-web-dataentry/javascript/ruleValidation.js 1970-01-01 00:00:00 +0000 +++ dhis-2/dhis-web/dhis-web-dataentry/src/main/webapp/dhis-web-dataentry/javascript/ruleValidation.js 2013-09-24 08:26:24 +0000 @@ -0,0 +1,1474 @@ + +/* + Based on ndef.parser, by Raphael Graf(r@undefined.ch) + http://www.undefined.ch/mparser/index.html + + Ported to JavaScript and modified by Matthew Crumley (email@matthewcrumley.com, http://silentmatt.com/) + + Modified by Stefan Börjesson to correspond to the functionality in the JAVA JEP library's standard function set. + +*/ + +// Added by stlsmiths 6/13/2011 +// re-define Array.indexOf, because IE doesn't know it ... +// +// from http://stellapower.net/content/javascript-support-and-arrayindexof-ie + + +/*if (!Array.indexOf) { + Array.prototype.indexOf = function (obj, start) { + for (var i = (start || 0); i < this.length; i++) { + if (this[i] === obj) { + return i; + } + } + return -1; + } +}*/ + + +Number.prototype.format = function() { + return this.toFixed(1).replace(/(\d)(?=(\d{3})+\.)/g, "$1,"); +}; + + +var Parser = (function (scope) +{ + function object(o) + { + function F() {} + F.prototype = o; + return new F(); + } + + var TNUMBER = 0; + var TOP1 = 1; + var TOP2 = 2; + var TVAR = 3; + var TFUNCALL = 4; + + function Token(type_, index_, prio_, number_) + { + this.type_ = type_; + this.index_ = index_ || 0; + this.prio_ = prio_ || 0; + this.number_ = (number_ !== undefined && number_ !== null) ? number_ : 0; + this.toString = function () + { + switch (this.type_) + { + case TNUMBER: + return this.number_; + case TOP1: + case TOP2: + case TVAR: + return this.index_; + case TFUNCALL: + return "CALL"; + default: + return "Invalid Token"; + } + }; + } + + function Expression(tokens, ops1, ops2, functions) { + this.tokens = tokens; + this.ops1 = ops1; + this.ops2 = ops2; + this.functions = functions; + } + + // Based on http://www.json.org/json2.js + var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, + escapable = /[\\\'\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, + meta = { // table of character substitutions + '\b': '\\b', + '\t': '\\t', + '\n': '\\n', + '\f': '\\f', + '\r': '\\r', + "'" : "\\'", + '\\': '\\\\' + }; + + function escapeValue(v) + { + if (typeof v === "string") + { + escapable.lastIndex = 0; + return escapable.test(v) ? + "'" + v.replace(escapable, function (a) { + var c = meta[a]; + return typeof c === 'string' ? c : + '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); + }) + "'" : + "'" + v + "'"; + } + return v; + } + + Expression.prototype = { + simplify: function (values) { + values = values || {}; + var nstack = []; + var newexpression = []; + var n1; + var n2; + var f; + var L = this.tokens.length; + var item; + var i = 0; + for (i = 0; i < L; i++) + { + item = this.tokens[i]; + var type_ = item.type_; + if (type_ === TNUMBER) + { + nstack.push(item); + } + else if (type_ === TVAR && (item.index_ in values)) + { + item = new Token(TNUMBER, 0, 0, values[item.index_]); + nstack.push(item); + } + else if (type_ === TOP2 && nstack.length > 1) + { + n2 = nstack.pop(); + n1 = nstack.pop(); + f = this.ops2[item.index_]; + item = new Token(TNUMBER, 0, 0, f(n1.number_, n2.number_)); + nstack.push(item); + } + else if (type_ === TOP1 && nstack.length > 0) + { + n1 = nstack.pop(); + f = this.ops1[item.index_]; + item = new Token(TNUMBER, 0, 0, f(n1.number_)); + nstack.push(item); + } + else + { + while (nstack.length > 0) + { + newexpression.push(nstack.shift()); + } + newexpression.push(item); + } + } + while (nstack.length > 0) + { + newexpression.push(nstack.shift()); + } + + return new Expression(newexpression, object(this.ops1), object(this.ops2), object(this.functions)); + }, + + substitute: function (variable, expr) { + if (!(expr instanceof Expression)) + { + expr = new Parser().parse(String(expr)); + } + var newexpression = []; + var L = this.tokens.length; + var item; + var i = 0; + for (i = 0; i < L; i++) + { + item = this.tokens[i]; + var type_ = item.type_; + if (type_ === TVAR && item.index_ === variable) + { + for (var j = 0; j < expr.tokens.length; j++) + { + var expritem = expr.tokens[j]; + var replitem = new Token(expritem.type_, expritem.index_, expritem.prio_, expritem.number_); + newexpression.push(replitem); + } + } + else + { + newexpression.push(item); + } + } + + var ret = new Expression(newexpression, object(this.ops1), object(this.ops2), object(this.functions)); + return ret; + }, + + evaluate: function (values) { + values = values || {}; + var nstack = []; + var n1; + var n2; + var f; + var L = this.tokens.length; + var item; + var i = 0; + for (i = 0; i < L; i++) + { + item = this.tokens[i]; + var type_ = item.type_; + if (type_ === TNUMBER) + { + nstack.push(item.number_); + } + else if (type_ === TOP2) + { + n2 = nstack.pop(); + n1 = nstack.pop(); + f = this.ops2[item.index_]; + nstack.push(f(n1, n2)); + } + else if (type_ === TVAR) + { + if (item.index_ in values) + { + nstack.push(values[item.index_]); + } + else if (item.index_ in this.functions) + { + nstack.push(this.functions[item.index_]); + } + else + { + throw new Error("undefined variable: " + item.index_); + } + } + else if (type_ === TOP1) + { + n1 = nstack.pop(); + f = this.ops1[item.index_]; + nstack.push(f(n1)); + } + else if (type_ === TFUNCALL) + { + n1 = nstack.pop(); + f = nstack.pop(); + if (f.apply && f.call) + { + if (Object.prototype.toString.call(n1) == "[object Array]") + { + nstack.push(f.apply(undefined, n1)); + } + else + { + nstack.push(f.call(undefined, n1)); + } + } + else + { + throw new Error(f + " is not a function"); + } + } + else + { + throw new Error("invalid Expression"); + } + } + if (nstack.length > 1) + { + throw new Error("invalid Expression (parity)"); + } + return nstack[0]; + }, + + toString: function (toJS) { + var nstack = []; + var n1; + var n2; + var f; + var L = this.tokens.length; + var item; + var i = 0; + for (i = 0; i < L; i++) + { + item = this.tokens[i]; + var type_ = item.type_; + if (type_ === TNUMBER) + { + nstack.push(escapeValue(item.number_)); + } + else if (type_ === TOP2) + { + n2 = nstack.pop(); + n1 = nstack.pop(); + f = item.index_; + if (toJS && f == "^") + { + nstack.push("Math.pow(" + n1 + "," + n2 + ")"); + } + else { + nstack.push("(" + n1 + f + n2 + ")"); + } + } + else if (type_ === TVAR) + { + nstack.push(item.index_); + } + else if (type_ === TOP1) + { + n1 = nstack.pop(); + f = item.index_; + if (f === "-") + { + nstack.push("(" + f + n1 + ")"); + } + else + { + nstack.push(f + "(" + n1 + ")"); + } + } + else if (type_ === TFUNCALL) + { + n1 = nstack.pop(); + f = nstack.pop(); + nstack.push(f + "(" + n1 + ")"); + } + else + { + throw new Error("invalid Expression"); + } + } + if (nstack.length > 1) + { + throw new Error("invalid Expression (parity)"); + } + return nstack[0]; + }, + + variables: function () { + var L = this.tokens.length; + var vars = []; + for (var i = 0; i < L; i++) + { + var item = this.tokens[i]; + if (item.type_ === TVAR && (vars.indexOf(item.index_) == -1)) { + vars.push(item.index_); + } + } + + return vars; + }, + + toJSFunction: function (param, variables) { + var f = new Function(param, "with(Parser.values) { return " + this.simplify(variables).toString(true) + "; }"); + return f; + } + }; + + function add(a, b) + { + return Number(a) + Number(b); + } + + function sub(a, b) + { + return a - b; + } + + function mul(a, b) + { + return a * b; + } + + function div(a, b) + { + return a / b; + } + + function mod(a, b) + { + return a % b; + } + + function sum(a, b, c) + { + return a+b+c; + } + + function concat(a, b) + { + return "" + a + b; + } + + function neg(a) + { + return -a; + } + + function random() + { + return Math.random(); + } + + function fac(a) //a! + { + a = Math.floor(a); + var b = a; + while (a > 1) + { + b = b * (--a); + } + return b; + } + + function pyt(a, b) + { + return Math.sqrt(a * a + b * b); + } + + function log10(a) + { + return Math.log(a) / Math.LN10; + } + + function sinh(a) + { + var myTerm1 = Math.pow(Math.E, a); + var myTerm2 = Math.pow(Math.E, -a); + + return (myTerm1-myTerm2)/2; + } + + function cosh(a) + { + var myTerm1 = Math.pow(Math.E, a); + var myTerm2 = Math.pow(Math.E, -a); + + return (myTerm1+myTerm2)/2; + } + + function tanh (a) + { + return (Math.exp(a)-Math.exp(-a))/(Math.exp(a)+Math.exp(-a)); + } + + function asinh(a) + { + return Math.log(a + Math.sqrt(a*a + 1)); + } + + function acosh (a) + { + return Math.log(a+Math.sqrt(a*a-1)); + } + + function atanh (a) + { + return 0.5*Math.log((1+a)/(1-a)); + } + + function binom(n, k) + { + var coeff = 1; + for (var i = n-k+1; i <= n; i++) coeff *= i; + for (var i = 1; i <= k; i++) coeff /= i; + return coeff; + } + + function append(a, b) + { + if (Object.prototype.toString.call(a) != "[object Array]") + { + return [a, b]; + } + a = a.slice(); + a.push(b); + return a; + } + + function Parser() + { + this.success = false; + this.errormsg = ""; + this.expression = ""; + + this.pos = 0; + + this.tokennumber = 0; + this.tokenprio = 0; + this.tokenindex = 0; + this.tmpprio = 0; + + this.ops1 = { + "sin": Math.sin, //In JEP + "cos": Math.cos, //In JEP + "tan": Math.tan, //In JEP + "asin": Math.asin, //In JEP + "acos": Math.acos, //In JEP + "atan": Math.atan, //In JEP + "sinh": sinh, //In JEP + "cosh": cosh, //In JEP + "tanh": tanh, //In JEP + "asinh": asinh, //In JEP + "acosh": acosh, //In JEP + "exp": Math.exp, //In JEP + "sqrt": Math.sqrt, //In JEP + "ln": Math.log, //In JEP + "log": log10, //In JEP + "abs": Math.abs, //In JEP + "ceil": Math.ceil, + "floor": Math.floor, + "round": Math.round, + "-": neg + + }; + + this.ops2 = { + "+": add, + "-": sub, + "*": mul, + "/": div, + "%": mod, + "^": Math.pow, + ",": append, + "||": concat + }; + + this.functions = + { + "rand": random, //In JEP + "fac": fac, + "min": Math.min, + "max": Math.max, + "pyt": pyt, + "pow": Math.pow, + "mod": mod, //In JEP + "sum": sum, //In JEP + "binom" : binom, //In JEP + "atan2": Math.atan2 + }; + + this.consts = + { + "E": Math.E, + "PI": Math.PI + }; + } + + Parser.parse = function (expr) + { + return new Parser().parse(expr); + }; + + Parser.evaluate = function (expr, variables) + { + return Parser.parse(expr).evaluate(variables); + }; + + Parser.Expression = Expression; + + Parser.values = { + sin: Math.sin, + cos: Math.cos, + tan: Math.tan, + asin: Math.asin, + acos: Math.acos, + atan: Math.atan, + sqrt: Math.sqrt, + log: Math.log, + abs: Math.abs, + ceil: Math.ceil, + floor: Math.floor, + round: Math.round, + random: random, + fac: fac, + exp: Math.exp, + min: Math.min, + max: Math.max, + pyt: pyt, + pow: Math.pow, + atan2: Math.atan2, + E: Math.E, + PI: Math.PI + }; + + var PRIMARY = 1 << 0; + var OPERATOR = 1 << 1; + var FUNCTION = 1 << 2; + var LPAREN = 1 << 3; + var RPAREN = 1 << 4; + var COMMA = 1 << 5; + var SIGN = 1 << 6; + var CALL = 1 << 7; + var NULLARY_CALL = 1 << 8; + + Parser.prototype = { + parse: function (expr) { + this.errormsg = ""; + this.success = true; + var operstack = []; + var tokenstack = []; + this.tmpprio = 0; + var expected = (PRIMARY | LPAREN | FUNCTION | SIGN); + var noperators = 0; + this.expression = expr; + this.pos = 0; + + while (this.pos < this.expression.length) + { + if (this.isOperator()) + { + if (this.isSign() && (expected & SIGN)) + { + if (this.isNegativeSign()) + { + this.tokenprio = 2; + this.tokenindex = "-"; + noperators++; + this.addfunc(tokenstack, operstack, TOP1); + } + expected = (PRIMARY | LPAREN | FUNCTION | SIGN); + } + else if (this.isComment()) + { + + } + else + { + if ((expected & OPERATOR) === 0) + { + this.error_parsing(this.pos, "unexpected operator"); + } + noperators += 2; + this.addfunc(tokenstack, operstack, TOP2); + expected = (PRIMARY | LPAREN | FUNCTION | SIGN); + } + } + else if (this.isNumber()) + { + if ((expected & PRIMARY) === 0) + { + this.error_parsing(this.pos, "unexpected number"); + } + var token = new Token(TNUMBER, 0, 0, this.tokennumber); + tokenstack.push(token); + + expected = (OPERATOR | RPAREN | COMMA); + } + else if (this.isString()) + { + if ((expected & PRIMARY) === 0) + { + this.error_parsing(this.pos, "unexpected string"); + } + var token = new Token(TNUMBER, 0, 0, this.tokennumber); + tokenstack.push(token); + + expected = (OPERATOR | RPAREN | COMMA); + } + else if (this.isLeftParenth()) + { + if ((expected & LPAREN) === 0) + { + this.error_parsing(this.pos, "unexpected \"(\""); + } + + if (expected & CALL) + { + noperators += 2; + this.tokenprio = -2; + this.tokenindex = -1; + this.addfunc(tokenstack, operstack, TFUNCALL); + } + + expected = (PRIMARY | LPAREN | FUNCTION | SIGN | NULLARY_CALL); + } + else if (this.isRightParenth()) + { + if (expected & NULLARY_CALL) + { + var token = new Token(TNUMBER, 0, 0, []); + tokenstack.push(token); + } + else if ((expected & RPAREN) === 0) + { + this.error_parsing(this.pos, "unexpected \")\""); + } + + expected = (OPERATOR | RPAREN | COMMA | LPAREN | CALL); + } + else if (this.isComma()) + { + if ((expected & COMMA) === 0) + { + this.error_parsing(this.pos, "unexpected \",\""); + } + this.addfunc(tokenstack, operstack, TOP2); + noperators += 2; + expected = (PRIMARY | LPAREN | FUNCTION | SIGN); + } + else if (this.isConst()) + { + if ((expected & PRIMARY) === 0) + { + this.error_parsing(this.pos, "unexpected constant"); + } + var consttoken = new Token(TNUMBER, 0, 0, this.tokennumber); + tokenstack.push(consttoken); + expected = (OPERATOR | RPAREN | COMMA); + } + else if (this.isOp2()) + { + if ((expected & FUNCTION) === 0) + { + this.error_parsing(this.pos, "unexpected function"); + } + this.addfunc(tokenstack, operstack, TOP2); + noperators += 2; + expected = (LPAREN); + } + else if (this.isOp1()) + { + if ((expected & FUNCTION) === 0) + { + this.error_parsing(this.pos, "unexpected function"); + } + this.addfunc(tokenstack, operstack, TOP1); + noperators++; + expected = (LPAREN); + } + else if (this.isVar()) + { + if ((expected & PRIMARY) === 0) + { + this.error_parsing(this.pos, "unexpected variable"); + } + var vartoken = new Token(TVAR, this.tokenindex, 0, 0); + tokenstack.push(vartoken); + + expected = (OPERATOR | RPAREN | COMMA | LPAREN | CALL); + } + else if (this.isWhite()) + { + } + else + { + if (this.errormsg === "") + { + this.error_parsing(this.pos, "unknown character"); + } + else + { + this.error_parsing(this.pos, this.errormsg); + } + } + } + if (this.tmpprio < 0 || this.tmpprio >= 10) + { + this.error_parsing(this.pos, "unmatched \"()\""); + } + while (operstack.length > 0) + { + var tmp = operstack.pop(); + tokenstack.push(tmp); + } + if (noperators + 1 !== tokenstack.length) + { + this.error_parsing(this.pos, "parity"); + } + + return new Expression(tokenstack, object(this.ops1), object(this.ops2), object(this.functions)); + }, + + evaluate: function (expr, variables) + { + return this.parse(expr).evaluate(variables); + }, + + error_parsing: function (column, msg) + { + this.success = false; + this.errormsg = "parse error [column " + (column) + "]: " + msg; + throw new Error(this.errormsg); + }, + + addfunc: function (tokenstack, operstack, type_) { + var operator = new Token(type_, this.tokenindex, this.tokenprio + this.tmpprio, 0); + while (operstack.length > 0) + { + if (operator.prio_ <= operstack[operstack.length - 1].prio_) + { + tokenstack.push(operstack.pop()); + } + else + { + break; + } + } + operstack.push(operator); + }, + + isNumber: function () { + var r = false; + var str = ""; + while (this.pos < this.expression.length) + { + var code = this.expression.charCodeAt(this.pos); + if ((code >= 48 && code <= 57) || code === 46) + { + str += this.expression.charAt(this.pos); + this.pos++; + this.tokennumber = parseFloat(str); + r = true; + } + else + { + break; + } + } + return r; + }, + + unescape: function(v, pos) { + var buffer = []; + var escaping = false; + + for (var i = 0; i < v.length; i++) + { + var c = v.charAt(i); + + if (escaping) + { + switch (c) + { + case "'": + buffer.push("'"); + break; + case '\\': + buffer.push('\\'); + break; + case '/': + buffer.push('/'); + break; + case 'b': + buffer.push('\b'); + break; + case 'f': + buffer.push('\f'); + break; + case 'n': + buffer.push('\n'); + break; + case 'r': + buffer.push('\r'); + break; + case 't': + buffer.push('\t'); + break; + case 'u': + // interpret the following 4 characters as the hex of the unicode code point + var codePoint = parseInt(v.substring(i + 1, i + 5), 16); + buffer.push(String.fromCharCode(codePoint)); + i += 4; + break; + default: + throw this.error_parsing(pos + i, "Illegal escape sequence: '\\" + c + "'"); + } + escaping = false; + } + else + { + if (c == '\\') + { + escaping = true; + } + else + { + buffer.push(c); + } + } + } + + return buffer.join(''); + }, + + isString: function () + { + var r = false; + var str = ""; + var startpos = this.pos; + if (this.pos < this.expression.length && this.expression.charAt(this.pos) == "'") + { + this.pos++; + while (this.pos < this.expression.length) + { + var code = this.expression.charAt(this.pos); + if (code != "'" || str.slice(-1) == "\\") + { + str += this.expression.charAt(this.pos); + this.pos++; + } + else + { + this.pos++; + this.tokennumber = this.unescape(str, startpos); + r = true; + break; + } + } + } + return r; + }, + + isConst: function () { + var str; + for (var i in this.consts) + { + if (true) + { + var L = i.length; + str = this.expression.substr(this.pos, L); + if (i === str) + { + this.tokennumber = this.consts[i]; + this.pos += L; + return true; + } + } + } + return false; + }, + + isOperator: function () { + var code = this.expression.charCodeAt(this.pos); + if (code === 43) // + + { + this.tokenprio = 0; + this.tokenindex = "+"; + } + else if (code === 45) // - + { + this.tokenprio = 0; + this.tokenindex = "-"; + } + else if (code === 124) // | + { + if (this.expression.charCodeAt(this.pos + 1) === 124) + { + this.pos++; + this.tokenprio = 0; + this.tokenindex = "||"; + } + else + { + return false; + } + } + else if (code === 42) // * + { + this.tokenprio = 1; + this.tokenindex = "*"; + } + else if (code === 47) // / + { + this.tokenprio = 2; + this.tokenindex = "/"; + } + else if (code === 37) // % + { + this.tokenprio = 2; + this.tokenindex = "%"; + } + else if (code === 94) // ^ + { + this.tokenprio = 3; + this.tokenindex = "^"; + } + else + { + return false; + } + this.pos++; + return true; + }, + + isSign: function () { + var code = this.expression.charCodeAt(this.pos - 1); + if (code === 45 || code === 43) // - + { + return true; + } + return false; + }, + + isPositiveSign: function () { + var code = this.expression.charCodeAt(this.pos - 1); + if (code === 43) // - + { + return true; + } + return false; + }, + + isNegativeSign: function () { + var code = this.expression.charCodeAt(this.pos - 1); + if (code === 45) // - + { + return true; + } + return false; + }, + + isLeftParenth: function () { + var code = this.expression.charCodeAt(this.pos); + if (code === 40) // ( + { + this.pos++; + this.tmpprio += 10; + return true; + } + return false; + }, + + isRightParenth: function () { + var code = this.expression.charCodeAt(this.pos); + if (code === 41) // ) + { + this.pos++; + this.tmpprio -= 10; + return true; + } + return false; + }, + + isComma: function () { + var code = this.expression.charCodeAt(this.pos); + if (code === 44) // , + { + this.pos++; + this.tokenprio = -1; + this.tokenindex = ","; + return true; + } + return false; + }, + + isWhite: function () { + var code = this.expression.charCodeAt(this.pos); + if (code === 32 || code === 9 || code === 10 || code === 13) + { + this.pos++; + return true; + } + return false; + }, + + isOp1: function () { + var str = ""; + for (var i = this.pos; i < this.expression.length; i++) + { + var c = this.expression.charAt(i); + if (c.toUpperCase() === c.toLowerCase()) + { + if (i === this.pos || (c != '_' && (c < '0' || c > '9'))) { + break; + } + } + str += c; + } + if (str.length > 0 && (str in this.ops1)) + { + this.tokenindex = str; + this.tokenprio = 5; + this.pos += str.length; + return true; + } + return false; + }, + + isOp2: function () { + var str = ""; + for (var i = this.pos; i < this.expression.length; i++) + { + var c = this.expression.charAt(i); + if (c.toUpperCase() === c.toLowerCase()) + { + if (i === this.pos || (c != '_' && (c < '0' || c > '9'))) + { + break; + } + } + str += c; + } + if (str.length > 0 && (str in this.ops2)) + { + this.tokenindex = str; + this.tokenprio = 5; + this.pos += str.length; + return true; + } + return false; + }, + + isVar: function () { + var str = ""; + for (var i = this.pos; i < this.expression.length; i++) + { + var c = this.expression.charAt(i); + if (c.toUpperCase() === c.toLowerCase()) + { + if (i === this.pos || (c != '_' && (c < '0' || c > '9'))) + { + break; + } + } + str += c; + } + if (str.length > 0) + { + this.tokenindex = str; + this.tokenprio = 4; + this.pos += str.length; + return true; + } + return false; + }, + + isComment: function () { + var code = this.expression.charCodeAt(this.pos - 1); + if (code === 47 && this.expression.charCodeAt(this.pos) === 42) + { + this.pos = this.expression.indexOf("*/", this.pos) + 2; + if (this.pos === 1) + { + this.pos = this.expression.length; + } + return true; + } + return false; + } + }; + + scope.Parser = Parser; + return Parser; +})(typeof exports === 'undefined' ? {} : exports); + + +/* + * Object for storing the result of the expression evaluation. + */ + +function ValidationResult() +{ + this.rightSide; + this.leftSide; + this.result; +} + +// +// Method to calculate the value of an expression +// + +function calculateExpression(exprIn) +{ + if(exprIn == null){ + return null; + } + var expr = Parser.parse(exprIn); + + var result = expr.evaluate(); + + return result; +} + +// +// Method to retrieve an array of the applicable validation rules. +// + +function getApplicableRules() +{ + var applicableRules = new Array(); + var allValidationRules = storageManager.getValidationRules(); + for(var i = 0; i < allValidationRules.length; i++) + { + if(isDataFieldsInCurrentForm(allValidationRules[i].leftSide.expression) && isDataFieldsInCurrentForm(allValidationRules[i].rightSide.expression)) + { + applicableRules.push(allValidationRules[i]); + } + } + + return applicableRules; +} + +// +// Method to retrieve the data fields in an Expression +// + +function isDataFieldsInCurrentForm(expr) +{ + var regex1 = /#{\w+\.\w+}/g; + var match1 = expr.match(regex1); + if(match1 != null) + { + for (var i = 0; i 0)) + { + return false; + } + } + } + return true; +} + +// +// Method to substitute the days value in an expression +// + + +function substituteDaysValues(expr, days) +{ + var regex1 = /\[days\]/g; + var outExpr = expr; + if(days != null) + { + outExpr = outExpr.replace(regex1, days); + } + else + { + outExpr = outExpr.replace(regex1, "0"); + } + return outExpr; +} + +// +// Method to substitute the data value token for its value. +// + +function substituteDataValues(expr, nullIfBlank) +{ + var regex1 = /#{\w+\.\w+}/g; + + var outExpr = expr; + + var match1 = expr.match(regex1); + if(match1 != null) + { + for (var i = 0; i': + result.result = (leftSideValue > rightSideValue); + return result; + case '>=': + result.result = (leftSideValue >= rightSideValue); + return result; + case '<': + result.result = (leftSideValue < rightSideValue); + return result; + case '<=': + result.result = (leftSideValue <= rightSideValue); + return result; + case '[Compulsory pair]': + result.result = (leftSide == null && rightSide == null) || (leftSide != null && rightSide != null); + return result; + default: + log("Validation rule has an invalid operator: " + operator); + throw "Invalid operator in expression"; + } + } + else + { + result.result = true; + return result; + } + } + else + { + result.result = true; + return result; + } +} + +// +// Method to run the validation rules offline from localstorage, based on the filled in information. +// + +function runOfflineValidations() +{ + var violations = new Array(); + var validationRules = getApplicableRules(); + for(var i = 0; i < validationRules.length; i++) + { + var leftSide = validationRules[i].leftSide.expression; + var rightSide = validationRules[i].rightSide.expression; + + leftSide = substituteValues(leftSide, validationRules[i].leftSide.isNullIfBlank); + rightSide = substituteValues(rightSide, validationRules[i].rightSide.isNullIfBlank); + + var result = validateExpression(leftSide, validationRules[i].operator, rightSide); + + if(!result.result) + { + validationRules[i].validationResult = result; + violations.push(validationRules[i]); + } + } + + var response = ''; + + if(violations.length == 0) + { + response = ''; + + } + else + { + + var orgUnit = $('#selectedOrganisationUnit').val(); + //Building error response + response += '

' + i18n_validation_result + ' 

'; + response += '

' + i18n_data_entry_screen_has_following_errors + '

'; + if(violations.length > 0) + { + //Table header + response += '

' + orgUnit + '

'; + response += ''; + response += ''; + response += ''; + response += ''; + response += ''; + response += ''; + response += ''; + response += ''; + + var listRow = 'class="listRow"'; + var listAltRow = 'class="listAlternateRow"'; + var rowClass = listRow; + + for(var i = 0; i < violations.length; i++) + { + response += ''; + response += ''; + //response += ''; + response += ''; + response += ''; + response += ''; + response += ''; + response += ''; + + if(rowClass == listRow) + { + rowClass = listAltRow; + } + else + { + rowClass = listRow; + } + } + + response += '
' + i18n_validation_rule + '' + i18n_expression + '' + i18n_left_side + '' + i18n_operator + '' + i18n_right_side + '
' + violations[i].description + '' + $('
').text(violations[i].rightSide.description).html() + ' ' + $('
').text(violations[i].operator).html() + ' ' + $('
').text(violations[i].leftSide.description).html() + '
' + violations[i].rightSide.description + ' ' + violations[i].operator + ' ' + violations[i].leftSide.description + '' + (violations[i].validationResult.leftSide == null? '0':violations[i].validationResult.leftSide.format())+ '' + violations[i].operator + '' + (violations[i].validationResult.rightSide == null? '0':violations[i].validationResult.rightSide.format() ) + '

'; + } + } + + return response; + +} + === modified file 'dhis-2/dhis-web/dhis-web-dataentry/src/main/webapp/dhis-web-dataentry/select.vm' --- dhis-2/dhis-web/dhis-web-dataentry/src/main/webapp/dhis-web-dataentry/select.vm 2013-07-18 10:10:22 +0000 +++ dhis-2/dhis-web/dhis-web-dataentry/src/main/webapp/dhis-web-dataentry/select.vm 2013-09-24 08:26:24 +0000 @@ -45,6 +45,12 @@ var i18n_childrens_forms = '$encoder.jsEscape( $i18n.getString( "childrens_forms" ) , "'")'; var i18n_no_periods_click_prev_year_button = '$encoder.jsEscape( $i18n.getString( "no_periods_click_prev_year_button" ) , "'")'; var i18n_view_comment = '$encoder.jsEscape( $i18n.getString( "view_comment" ) , "'")'; +var i18n_data_entry_screen_has_following_errors = '$encoder.jsEscape( $i18n.getString( "data_entry_screen_has_following_errors" ) , "'")'; +var i18n_validation_rule = '$encoder.jsEscape( $i18n.getString( "validation_rule" ) , "'")'; +var i18n_expression = '$encoder.jsEscape( $i18n.getString( "expression" ) , "'")'; +var i18n_left_side = '$encoder.jsEscape( $i18n.getString( "left_side" ) , "'")'; +var i18n_operator = '$encoder.jsEscape( $i18n.getString( "operator" ) , "'")'; +var i18n_right_side = '$encoder.jsEscape( $i18n.getString( "right_side" ) , "'")';