Last weekend I discovered an excellent project to learn about and practice jQuery. The project is by Chris Coyier a web developer who writes a blog called CSS-Tricks. The project Chris walks you through is entitled Creating an Incredible jQuery Calculator. This project walk through is very easy to understand and it was a lot of fun. After looking through Chris’s work I decided to take a stab at using his ideas as a jumping off spot. So, I modified the code and created a version of the jQuery Calculator for myself with some additions and perhaps a few improvements.
At the outset I had 3 main goals that I wanted to accomplish in my version of the calculator:
- Eliminate all the graphics used for the calculator GUI. Instead, I wanted my calculator’s interface to be drawn entirely with CSS.
- Add functionality to change the sign (plus/minus) of numbers. Most simple calculators have the ability to change the numbers from positive to negative and visa versa.
- Add percentage functionality. Again, this is a common button you see on simple calculators.
Along the way I discovered a few additional things that I ended up including to the finished calculator. Below is my explanation of my version of the jQuery Calculator. I encourage you to take a look at Chris’s version and his code explanation first as this will help you understand my modifications. If you just want to see my calculator in action then click Glen’s jQuery Calculator link or the image below.
This first section is the actual HTML source code. Here I just added the divs for the plus/minus (change sign) button and the percent button. One interesting thing I also did was use the actual HTML accent entity code for the classic division symbol. This is the symbol most often seen on a real calculator. Using the accent entity code made for an interesting code challenge when checking for the division function in the jQuery code. I had to use a string function, String.fromCharCode(247), in order to make it work.
Another important element is the div with the class of numpad. This div is used to create the main body of the calculator in the stylesheet.
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<title>jQuery Calculator Improved</title>
<link href='https://fonts.googleapis.com/css?family=Rock+Salt' rel='stylesheet' type='text/css'>
<link type="text/css" rel="stylesheet" href="css/jcalcstyle.css" />
<link rel="stylesheet" href="css/clock.css">
<script src="js/jquery.js" type="text/javascript"></script>
<script type="text/javascript" src="js/jcalc.js"></script>
</head>
<body>
<div id="titleSection">
<h1>The jQuery Calculator Improved</h1>
<hr noshade width="500" size="4" color="#000000" />
<p>This calculator was developed based on <a class="chrisLink" href="https://code.tutsplus.com/tutorials/creating-an-incredible-jquery-calculator--pre-8813" target="_blank"> Chris Coyler's original code</a>.</p>
<p>This new calculator has two new functions, the change sign and percent functions.</p>
<p>In addition, this calculator's GUI is produced entirely <br />with CSS. No graphics are used.</p>
<p>Open it up, drag it around, push buttons,<br /> and calculate stuff!</p>
<img src="images/minicalc.png" alt="open calculator" id="opener" />
</div>
<div id="calculator">
<div class="numpad"></div>
<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-button zero" href="#">0</a>
<a class="num-button dot" href="#">.</a>
<a class="clear-button clear" href="#">C</a>
<a class="function-button divide" href="#">÷</a>
<a class="function-button multiply" href="#">x</a>
<a class="function-button add" href="#">+</a>
<a class="function-button subtract" href="#">–</a>
<a class="percent-button percent" href="#">%</a>
<a class="plusminus-button plusminus" href="#">±</a>
<a class="equals-button" href="#">=</a>
<div id="closer">x</div>
</div>
<div id="notePad">Visit my web site and read my <a style="color:#ca483c; text-decoration:underline" href="https://www.glenmtaylor.com/jquery-calculator-project">blog post</a> about this project. <br />Thanks, <br /> Glen</div>
<div id="clock">
<article class="clock simple">
<div class="hours-container">
<div class="hours"></div>
</div>
<div class="minutes-container">
<div class="minutes"></div>
</div>
<div class="seconds-container">
<div class="seconds"></div>
</div>
</article>
</div>
<script type="text/javascript" src="js/jquery-ui-personalized-1.6rc6.min.js"></script>
<script src="js/clock.js"></script>
</body>
</html>
The real action for the GUI happens here in the stylesheet. For the buttons I used the border-radius style. The key to making the perfect round button is to make sure the width and the height are the same size. Then all you do is set border-radius to 50%. Another key style I used was the radial-gradient. This gives the buttons a 3-D look. In the jQuery code I also captured click events for the function buttons and then reversed the gradient to make the buttons appear as if they had been pushed down. The closer class has another interesting style, border: inset. This gives the small circular button used to minimize the calculator a beveled look. Finally I used rgba color styles for the calculator body and display so that the background images on the page could be seen through it.
@charset "UTF-8";
/* CSS Document */
* { margin: 0; padding: 0; }
body { background-image:url(../images/wood-desk-top.jpg); background-position: -50px 0; background-repeat: no-repeat; font: 25px "Arial Black", Arial, Sans-Serif; }
#page-wrap { width: 500px; margin: 25px auto; }
h1 { font-size: 22px; color:DodgerBlue; text-shadow: 2px 2px 2px #ffffff; }
p { font: 18px Arial, Sans-Serif; color:#ffffff; text-shadow: 1px 1px 1px #000000; padding-top: 5px; }
a { color: black; text-decoration: none; outline: none; }
img { margin-top:5px;}
#titleSection { margin: 10px 0 0 10px;}
.chrisLink { color: yellow; text-decoration: underline; outline: none; }
#calculator { width: 266px; height: 400px; background-color:transparent;
position: relative; display:none;}
.numpad{
width: 202px; height: 270px; background-color:rgba(201,201,201,0.5); top: 67px; left: 15px;
position: absolute; border:solid #FFC100; border-radius:0px 0px 25px 25px;
}
#display { background:rgba(181,238, 249, 0.95); border:solid #FFC100; border-radius:25px 25px 0 0; position: absolute;
top: 15px; left: 15px; width: 197px; text-align: right; padding-right:5px;
font: 35px "Arial Black", Arial, Sans-Serif; }
.num-button {
width: 40px;
height: 40px;
padding: 3px 0 0 0;
text-align: center;
border-radius: 50%;
background-image: -webkit-radial-gradient(45px 45px, circle cover, gray, white);
background-image: -moz-radial-gradient(45px 45px, circle cover, gray, white);
background-image: radial-gradient(45px 45px, circle cover, gray, white);
position: absolute;
display: block;
}
.zero-button {
width: 90px;
height: 40px;
padding: 3px 0 0 0;
text-align: center;
border-radius: 25px;
background-image: -webkit-radial-gradient(45px 45px, circle cover, gray, white);
background-image: -moz-radial-gradient(45px 45px, circle cover, gray, white);
background-image: radial-gradient(45px 45px, circle cover, gray, white);
position: absolute;
display: block;
}
.clear-button {
width: 40px;
height: 40px;
padding: 3px 0 0 0;
text-align: center;
border-radius: 50%;
color: firebrick;
background-image: -webkit-radial-gradient(45px 45px, circle cover, gray, white);
background-image: -moz-radial-gradient(45px 45px, circle cover, gray, white);
background-image: radial-gradient(45px 45px, circle cover, gray, white);
position: absolute;
display: block;
}
.function-button {
width: 40px;
height: 40px;
padding: 3px 0 0 0;
text-align: center;
border-radius: 50%;
background-image: -webkit-radial-gradient(45px 45px, circle cover, orange, yellow);
background-image: -moz-radial-gradient(45px 45px, circle cover, orange, yellow);
background-image: radial-gradient(45px 45px, circle cover, orange, yellow);
position: absolute; display: block;
}
.plusminus-button {
width: 40px;
height: 40px;
padding: 3px 0 0 0;
text-align: center;
border-radius: 50%;
background-image: -webkit-radial-gradient(45px 45px, circle cover, gray, lightskyblue);
background-image: -moz-radial-gradient(45px 45px, circle cover, gray, lightskyblue);
background-image: radial-gradient(45px 45px, circle cover, gray, lightskyblue);
position: absolute; display: block;
}
.percent-button {
width: 40px;
height: 40px;
padding: 3px 0 0 0;
text-align: center;
border-radius: 50%;
background-image: -webkit-radial-gradient(45px 45px, circle cover, gray, lightskyblue);
background-image: -moz-radial-gradient(45px 45px, circle cover, gray, lightskyblue);
background-image: radial-gradient(45px 45px, circle cover, gray, lightskyblue);
position: absolute; display: block;
}
.function-button:active,
.pendingFunction { background-image: -webkit-radial-gradient(45px 45px, circle cover, yellow, orange); }
.equals-button {
width: 40px;
height: 40px;
top: 284px;
left: 176px;
padding: 3px 0 0 0;
text-align: center;
border-radius: 50%;
background-image: -webkit-radial-gradient(45px 45px, circle cover, orange, yellow);
background-image: -moz-radial-gradient(45px 45px, circle cover, orange, yellow);
background-image: radial-gradient(45px 45px, circle cover, orange, yellow);
position: absolute; display: block;
}
.seven { top: 131px; left: 22px; }
.eight { top: 131px; left: 72px; }
.nine { top: 131px; left: 125px; }
.four { top: 182px; left: 22px; }
.five { top: 182px; left: 72px; }
.six { top: 182px; left: 125px; }
.one { top: 233px; left: 22px; }
.two { top: 233px; left: 72px; }
.three { top: 233px; left: 125px; }
.zero { top: 284px; left: 22px; }
.dot { top: 284px; left: 125px; }
.clear { top: 80px; left: 22px; }
.divide { top: 80px; left: 176px; }
.multiply { top: 131px; left: 176px; }
.add { top: 182px; left: 176px; }
.subtract { top: 233px; left: 176px; }
.percent { top: 80px; left: 125px; }
.plusminus { top: 80px; left: 73px; }
#closer { font-size:12px; color:white; width:20px; height:20px; border-radius:50%; background:black; border:inset; position: absolute; top: 9px; left: 7px; text-align:center; }
#notePad { width: 400px; font-family: 'Rock Salt', cursive; position:absolute; top: 185px; left: 800px; -webkit-transform:rotate(12deg); -moz-transform: rotate(12deg); -ms-transform: rotate(12deg); z-index: -1; }
In the jQuery code I had to add functionality for the change sign and percent functionality. This involved using logic that captured when certain events took place. One challenge I had to overcome was when I needed the second number in an equation to have the sign changed. I used Chris’s logic from his number selection functionality and modified it to suit the circumstances for changing signs.
For the percent functionality I was surprised that there are actually two logic models for a percent sign on a calculator, one for addition/subtraction and another for multiplication/division. With addition and subtraction the logic is:
88 First number is 88
+ Addition is the operation
5 5 is the second value
% Percent has been pressed and this is now a modifier of the second value 5 and then acts as a second modifier of 88.
88 x (5 / 100) = 4.4 – Now this is the second number.
= 88 + 4.4 = 92.4
With multiplication and division the logic is different.
88 First number is 88
x Multiplication is the operation
5 5 is the second value
% Percent has been pressed and this is now a modifier of the second value 5
5 / 100 = 0.05 Now this is the second number
= 88 x 0.05 = 4.4
Therefore, I had to incorporate this logic into the percent function. Also, I had to account for the scenario when the percent button was selected after only the first number was selected.
Other new aspects that I added to Chris’s include the mouse events that interact with the styles that give the calculator a dynamic feel.
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);
}
function debugStates() {
console.log("------------")
console.log("isPendingFunction: " + $("#display").data("isPendingFunction"));
console.log("thePendingFunction: " + $("#display").data("thePendingFunction"));
console.log("valueOneLocked: " + $("#display").data("valueOneLocked"));
console.log("valueTwoLocked: " + $("#display").data("valueTwoLocked"));
console.log("valueOne: " + $("#display").data("valueOne"));
console.log("valueTwo: " + $("#display").data("valueTwo"));
console.log("fromPrevious: " + $("#display").data("fromPrevious"));
};
$(function(){
resetCalculator("0");
$(".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);
}
});
// Start Clear Button
$(".clear-button").click(function(){
resetCalculator("0");
});
// End Clear Button
//Start Change Sign of Number
$(".plusminus-button").click(function(){
if (($("#display").data("valueOneLocked") == false) && ($("#display").data("valueTwoLocked") == false)) {
var changesign = $("#display").val();
changesign = changesign * -1;
$("#display").val(changesign);
}
else if (($("#display").data("valueOneLocked") == true)) {
var changesign = $("#display").data("valueTwo");
changesign = changesign * -1;
$("#display").data("valueTwo", changesign);
$("#display").val(changesign);
}
else if (($("#display").data("valueTwoLocked") == true)) {
var changesign = $("#display").data("valueOne");
changesign = changesign * -1;
$("#display").data("valueOne", changesign);
$("#display").val(changesign);
}
});
// Percent operations
$(".percent-button").click(function(){
if (($("#display").data("valueOneLocked") == false) && ($("#display").data("valueTwoLocked") == false)) {
var percentNum = $("#display").val();
percentNum = percentNum / 100;
$("#display").val(percentNum);
}
else if (($("#display").data("valueTwoLocked") == true) && ($("#display").data("thePendingFunction") == "–") || ($("#display").data("thePendingFunction") == "+")) {
var percentNum = $("#display").data("valueTwo");
var newNum = $("#display").data("valueOne");
percentNum = percentNum / 100;
newNum = newNum * percentNum;
$("#display").val(newNum);
$("#display").data("valueTwo", newNum);
}
else if (($("#display").data("valueTwoLocked") == true) && ($("#display").data("thePendingFunction") == "x") || ($("#display").data("thePendingFunction") == String.fromCharCode(247))) {
var percentNum = $("#display").data("valueTwo");
percentNum = percentNum / 100;
$("#display").val(percentNum);
$("#display").data("valueTwo", percentNum);
}
});
$(".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");
});
$(".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") == "–") {
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") == String.fromCharCode(247)) {
var finalValue = parseFloat($("#display").data("valueOne")) / parseFloat($("#display").data("valueTwo"));
}
if (finalValue % 1 != 0) {
var decimalPlaces = finalValue.toString().split(".")[1].length;
if (decimalPlaces > 6) {
finalValue = finalValue.toPrecision(6);
$("#display").val(finalValue);
}
else {
finalValue = finalValue.toPrecision();
$("#display").val(finalValue);
}
}
else {
$("#display").val(finalValue);
}
resetCalculator(finalValue);
$("#display").data("fromPrevious", true);
} else {
// both numbers are not locked, do nothing.
}
});
$("#calculator").draggable();
$("#opener, #closer").click(function(){
$("#opener").toggle();
$("#calculator").toggle();
});
//Additional Mouse events
$("#opener, #closer, .numpad").mouseenter(function() {
$(this).css('cursor','pointer');
});
$("#closer").mouseenter(function() {
$(this).css({
'background': 'white',
'color': 'black'
})
});
$("#closer").mouseleave(function() {
$(this).css({
'background': 'black',
'color': 'white'
})
});
});
This project was a ton of fun. I really want to thank Chris for his post, Creating an Incredible jQuery Calculator.
I might extend this further by making it into a scientific calculator. We’ll see. 🙂