Advertisement

Creating an Incredible jQuery Calculator

by

This Cyber Monday Tuts+ courses will be reduced to just $3 (usually $15). Don't miss out.

Sure is nice to have a letter opener right next to your mail basket, a shoehorn next to your loafers, and a flashlight by your nightstand. Likewise, it's nice to have a calculator handy when you need to perform some math. This tutorial and download shows you how to put one right on any website.


User Interface Consideration


Some buttons with numbers 1 through 9, a couple of functions, and a big "equals" button. That's all a calculator is right? Should be a peice of cake. Any programming langauge can easily handle this. Of course, but even something as dead simple as a calculator has quite a few things to take into consideration when designing how exactly it works.

The screencast that accompanies this article is going to focused mostly on those things, and less about the down-and-dirty coding. While this written portion will be focused on the code. Nevertheless, here is a quick overview of some of the little user interface details:

  • When the calculator loads, 0 (zero) is in the display. Pressing the number 2 doesn't make the number 02, it's just 2.
  • When you press a function key, the number currently in the display "locks" as the first number in the calculation, but it just stays right where it is. When another number is pressed, the number is replaced.
  • If a function key is pressed, and then a different function key is pressed, the most recent one pressed is the function that will be used.
  • After a calculation is performed, there are two possible scenarios. A) A new number is pressed. This will totally reset the calculator and start fresh with this number. B) a function key is pressed, which will lock the total as the "first" number, allowing a second calculation to be performed on the total
  • The clear button needs to behave as advertised, and totally reset the calculator
  • Visual feedback needs to be used when appropriate, but more importantly, the functionality needs to match the standard set up by the zillions of calculators on desktops worldwide/

The list could go on even, but you see even the simplest of interfaces require much consideration.


The HTML Markup

Nothing fancy here. We need a container for the calculator, we need a display, and we need a bunch of buttons. <div>, <input>, and <a> will do nicely. JavaScript is doing all the work here, there is no "graceful degradition" going on. If JavaScript is disabled, this calculator isn't going to work, so we'll just hide it completely. So, we aren't going to be making this a real <form>, so why the <input>? Inputs allow for text to be manually entered, which we will allow. They allow for easy selectability, and they have the "value" attribute, which is handy for getting and setting.

Here is the whole kit and kaboodle:

<div id="calculator"> 
 
	<input type="text" id="display" /> 
		 
	<a class="num-button seven" href="#">7</a> 
	<a class="num-button eight" href="#">8</a> 
	<a class="num-button nine" href="#">9</a> 
	 
	<a class="num-button four" href="#">4</a> 
	<a class="num-button five" href="#">5</a> 
	<a class="num-button six" href="#">6</a> 
	 
	<a class="num-button one" href="#">1</a> 
	<a class="num-button two" href="#">2</a> 
	<a class="num-button three" href="#">3</a> 
	 
	<a class="num-button zero" href="#">0</a> 
	<a class="num-button dot" href="#">.</a> 
	<a class="clear-button clear" href="#">C</a> 
	 
	<a class="function-button add" href="#"> </a> 
	<a class="function-button subtract" href="#">&ndash;</a> 
	<a class="function-button multiply" href="#">x</a> 
	<a class="function-button divide" href="#">/</a> 
	 
	<a class="equals-button" href="#">=</a> 
		 
</div>

Full Screencast



Creating the Images


We only need a few. The background of the whole calculator, a "number" style button, a "function" style button, and the "equals" button. Each number and function doesn't need it's own image, we'll just lay text right over top. I think "Arial Black" makes a nice bold and calculator-like font.

Later when we make the calculator open and close, we'll use the "mini" calculator graphic and the "close" button.


Styling With CSS

Again nothing too fancy here. Absolute positioning does most of the grunt work making things go where they should go. We set position: relative on the calculator div itself and then we are free to absolutely position inside of it.

Here is the lot of it:

*				{ margin: 0; padding: 0; } 
body				{ font: 25px "Arial Black", Arial, Sans-Serif; } 
 
#page-wrap		    	{ width: 500px; margin: 25px auto; } 
 
h1				{ font-size: 22px; } 
p				{ font: 14px Arial, Sans-Serif; } 
a				{ color: black; text-decoration: none; outline: none; } 
 
#calculator			{ width: 266px; height: 400px; background: url(../images/calc-bg.png) no-repeat; 
	 			  position: relative; display: none; } 
 
#display 			{ background: none; border: none; position: absolute;  
				  top: 15px; left: 15px; width: 197px; text-align: right; 
			  	  font: 35px "Arial Black", Arial, Sans-Serif; } 
					 
.num-button			{ width: 44px; height: 41px; padding: 3px 0 0 0; text-align: center;  
	 		      	  background: url(../images/button-bg.png) no-repeat;  
				  position: absolute; display: block; } 
