Skip to content

Instantly share code, notes, and snippets.

@grayrest
Created January 23, 2010 06:45
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save grayrest/284468 to your computer and use it in GitHub Desktop.
Save grayrest/284468 to your computer and use it in GitHub Desktop.
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""
"http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<title>jQuery calculator demo</title>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.0/jquery.min.js" type="text/javascript" charset="utf-8"></script>
</head>
<body>
<div id="calculator">
<input name="display" class="expr" id="expr" value="" size="25" />
</div>
<script type="text/javascript">
//I'm being lazy here and using a global for the text field.
var expr = $('#expr');
// The list of operations we support in the calc.
var operations = {
'\u221A' : Math.sqrt,
'x\u00B2' : function(x){return x * x;},
'\u00B1' : function(x){return x * -1;},
'cos' : Math.cos,
'sin' : Math.sin,
'tan' : Math.tan,
'ln' : Math.log,
'exp' : Math.exp,
'AC' : function(){ expr.val('0'); },
'Del\u2190': function(){ expr.val(expr.val().slice(0,-1)); },
'=' : function(){
try{
expr.val(eval(expr.val() || 0))
} catch (e) {
//clear if we have an evaluation error somehow
operations['AC']();
}
}
};
// given a character code, appends the appropriate char onto the
// end of the text input.
var key_literal = function(which){
expr.val(expr.val() + String.fromCharCode(which))
}
// keys bound to operations. This set is meant to be called
// using keyDown. The each loop does the actual binding.
var key_ops = {
'13' : '=', // Enter
'107' : '=', // =
'8' : 'Del\u2190', // BS
'27' : 'AC' // Esc
};
$.each(key_ops, function(k,fn){
key_ops[k] = operations[fn];
});
// The set of valid input characters to our text field.
// There are plugins to do this, but this is a key handling
// demo.
var key_map = $.extend(key_map,{
'43' : key_literal, // +
'45' : key_literal, // -
'42' : key_literal, // *
'47' : key_literal, // /
'40' : key_literal, // (
'41' : key_literal, // )
'46' : key_literal // .
});
// this loop tacks the digits onto the literals list
for(var i = 48; i < 58; i++){
key_map[''+i] = key_literal;
}
// Okay, here's the main event. The important thing to know is
// that there are TWO types of key events in javascript:
// keydown/keyup and keypress.
//
// The keydown/keyup pair fires one time on the falling/rising
// edge of the keystroke, respectively. They use the RAW key
// codes from the keyboard and you check them (in jQuery) using
// keyCode. This raw keyCode makes them useful for checking
// for Escape, Arrow Keys, and anything else that isn't
// normally printable.
//
// The main reason you don't want to use this for printable
// characters is due to international keyboard maps. The
// shift+4 returns the same value as normal 4. If you're using
// a US keyboard layout you might naïvely get the code, check
// the shiftKey and assume the user typed $. This would cause
// the incorrect value to be entered for all the other peoples
// in the world.
//
// The other event is the keypress event. This will fire
// repeatedly if the user holds down a key and you check the
// value (in jQuery) using whcih. This attribute will contain
// the ASCII value for the associated character allowing you to
// use String.fromCharCode to look up the appropriate character
// and insert it in your doc. This won't work for most
// non-printable characters.
// This key handler inserts one of our (valid only) printable
// characters into our text area. Since it's on the document,
// it will actually work with the focus anywhere in the page.
$(document).keypress(function(e){
if(!e.ctrlKey && !e.altKey && !e.metaKey){
if(e.which in key_map) key_map[e.which](e.which);
}
});
// This is the matching page-wide operations handler. It takes
// care of the non-printable characters we care about.
$(document).keydown(function(e){
if(!e.ctrlKey && !e.altKey && !e.metaKey){
if(e.keyCode in key_ops) key_ops[e.keyCode]();
}
});
//This handler specially handles the case when we're in the
//text field.
expr.keypress(function(e){
if(!e.ctrlKey && !e.altKey && !e.metaKey){
if(!(e.which in key_map)){
//don't allow non-valid keys
e.preventDefault();
} else {
// prevent key_literal from moving the insertion
// point. It's annoying to click in the middle to
// edit and have your typed text jump to the end
e.stopPropagation();
}
}
});
// With all the setup out of the way, the calculator itself is
// super-easy. Instead of mucking about with all the markup,
// I'll build it programatically.
//
// Note that we have a functional keyboard calculator at this
// point without the need for buttons. The buttons provide us
// some extra functions but aren't at all necessary for the
// basic functioning of our calculator.
var calculator_layout = [
['AC' , '(' , ')' , 'Del\u2190'],
['cos' , 'sin' , 'tan' ],
['\u221A' , 'x\u00B2' , 'exp' , 'ln'],
['7' , '8' , '9' , '/' ],
['4' , '5' , '6' , '*' ],
['1' , '2' , '3' , '-' ],
['0' , '.' , '\u00B1' , '+' ],
['=']];
// Given the above layout, put each row in a div and turn each
// string into a button showing the corresponding text.
//
// Note that if you're inserting a large amount of nodes or
// have complex CSS rules, it's much faster do append to a
// documentFragment and then swap in/append the fragment.Doing
// it this way is slightly easier but forces a layout reflow
// every time we do the append to #calculator.
$.each(calculator_layout, function(i, row){
var el_row = $('<div class="calc_row"></div>');
$.each(row, function(j, key){
el_row.append('<input type="button" class="calckey" value="'+key+'" />');
});
$('#calculator').append(el_row);
});
// Now that our buttons are created, we select them all...
$('.calckey')
// style them to get them looking grid-like...
.css('width','45px')
// and set up the click behavior on each...
.click(function(e){
var self = $(this),
val = self.val();
if(val in operations){
// This test is some javascript ninja magic. The
// .length property on a function is the number of
// arguments it expects. The operations just happen
// to be written so that all the mathematic
// operatons take one parameter.
if(operations[val].length == 1){
operations['='](); //evaluate
expr.val(operations[val](parseFloat(expr.val())));
} else {
operations[val]();
}
} else {
expr.val(expr.val() + val)
}
})
// And I'll special case the equals button so it looks
// nice. You could also use .find('input[value="="]')
// instead of .last()
.last().css('width',45*4+'px');
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment