Calculator, Perl, WebApp

How to Create a Four Function Web Calculator using PERL

 


Overview

It is assumed that the reader hasBrowser_Server some knowledge of HTML (HyperText Markup Language) and programming logic.  Access to reference materials is recommended!

The four-function calculator PL (Perl) page uses HTML, CSS and JQuery for web page presentation and Perl programming logic.

This application utilizes a client-server architecture where the client (Web Browser) initiates a request by POSTing to the web server using HTTP (Hypertext Transfer Protocol).  In turn, the web server reciprocates with a web page to the client (Web Browser) as a response.

The following sections exhibit and explain the code snippets for web presentation and page processing logic including installation instructions onto a Windows operating system.

This web calculator is hosted at this link:   Perl_Calculator


Web Page Presentation

#'
#'********************************************************
#' Browser output
#'********************************************************
#'
print "Content-type: text/html";
print "\n\n";

print <<HTMLStuff;

<html>

<head>

<style>
#t1 {color:green; font-size: 8pt; text-align: right;}
#t2 {color:red; font-size: 8pt; text-align: right;}
#btn {width:32; height:32;}
</style>

<title>myCalc</title>

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<!--
<script type="text/javascript" src="Sys_web/Incl/jquery-latest.js">
</script>
-->
<!-- Script to scroll both result windows simultanously -->
<script type="text/javascript">
        \$(function() {
            \$('#t1').scroll(function() {
            \$('#t2')
                .scrollTop(\$('#t1').scrollTop());
            });
      \$('#t2').scroll(function() {
            \$('#t1')
                .scrollTop(\$('#t2').scrollTop());
            });
        });
    </script>
    
</head>

<body>

<!-- Calculator FORM -->
<form method="POST" action="$myScriptName" name="CalculateIT">
<!-- Place border on FORM and border title accordingly -->
<fieldset style="border-color:green; background-color:beige">
<legend style="color:blue; font-size: 18pt; background-color:lightsteelblue">$myTitle</legend>
<!-- Audit Trail and Intermediate Result textboxes -->
<table align="center">
<tr>
<td align="center">Audit Trail</td>
<td align="center">Intermediate Results</td>
</tr>
<tr>
<td><textarea id="t1" rows="15" name="AuditTape" cols="30"  readonly="readonly">$frm_AuditTape</textarea></td>
<td><textarea id="t2" rows="15" name="AccumTape" cols="50" readonly="readonly">$frm_AccumTape</textarea></td>
</tr>
</table>

<!-- Script to scroll both result windows to bottom of window simultaneously -->
<script type="text/javascript">
function Scroll_to_Bottom() 
{
document.getElementById('t1').scrollTop=document.getElementById('t1').scrollHeight;
document.getElementById('t2').scrollTop=document.getElementById('t2').scrollHeight;
}
// Call the function
Scroll_to_Bottom ();
    </script>

<!-- Audit Trail and Intermediate Result textboxes -->
<br/> 
<table border="1" cellpadding="10" cellspacing="0" align="center" style="border-collapse: collapse ; background-color:DarkSeaGreen">
<tr>
<td>
<!-- Place border on keyboard and display components -->
<fieldset style="border-color:green; background-color:lightsteelblue">
<!-- Calculator Display Title -->
$MaxInput Digit Display
<br/>
<!-- Calculator Display -->
  <input name="NumIn" size="15" value="$frm_NumIn" style="text-align:right; font-size: 10pt" readonly="readoly">
<!-- Calculator Error Message area -->
<br/>
<font color=red size="2">$ErrMsg</font>
<!-- Calculator Keypad -->
<br/>
      <input id="btn" type="submit" value="C"   name="Fclear" >&nbsp;
      <input id="btn" type="submit" value="CE"  name="Fcleare" >&nbsp;
      <input id="btn" type="submit" value="off" name="Poff" style="background-color: red; ">&nbsp;
      <input id="btn" type="submit" value="on"  name="Pon" style="background-color: green; color:lawngreen; ">&nbsp;