.clear-button			{ width: 44px; height: 41px; padding: 3px 0 0 0; text-align: center;  
	 		          background: url(../images/button-bg.png) no-repeat;  
				  position: absolute; display: block; } 
.function-button    		{ width: 44px; height: 41px; padding: 3px 0 0 0; text-align: center;  
	 		          background: url(../images/function-button-bg.png) no-repeat;  
				  position: absolute; display: block; } 
.function-button:active, 
.pendingFunction    		{ background-position: bottom left; } 
					 
.seven				{ top: 86px; left: 15px; } 
.eight				{ top: 86px; left: 66px; } 
.nine				{ top: 86px; left: 118px; } 
 
/* ... the rest of the buttons ... */ 
 
.multiply			{ top: 188px; left: 169px; } 
.divide				{ top: 237px; left: 169px; } 
 
.equals-button			{ width: 206px; height: 42px; text-align: center; top: 293px; left: 15px; 
	 			  background: url(../images/equals-bg.png) no-repeat; position: absolute;  
				  display: block; }

Setting Up jQuery Enviornment

In the HTML itself, we have some calls to make in order to get ready to start writing our own jQuery JavaScript. Namely, loading the jQuery and jQuery UI libraries and calling our soon-to-exist JavaScript file.

<script src="http://www.google.com/jsapi" type="text/javascript"></script> 
<script type="text/javascript"> 
    google.load("jquery", "1.3.1"); 
</script> 
<script type="text/javascript" src="js/jquery-ui-personalized-1.6rc6.min.js"></script> 
<script type="text/javascript" src="js/example.js"></script>

Getting Set Up

Let's put the very basics in. We'll need the classic "When the DOM is ready" statement to kick things off. This is common to all JavaScript as we don't want to be doing anything with elements before they all have loaded. Then we set up some basic events that we know are going to happen. We'll have a click event and function for each type of button. We'll also call the jQuery UI function to make our calculator draggable, as well as the tiny little function the allows for toggling it's visibility.

$(function(){ 
		 
	$(".num-button").click(function(){ 
		// do stuff 
	}); 
	 
	$(".clear-button").click(function(){ 
		// do stuff 
	}); 
	 
	$(".function-button").click(function(){ 
		// do stuff 
	}); 
	 
	$(".equals-button").click(function(){ 
	     // do stuff 
	}); 
	 
	$("#calculator").draggable(); 
	 
	$("#opener, #closer").click(function(){ 
		$("#opener").toggle(); 
		$("#calculator").toggle(); 
	}); 
	 
});

Using .data() for Variables, Not Globals

Variables in JavaScript are local to the function they are created in. When you need to pass one to another, you do so by passing it as a parameter when you call the new function. That's all well and good, but we have seven of them and most of our functions are going to need all of them in some way or another. That could get kinda hairy passing all of them all the time. What we really need are global variables, but as I understand it that's kind of a no-no (something with infringing on namespaces).

jQuery has a better way to handle this anyway, the data() function. We can attach "data" to any jQuery object, and that data travels with that object where ever it goes. Basically like an attribute, only you can't really see it in the DOM. This basically does what global variables would do for us and it's just as easy to use. Here are the bits of data that we will use:

  • ValueOne - The first number in the equation
  • ValueOneLocked - Is that first number ready to go?
  • ValueTwo - The second number in the equation
  • ValueTwoLocked - Is that second number ready to go?
  • isPendingFunction - Is there a function selected?
  • thePendingFunction - Which one?
  • fromPrevious - Has a calculation JUST been performed?

We are going to append these bits of data to the #display input, which will kind of be the brains of our calculator. Kinda makes sense. We'll need to set up default values for all these bits of data, but let's think a moment. We could do it right in the main function, but let's be smart and abstract it away to a function. There will be multiple scenarios we will need to reset these values, so let's create a function called resetCalculator to do the job. Then we can call that function whenever we need it. Let's also accept a number paramater, and reset the calculator to that value. When the calculator loads, that will be zero, but after a function, we'll reset again only reset to the final value of the calculation.

function resetCalculator(curValue) { 
	$("#display").val(curValue); 
	$(".function-button").removeClass("pendingFunction"); 
	$("#display").data("isPendingFunction", false); 
	$("#display").data("thePendingFunction", ""); 
	$("#display").data("valueOneLocked", false); 
	$("#display").data("valueTwoLocked", false); 
	$("#display").data("valueOne", curValue); 
	$("#display").data("valueTwo", 0); 
	$("#display").data("fromPrevious", false); 
}

When You Click a Number...

As it turns out, what the calculator does when you click a number is the most complicated bit of code in the whole thing. There are four "states" the calculator can be in and it will behave different when you click on a number depending on that state. They are:

  • A) Clicking on a number in a totally fresh state (display shows zero), or when neither number is yet locked.
  • B) Clicking on a number after a function button has just been selected.
  • C) Clicking on a number after a function button has been selected and additiona numbers have been entered.
  • D) Clicking on a number after a calculation has just been performed.

