1 /**
  2  * @class FormulaCalc
  3  * @description Calculator object that calculates based on a given formula and mapped variables.  Calculates across multiple rows as well,
  4  * updating a single totals value based on a given mathematical operator.
  5  * @constructor
  6  * @param {Object} config Configuration object, all options of slikcalc.BaseCalc plus the following:
  7  * <ul>
  8  * 	<li>formula : Mathematical formula in string form.  Variables denoted within the '{}' that map to vars definitions passed in on the addRow method.  Example: "{a} + {b} = {c}".  "{a} + {b}" is used as the formula, and {c} becomes the position that the eval'd result is placed into.</li>
  9  *  <li>total.vars : Variables configuration object, see slikcalc.FormulaCalc.addRow() for details.  If passed in here, an initial row is added</li>
 10  * </ul>
 11  */
 12 slikcalc.FormulaCalc = function(config) {
 13 	this.parent.constructor.call(this, config);
 14 	config = config || {};
 15 	this.formula = config.formula || '';
 16 	this.rows = [];
 17 	this.variables = [];
 18 	if(config.vars !== undefined) {
 19 		this.addRow({ vars : config.vars });
 20 	}
 21 };
 22 slikcalc.extend(slikcalc.FormulaCalc, slikcalc.BaseCalc);
 23 
 24 /**
 25  * @description Formula used to calculate the total for a row
 26  */
 27 slikcalc.FormulaCalc.prototype.formula = null;
 28 
 29 /**
 30  * @description Holds the formula value once it has been parsed, and the equality portion has been removed
 31  */
 32 slikcalc.FormulaCalc.prototype.formulaParsed = null;
 33 
 34 /**
 35  * @description Property to hold the variable from the formula that specifies where the result goes
 36  */
 37 slikcalc.FormulaCalc.prototype.resultVar = null;
 38 
 39 /**
 40  * @description Regular Expression to find variables in the formula
 41  */
 42 slikcalc.FormulaCalc.prototype.varMatch = /\{(\w)\}/gi;
 43 
 44 /**
 45  * @description {Array} Array of row objects for the calculator
 46  */
 47 slikcalc.FormulaCalc.prototype.rows = null;
 48 
 49 /**
 50  * @description {Array} Array of the variables parsed from the formula
 51  */
 52 slikcalc.FormulaCalc.prototype.variables = null;
 53 
 54 /**
 55  * @description Method run on page load to parse the formula, and pull out variables within it.  
 56  */
 57 slikcalc.FormulaCalc.prototype.initialize = function() {
 58 	this.formulaParsed = this.formula;
 59 	if(this.formulaParsed.indexOf('=') !== -1) {
 60 		var formulaSplit = this.formulaParsed.split('=');
 61 		this.formulaParsed = formulaSplit[0];
 62 		this.resultVar = this.varMatch.exec(slikcalc.trim(formulaSplit[1]))[1];
 63 	}
 64 	this.varMatch.lastIndex = 0;
 65 	while((result = this.varMatch.exec(this.formulaParsed)) !== null) {
 66 		this.variables.push(result[1]);
 67 	}
 68 	this.varMatch.lastIndex = 0;
 69 };
 70 
 71 /**
 72  * @description Adds a row to the calculator to be included in the calculations
 73  * @param {Object} rowConfig Configuration object for the row being added to the calculator.  It may contain the following options:
 74  * <ul>
 75  *  <li>vars : Object containing one to many variable definitions</li>
 76  *  <li>vars.x : Configuration object for variable 'x' where x represents a variable in the formula</li>
 77  *  <li>vars.x.id : Element id for the input mappted to variable 'x' in formula</li>
 78  *  <li>vars.x.defaultValue : (Optional) Value used in place of empty/null for variable 'x'.  Defaults to 0</li>
 79  *  <li>checkbox.id : Element id of checkbox. Required if config[checkbox] included</li>
 80  *  <li>checkbox.invert : Defaults to false. If true, row is included in total calculcation when un-checked, and omitted when checked</li>
 81  * </ul>
 82  */
 83 slikcalc.FormulaCalc.prototype.addRow = function(rowConfig) {
 84 	rowConfig = rowConfig || {};
 85 	if(rowConfig.checkbox !== undefined) {
 86 		slikcalc.addListener(rowConfig.checkbox.id, 'click', this.processCalculation, this);
 87 		rowConfig.checkbox.invert = rowConfig.checkbox.invert || false;
 88 	}
 89 	for(var idx in rowConfig.vars) {
 90         if(rowConfig.vars.hasOwnProperty(idx)) {
 91             var variable = rowConfig.vars[idx];
 92             variable.defaultValue = variable.defaultValue || 0;
 93             rowConfig.registerListeners = rowConfig.registerListeners === true || (this.registerListeners === true && rowConfig.registerListeners !== false);
 94             if(rowConfig.registerListeners === true) {
 95                 slikcalc.addListener(variable.id, 'keyup', this.keyupEvent, this);
 96             }
 97         }
 98 	}
 99 	this.rows.push(rowConfig);
100 };
101 
102 /**
103  * @description Processes the rows and applies the formula to each one.
104  */
105 slikcalc.FormulaCalc.prototype.calculate = function() {
106 	var total = 0.00;
107 	for(var idx in this.rows) {
108         if(this.rows.hasOwnProperty(idx)) {
109             var includeRow = true, rowTotal, formulaString = this.formulaParsed;
110             if(this.rows[idx].checkbox !== undefined) {
111                 var checkbox = this.rows[idx].checkbox;
112 				includeRow = (checkbox.invert !== slikcalc.get(checkbox.id).checked);
113             }
114             for(var varIdx in this.variables) {
115                 if(this.variables.hasOwnProperty(varIdx)) {
116                     var variableName = this.variables[varIdx];
117                     var variable = this.rows[idx].vars[variableName];
118                     var value = variable.defaultValue;
119                     if(slikcalc.get(variable.id) !== null) {
120                         value = slikcalc.getValue(variable.id);
121                         value = value === '' ? variable.defaultValue : value;
122                         value = slikcalc.formatCurrency(value);
123                     }
124                     var variableRegex = new RegExp("\\{" + variableName + "\\}");
125                     formulaString = formulaString.replace(variableRegex, value);
126                 }
127             }
128             rowTotal = slikcalc.formatCurrency(eval(formulaString));
129             if(this.resultVar !== null) {
130                 var resultId = this.rows[idx].vars[this.resultVar].id;
131                 slikcalc.setAmount(resultId, rowTotal);
132             }
133             if(includeRow === true && this.totalOperator !== null) {
134 				total = this.calculateTotal(total, parseFloat(rowTotal));
135             }
136         }
137 	}
138 	if(this.totalId !== null) {
139 		slikcalc.setAmount(this.totalId, total);
140 	}
141 };