<br/>
      <input id="btn" type="submit" value="7"   name="B7" >&nbsp;
      <input id="btn" type="submit" value="8"   name="B8" >&nbsp;
      <input id="btn" type="submit" value="9"   name="B9" >&nbsp;
      <input id="btn" type="submit" value="/"   name="Fdiv" >&nbsp;
<br/>
      <input id="btn" type="submit" value="4"   name="B4" >&nbsp;
      <input id="btn" type="submit" value="5"   name="B5" >&nbsp;
      <input id="btn" type="submit" value="6"   name="B6" >&nbsp;
      <input id="btn" type="submit" value="x"   name="Fmult" >&nbsp;
<br/>
      <input id="btn" type="submit" value="1"   name="B1" >&nbsp;
      <input id="btn" type="submit" value="2"   name="B2" >&nbsp;
      <input id="btn" type="submit" value="3"   name="B3" >&nbsp;
      <input id="btn" type="submit" value="-"   name="Fminus" >&nbsp;
<br/>
      <input id="btn" type="submit" value="0"   name="B0" >&nbsp;
      <input id="btn" type="submit" value="."   name="Bdot" >&nbsp;
      <input id="btn" type="submit" value="="   name="Fequal" >&nbsp;
      <input id="btn" type="submit" value="+"   name="Fplus" >&nbsp;
</fieldset>
</td>
</tr>
</table>
</fieldset>
<input type="hidden" value="$frm_Session_NumberIn"   name="Session_NumberIn" >&nbsp;
<input type="hidden" value="$frm_Session_NumberInFunction"   name="Session_NumberInFunction" >&nbsp;
</form>
<u><i>myCalc</i></u> is for illustrative and educational purposes and does not represent a fully robust and tested calculator product.<br>
Copyright &copy; 2015  Larry Belmontes, Jr.
</body>

</html>

HTMLStuff

The above snippet represents the HTML BODY section which is rendered to the browser via a set of PRINT statements.  The FORM, which is within the BODY, contains three components (keypad, audit trail and results) used to interact with the web browser and web server.  Below is a description of HTML used in this web page.

Line Description
4 FORM declaration containing all interactive components contained within the <FORM> and </FORM> HTML tags.
When form is submitted (any keypad button is pressed), form data is POST-ed to the web server by invoking ASPX script calc_42.aspx.
6-7 Declare a green border with the title of myWeb Calculator.  The border surrounds all calculator components.  A beige background is applied within the green boarder.
9-18 Declare a one-row, two-column table containing two text boxes (AuditTape, AccumTape) centered horizontally within the FORM.
21-29 Javascript to scroll down both text boxes (AuditTape, AccumTape) simultaneously keeping current activity visible.
33 Declares a one-row, one-column table to contain the display and keypad components centered horizontally within the FORM.  A DarkSeaGreen background is applied to the this table.
37 Declare a green border with a lightsteelblue background for keypad components (display, error message, keypad).
39 Display title (e.g. 12 Digit Display) on the calculator keypad.
42 Numeric entry text box on the calculator keypad.
45 Error message area to display errors in red.
48-71 The buttons representing the calculator keypad.  When any keypad button is pressed, the FORM contents are POSTed to the web server for processing.
   
   

Application Processing Logic

#!/usr/bin/perl