Our behavior needs to be the following:

  • A) Append the new number to what is already there. If it's a zero, replace that.
  • B) Lock the first number, replace the display with the new number.
  • C) Append the new number to what is already there.
  • D) Reset the calculator to that number number.

And here is all that in code:

$(".num-button").click(function(){ 
 
	if ($("#display").data("fromPrevious") == true) { 
 
		resetCalculator($(this).text()); 
	 
	} else if (($("#display").data("isPendingFunction") == true) && ($("#display").data("valueOneLocked") == false)) { 
	 
		$("#display").data("valueOne", $("#display").val()); 
		$("#display").data("valueOneLocked", true); 
	 
		$("#display").val($(this).text()); 
		$("#display").data("valueTwo", $("#display").val()); 
		$("#display").data("valueTwoLocked", true); 
 
	// Clicking a number AGAIN, after first number locked and already value for second number	 
	} else if (($("#display").data("isPendingFunction") == true) && ($("#display").data("valueOneLocked") == true)) { 
 
		var curValue = $("#display").val(); 
		var toAdd = $(this).text(); 
 
		var newValue = curValue   toAdd; 
 
		$("#display").val(newValue); 
	 
		$("#display").data("valueTwo", $("#display").val()); 
		$("#display").data("valueTwoLocked", true); 
 
	// Clicking on a number fresh	 
	} else { 
 
		var curValue = $("#display").val(); 
		if (curValue == "0") { 
			curValue = ""; 
		} 
 
		var toAdd = $(this).text(); 
 
		var newValue = curValue   toAdd; 
 
		$("#display").val(newValue); 
 
	} 
	 
});

The function button is easier to deal with. Basically when you click it, it locks the first number so the number buttons know that it's time to start the second number. There is one special consideration though, and that is what to do if a function button is pressed immediately after a calcuation is performed. In that case, the first number needs to lock in as the final value and already get ready to accept that second number. This allows for chaining of calcuations, which is quite useful.

When clicked, all function buttons are cleared of a special "pendingFunction" class, and then that class is applied to the specific one clicked. That gives us the CSS control to move the background position and give some visual feedback on which is the currently active function.


$(".function-button").click(function(){ 
 
	if ($("#display").data("fromPrevious") == true) { 
		resetCalculator($("#display").val()); 
		$("#display").data("valueOneLocked", false); 
		$("#display").data("fromPrevious", false) 
	} 
	 
	// Let it be known that a function has been selected 
	var pendingFunction = $(this).text(); 
	$("#display").data("isPendingFunction", true); 
	$("#display").data("thePendingFunction", pendingFunction); 
	 
	// Visually represent the current function 
	$(".function-button").removeClass("pendingFunction"); 
	$(this).addClass("pendingFunction"); 
});

And the Answer Is...

The equals function is also fairly straightforward. The first thing we do is check if we are in fact ready to be doing any calculation. We know this if BOTH numbers are "locked". If they are, do a little math to get the final number and then "reset" the calculator that final value (and set the "fromPrevious" to true so that we know the delicate state we are in).

If the numbers are not locked.... do nothing.

$(".equals-button").click(function(){ 
 
	if (($("#display").data("valueOneLocked") == true) && ($("#display").data("valueTwoLocked") == true)) { 
		 
		if ($("#display").data("thePendingFunction") == " ") { 
			var finalValue = parseFloat($("#display").data("valueOne"))   parseFloat($("#display").data("valueTwo")); 
		} else if ($("#display").data("thePendingFunction") == "%u2013") { 
			var finalValue = parseFloat($("#display").data("valueOne")) - parseFloat($("#display").data("valueTwo")); 
		} else if ($("#display").data("thePendingFunction") == "x") { 
			var finalValue = parseFloat($("#display").data("valueOne")) * parseFloat($("#display").data("valueTwo")); 
		} else if ($("#display").data("thePendingFunction") == "/") { 
			var finalValue = parseFloat($("#display").data("valueOne")) / parseFloat($("#display").data("valueTwo")); 
		} 
		 
		$("#display").val(finalValue); 
					 
		resetCalculator(finalValue); 
		$("#display").data("fromPrevious", true); 
					 
	} else { 
		// both numbers are not locked, do nothing. 
	} 
	 
});

CLEAR!

Last but not least, the clear button is the simplest of them all. Press it, clear calcuator to zero.

$(".clear-button").click(function(){ 
	resetCalculator("0"); 
});

And We're Done



That does it folks. I hope between the live example, the screencast, and this written tutorial this has been a valuable learning session. I certainly learned a few things while creating it. If you can think of any extremely useful applications of this, contact me and let me know.

Advertisement