Difference between revisions of "IBookAuthor: Javascript Calculator"
(→The Javascript Calculator) |
(→Creating the Widget) |
||
(19 intermediate revisions by the same user not shown) | |||
Line 1: | Line 1: | ||
--[[User:Thiebaut|D. Thiebaut]] 17:44, 27 January 2013 (EST) | --[[User:Thiebaut|D. Thiebaut]] 17:44, 27 January 2013 (EST) | ||
---- | ---- | ||
+ | {| | ||
+ | | | ||
+ | __TOC__ | ||
+ | | | ||
+ | | ||
+ | | | ||
+ | <videoflashright>9ZkBmIJ46-U</videoflashright> | ||
+ | |} | ||
+ | <br /> | ||
=The Javascript Calculator= | =The Javascript Calculator= | ||
+ | [[Image:JavascriptCalculatorSimulator.png|right|150px]] | ||
+ | You should first read the excellent article by Simon Southwell<ref name="javascriptCalculator">How to Write a Calculator Simulator, | ||
+ | Simon Southwell, [http://www.anita-simulators.org.uk/calc/calc_example/article2_front.htm http://www.anita-simulators.org.uk/calc/calc_example/article2_front.htm], published March 2004, captured Jan. 2013.</ref>. | ||
+ | |||
+ | 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 [[Javascript Calculator Simulator| here]]. A multi-part PDF copy of the article is available at the bottom of this page. | ||
+ | |||
+ | =Creating the Widget= | ||
+ | |||
+ | Creating the widget simply requires copying the [[IBookAuthor:_A_Skeleton_Widget | skeleton widget]] presented in this [[Tutorials|collection of tutorials]], adding a new directory in it called '''images''' to hold all the png 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<ref name=AppleIBookRecommendations>iBooks Author: About HTML widget creation, Apple, [http://support.apple.com/kb/HT5068 http://support.apple.com/kb/HT5068], Oct. 2012, captured Jan 2013.</ref>: ''Resources/'', ''Support Files/Resources/'', or ''Contents/Resources/''. I'''nfo.plist''' is the required file describing the bundle using Apple's description language. | ||
+ | <code><pre> | ||
+ | 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 | ||
+ | |||
+ | </pre></code> | ||
+ | |||
+ | ==Sources== | ||
+ | |||
+ | ===Info.plist=== | ||
+ | |||
+ | <code><pre> | ||
+ | <?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> | ||
− | + | </pre></code> | |
− | + | ||
+ | |||
+ | ===calc.html=== | ||
+ | |||
+ | <code><pre> | ||
+ | |||
+ | <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> | ||
+ | |||
+ | </pre></code> | ||
+ | |||
+ | =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. | ||
+ | |||
+ | <br /> | ||
+ | <center>[[Image:iPadScreenCaptureCalculatorSimulator.png |700px]]</center> | ||
+ | <br /> | ||
+ | <br /> | ||
+ | <center>[[Image:iPadScreenCaptureCalculatorSimulatorRunning.png |700px]]</center> | ||
+ | <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= | ||
Line 13: | Line 552: | ||
<br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /> | <br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /> | ||
<br /><br /><br /><br /><br /> | <br /><br /><br /><br /><br /> | ||
− | [[Category:iBook Author]][[Category:IBook Widget]] | + | [[Category:iBook Author]][[Category:IBook Widget]][[Category:Tutorials]] |
Latest revision as of 08:59, 11 March 2014
--D. Thiebaut 17:44, 27 January 2013 (EST)
|
|
The Javascript Calculator
You should first read the excellent 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. A multi-part PDF copy of the article is available at the bottom of this page.
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 png 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.