#'********************************************************
#' Set warnings and diagnostics to output on browser
#'********************************************************
use warnings;
use diagnostics;
use CGI::Carp 'fatalsToBrowser';
#'********************************************************
#' Use filename basename function; utf8 character set and encoding
#'********************************************************
use File::Basename;
use utf8;
use encoding('utf8');
#'
#'********************************************************
#' My first web calculator
#'
#' Author     : Elario Belmontes, Jr.
#' Description: Four function calculator simulator for
#'              educational purposes to illustrate the 
#'              use of a web page on a personal web
#'              server
#' Languages  : Perl, JAVASCRIPT, JQUERY, HTML, CSS
#' File Name  : calc_42.pl
#' Disclaimer: This software is intended for educational learning
#'             purposes.
#'
#'             This software is provided "AS IS" and without any
#'             expressed or implied warranties, including, without
#'             limitation, the implied warranties of merchantability
#'             and fitness for a particular purpose.
#'
#'             No guarantee; No warranty; Install / Use at your own risk.
#'
#'
#' Copyright (C) 2015  Larry Belmontes, Jr. 
#'
#'
#'
#'********************************************************
#'
#'
#'********************************************************
#' Functions Section
#'********************************************************
#'
#'
#'********************************************************
#' Function: Do_Math
#' Math functions: add, subtract, multiply, divide
#'********************************************************/
sub Do_Math()
{
  if ($frm_NumIn gt ""){
    $frm_AccumTape = $frm_AccumTape . $frm_Session_NumberIn . $frm_Session_NumberInFunction;
    ComputeIT();
    $frm_AuditTape = $frm_AuditTape . $frm_NumIn . " " . $form_data{$frm_Control} . " " . chr(13) . chr(10);
    $frm_AccumTape = $frm_AccumTape . $frm_NumIn . "=" . $frm_Session_NumberIn . " " .  chr(13) . chr(10);
    $frm_Session_NumberInFunction = $form_data{$frm_Control};
    $frm_NumIn = ""
  }
  else
  {
    $ErrMsg = "No data entered...";
  }
  return;
}
#'
#'********************************************************
#' Function: Finalize_Math
#' Terminate Math function: equals key
#'********************************************************/
sub Finalize_Math()
{
  if ($frm_NumIn gt ""){
    $frm_AccumTape = $frm_AccumTape . $frm_Session_NumberIn . $frm_Session_NumberInFunction;
    ComputeIT();
    $frm_AuditTape = $frm_AuditTape . $frm_NumIn . " " . $form_data{$frm_Control} . " " . chr(13) . chr(10);
    $frm_AuditTape = $frm_AuditTape . $frm_Session_NumberIn . " T " . chr(13) . chr(10);
    $frm_AccumTape = $frm_AccumTape . $frm_NumIn . "=" . $frm_Session_NumberIn . " " . chr(13) . chr(10);
    $frm_AccumTape = $frm_AccumTape . "^^^^^^^^^^^^^^^^^^^^^^^ " . chr(13) . chr(10);
    $frm_NumIn = "";
    $frm_Session_NumberIn = "";
    $frm_Session_NumberInFunction = "";
  }
  else
  {
    $ErrMsg = "No data entered...";
  }
  return;
}
#'
#'********************************************************
#' Function: Clear_key
#' Housekeep: ClearAll key
#'********************************************************/
sub Clear_key()
{
  $frm_AuditTape = "";
  $frm_AccumTape = "";
  $frm_NumIn = "";
  $frm_Session_NumberIn = "";
  $frm_Session_NumberInFunction = "";
  return;
}
#'
#'********************************************************
#' Function: Power_Off_key
#' Housekeep: Power Off key
#'********************************************************/
sub Power_Off_key()
{
  #
  # No need to destroy session variables as they
  # exist on the HTML FORM
  #
  print "Content-type: text/html";
  print "\n\n";
  print "Powered Off!<br><br>Click ";
  print "<a title=" . chr(34) . "Power On Calculator" . chr(34);
  print " href=" . chr(34) . $myScriptName . chr(34) . "><font color=" . chr(34) . "#FFFFFF";
  print "><span style=" . chr(34) . "background-color: #0000CC" . chr(34);
  print ">here</span></font></a>" . " to power on myWeb Calculator...";
  return;
}
#'
#'********************************************************
#' Function: Power_On_key
#' Housekeep: Power On
#'********************************************************/
sub Power_On_key()
{
  $ErrMsg = "Power already ON...";
  return;
}
#'
#'********************************************************
#' Function: Clear_Entry_key
#' Housekeep: Clear entry 
#'********************************************************/
sub Clear_Entry_key()
{
  $frm_NumIn = "";
  return;
}
#'
#'********************************************************
#' Function: Numeric_key
#' Numeric entry
#'  Limit to n digits entry based on MaxInput excluding decimal point
#'********************************************************/
sub Numeric_key()
{
  if (($frm_NumIn =~ tr/.//) eq 0){ 
    #' no decimal found in number input
    #' check for MAXInput - 1
    if (length($frm_NumIn) > ($MaxInput - 1)){
      $ErrMsg = "Input limited to " . $MaxInput . " digits!";
    }
    else
    {
      $frm_NumIn = $frm_NumIn . $form_data{$frm_Control};
    }	
  }
  else
  {
    #' found decimal point in number input
    #'  check for MAXInput
    if (length($frm_NumIn) > $MaxInput){
      $ErrMsg = "Input limited to " . $MaxInput . " digits!";
    }
    else
    {
      $frm_NumIn = $frm_NumIn . $form_data{$frm_Control};
    }
  }
  return;
}
#'
#'********************************************************
#' Function: Decimal_key
#' Decimal point entry
#'********************************************************/
sub Decimal_key()
{
  if (($frm_NumIn =~ tr/.//) eq 0){
    $frm_NumIn = $frm_NumIn . ".";
  }
  return;
}
#'
#'********************************************************
#' Function: ComputeIT
#' Compute last result with current numeric entry
#'   based on math function
#'********************************************************/
sub ComputeIT()
{
  if ($frm_Session_NumberInFunction eq ""){
    $frm_Session_NumberIn = $frm_NumIn;
  }
  elsif ($frm_Session_NumberInFunction eq "+"){
    $frm_Session_NumberIn = $frm_Session_NumberIn + $frm_NumIn;
  }
  elsif ($frm_Session_NumberInFunction eq "-"){
    $frm_Session_NumberIn = $frm_Session_NumberIn - $frm_NumIn;
  }
  elsif ($frm_Session_NumberInFunction eq "x"){
    $frm_Session_NumberIn = $frm_Session_NumberIn * $frm_NumIn;
  }	
  elsif ($frm_Session_NumberInFunction eq "/"){
    $frm_Session_NumberIn = $frm_Session_NumberIn / $frm_NumIn;
  }
  return;
}
#'
#'********************************************************
#' Script Mainline 
#'********************************************************
#'
#'
#'********************************************************
#' Create/Resume a session
#'********************************************************
#'
# Session variables exist as INPUT hidden fields 
#' for this script
#'
#'********************************************************
#' Get my script file name
#'********************************************************
#'
$myScriptName = basename($ENV{'SCRIPT_NAME'});
#'
#'********************************************************
#' Initialize variables
#'********************************************************
#'
$myTitle = "myWeb Calculator";
$ErrMsg = "&nbsp;";
$MaxInput = 12;
$form = "";
#'
#'********************************************************
#' Process FORM post (i.e. calculator button pressed)
#'********************************************************
#'
if ($ENV{'REQUEST_METHOD'} eq "POST") {
  #'********************************************************
  #' Obtain data from FORM controls
  #'********************************************************
  read(STDIN, $form, $ENV{'CONTENT_LENGTH'});
  #'********************************************************
  #' Create associative array form_data
  #'********************************************************
  @name_value_pairs = split(/&/,$form);
  foreach $name_value (@name_value_pairs) {
    ($name, $value) = split(/=/, $name_value);
    $value =~ tr/+/ /;
    $value =~ s/%(..)/pack("C", hex($1))/eg;
    $form_data{$name} = $value;
  }
  #'********************************************************
  #' Initialize variables
  #'********************************************************
  $frm_AuditTape = $form_data{'AuditTape'};
  $frm_NumIn = $form_data{'NumIn'};
  $frm_AccumTape = $form_data{'AccumTape'};
  #'********************************************************
  #' The following are used as session variables
  #'********************************************************
  $frm_Session_NumberIn = $form_data{'Session_NumberIn'};
  $frm_Session_NumberInFunction = $form_data{'Session_NumberInFunction'};
  #'
  #'********************************************************
  #' Button on FORM pressed -
  #'   Process number, function or housekeep button on form
  #'
  #'   Loop through each FORM field and take appropriate
  #'     action from POST data
  #'       (i.e.  AuditTape=&AccumTape=&NumIn=&B8=8)
  #'********************************************************/
  foreach $frm_Control (keys(%form_data)) {
    #'
    #'********************************************************
    #' Math functions: add, subtract, multiply, divide
    #'********************************************************/
    if ($frm_Control ~~ ["Fplus", "Fminus", "Fmult", "Fdiv"]){
        Do_Math();
    }
    #'
    #'********************************************************
    #' Terminate Math function: equals
    #'********************************************************/
    elsif ($frm_Control eq "Fequal"){
        Finalize_Math();
    }
    #'
    #'********************************************************
    #' Housekeep: Clear all
    #'********************************************************/
    elsif ($frm_Control eq "Fclear"){
        Clear_key();
    }
    #'
    #'********************************************************
    #' Housekeep: Power Off
    #'********************************************************
    elsif ($frm_Control eq "Poff"){
      Power_Off_key();
      exit();
    }
    #'
    #'********************************************************
    #' Housekeep: Power On
    #'********************************************************/
    elsif ($frm_Control eq "Pon"){
   			Power_On_key();
    }
   		#'
    #'********************************************************
    #' Housekeep: Clear entry
    #'********************************************************/
    elsif ($frm_Control eq "Fcleare"){
      Clear_Entry_key();
    }
    #'
    #'********************************************************
    #' Numeric entry
     	#'  Limit to 16 digits entry excluding decimal point
    #'********************************************************/
   		elsif ($frm_Control ~~ ["B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9"]){
      Numeric_key();
    }
    #'
    #'********************************************************
    #' Decimal point entry
    #'********************************************************/
   		elsif ($frm_Control eq "Bdot"){
      Decimal_key();
    }
  }
}	
else	
{
  #'
  #'********************************************************
  #' NOT a POST, initialize session variables 
  #'  Assume first time invoked
  #' ** Session variables are kept as hidden INPUT fields in form
  #'********************************************************/
  $frm_AuditTape = "";
  $frm_AccumTape = "";
  $frm_NumIn = "";
  $myTitle = "Welcome to myWeb Calculator and happy calculating!";
  $frm_Session_NumberIn = "";
  $frm_Session_NumberInFunction = "";	
}

The above code snippet includes functions (subroutines) and mainline logic.  Functions are defined before the mainline.  One unique note about  Perl, subroutines and functions are used interchangeably because the subroutine (sub verb) always returns one value.  Thus, the synonymous term usage.

On initial calculator submission, the client PC web browser sends a calc_42.pl URL request to the web server (IIS) via HTTP.  The calc_42.pl code starts execution on IIS at line 1 by importing various function and setting directives.

All subroutines are defined within lines 44-216.

The script mainline starts with initializing variables (lines 234-243), including the setting of variable myScriptname (line 234), and first time action logic (lines 346-359).  An HTML response is constructed from the code between the <HTML> and </HTML> tags that includes STYLE, JavaScript and FORM code via a set of Perl PRINT statements (lines 5-129 in the Web Presentation Page section).  The HTML response is sent via HTTP to the client PC and rendered by the web browser as an initial calculator page.

On subsequent calculation submission, the client PC web browser posts a calc_42.pl request to the web server (IIS) via HTTP.  The calc_42.pl code starts executions on IIS at line 1.  POST action logic (lines 249-344) processing occurs via the associated functions (e.g. Call Numeric_key when key 0-9 is clicked) including the updating of FORM fields.  An HTML response is constructed from code between the <HTML> and </HTML> tags that includes STYLE, JavaScript and FORM code via a set of Python PRINT statements.  The HTML response is sent via HTTP to the client PC and rendered by the web browser as an updated calculator page.  Below is a description of Perl logic for the above snippet.

Line Description
234 Initialize myScriptName variable
240 Set myTitle to “myWeb Calculator”
241 Set error message (ErrMsg) to blanks
242 Set maximum number of digits (MaxInput) to 12
243 Initialize $form variable
249-344 Logic to execute when post method = POST (keypad button has been pressed)

Line Description
253 Read POSTed HTML FORM data via STDIN in the form of name-value pairs (&prm1=val1,&prm2=val2,etc.)
254 Create form data names and values
267-269 Fetch form variables from textboxes (AuditTape, AccumTape) and current display value (NumIn)
273-274 Set session variables to store data between calculator interactions
284
Loop
Loop through each form variable testing for buttons
289-290 When buttons  +   –   x  or  /  are detected, call subroutine Do_Math to process math function of add, subtract, multiply or divide updating result windows
296-297 When button  =  is detected, call subroutine Finalize_Math to complete current math operation updating result windows
303-304 When button  C  is detected, call subroutine Clear_key to clear result windows and calculator display
318-319   When button  on  is detected, call subroutine Power_On_key to clear web page and present hyperlink to power on calculator
310-312 When button  off  power is detected, call subroutine Power_Off_key to post error message “Power already ON…”
325-326 When button  CE   is detected, call subroutine Clear_Entry_key to clear numeric entry display
333-334 When buttons   0 1 2 3 4 5 6 7 8  or   9   are detected, call subroutine Numeric_key to enter digit into numeric entry display
340-341 When button  .  is detected, call subroutine Decimal_key to enter decimal into numeric entry display
End Loop  
171-176 Logic to execute when post method does not = POST (first time action)

Line Description
171-173 Initialize various form input fields
174 Set myTitle to “Welcome to myCalculator and happy calculating!”
175-176 Initialize input hidden fields (used as session variables) to store data between calculator interactions
   

Download and Installing Perl Web Calculator

This application was successfully installed and tested on:

  • Windows Home Server (part of MS Server 2003), IIS 6 with Perl support, IE10, Chrome41
  • Windows 7 Home Premium 64-bit,, IIS 7, IE 10, Chrome v41 (this machine acted as a client only )

** Used Perl 5.2 for IIS from perl.org

1. The web server, IIS (Information Internet Services), must be installed with Perl support on your desktop or laptop computer before attempting to run this web application.

2. Download the ZIP file and extract to a temporary directory.

Download calc_42_pl

3. Open the ZIP file and copy the file, calc_42.pl,  to the IIS webroot directory.
Typically, the default directory name is c:\Inetpub\wwwroot\

4. Open a browser window (e.g. Internet Explorer or Chrome)

5. Type http://localhost/calc_42.pl in the address bar and press ENTER.

6. The calculator web page should display.

7. On the calculator keyboard –
Click 3.  Click +.  Click 7.  Click /.  Click 2.   Click =.  The result should be 5.

8. Happy calculating!!


Next Learning Experience

Extend the calculator exercise into plain-text presentation on IBM mainframe software using COBOL and an online application layer.

Also, developing the calculator logic using Perl widens the comparable similarities between Perl, Python, PHP, ASP, Javascript, and ASPX.

Stay tuned and continue review the related posts below!

Leave a Reply

Your email address will not be published. Required fields are marked *


CAPTCHA Image
Reload Image

This site uses Akismet to reduce spam. Learn how your comment data is processed.