IBookAuthor: Javascript Calculator
--D. Thiebaut 17:44, 27 January 2013 (EST)
Contents
The Javascript Calculator
You should first read the article by Simon Southwell[1].
Southwell's article is a step by step description of the features of this simulator, showing how to "cut" the photo of a calculator into several areas corresponding to the keys and to the digits, and how to simulate the behavior of a real calculator when the user presses the keys on the screen. On a computer without a touch-screen display the user must use the mouse to click the keys. On an iPad or tablet, the user simply presses the keys on the screen. It's very cool when you install it and it first works!
The javascript code provided by Southwell is available here.
Creating the Widget
Creating the widget simply requires copying the skeleton widget presented in this collection of tutorials, adding a new directory in it called images to hold all the pngs files representing the various parts of the calculator (keys, display digits), and updating the Info.plist file with the name of the package (although not really necessary).
File Hierarchy
calculator.wdgt
calculator.wdgt/calc.html
calculator.wdgt/Default.png
calculator.wdgt/Default@2x.png
calculator.wdgt/images
calculator.wdgt/images/display.jpg
calculator.wdgt/images/display_left.jpg
calculator.wdgt/images/display_right.jpg
calculator.wdgt/images/index.html
calculator.wdgt/images/keys.jpg
calculator.wdgt/images/lcd0.jpg
calculator.wdgt/images/lcd0dot.jpg
calculator.wdgt/images/lcd1.jpg
calculator.wdgt/images/lcd1dot.jpg
calculator.wdgt/images/lcd2.jpg
calculator.wdgt/images/lcd2dot.jpg
calculator.wdgt/images/lcd3.jpg
calculator.wdgt/images/lcd3dot.jpg
calculator.wdgt/images/lcd4.jpg
calculator.wdgt/images/lcd4dot.jpg
calculator.wdgt/images/lcd5.jpg
calculator.wdgt/images/lcd5dot.jpg
calculator.wdgt/images/lcd6.jpg
calculator.wdgt/images/lcd6dot.jpg
calculator.wdgt/images/lcd7.jpg
calculator.wdgt/images/lcd7dot.jpg
calculator.wdgt/images/lcd8.jpg
calculator.wdgt/images/lcd8dot.jpg
calculator.wdgt/images/lcd9.jpg
calculator.wdgt/images/lcd9dot.jpg
calculator.wdgt/images/lcde.jpg
calculator.wdgt/images/lcdminus.jpg
calculator.wdgt/images/lcdoff.jpg
calculator.wdgt/images/top.jpg
calculator.wdgt/Info.plist
Files
Info.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleDisplayName</key>
<string>skeleton2</string>
<key>CFBundleIdentifier</key>
<string>com.thiebaut.widget.Untitled</string>
<key>CFBundleName</key>
<string>skeleton2</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>CloseBoxInsetX</key>
<integer>15</integer>
<key>CloseBoxInsetY</key>
<integer>15</integer>
<key>Height</key>
<integer>380</integer>
<key>MainHTML</key>
<string>calc.html</string>
<key>Width</key>
<integer>280</integer>
</dict>
</plist>
calc.html
<html>
<head>
<title>Calculator Example</title>
</head>
<body topmargin=0 leftmargin=0 marginheight=0 marginwidth=0 onkeypress="javascript:keyboard(event)">
<table align=center border=0 cellpadding=0 cellspacing=0>
<tr> <td colspan=10><img src="images/top.jpg"></td> </tr>
<tr>
<td><img name="dl" src="images/display_left.jpg"></td>
<td><img name="d7" src="images/lcdoff.jpg"></td>
<td><img name="d6" src="images/lcdoff.jpg"></td>
<td><img name="d5" src="images/lcdoff.jpg"></td>
<td><img name="d4" src="images/lcdoff.jpg"></td>
<td><img name="d3" src="images/lcdoff.jpg"></td>
<td><img name="d2" src="images/lcdoff.jpg"></td>
<td><img name="d1" src="images/lcdoff.jpg"></td>
<td><img name="d0" src="images/lcd0dot.jpg"></td>
<td><img name="dr" src="images/display_right.jpg"></td>
</tr>
<tr><td colspan=10><img src="images/keys.jpg" border=0 usemap="#keymap"></td></tr>
</table>
<map name="keymap">
<area shape=rect coords=" 16, 39, 46, 68" href="javascript:key_pressed('o')" title="o"></area>
<area shape=rect coords=" 55, 39, 85, 68" href="javascript:key_pressed('c')" title="c"></area>
<area shape=rect coords=" 94, 39,124, 68" href="javascript:key_pressed('r')" title="r"></area>
<area shape=rect coords="133, 39,163, 68" href="javascript:key_pressed('m')" title="m"></area>
<area shape=rect coords="169, 39,201, 68" href="javascript:key_pressed('M')" title="M"></area>
<area shape=rect coords=" 16, 74, 46,100" href="javascript:key_pressed('7')" title="7"></area>
<area shape=rect coords=" 55, 74, 85,100" href="javascript:key_pressed('8')" title="8"></area>
<area shape=rect coords=" 94, 74,124,100" href="javascript:key_pressed('9')" title="9"></area>
<area shape=rect coords="133, 74,163,100" href="javascript:key_pressed('%')" title="%"></area>
<area shape=rect coords="169, 74,201,100" href="javascript:key_pressed('s')" title="s"></area>
<area shape=rect coords=" 16,112, 46,138" href="javascript:key_pressed('4')" title="4"></area>
<area shape=rect coords=" 55,112, 85,138" href="javascript:key_pressed('5')" title="5"></area>
<area shape=rect coords=" 94,112,124,138" href="javascript:key_pressed('6')" title="6"></area>
<area shape=rect coords="133,112,163,138" href="javascript:key_pressed('*')" title="*"></area>
<area shape=rect coords="169,112,201,138" href="javascript:key_pressed('/')" title="/"></area>
<area shape=rect coords=" 16,145, 46,171" href="javascript:key_pressed('1')" title="1"></area>
<area shape=rect coords=" 55,145, 85,171" href="javascript:key_pressed('2')" title="2"></area>
<area shape=rect coords=" 94,145,124,171" href="javascript:key_pressed('3')" title="3"></area>
<area shape=rect coords="169,145,201,171" href="javascript:key_pressed('-')" title="-"></area>
<area shape=rect coords=" 16,181, 46,207" href="javascript:key_pressed('0')" title="0"></area>
<area shape=rect coords=" 55,181, 85,207" href="javascript:key_pressed('.')" title="."></area>
<area shape=rect coords=" 94,181,124,207" href="javascript:key_pressed('i')" title="i"></area>
<area shape=rect coords="133,145,163,207" href="javascript:key_pressed('+')" title="+"></area>
<area shape=rect coords="169,181,201,207" href="javascript:key_pressed('=')" title="="></area>
</map>
<script type=text/javascript>
var is_netscape = (navigator.appName=="Netscape") ? 1 : 0;
lcd0 = new Image(20, 45); lcd0.src="images/lcd0.jpg";
lcd1 = new Image(20, 45); lcd1.src="images/lcd1.jpg";
lcd2 = new Image(20, 45); lcd2.src="images/lcd2.jpg";
lcd3 = new Image(20, 45); lcd3.src="images/lcd3.jpg";
lcd4 = new Image(20, 45); lcd4.src="images/lcd4.jpg";
lcd5 = new Image(20, 45); lcd5.src="images/lcd5.jpg";
lcd6 = new Image(20, 45); lcd6.src="images/lcd6.jpg";
lcd7 = new Image(20, 45); lcd7.src="images/lcd7.jpg";
lcd8 = new Image(20, 45); lcd8.src="images/lcd8.jpg";
lcd9 = new Image(20, 45); lcd9.src="images/lcd9.jpg";
lcd0dot = new Image(20, 45); lcd0dot.src="images/lcd0dot.jpg";
lcd1dot = new Image(20, 45); lcd1dot.src="images/lcd1dot.jpg";
lcd2dot = new Image(20, 15); lcd2dot.src="images/lcd2dot.jpg";
lcd3dot = new Image(20, 45); lcd3dot.src="images/lcd3dot.jpg";
lcd4dot = new Image(20, 45); lcd4dot.src="images/lcd4dot.jpg";
lcd5dot = new Image(20, 45); lcd5dot.src="images/lcd5dot.jpg";
lcd6dot = new Image(20, 45); lcd6dot.src="images/lcd6dot.jpg";
lcd7dot = new Image(20, 45); lcd7dot.src="images/lcd7dot.jpg";
lcd8dot = new Image(20, 45); lcd8dot.src="images/lcd8dot.jpg";
lcd9dot = new Image(20, 45); lcd9dot.src="images/lcd9dot.jpg";
lcdoff = new Image(20, 45); lcdoff.src="images/lcdoff.jpg";
lcdminus = new Image(20, 45); lcdminus.src="images/lcdminus.jpg";
lcde = new Image(20, 45); lcde.src="images/lcde.jpg";
var x=0;
var y=0;
var m=0;
var disp="";
var is_new_num = true;
var is_decimal = false;
var last_op = "";
var error = false;
// ---------------------------------------------------------
function update_display(dspin) {
var disp_array = new Array();
var dot_active = false;
var dsp=dspin;
var lcds=0;
var idx=dsp.length;
if (error) {
document.d7.src = lcdoff.src;
document.d6.src = lcdoff.src;
document.d5.src = lcdoff.src;
document.d4.src = lcdoff.src;
document.d3.src = lcdoff.src;
document.d2.src = lcdminus.src;
document.d1.src = lcde.src;
document.d0.src = lcdminus.src;
return;
}
if (dspin.indexOf('.') == -1) {
dsp = dspin + '.';
idx++;
}
while (lcds < 8) {
idx--;
digit = dsp.charAt(idx);
if (digit == '.')
dot_active = true;
else if (digit == '-') {
disp_array[lcds] = lcdminus.src;
lcds++;
} else if (digit && "0123456789".indexOf(digit) != -1) {
if (dot_active)
disp_array[lcds] = "images/lcd"+digit+"dot.jpg";
else
disp_array[lcds] = "images/lcd"+digit+".jpg";
dot_active = false;
lcds++;
} else {
disp_array[lcds] = lcdoff.src;
lcds++;
}
}
document.d7.src = disp_array[7];
document.d6.src = disp_array[6];
document.d5.src = disp_array[5];
document.d4.src = disp_array[4];
document.d3.src = disp_array[3];
document.d2.src = disp_array[2];
document.d1.src = disp_array[1];
document.d0.src = disp_array[0];
}
// ---------------------------------------------------------
function rnd_to_display(val) {
var rnd_factor;
var result = (val < 0) ? -1 * val : val;
rnd_factor = 10000000;
while (result >= 10) {
result = result / 10;
rnd_factor = rnd_factor / 10;
}
if (val < 0)
rnd_factor = rnd_factor / 10;
result = (Math.round(val * rnd_factor))/rnd_factor;
return result;
}
// ---------------------------------------------------------
function reduce (op) {
if (op == '+')
x = x + y;
if (op == '-')
x = x - y;
if (op == '*')
x = x * y;
if (op == '/') {
if (y == 0) {
error = true;;
return;
}
x = x / y;
}
x = rnd_to_display(x);
if (x < -9999999 || x > 99999999)
error = true;
}
// ---------------------------------------------------------
function keyboard(e) {
var ky, code;
code = is_netscape ? e.which : e.keyCode;
ky = String.fromCharCode(code);
if (ky == 't')
test();
else
key_pressed(ky);
}
// ---------------------------------------------------------
function key_pressed(key) {
if (error && key != 'o')
return;
if ((key && ".0123456789".indexOf(key)) != -1) {
if (key == '.') {
if (is_new_num) {
disp = "0.";
is_new_num = false;
}
is_decimal = true;
} else {
if (!is_new_num && disp.length == 9)
return;
else if (is_decimal) {
disp = disp+key;
} else {
if (is_new_num)
disp = key+'.';
else
disp = disp.substring(0, disp.length-1)+key+'.';
}
y = parseFloat(disp);
is_new_num = false;
}
} else if ("*/+-".indexOf(key) != -1) {
if (last_op != "") {
reduce(last_op);
disp = x.toString();
} else
x = y;
is_new_num = true;
is_decimal = false;
last_op = key;
} else if ("=%".indexOf(key) != -1) {
if (last_op != "") {
if (key == '%')
y = y/100;
reduce(last_op);
y = x;
disp = x.toString();
}
is_new_num = true;
is_decimal = false;
last_op = "";
} else {
if (key == 's') {
if (y < 0)
error = true;
else {
y = rnd_to_display(Math.sqrt(y));
disp = y.toString();
}
is_new_num = true;
is_decimal = false;
} else if (key == 'i') {
y = -1 * y;
if (y < 0) disp = "-" + disp;
else disp = disp.substring(1, disp.length);
} else if (key == 'c') {
y = 0;
disp = "0.";
is_new_num = true;
is_decimal = false;
} else if (key == 'o') {
x = 0;
y = 0;
m = 0;
disp = "0.";
is_new_num = true;
is_decimal = false;
last_op = "";
error = false;
} else if (key == 'r') {
y = m;
disp = y.toString();
is_new_num = true;
is_decimal = false;
} else if ("mM".indexOf(key) != -1) {
m = m + y * ((key == 'm') ? 1 : -1);
is_new_num = true;
is_decimal = false;
}
}
update_display(disp);
}
// ---------------------------------------------------------
testdata = "o:0.@" + // initialise
"15.3 + 1.89 = : 17.19 @" + // Add
"15.3 - 1.89 = : 13.41 @" + // Subtract
"15.3 * 1.89 = : 28.917 @" + // Multiply
"15.3 / 1.89 = : 8.0952381 @" + // Divide
"15.3 + 1.89 i = : 13.41 @" + // change sign
"15.3 s + 1.89 = : 5.8015214 @" + // square root
"15.3 * 1.89 % : 0.28917 @" + // percent
"22.5 c 15.3 + 1.89 = : 17.19 @" + // clear entry
"1.89 m 15.3 + r = : 17.19 @" + // M+ and recall
"3.78 M 15.3 - r = : 17.19 @" + // M- and recall
"55 + 99 = : 154. @" + // Trailing decimal
"o:0."; // last line
// ---------------------------------------------------------
function test() {
var answer = false; // State indicating reading expected answer
var testnum = 0; // test number
var failures = new Array(); // List of test failures
var fidx = 0; // Failure index
var testchar = ""; // Current input character
if (!confirm("Run the self tests?\n(may take some seconds)"))
return;
// Scan through all test data ...
for (tidx = 0; tidx < testdata.length; tidx = tidx + 1) {
// get next input character
testchar = testdata.charAt(tidx);
// Non white-space input
if (testchar != ' ') {
// If answer delimiter, clear expected answer string and set state
if (testchar == ':') {
answer = true;
expected = "";
}
// If test delimiter, check answer
else if (testchar == '@') {
// Clear state
answer = false;
// Get calculator's result
result = disp;
// Check result matches expected, and log if not
if (parseFloat(expected) != parseFloat(result))
failures[fidx++] = testnum;
testnum++;
}
// Whilst in answer state, append input to expected variable
else if (answer == true)
expected = expected + testchar;
// Input key sequences
else {
// Abort if an invalid character found in the test data
if ("0123456789.+-*/=%simMroc".indexOf(testchar) == -1) {
alert("Invalid input character ("+testchar+") in test sequence\n"+
"at line "+testnum);
return;
}
key_pressed(testchar);
}
}
}
// Clear testchar and reuse for failure message
testchar = "";
// Add all failure test numbers to string
for (tidx = 0; tidx < fidx; tidx++)
testchar = testchar + " " + failures[tidx];
// Display test results
if (fidx > 0)
alert (fidx+" failures at lines:"+testchar);
else
alert ("All "+ (testnum-1) + " tests pass");
}
</script>
</body>
</html>
Installation
- Make sure the widget files are stored in a directory ending in wdgt. Here we used calculator.wdgt.
- In iBook Author, insert a widget, and pick HTML.
- Select the new widget and position it where you want in your iBook page.
- In the Inspector window, choose the folder calculator.wdgt and the Default image (Default.png) should appear in the widget.
- Connect your iPad to the Mac on which you are creating your iBook and preview the iBook. Verify that the widget works.
References
- ↑ How to Write a Calculator Simulator, Simon Southwell, http://www.anita-simulators.org.uk/calc/calc_example/article2_front.htm, published March 2004, captured Jan. 2013.