Calculator Sample Overview
The Calculator sample application demonstrates how you can create a calculator with basic mathematical operations.
For information on creating the sample application project in the IDE, see Creating Sample Applications.
The following figure illustrates the main screen of the Calculator.
Figure: Calculator screen
The application opens with the Calculator screen, where you can perform mathematical operations by clicking the applicable buttons.
Source Files
You can create and view the sample application project including the source files in the IDE.
File name | Description |
---|---|
config.xml | This file contains the application information for the platform to install and launch the application, including the view mode and the icon to be used in the device menu. |
css/style.css | This file contains the CSS styling for the application UI. |
img/ | This directory contains the images used to create the user interface. |
index.html | This is a starting file from which the application starts loading. It contains the layout of the application screens. |
js/app.js | This file contains the code for the main application module used for initialization. |
js/model.js | This file contains the application model (handles mathematical operations). |
js/systeminfo.js | This file contains the battery state handling code. |
js/ui.js | This file contains the implementation code for the user interface. |
Implementation
All JavaScript files are loaded directly from the index.html file. There is also initialization started by calling the init() method of the app module, which acts as an application controller.
<!--index.html--> <script src="js/systeminfo.js"></script> <script src="js/app.js"></script> <script src="js/ui.js"></script> <script src="js/model.js"></script> <script> app.init(); </script>
The app module initializes all other modules:
- systeminfo is responsible for checking the battery level
- model encapsulates the calculator logic (equation state and mathematical operations)
- ui is responsible for managing the UI (updating and listening to events)
Finally, the app module requests the ui module to update the equation field with a value obtained from the model module. Now, the application is ready for use.
/* js/app.js */ init: function init() { 'use strict'; systeminfo.init(); model.init(); ui.init(); this.refreshEquation(); }, refreshEquation: function refreshEquation() { 'use strict'; ui.showEquation(model.equation); }
The application layout is defined directly in the index.html file. It is styled by the CSS file and managed by the ui module. The ui module listens to the touch events on the calculator buttons, updates their press state, and passes the control to the app module to run the proper action. It also exposes some methods which allow the controller module (app) to update the equation and its result. Formatting the fields is also its responsibility.
<!--index.html--> <table id="screen"><tr> <td id="display" valign="middle"> <div id="overflow_top"></div> <div id="overflow_bottom"></div> <div id="equation"></div> <div id="result" class="empty"><span id="resultvalue"></span></div> </td> </tr></table> <div id="numpad"> <div id="key_7" class="key" style="clear: both;"></div> <div id="key_8" class="key"></div> <div id="key_9" class="key"></div> <div id="key_c" class="key"></div> <div id="key_del" class="key long-tap-repeat"></div> <div id="key_4" class="key" style="clear: both;"></div> <div id="key_5" class="key"></div> <div id="key_6" class="key"></div> <div id="key_div" class="key"></div> <div id="key_mul" class="key"></div> <div id="key_1" class="key" style="clear: both;"></div> <div id="key_2" class="key"></div> <div id="key_3" class="key"></div> <div id="key_sub" class="key"></div> <div id="key_add" class="key"></div> <div id="key_0" class="key" style="clear: both;"></div> <div id="key_dec" class="key"></div> <div id="key_sign" class="key"></div> <div id="key_eql" class="longkey"></div> </div>
/* js/ui.js */ showEquation: function showEquation(equation) { 'use strict'; var e, element, elementText, span, equationElement, length; equationElement = document.getElementById('equation'); equationElement.innerHTML = ''; length = equation.length; for (e = 0; e < length; e += 1) { element = equation[e]; span = document.createElement('span'); elementText = element; if (Object.keys(this.operatorDisplays).indexOf(element) !== -1) { span.className = 'operator'; elementText = this.operatorDisplays[element]; } else { elementText = app.addSeparators(elementText); } elementText = elementText.replace(/-/g, '−'); span.innerHTML = elementText; equationElement.appendChild(span); } }, /* Shows string in result element */ /* @param {string} result */ /* @private */ show: function show(result) { 'use strict'; if (result === '') { return this.clear(); } this.equationElement.classList.add('top'); this.resultValueElement.innerHTML = result.replace(/-/g, '−'); } /* Shows result in result element */ /* @param {string} result */ showResult: function showResult(result) { 'use strict'; this.show(result); this.result = true; }
The model module is responsible for the calculator logic. Its internal implementation of the equation is based on an array which stores each component (number, operator) as a separate string. The module exposes a set of functions to modify the equation and finally to compute its value. It also keeps the equation state valid by refusing to add the wrong component.
The model module's public interface consists of following functions:
- addDigit() adds a single digit to the equation
- addOperator() adds an operator (+, -, *, /) to the equation
- addDecimal() adds a decimal point to the equation
- deleteLast() deletes the last digit or operator
- resetEquation() clears the whole equation
- changeSign() changes the sign of the last equation component
- isEmpty() returns true if the equation is empty (does not contain any component)
- calculate() calculates the equation value
When the user touches any button (in this case, the "equal" button flow is presented), the ui module notifies the controller module (app) by calling its processKey() method. The request is dispatched and finally the calculate() method of the model module is called. When the result is obtained, the controller module (app) requests the ui module to update the adequate field in the UI by calling the showResult() method.
/* js/app.js */ processKey: function processKey(key) { 'use strict'; var keys = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']; if (ui.isResultVisible()) { if (Object.keys(this.operatorKeys).indexOf(key) === -1 && key !== 'del' && key !== 'eql' && key !== 'sign') { model.resetEquation(); } } ui.clearResult(); if (keys.indexOf(key) !== -1) { this.pushDigits(key); } else if (Object.keys(this.operatorKeys).indexOf(key) !== -1) { model.addOperator(this.operatorKeys[key]); } else if (key === 'dec') { model.addDecimal(); } else if (key === 'del') { model.deleteLast(); } else if (key === 'c') { model.resetEquation(); } else if (key === 'sign') { model.changeSign(); } if (key === 'eql' && !model.isEmpty()) { this.calculate(); } this.refreshEquation(); }, calculate: function calculate() { 'use strict'; var result = ''; try { result = model.calculate(); result = this.addSeparators(result); ui.showResult('= ' + result); } catch (e) { if (e instanceof EquationInvalidFormatError) { ui.showResult('Wrong format'); } else if (e instanceof CalculationError) { ui.showResult('Invalid operation'); } else if (e instanceof InfinityError) { ui.showResult((e.positive ? '' : '−') + '∞'); } else { ui.showError('Unknown error.'); console.warn(e); } } }
The calculate() method of the model module does some checks to ensure the equation correctness and finally mergers all its components into one string and runs it as a JavaScript expression through the eval() method to obtain its value. If no error occurs, the equation result is returned.
/* js/model.js */ /* Calculates equation value */ /* @return {string} */ calculate: function calculate() { 'use strict'; var evaluation = '', result, /* Checks whether the matched number is zero */ /* @param {string} m Whole match including the division operator */ /* @param {string} p1 Whole number, including sign and parenthesis */ /* @param {string} number The matched number */ /* @return {string} */ checkDivisionByZero = function checkDivisionByZero(m, p1, number) { if (parseFloat(number) === 0) { throw new DivisionByZeroError(); } return '/ ' + number; }; if (this.calculated) { this.replaceLeftOperand(this.lastCalculationResult); } if (!this.isValidEquation()) { throw new EquationInvalidFormatError(); } this.calculated = false; /* Evaluate the equation */ try { evaluation = this.equation.join(' '); evaluation = evaluation.replace(/\/ *(\(?\-?([0-9\.]+)\)?)/g, checkDivisionByZero); result = eval('(' + evaluation + ')'); if (Math.abs(result) < 1.0E-300) { result = 0; } } catch (e) { console.error(e); throw new CalculationError(); } if (isNaN(result)) { throw new CalculationError(); } if (result === Infinity || result === -Infinity) { throw new InfinityError(result === Infinity); } this.calculated = true; /* Format the result value */ result = this.formatValue(result); /* Save the calculated result */ this.lastCalculationResult = result; return result; }