Difference between revisions of "IBookAuthor: Javascript Calculator"
(→References) |
|||
Line 527: | Line 527: | ||
<center>[[Image:iPadScreenCaptureCalculatorSimulatorRunning.png |700px]]</center> | <center>[[Image:iPadScreenCaptureCalculatorSimulatorRunning.png |700px]]</center> | ||
<br /> | <br /> | ||
+ | |||
+ | =Misc= | ||
+ | PDF Copies of Southwell's article can be found here: | ||
+ | [[media:SimonSouthwellJavascriptCalculator0.pdf| Introduction]], | ||
+ | [[media:SimonSouthwellJavascriptCalculator1.pdf| Part 1]], | ||
+ | [[media:SimonSouthwellJavascriptCalculator2.pdf| Part 2]], | ||
+ | [[media:SimonSouthwellJavascriptCalculator3.pdf| Part 3]], | ||
+ | [[media:SimonSouthwellJavascriptCalculator4.pdf| Part 4]], | ||
+ | [[media:SimonSouthwellJavascriptCalculator5.pdf| Part 5]], and | ||
+ | [[media:SimonSouthwellJavascriptCalculatorAppendix.pdf| Appendix]]. | ||
+ | |||
=References= | =References= |
Revision as of 10:41, 28 January 2013
--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
The list below shows the complete hierarchy of files for the widget. calculator.wdgt is the name of the directory. calc.html is the main html file containing the javascript code for the calculator. Default.png is a single resolution image of the calculator (which will appear in the place holder for the widget in the iBook). Default@2x.png is the double resolution image of the same calculator, for Retina displays. images is the directory containing all the different parts of the calculator. Apple recommends avoiding the following names[2]: Resources/, Support Files/Resources/, or Contents/Resources/. Info.plist is the required file describing the bundle using Apple's description language.
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
Sources
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.
Misc
PDF Copies of Southwell's article can be found here: Introduction, Part 1, Part 2, Part 3, Part 4, Part 5, and Appendix.
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.
- ↑ iBooks Author: About HTML widget creation, Apple, http://support.apple.com/kb/HT5068, Oct. 2012, captured Jan 2013.