META-INF.web-resources.voila-runtime.jquery.calculator.jquery.calculator.js Maven / Gradle / Ivy
The newest version!
/* http://keith-wood.name/calculator.html
Calculator field entry extension for jQuery v1.3.2.
Written by Keith Wood (kbwood{at}iinet.com.au) October 2008.
Dual licensed under the GPL (http://dev.jquery.com/browser/trunk/jquery/GPL-LICENSE.txt) and
MIT (http://dev.jquery.com/browser/trunk/jquery/MIT-LICENSE.txt) licenses.
Please attribute the author if you use it. */
(function($) { // hide the namespace
var PROP_NAME = 'calculator';
/* Calculator manager.
Use the singleton instance of this class, $.calculator, to interact with the plugin.
Settings for calculator fields are maintained in instance objects,
allowing multiple different settings on the same page. */
function Calculator() {
this._curInst = null; // The current instance in use
this._disabledFields = []; // List of calculator inputs that have been disabled
this._showingCalculator = false; // True if the popup panel is showing , false if not
this._showingKeystrokes = false; // True if showing keystrokes for calculator buttons
/* The definitions of the buttons that may appear on the calculator.
Key is ID. Fields are display text, button type, function,
class(es), field name, keystroke, keystroke name. */
this._keyDefs = {
'_0': ['0', this.digit, null, '', '0', '0'],
'_1': ['1', this.digit, null, '', '1', '1'],
'_2': ['2', this.digit, null, '', '2', '2'],
'_3': ['3', this.digit, null, '', '3', '3'],
'_4': ['4', this.digit, null, '', '4', '4'],
'_5': ['5', this.digit, null, '', '5', '5'],
'_6': ['6', this.digit, null, '', '6', '6'],
'_7': ['7', this.digit, null, '', '7', '7'],
'_8': ['8', this.digit, null, '', '8', '8'],
'_9': ['9', this.digit, null, '', '9', '9'],
'_A': ['A', this.digit, null, 'hex-digit', 'A', 'a'],
'_B': ['B', this.digit, null, 'hex-digit', 'B', 'b'],
'_C': ['C', this.digit, null, 'hex-digit', 'C', 'c'],
'_D': ['D', this.digit, null, 'hex-digit', 'D', 'd'],
'_E': ['E', this.digit, null, 'hex-digit', 'E', 'e'],
'_F': ['F', this.digit, null, 'hex-digit', 'F', 'f'],
'_.': ['.', this.digit, null, 'decimal', 'DECIMAL', '.'],
'_+': ['+', this.binary, this._add, 'arith add', 'ADD', '+'],
'_-': ['-', this.binary, this._subtract, 'arith subtract', 'SUBTRACT', '-'],
'_*': ['*', this.binary, this._multiply, 'arith multiply', 'MULTIPLY', '*'],
'_/': ['/', this.binary, this._divide, 'arith divide', 'DIVIDE', '/'],
'_%': ['%', this.unary, this._percent, 'arith percent', 'PERCENT', '%'],
'_=': ['=', this.unary, this._equals, 'arith equals', 'EQUALS', '='],
'+-': ['±', this.unary, this._plusMinus, 'arith plus-minus', 'PLUS_MINUS', '#'],
'PI': ['pi', this.unary, this._pi, 'pi', 'PI', 'p'],
'1X': ['1/x', this.unary, this._inverse, 'fn inverse', 'INV', 'i'],
'LG': ['log', this.unary, this._log, 'fn log', 'LOG', 'l'],
'LN': ['ln', this.unary, this._ln, 'fn ln', 'LN', 'n'],
'EX': ['eⁿ', this.unary, this._exp, 'fn exp', 'EXP', 'E'],
'SQ': ['x²', this.unary, this._sqr, 'fn sqr', 'SQR', '@'],
'SR': ['√', this.unary, this._sqrt, 'fn sqrt', 'SQRT', '!'],
'XY': ['x^y', this.binary, this._power, 'fn power', 'POWER', '^'],
'RN': ['rnd', this.unary, this._random, 'random', 'RANDOM', '?'],
'SN': ['sin', this.unary, this._sin, 'trig sin', 'SIN', 's'],
'CS': ['cos', this.unary, this._cos, 'trig cos', 'COS', 'o'],
'TN': ['tan', this.unary, this._tan, 'trig tan', 'TAN', 't'],
'AS': ['asin', this.unary, this._asin, 'trig asin', 'ASIN', 'S'],
'AC': ['acos', this.unary, this._acos, 'trig acos', 'ACOS', 'O'],
'AT': ['atan', this.unary, this._atan, 'trig atan', 'ATAN', 'T'],
'MC': ['#memClear', this.unary, this._memClear, 'memory mem-clear', 'MEM_CLEAR', 'x'],
'MR': ['#memRecall', this.unary, this._memRecall, 'memory mem-recall', 'MEM_RECALL', 'r'],
'MS': ['#memStore', this.unary, this._memStore, 'memory mem-store', 'MEM_STORE', 'm'],
'M+': ['#memAdd', this.unary, this._memAdd, 'memory mem-add', 'MEM_ADD', '>'],
'M-': ['#memSubtract', this.unary, this._memSubtract, 'memory mem-subtract', 'MEM_SUBTRACT', '<'],
'BB': ['#base2', this.control, this._base2, 'base base2', 'BASE_2', 'B'],
'BO': ['#base8', this.control, this._base8, 'base base8', 'BASE_8', 'C'],
'BD': ['#base10', this.control, this._base10, 'base base10', 'BASE_10', 'D'],
'BH': ['#base16', this.control, this._base16, 'base base16', 'BASE_16', 'H'],
'DG': ['#degrees', this.control, this._degrees, 'angle degrees', 'DEGREES', 'G'],
'RD': ['#radians', this.control, this._radians, 'angle radians', 'RADIANS', 'R'],
'BS': ['#backspace', this.control, this._undo, 'undo', 'UNDO', 8, 'BSp'], // backspace
'CE': ['#clearError', this.control, this._clearError, 'clear-error', 'CLEAR_ERROR', 36, 'Hom'], // home
'CA': ['#clear', this.control, this._clear, 'clear', 'CLEAR', 35, 'End'], // end
'@X': ['#close', this.control, this._close, 'close', 'CLOSE', 27, 'Esc'], // escape
'@U': ['#use', this.control, this._use, 'use', 'USE', 13, 'Ent'], // enter
'@E': ['#erase', this.control, this._erase, 'erase', 'ERASE', 46, 'Del'], // delete
' ': ['', this.space, null, 'space', 'SPACE'],
'_ ': ['', this.space, null, 'half-space', 'HALF_SPACE'],
'??': ['??', this.unary, this._noOp]
};
this._keyCodes = {};
this._keyChars = {};
for (var code in this._keyDefs) {
if (this._keyDefs[code][4]) {
this[this._keyDefs[code][4]] = code;
}
if (this._keyDefs[code][5]) {
if (typeof this._keyDefs[code][5] == 'number') {
this._keyCodes[this._keyDefs[code][5]] = code;
}
else {
this._keyChars[this._keyDefs[code][5]] = code;
}
}
}
this.regional = []; // Available regional settings, indexed by language code
this.regional[''] = { // Default regional settings
decimalChar: '.', // Character for the decimal point
buttonText: '...', // Display text for trigger button
buttonStatus: 'Open the calculator', // Status text for trigger button
closeText: 'Close', // Display text for close link
closeStatus: 'Close the calculator', // Status text for close link
useText: 'Use', // Display text for use link
useStatus: 'Use the current value', // Status text for use link
eraseText: 'Erase', // Display text for erase link
eraseStatus: 'Erase the value from the field', // Status text for erase link
backspaceText: 'BS', // Display text for backspace link
backspaceStatus: 'Erase the last digit', // Status text for backspace link
clearErrorText: 'CE', // Display text for clear error link
clearErrorStatus: 'Erase the last number', // Status text for clear error link
clearText: 'CA', // Display text for clear link
clearStatus: 'Reset the calculator', // Status text for clear link
memClearText: 'MC', // Display text for memory clear link
memClearStatus: 'Clear the memory', // Status text for memory clear link
memRecallText: 'MR', // Display text for memory recall link
memRecallStatus: 'Recall the value from memory', // Status text for memory recall link
memStoreText: 'MS', // Display text for memory store link
memStoreStatus: 'Store the value in memory', // Status text for memory store link
memAddText: 'M+', // Display text for memory add link
memAddStatus: 'Add to memory', // Status text for memory add link
memSubtractText: 'M-', // Display text for memory subtract link
memSubtractStatus: 'Subtract from memory', // Status text for memory subtract link
base2Text: 'Bin', // Display text for base 2 link
base2Status: 'Switch to binary', // Status text for base 2 link
base8Text: 'Oct', // Display text for base 8 link
base8Status: 'Switch to octal', // Status text for base 8 link
base10Text: 'Dec', // Display text for base 10 link
base10Status: 'Switch to decimal', // Status text for base 10 link
base16Text: 'Hex', // Display text for base 16 link
base16Status: 'Switch to hexadecimal', // Status text for base 16 link
degreesText: 'Deg', // Display text for degrees link
degreesStatus: 'Switch to degrees', // Status text for degrees link
radiansText: 'Rad', // Display text for radians link
radiansStatus: 'Switch to radians', // Status text for radians link
isRTL: false // True if right-to-left language, false if left-to-right
};
this._defaults = { // Global defaults for all the calculator instances
showOn: 'focus', // 'focus' for popup on focus, 'button' for trigger button,
// 'both' for either, 'operator' for non-numeric character entered,
// 'opbutton' for operator/button combination
buttonImage: '', // URL for trigger button image
buttonImageOnly: false, // True if the image appears alone, false if it appears on a button
isOperator: null, // Call back function to determine if a keystroke opens the calculator
showAnim: 'show', // Name of jQuery animation for popup
showOptions: {}, // Options for enhanced animations
duration: 'fast', // Duration of display/closure
appendText: '', // Display text following the input box, e.g. showing the format
useThemeRoller: false, // True to add ThemeRoller classes
calculatorClass: '', // Additional CSS class for the calculator for an instance
prompt: '', // Text across the top of the calculator
layout: this.standardLayout, // Layout of keys
value: 0, // The initial value for inline calculators
base: 10, // The numeric base for calculations
precision: 10, // The number of digits of precision to use in rounding for display
memoryAsCookie: false, // True to save memory into cookie, false for memory only
cookieName: 'calculatorMemory', // Name of cookie for memory
cookieExpires: 24 * 60 * 60, // The time that the memory cookie expires as a Date or number of seconds
cookiePath: '', // The path for the memory cookie
useDegrees: false, // True to use degress for trigonometric functions, false for radians
constrainInput: true, // True to restrict typed characters to numerics, false to allow anything
onOpen: null, // Define a callback function before the panel is opened
onButton: null, // Define a callback function when a button is activated
onClose: null // Define a callback function when the panel is closed
};
$.extend(this._defaults, this.regional['']);
this.mainDiv = $('').
click(this._focusEntry);
}
$.extend(Calculator.prototype, {
// Class name added to elements to indicate already configured with calculator
markerClassName: 'hasCalculator',
digit: 'd', // Indicator of a digit key
binary: 'b', // Indicator of a binary operation key
unary: 'u', // Indicator of a unary operation key
control: 'c', // Indicator of a control key
space: 's', // Indicator of a space
_mainDivClass: 'calculator-popup', // The name of the main calculator division marker class
_inlineClass: 'calculator-inline', // The name of the inline marker class
_appendClass: 'calculator-append', // The name of the appended text marker class
_triggerClass: 'calculator-trigger', // The name of the trigger marker class
_disableClass: 'calculator-disabled', // The name of the disabled covering marker class
_inlineEntryClass: 'calculator-keyentry', // The name of the inline entry marker class
_resultClass: 'calculator-result', // The name of the calculator result marker class
_focussedClass: 'calculator-focussed', // The name of the focussed marker class
_keystrokeClass: 'calculator-keystroke', // The name of the keystroke marker class
_coverClass: 'calculator-cover', // The name of the IE select cover marker class
/* The standard calculator layout with simple operations. */
standardLayout: [' BSCECA', '_1_2_3_+@X', '_4_5_6_-@U', '_7_8_9_*@E', '_0_._=_/'],
/* The extended calculator layout with common scientific operations. */
scientificLayout: ['@X@U@E BSCECA', 'DGRD _ MC_ _7_8_9_+', 'SNASSRLG_ MR_ _4_5_6_-',
'CSACSQLN_ MS_ _1_2_3_*', 'TNATXYEX_ M+_ _0_.+-_/', 'PIRN1X _ M-_ _%_='],
/* Override the default settings for all instances of calculator.
@param settings (object) the new settings to use as defaults
@return (object) the calculator object for chaining further calls */
setDefaults: function(settings) {
extendRemove(this._defaults, settings || {});
return this;
},
/* Add a new key definition.
@param code (string) the two-character code for this key
@param label (string) the display label for this key
@param type (boolean) true if this is a binary operator,
false if a unary operator, or (string) key type - use
constants ($.calculator.) digit, binary, unary, space, control
@param func (function) the function that applies this key -
it is expected to take a parameter of the current instance
@param style (string, optional) any additional CSS styles for this key
@param constant (string, optional) the name of a constant to create for this key
@param keystroke (char or number) the character or key code of the keystroke for this key
@param keyName (string) the name of the keystroke for this key
@return (object) the calculator object for chaining further calls */
addKeyDef: function(code, label, type, func, style, constant, keystroke, keyName) {
this._keyDefs[code] = [label, (typeof type == 'boolean' ?
(type ? this.binary : this.unary) : type), func, style, constant, keystroke, keyName];
if (constant) {
this[constant] = code;
}
if (keystroke) {
if (typeof keystroke == 'number') {
this._keyCodes[keystroke] = code;
}
else {
this._keyChars[keystroke] = code;
}
}
return this;
},
/* Attach the calculator to a jQuery selection.
@param target (element) the target input field or division/span
@param settings (object) the new settings to use for this instance */
_attachCalculator: function(target, settings) {
var $target = $(target);
var inline = target.nodeName.toLowerCase() != 'input';
var keyEntry = (!inline ? $target :
$(''));
var inst = {_input: keyEntry, _inline: inline, memory: 0,
_mainDiv: (inline ? $('') :
this.mainDiv)};
inst.settings = $.extend({}, settings || {});
if (this._get(inst, 'memoryAsCookie')) {
var memory = this._getMemoryCookie(inst);
if (memory && !isNaN(memory)) {
inst.memory = memory;
}
}
this._connectCalculator(target, inst);
if (inline) {
$target.append(keyEntry).append(inst._mainDiv).
bind('click.calculator', function() { keyEntry.focus(); });
this._reset(inst, '0');
this._setValue(inst);
this._updateCalculator(inst);
}
else if ($(target).is(':disabled')) {
this._disableCalculator(target);
}
},
/* Attach the calculator to an input field or division/span.
@param target (element) the target control
@param inst (object) the instance settings */
_connectCalculator: function(target, inst) {
var control = $(target);
if (control.hasClass(this.markerClassName)) {
return;
}
var appendText = this._get(inst, 'appendText');
var isRTL = this._get(inst, 'isRTL');
if (appendText) {
control[isRTL ? 'before' : 'after'](
'' + appendText + '');
}
if (!inst._inline) {
var showOn = this._get(inst, 'showOn');
if (showOn == 'focus' || showOn == 'both') { // pop-up calculator when in the marked field
control.focus(this._showCalculator);
}
if (showOn == 'button' || showOn == 'both' || showOn == 'opbutton') { // pop-up calculator when button clicked
var buttonText = this._get(inst, 'buttonText');
var buttonStatus = this._get(inst, 'buttonStatus');
var buttonImage = this._get(inst, 'buttonImage');
var trigger = $(this._get(inst, 'buttonImageOnly') ?
$('').attr(
{src: buttonImage, alt: buttonStatus, title: buttonStatus}) :
$('').
html(buttonImage == '' ? buttonText :
$('').attr({src: buttonImage})));
control[isRTL ? 'before' : 'after'](trigger);
trigger.addClass(this._triggerClass).click(function() {
if ($.calculator._showingCalculator && $.calculator._lastInput == target) {
$.calculator._hideCalculator();
}
else {
$.calculator._showCalculator(target);
}
return false;
});
}
}
inst._input.keydown(this._doKeyDown).keyup(this._doKeyUp).keypress(this._doKeyPress);
if (inst._inline) {
inst._mainDiv.keydown(this._doKeyDown).keyup(this._doKeyUp).
keypress(this._doKeyPress);
inst._input.focus(function() {
if (!$.calculator._isDisabledCalculator(control[0])) {
inst._focussed = true;
$('.' + $.calculator._resultClass, inst._mainDiv).
addClass($.calculator._focussedClass);
}
}).blur(function() {
inst._focussed = false;
$('.' + $.calculator._resultClass, inst._mainDiv).
removeClass($.calculator._focussedClass);
});
}
control.addClass(this.markerClassName).
bind("setData.calculator", function(event, key, value) {
inst.settings[key] = value;
}).bind("getData.calculator", function(event, key) {
return this._get(inst, key);
});
$.data(target, PROP_NAME, inst);
$.data(inst._input[0], PROP_NAME, inst);
},
/* Detach calculator from its control.
@param target (element) the target input field or division/span */
_destroyCalculator: function(target) {
var control = $(target);
if (!control.hasClass(this.markerClassName)) {
return;
}
var inst = $.data(target, PROP_NAME);
inst._input.unbind('keydown', this._doKeyDown).
unbind('keyup', this._doKeyUp).
unbind('keypress', this._doKeyPress);
control.siblings('.' + this._appendClass).remove().end().
siblings('.' + this._triggerClass).remove().end().
prev('.' + this._inlineEntryClass).remove().end().
removeClass(this.markerClassName).
unbind('focus', this._showCalculator).
unbind('click.calculator').empty();
$.removeData(inst._input[0], PROP_NAME);
$.removeData(target, PROP_NAME);
},
/* Enable the calculator for a jQuery selection.
@param target (element) the target input field or division/span */
_enableCalculator: function(target) {
var control = $(target);
if (!control.hasClass(this.markerClassName)) {
return;
}
var nodeName = target.nodeName.toLowerCase();
if (nodeName == 'input') {
target.disabled = false;
control.siblings('button.' + this._triggerClass).
each(function() { this.disabled = false; }).end().
siblings('img.' + this._triggerClass).
css({opacity: '1.0', cursor: ''});
}
else if (nodeName == 'div' || nodeName == 'span') {
control.find('.' + this._inlineEntryClass + ',button').attr('disabled', '').end().
children('.' + this._disableClass).remove();
}
this._disabledFields = $.map(this._disabledFields,
function(value) { return (value == target ? null : value); }); // delete entry
},
/* Disable the calculator for a jQuery selection.
@param target (element) the target input field or division/span */
_disableCalculator: function(target) {
var control = $(target);
if (!control.hasClass(this.markerClassName)) {
return;
}
var nodeName = target.nodeName.toLowerCase();
if (nodeName == 'input') {
target.disabled = true;
control.siblings('button.' + this._triggerClass).
each(function() { this.disabled = true; }).end().
siblings('img.' + this._triggerClass).
css({opacity: '0.5', cursor: 'default'});
}
else if (nodeName == 'div' || nodeName == 'span') {
var inline = control.children('.' + this._inlineClass);
var offset = inline.offset();
var relOffset = {left: 0, top: 0};
inline.parents().each(function() {
if ($(this).css('position') == 'relative') {
relOffset = $(this).offset();
return false;
}
});
control.find('.' + this._inlineEntryClass + ',button').attr('disabled', 'disabled').end().
prepend('');
}
this._disabledFields = $.map(this._disabledFields,
function(value) { return (value == target ? null : value); }); // delete entry
this._disabledFields[this._disabledFields.length] = target;
},
/* Is the input field or division/span disabled as a calculator?
@param target (element) the target control
@return (boolean) true if disabled, false if enabled */
_isDisabledCalculator: function(target) {
return (target && $.inArray(target, this._disabledFields) > -1);
},
/* Update the settings for calculator attached to an input field or division/span.
@param target (element) the target control
@param name (object) the new settings to update or
(string) the name of the setting to change
@param value (any) the new value for the setting (omit if above is an object) */
_changeCalculator: function(target, name, value) {
var settings = name || {};
if (typeof name == 'string') {
settings = {};
settings[name] = value;
}
var inst = $.data(target, PROP_NAME);
if (inst) {
if (this._curInst == inst) {
this._hideCalculator();
}
extendRemove(inst.settings, settings);
if (inst._inline) {
this._setValue(inst);
}
this._updateCalculator(inst);
}
},
/* Pop-up the calculator for a given input field or division/span.
@param input (element) the control attached to the calculator or
(event) if triggered by focus */
_showCalculator: function(input) {
input = input.target || input;
if ($.calculator._isDisabledCalculator(input) ||
$.calculator._lastInput == input) { // already here
return;
}
var inst = $.data(input, PROP_NAME);
$.calculator._hideCalculator(null, '');
$.calculator._lastInput = input;
$.calculator._pos = $.calculator._findPos(input);
$.calculator._pos[1] += input.offsetHeight; // add the height
var isFixed = false;
$(input).parents().each(function() {
isFixed |= $(this).css('position') == 'fixed';
return !isFixed;
});
if (isFixed && $.browser.opera) { // correction for Opera when fixed and scrolled
$.calculator._pos[0] -= document.documentElement.scrollLeft;
$.calculator._pos[1] -= document.documentElement.scrollTop;
}
var offset = {left: $.calculator._pos[0], top: $.calculator._pos[1]};
$.calculator._pos = null;
// determine sizing offscreen
inst._mainDiv.css({position: 'absolute', display: 'block', top: '-1000px',
width: ($.browser.opera ? '1000px' : 'auto')});
// callback before calculator opening
var onOpen = $.calculator._get(inst, 'onOpen');
if (onOpen) {
onOpen.apply((inst._input ? inst._input[0] : null), // trigger custom callback
[(inst._inline ? inst.curValue : inst._input.val()), inst]);
}
$.calculator._reset(inst, inst._input.val());
$.calculator._updateCalculator(inst);
// and adjust position before showing
offset = $.calculator._checkOffset(inst, offset, isFixed);
inst._mainDiv.css({position: (isFixed ? 'fixed' : 'absolute'), display: 'none',
left: offset.left + 'px', top: offset.top + 'px'});
var showAnim = $.calculator._get(inst, 'showAnim');
var duration = $.calculator._get(inst, 'duration');
duration = (duration == 'normal' && $.ui && $.ui.version >= '1.8' ? '_default' : duration);
var postProcess = function() {
$.calculator._showingCalculator = true;
var borders = $.calculator._getBorders(inst._mainDiv);
inst._mainDiv.find('iframe.' + $.calculator._coverClass). // IE6- only
css({left: -borders[0], top: -borders[1],
width: inst._mainDiv.outerWidth(), height: inst._mainDiv.outerHeight()});
};
if ($.effects && $.effects[showAnim]) {
var data = inst._mainDiv.data(); // Update old effects data
for (var key in data) {
if (key.match(/^ec\.storage\./)) {
data[key] = inst._mainDiv.css(key.replace(/ec\.storage\./, ''));
}
}
inst._mainDiv.data(data).show(showAnim,
$.calculator._get(inst, 'showOptions'), duration, postProcess);
}
else {
inst._mainDiv[showAnim || 'show']((showAnim ? duration : ''), postProcess);
}
if (!showAnim) {
postProcess();
}
if (inst._input[0].type != 'hidden') {
inst._input[0].focus();
}
$.calculator._curInst = inst;
},
/* Reinitialise the calculator.
@param inst (object) the instance settings
@param value (number) the starting value */
_reset: function(inst, value) {
var base = this._get(inst, 'base');
var decimalChar = this._get(inst, 'decimalChar');
value = '' + (value || 0);
value = (decimalChar != '.' ? value.replace(new RegExp(decimalChar), '.') : value);
inst.curValue = (base == 10 ? parseFloat(value) : parseInt(value, base)) || 0;
inst.dispValue = this._setDisplay(inst);
inst.prevValue = inst._savedValue = 0;
inst._pendingOp = inst._savedOp = this._noOp;
inst._newValue = true;
},
/* Retrieve the memory value from a cookie, if any.
@param inst (object) the instance settings
@return the memory cookie value or NaN/null if unavailable */
_getMemoryCookie: function(inst) {
var re = new RegExp('^.*' + this._get(inst, 'cookieName') + '=([^;]*).*$');
return parseFloat(document.cookie.replace(re, '$1'));
},
/* Save the memory value as a cookie.
@param inst (object) the instance settings */
_setMemoryCookie: function(inst) {
if (!this._get(inst, 'memoryAsCookie')) {
return;
}
var expires = this._get(inst, 'cookieExpires');
if (typeof expires == 'number') {
var time = new Date();
time.setTime(time.getTime() + expires * 1000);
expires = time.toUTCString();
}
else if (expires.constructor == Date) {
expires = time.toUTCString();
}
else {
expires = '';
}
document.cookie = this._get(inst, 'cookieName') + '=' + inst.memory + '; expires=' + expires +
'; path=' + this._get(inst, 'cookiePath');
},
/* Set the initial value for display.
@param inst (object) the instance settings */
_setValue: function(inst) {
inst.curValue = this._get(inst, 'value') || 0;
inst.dispValue = this._setDisplay(inst);
},
/* Generate the calculator content.
@param inst (object) the instance settings */
_updateCalculator: function(inst) {
var borders = this._getBorders(inst._mainDiv);
inst._mainDiv.html(this._generateHTML(inst)).
find('iframe.' + this._coverClass). // IE6- only
css({left: -borders[0], top: -borders[1],
width: inst._mainDiv.outerWidth(), height: inst._mainDiv.outerHeight()});
inst._mainDiv.removeClass().addClass(this._get(inst, 'calculatorClass') +
(this._get(inst, 'useThemeRoller') ? ' ui-widget ui-widget-content' : '') +
(this._get(inst, 'isRTL') ? ' calculator-rtl ' : '') + ' ' +
(inst._inline ? this._inlineClass : this._mainDivClass));
if (this._curInst == inst) {
inst._input.focus();
}
},
/* Retrieve the size of left and top borders for an element.
@param elem (jQuery object) the element of interest
@return (number[2]) the left and top borders */
_getBorders: function(elem) {
var convert = function(value) {
var extra = ($.browser.msie ? 1 : 0);
return {thin: 1 + extra, medium: 3 + extra, thick: 5 + extra}[value] || value;
};
return [parseFloat(convert(elem.css('border-left-width'))),
parseFloat(convert(elem.css('border-top-width')))];
},
/* Check positioning to remain on screen.
@param inst (object) the instance settings
@param offset (object) the current offset
@param isFixed (boolean) true if the input field is fixed in position
@return (object) the updated offset */
_checkOffset: function(inst, offset, isFixed) {
var pos = inst._input ? this._findPos(inst._input[0]) : null;
var browserWidth = window.innerWidth || document.documentElement.clientWidth;
var browserHeight = window.innerHeight || document.documentElement.clientHeight;
var scrollX = document.documentElement.scrollLeft || document.body.scrollLeft;
var scrollY = document.documentElement.scrollTop || document.body.scrollTop;
if (($.browser.msie && parseInt($.browser.version, 10) < 7) || $.browser.opera) {
// recalculate width as otherwise set to 100%
var width = 0;
$('.calculator-row', inst._mainDiv).find('button:last').each(function() {
width = Math.max(width, this.offsetLeft + this.offsetWidth +
parseInt($(this).css('margin-right'), 10));
});
inst._mainDiv.css('width', width);
}
// reposition calculator panel horizontally if outside the browser window
if (this._get(inst, 'isRTL') ||
(offset.left + inst._mainDiv.outerWidth() - scrollX) > browserWidth) {
offset.left = Math.max((isFixed ? 0 : scrollX),
pos[0] + (inst._input ? inst._input.outerWidth() : 0) -
(isFixed ? scrollX : 0) - inst._mainDiv.outerWidth() -
(isFixed && $.browser.opera ? document.documentElement.scrollLeft : 0));
}
else {
offset.left -= (isFixed ? scrollX : 0);
}
// reposition calculator panel vertically if outside the browser window
if ((offset.top + inst._mainDiv.outerHeight() - scrollY) > browserHeight) {
offset.top = Math.max((isFixed ? 0 : scrollY),
pos[1] - (isFixed ? scrollY : 0) - inst._mainDiv.outerHeight() -
(isFixed && $.browser.opera ? document.documentElement.scrollTop : 0));
}
else {
offset.top -= (isFixed ? scrollY : 0);
}
return offset;
},
/* Find an object's position on the screen.
@param obj (element) the element to find the position for
@return (int[2]) the element's position */
_findPos: function(obj) {
while (obj && (obj.type == 'hidden' || obj.nodeType != 1)) {
obj = obj.nextSibling;
}
var position = $(obj).offset();
return [position.left, position.top];
},
/* Hide the calculator from view.
@param input (element) the control attached to the calculator
@param duration (string) the duration over which to close the calculator */
_hideCalculator: function(input, duration) {
var inst = this._curInst;
if (!inst || (input && inst != $.data(input, PROP_NAME))) {
return;
}
if (this._showingCalculator) {
duration = (duration != null ? duration : this._get(inst, 'duration'));
duration = (duration == 'normal' && $.ui && $.ui.version >= '1.8' ? '_default' : duration);
var showAnim = this._get(inst, 'showAnim');
if ($.effects && $.effects[showAnim]) {
inst._mainDiv.hide(showAnim, this._get(inst, 'showOptions'), duration);
}
else {
inst._mainDiv[(showAnim == 'slideDown' ? 'slideUp' :
(showAnim == 'fadeIn' ? 'fadeOut' : 'hide'))](showAnim ? duration : '');
}
}
var onClose = this._get(inst, 'onClose');
if (onClose) {
onClose.apply((inst._input ? inst._input[0] : null), // trigger custom callback
[(inst._inline ? inst.curValue : inst._input.val()), inst]);
}
if (this._showingCalculator) {
this._showingCalculator = false;
this._lastInput = null;
}
this._curInst = null;
},
/* Close calculator if clicked elsewhere.
@param event (event) the mouseclick details */
_checkExternalClick: function(event) {
if (!$.calculator._curInst) {
return;
}
var target = $(event.target);
if (!target.parents().andSelf().hasClass($.calculator._mainDivClass) &&
!target.hasClass($.calculator.markerClassName) &&
!target.parents().andSelf().hasClass($.calculator._triggerClass) &&
$.calculator._showingCalculator) {
$.calculator._hideCalculator();
}
},
/* Focus back onto the input field. */
_focusEntry: function() {
if ($.calculator._curInst && $.calculator._curInst._input) {
$.calculator._curInst._input.focus();
}
},
/* Handle keystrokes.
@param e (event) the key event */
_doKeyDown: function(e) {
var handled = false;
var inst = $.data(e.target, PROP_NAME);
var div = (inst && inst._inline ? $(e.target).parent()[0] : null);
if (e.keyCode == 9) { // tab
$.calculator.mainDiv.stop(true, true);
$.calculator._hideCalculator();
if (inst && inst._inline) {
inst._input.blur();
}
}
else if ($.calculator._showingCalculator ||
(div && !$.calculator._isDisabledCalculator(div))) {
if (e.keyCode == 18) { // alt - show keystrokes
if (!$.calculator._showingKeystrokes) {
inst._mainDiv.find('.' + $.calculator._keystrokeClass).show();
$.calculator._showingKeystrokes = true;
}
handled = true;
}
else {
var code = $.calculator._keyCodes[e.keyCode];
if (code) {
$('button[keystroke="' + code + '"]', inst._mainDiv).not(':disabled').click();
handled = true;
}
}
}
else if (e.keyCode == 36 && e.ctrlKey && inst && !inst._inline) {
$.calculator._showCalculator(this); // display the date picker on ctrl+home
}
if (handled) {
e.preventDefault();
e.stopPropagation();
}
return !handled;
},
/* Hide keystrokes, if showing.
@param e (event) the key event */
_doKeyUp: function(e) {
if ($.calculator._showingKeystrokes) {
var inst = $.data(e.target, PROP_NAME);
inst._mainDiv.find('.' + $.calculator._keystrokeClass).hide();
$.calculator._showingKeystrokes = false;
}
},
/* Convert characters into button clicks.
@param e (event) the key event
@return true if keystroke allowed, false if not */
_doKeyPress: function(e) {
var inst = $.data(e.target, PROP_NAME);
if (!inst) {
return true;
}
var div = (inst && inst._inline ? $(e.target).parent()[0] : null);
var ch = String.fromCharCode(e.charCode == undefined ? e.keyCode : e.charCode);
var base = $.calculator._get(inst, 'base');
var decimalChar = $.calculator._get(inst, 'decimalChar');
var showOn = $.calculator._get(inst, 'showOn');
var isOperator = $.calculator._get(inst, 'isOperator') || $.calculator._isOperator;
if (!$.calculator._showingCalculator && !div &&
(showOn == 'operator' || showOn == 'opbutton') &&
isOperator.apply(inst._input, [ch, e, inst._input.val(), base, decimalChar])) {
$.calculator._showCalculator(this); // display the date picker on operator usage
$.calculator._showingCalculator = true;
}
if ($.calculator._showingCalculator ||
(div && !$.calculator._isDisabledCalculator(div))) {
var code = $.calculator._keyChars[ch == decimalChar ? '.' : ch];
if (code) {
$('button[keystroke="' + code + '"]', inst._mainDiv).not(':disabled').click();
}
return false;
}
if (ch >= ' ' && $.calculator._get(inst, 'constrainInput')) {
var pattern = new RegExp('^-?' + (base == 10 ? '[0-9]*(\\' + decimalChar + '[0-9]*)?' :
'[' + '0123456789abcdef'.substring(0, base) + ']*') + '$');
return (inst._input.val() + ch).toLowerCase().match(pattern) != null;
}
return true;
},
/* Determine whether or not a keystroke is a trigger for opening the calculator.
@param ch (char) the current character
@param event (KeyEvent) the entire key event
@param value (string) the current input value
@param base (number) the current number base
@param decimalChar (char) the current decimal character
@return true if a trigger, false if not */
_isOperator: function(ch, event, value, base, decimalChar) {
return ch > ' ' && !(ch == '-' && value == '') &&
('0123456789abcdef'.substr(0, base) + '.' + decimalChar).indexOf(ch.toLowerCase()) == -1;
},
/* Get a setting value, defaulting if necessary.
@param inst (object) the instance settings
@param name (string) the name of the setting
@return (any) the value of the setting, or its default if not set explicitly */
_get: function(inst, name) {
return inst.settings[name] !== undefined ?
inst.settings[name] : this._defaults[name];
},
/* Generate the HTML for the current state of the calculator.
@param inst (object) the instance settings
@return (string) the HTML for this calculator */
_generateHTML: function(inst) {
var useTR = this._get(inst, 'useThemeRoller');
var isRTL = this._get(inst, 'isRTL');
var prompt = this._get(inst, 'prompt');
var layout = this._get(inst, 'layout');
var base = this._get(inst, 'base');
var useDegrees = this._get(inst, 'useDegrees');
var html = (!prompt ? '' : ' ') +
' ';
for (var i = 0; i < layout.length; i++) {
html += '';
for (var j = 0; j < layout[i].length; j += 2) {
var code = layout[i].substr(j, 2);
var def = this._keyDefs[code] || this._keyDefs['??'];
var label = (def[0].charAt(0) == '#' ? this._get(inst, def[0].substr(1) + 'Text') : def[0]);
var status = (def[0].charAt(0) == '#' ? this._get(inst, def[0].substr(1) + 'Status') : '');
var styles = (def[3] ? def[3].split(' ') : []);
for (var k = 0; k < styles.length; k++) {
styles[k] = 'calculator-' + styles[k];
}
styles = styles.join(' ');
var uiActive = (useTR ? ' ui-state-active' : '');
var uiHighlight = (useTR ? ' ui-state-highlight' : '');
html += (def[1] == this.space ? '' :
(inst._inline && (def[2] == '._close' || def[2] == '._erase') ? '' :
''));
}
html += '';
}
html += '' +
(!inst._inline && $.browser.msie && parseInt($.browser.version, 10) < 7 ? // IE6- only
'' : '');
html = $(html);
html.find('button').mouseover(function() { $.calculator._saveClasses = this.className; }).
mousedown(function() {
$(this).addClass('calculator-key-down' + (useTR ? ' ui-state-active' : ''));
}).
mouseup(function() { $(this).removeClass().addClass($.calculator._saveClasses); }).
mouseout(function() { $(this).removeClass().addClass($.calculator._saveClasses); }).
click(function() { $.calculator._handleButton(inst, $(this)); });
return html;
},
/* Generate the display value.
Tidy the result to avoid JavaScript rounding errors.
@param inst (object) the instance settings
@return (string) the rounded and formatted display value */
_setDisplay: function(inst) {
var precision = this._get(inst, 'precision');
var fixed = new Number(inst.curValue).toFixed(precision).valueOf(); // Round to 14 digits precision
var exp = fixed.replace(/^.+(e.+)$/, '$1').replace(/^[^e].*$/, ''); // Extract exponent
if (exp) {
fixed = new Number(fixed.replace(/e.+$/, '')).toFixed(precision).valueOf(); // Round mantissa
}
return parseFloat(fixed.replace(/0+$/, '') + exp). // Recombine
toString(this._get(inst, 'base')).toUpperCase().
replace(/\./, this._get(inst, 'decimalChar'));
},
/* Send notification of a button activation.
@param inst (object) the instance settings
@param label (string) the label from the button */
_sendButton: function(inst, label) {
var onButton = this._get(inst, 'onButton');
if (onButton) {
onButton.apply((inst._input ? inst._input[0] : null),
[label, inst.dispValue, inst]); // trigger custom callback
}
},
/* Handle a button press.
@param inst (object) the current instance settings
@param button (jQuery) the button pressed */
_handleButton: function(inst, button) {
var keyDef = this._keyDefs[button.attr('keystroke')];
if (!keyDef) {
return;
}
var label = button.text().substr(0, button.text().length -
button.children('.calculator-keystroke').text().length);
switch (keyDef[1]) {
case this.control:
keyDef[2].apply(this, [inst, label]); break;
case this.digit:
this._digit(inst, label); break;
case this.binary:
this._binaryOp(inst, keyDef[2], label); break;
case this.unary:
this._unaryOp(inst, keyDef[2], label); break;
}
if ($.calculator._showingCalculator || inst._inline) {
inst._input.focus();
}
},
/* Do nothing. */
_noOp: function(inst) {
},
/* Add a digit to the number in the calculator.
@param inst (object) the instance settings
@param digit (string) the digit to append */
_digit: function(inst, digit) {
var decimalChar = this._get(inst, 'decimalChar');
inst.dispValue = (inst._newValue ? '' : inst.dispValue);
if (digit == decimalChar && inst.dispValue.indexOf(digit) > -1) {
return;
}
inst.dispValue = (inst.dispValue + digit).replace(/^0(\d)/, '$1').
replace(new RegExp('^(-?)([\\.' + decimalChar + '])'), '$10$2');
if (decimalChar != '.') {
inst.dispValue = inst.dispValue.replace(new RegExp('^' + decimalChar), '0.');
}
var base = this._get(inst, 'base');
var value = (decimalChar != '.' ?
inst.dispValue.replace(new RegExp(decimalChar), '.') : inst.dispValue);
inst.curValue = (base == 10 ? parseFloat(value) : parseInt(value, base));
inst._newValue = false;
this._sendButton(inst, digit);
this._updateCalculator(inst);
},
/* Save a binary operation for later use.
@param inst (object) the instance settings
@param op (function) the binary function
@param label (string) the button label */
_binaryOp: function(inst, op, label) {
if (!inst._newValue && inst._pendingOp) {
inst._pendingOp(inst);
var base = this._get(inst, 'base');
inst.curValue = (base == 10 ? inst.curValue : Math.floor(inst.curValue));
inst.dispValue = this._setDisplay(inst);
}
inst.prevValue = inst.curValue;
inst._newValue = true;
inst._pendingOp = op;
this._sendButton(inst, label);
this._updateCalculator(inst);
},
_add: function(inst) {
inst.curValue = inst.prevValue + inst.curValue;
},
_subtract: function(inst) {
inst.curValue = inst.prevValue - inst.curValue;
},
_multiply: function(inst) {
inst.curValue = inst.prevValue * inst.curValue;
},
_divide: function(inst) {
inst.curValue = inst.prevValue / inst.curValue;
},
_power: function(inst) {
inst.curValue = Math.pow(inst.prevValue, inst.curValue);
},
/* Apply a unary operation to the calculator.
@param inst (object) the instance settings
@param op (function) the unary function
@param label (string) the button label */
_unaryOp: function(inst, op, label) {
inst._newValue = true;
op.apply(this, [inst]);
var base = this._get(inst, 'base');
inst.curValue = (base == 10 ? inst.curValue : Math.floor(inst.curValue));
inst.dispValue = this._setDisplay(inst);
this._sendButton(inst, label);
this._updateCalculator(inst);
},
_plusMinus: function(inst) {
inst.curValue = -1 * inst.curValue;
inst.dispValue = this._setDisplay(inst);
inst._newValue = false;
},
_pi: function(inst) {
inst.curValue = Math.PI;
},
/* Perform a percentage calculation.
@param inst (object) the instance settings */
_percent: function(inst) {
if (inst._pendingOp == this._add) {
inst.curValue = inst.prevValue * (1 + inst.curValue / 100);
}
else if (inst._pendingOp == this._subtract) {
inst.curValue = inst.prevValue * (1 - inst.curValue / 100);
}
else if (inst._pendingOp == this._multiply) {
inst.curValue = inst.prevValue * inst.curValue / 100;
}
else if (inst._pendingOp == this._divide) {
inst.curValue = inst.prevValue / inst.curValue * 100;
}
inst._savedOp = inst._pendingOp;
inst._pendingOp = this._noOp;
},
/* Apply a pending binary operation.
@param inst (object) the instance settings */
_equals: function(inst) {
if (inst._pendingOp == this._noOp) {
if (inst._savedOp != this._noOp) {
// Following x op y =: =, z =
inst.prevValue = inst.curValue;
inst.curValue = inst._savedValue;
inst._savedOp(inst);
}
}
else {
// Normal: x op y =
inst._savedOp = inst._pendingOp;
inst._savedValue = inst.curValue;
inst._pendingOp(inst);
inst._pendingOp = this._noOp;
}
},
_memAdd: function(inst) {
inst.memory += inst.curValue;
this._setMemoryCookie(inst);
},
_memSubtract: function(inst) {
inst.memory -= inst.curValue;
this._setMemoryCookie(inst);
},
_memStore: function(inst) {
inst.memory = inst.curValue;
this._setMemoryCookie(inst);
},
_memRecall: function(inst) {
inst.curValue = inst.memory;
},
_memClear: function(inst) {
inst.memory = 0;
this._setMemoryCookie(inst);
},
_sin: function(inst) {
this._trig(inst, Math.sin);
},
_cos: function(inst) {
this._trig(inst, Math.cos);
},
_tan: function(inst) {
this._trig(inst, Math.tan);
},
_trig: function(inst, op) {
var useDegrees = this._get(inst, 'useDegrees');
inst.curValue = op(inst.curValue * (useDegrees ? Math.PI / 180 : 1));
},
_asin: function(inst) {
this._atrig(inst, Math.asin);
},
_acos: function(inst) {
this._atrig(inst, Math.acos);
},
_atan: function(inst) {
this._atrig(inst, Math.atan);
},
_atrig: function(inst, op) {
inst.curValue = op(inst.curValue);
if (this._get(inst, 'useDegrees')) {
inst.curValue = inst.curValue / Math.PI * 180;
}
},
_inverse: function(inst) {
inst.curValue = 1 / inst.curValue;
},
_log: function(inst) {
inst.curValue = Math.log(inst.curValue) / Math.log(10);
},
_ln: function(inst) {
inst.curValue = Math.log(inst.curValue);
},
_exp: function(inst) {
inst.curValue = Math.exp(inst.curValue);
},
_sqr: function(inst) {
inst.curValue *= inst.curValue;
},
_sqrt: function(inst) {
inst.curValue = Math.sqrt(inst.curValue);
},
_random: function(inst) {
inst.curValue = Math.random();
},
_base2: function(inst, label) {
this._changeBase(inst, label, 2);
},
_base8: function(inst, label) {
this._changeBase(inst, label, 8);
},
_base10: function(inst, label) {
this._changeBase(inst, label, 10);
},
_base16: function(inst, label) {
this._changeBase(inst, label, 16);
},
/* Change the number base for the calculator.
@param inst (object) the instance settings
@param label (string) the button label
@param newBase (number) the new number base */
_changeBase: function(inst, label, newBase) {
inst.settings.base = newBase;
inst.curValue = (newBase == 10 ? inst.curValue : Math.floor(inst.curValue));
inst.dispValue = this._setDisplay(inst);
inst._newValue = true;
this._sendButton(inst, label);
this._updateCalculator(inst);
},
_degrees: function(inst, label) {
this._degreesRadians(inst, label, true);
},
_radians: function(inst, label) {
this._degreesRadians(inst, label, false);
},
/* Swap between degrees and radians for trigonometric functions.
@param inst (object) the instance settings
@param label (string) the button label
@param useDegrees (boolean) true to use degrees, false for radians */
_degreesRadians: function(inst, label, useDegrees) {
inst.settings.useDegrees = useDegrees;
this._sendButton(inst, label);
this._updateCalculator(inst);
},
/* Erase the last digit entered.
@param inst (object) the instance settings
@param label (string) the button label */
_undo: function(inst, label) {
inst.dispValue = inst.dispValue.substr(0, inst.dispValue.length - 1) || '0';
var base = this._get(inst, 'base');
inst.curValue = (base == 10 ? parseFloat(inst.dispValue) : parseInt(inst.dispValue, base));
this._sendButton(inst, label);
this._updateCalculator(inst);
},
/* Erase the last number entered.
@param inst (object) the instance settings
@param label (string) the button label */
_clearError: function(inst, label) {
inst.dispValue = '0';
inst.curValue = 0;
inst._newValue = true;
this._sendButton(inst, label);
this._updateCalculator(inst);
},
/* Reset the calculator.
@param inst (object) the instance settings
@param label (string) the button label */
_clear: function(inst, label) {
this._reset(inst, 0);
this._sendButton(inst, label);
this._updateCalculator(inst);
},
/* Close the calculator without changing the value.
@param inst (object) the instance settings
@param label (string) the button label */
_close: function(inst, label) {
this._finished(inst, label, inst._input.val());
},
/* Copy the current value and close the calculator.
@param inst (object) the instance settings
@param label (string) the button label */
_use: function(inst, label) {
if (inst._pendingOp != this._noOp) {
this._unaryOp(inst, this._equals, label);
}
var roundedValue = dojo.number.format(inst.curValue, {places:2});
this._finished(inst, label, roundedValue);
},
/* Erase the field and close the calculator.
@param inst (object) the instance settings
@param label (string) the button label */
_erase: function(inst, label) {
this._reset(inst, 0);
this._updateCalculator(inst);
this._finished(inst, label, '');
},
/* Finish with the calculator.
@param inst (object) the instance settings
@param label (string) the button label
@param value (string) the new field value */
_finished: function(inst, label, value) {
if (inst._inline) {
this._curInst = inst;
}
else {
inst._input.val(value);
}
this._sendButton(inst, label);
this._hideCalculator(inst._input[0]);
}
});
/* jQuery extend now ignores nulls!
@param target (object) the object to extend
@param props (object) the new settings
@return (object) the updated target */
function extendRemove(target, props) {
$.extend(target, props);
for (var name in props) {
if (props[name] == null || props[name] == undefined) {
target[name] = props[name];
}
}
return target;
}
/* Invoke the calculator functionality.
@param options (string) a command, optionally followed by additional parameters or
(object) settings for attaching new calculator functionality
@return (object) the jQuery object */
$.fn.calculator = function(options) {
var otherArgs = Array.prototype.slice.call(arguments, 1);
if (options == 'isDisabled') {
return $.calculator['_' + options + 'Calculator'].
apply($.calculator, [this[0]].concat(otherArgs));
}
return this.each(function() {
if (typeof options == 'string') {
if (!$.calculator['_' + options + 'Calculator']) {
throw 'Unknown operation: ' + options;
}
$.calculator['_' + options + 'Calculator'].
apply($.calculator, [this].concat(otherArgs));
}
else {
$.calculator._attachCalculator(this, options);
}
});
};
$.calculator = new Calculator(); // singleton instance
// Add the calculator division and external click check
$(function() {
$(document.body).append($.calculator.mainDiv).
mousedown($.calculator._checkExternalClick);
});
})(jQuery);
© 2015 - 2025 Weber Informatics LLC | Privacy Policy