1 /** 2 * @class BaseCalc 3 * @constructor 4 * @description Base Calculator class handles common configuration options, provides utility methods, an interface for extending, 5 * and runs calculator's initialize method on page load if it exists. 6 * @param {Object} config Configuration object with the following options: 7 * <ul> 8 * <li>total.id : (Optional) Element ID to place end result of column calculation</li> 9 * <li>total.operator : (Optional) ( +, -, *, x, / ) Mathematical operator to apply against each row to produce end result. Defaults to '+'</li> 10 * <li>calcOnLoad : (Optional) Defaults to false. If true, on page load the calculate method is fired</li> 11 * <li>registerListeners : (Optional) Defaults to false. If true, event listeners are attached to inputs that fire the calculate method</li> 12 * </ul> 13 */ 14 slikcalc.BaseCalc = function(config) { 15 config.total = config.total || {}; 16 this.totalId = config.total.id || null; 17 this.totalOperator = config.total.operator || '+'; 18 this.calcOnLoad = config.calcOnLoad || false; 19 this.calculationComplete = slikcalc.createCustomEvent('calculationComplete'); 20 this.registerListeners = config.registerListeners || false; 21 slikcalc.addOnLoad(this.baseInitialize, this); 22 }; 23 24 slikcalc.BaseCalc.prototype = { 25 26 /** 27 * @description Reference to slikcalc custom event to fire when the calculation has been 28 * completed for this calculator 29 */ 30 calculationComplete : null, 31 32 /** 33 * @description Internal value of the time when the last keyup event fired used to make sure and 34 * fire the calculate event only after there is a delay in typing for performance reasons 35 */ 36 lastKeyUp : null, 37 38 /** 39 * @description Internal value tracking the number of calculations triggered used to prevent callbacks from firing out of synch 40 */ 41 calculations : 0, 42 43 /** 44 * @description Element ID for the total value of the calculator 45 */ 46 totalId : null, 47 48 /** 49 * @description Mathematical operator to apply against each row to produce end result ( +, -, *, x, / ) 50 */ 51 totalOperator : null, 52 53 /** 54 * @description Boolean value to signal if the calculator should fire initially when the page loads 55 */ 56 calcOnLoad : false, 57 58 /** 59 * @description Boolean value indicating if event listeners should be placed on relevant elements to tracking key events and clicking 60 */ 61 registerListeners : false, 62 63 /** 64 * @description Configuration value for the pause in keyup events to wait for before calculating. 65 * Setting this to zero will cause calculations to perform on each keyup event, which could become costly with many calculators 66 * chained together 67 */ 68 keyupDelay: 600, 69 70 /** 71 * @description Internal boolean to track if the calculator has been initialized yet 72 */ 73 initialized : false, 74 75 /** 76 * @description Base initialize method to handle calling subclass initialize(), and sets initialized property. 77 * Also calls processCalculation() if calcOnLoad is true 78 */ 79 baseInitialize : function() { 80 if(this.initialized === false) { 81 this.initialized = true; 82 if(this.initialize !== undefined && typeof this.initialize === 'function') { 83 this.initialize(); 84 } 85 if(this.calcOnLoad === true) { 86 this.processCalculation(); 87 } 88 } 89 }, 90 91 /** 92 * @description Sets up event chaining for BaseCalc objects. The object passed in is returned to allow for a fluent interface 93 * this.calculate will be called after dependCalc.calculate 94 * @param {BaseCalc} dependCalc BaseCalc object to attach event to 95 */ 96 dependsOn : function(dependCalc) { 97 slikcalc.bindEvent(dependCalc.calculationComplete, this.processCalculation, this); 98 return dependCalc; 99 }, 100 101 /** 102 * @description Sets up event chaining for BaseCalc objects. The object passed in is returned to allow for a fluent interface 103 * this.calculate will be called before triggeredCalc.calculate 104 * @param {BaseCalc} triggeredCalc BaseCalc object to attach event to 105 */ 106 triggers : function(triggeredCalc) { 107 slikcalc.bindEvent(this.calculationComplete, triggeredCalc.processCalculation, triggeredCalc); 108 return triggeredCalc; 109 }, 110 111 /** 112 * @description Wrapper method to trigger processCalculation when there is a pause in users key events 113 */ 114 keyupEvent : function() { 115 this.lastKeyup = new Date().getTime(); 116 this.calculations = this.calculations + 1; 117 var that = this, calculation = this.calculations; 118 setTimeout(function() { 119 var currentTime = new Date().getTime(), difference = currentTime - that.lastKeyup; 120 if(calculation == that.calculations && difference > that.keyupDelay) { 121 that.processCalculation(); 122 } 123 }, (this.keyupDelay+100)); 124 }, 125 126 /** 127 * @description Calculates the total amount, dependant upon the totalOperator value. 128 * Seperated into conditional statements for better performance than using 'eval()', and to handle operator unique functionality 129 * @param {Float} total Initial value of total. 130 * @param {Float} amount New amount to calculate in conjunction with total. 131 */ 132 calculateTotal : function(total, amount) { 133 if(this.totalOperator === '+') { 134 total = total === null ? 0.00 : total; 135 total = total + amount; 136 }else if(this.totalOperator === '-') { 137 if(total === null) { 138 total = amount; 139 }else { 140 total = total - amount; 141 } 142 }else if(this.totalOperator === '*' || this.totalOperator === 'x') { 143 total = total === null ? 1 : total; 144 total = total * amount; 145 }else if(this.totalOperator === '/') { 146 if(total === null) { 147 total = amount; 148 }else { 149 total = total / amount; 150 } 151 } 152 return total; 153 }, 154 155 /** 156 * @description Wrapper method for concrete class' `calculate` method 157 */ 158 processCalculation: function() { 159 if(this.initialized === false) { 160 this.baseInitialize(); 161 } 162 this.calculate(); 163 slikcalc.fireEvent(this.calculationComplete); 164 }, 165 166 /** 167 * @description Abstract method to be implemented in sub-classes. 168 */ 169 calculate : function() { 170 throw new Error('Must implement calculate method in sub-class of BaseCalc'); 171 } 172 };