IBookAuthor: Javascript Calculator

From dftwiki3
Revision as of 10:09, 28 January 2013 by Thiebaut (talk | contribs) (Files)
Jump to: navigation, search

--D. Thiebaut 17:44, 27 January 2013 (EST)


The Javascript Calculator

JavascriptCalculatorSimulator.png

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

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.


IPadScreenCaptureCalculatorSimulator.png


References

  1. 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.