303 lines
11 KiB
JavaScript
303 lines
11 KiB
JavaScript
/**
|
|
* jQuery.NumPad
|
|
*
|
|
* Copyright (c) 2015 Andrej Kabachnik
|
|
*
|
|
* Licensed under the MIT license:
|
|
* http://www.opensource.org/licenses/mit-license.php
|
|
*
|
|
* Project home:
|
|
* https://github.com/kabachello/jQuery.NumPad
|
|
*
|
|
* Version: 1.4
|
|
*
|
|
*/
|
|
(function($){
|
|
|
|
// From https://stackoverflow.com/questions/4963053/focus-to-input-without-scrolling
|
|
var cursorFocus = function(elem) {
|
|
var x = window.scrollX, y = window.scrollY;
|
|
elem.focus();
|
|
window.scrollTo(x, y);
|
|
}
|
|
|
|
$.fn.numpad=function(options){
|
|
|
|
if (typeof options == 'string'){
|
|
var nmpd = $.data(this[0], 'numpad');
|
|
if (!nmpd) throw "Cannot perform '" + options + "' on a numpad prior to initialization!";
|
|
switch (options){
|
|
case 'open':
|
|
nmpd.open(nmpd.options.target ? nmpd.options.target : this.first());
|
|
break;
|
|
case 'close':
|
|
nmpd.open(nmpd.options.target ? nmpd.options.target : this.first());
|
|
break;
|
|
}
|
|
return this;
|
|
}
|
|
|
|
// Apply the specified options overriding the defaults
|
|
options = $.extend({}, $.fn.numpad.defaults, options);
|
|
|
|
// Create a numpad. One for all elements in this jQuery selector.
|
|
// Since numpad() can be called on multiple elements on one page, each call will create a unique numpad id.
|
|
var id = 'nmpd' + ($('.nmpd-wrapper').length + 1);
|
|
var nmpd = {};
|
|
return this.each(function(){
|
|
|
|
// If an element with the generated unique numpad id exists, the numpad had been instantiated already.
|
|
// Otherwise create a new one!
|
|
if ($('#'+id).length == 0) {
|
|
/** @var nmpd jQuery object containing the entire numpad */
|
|
nmpd = $('<div id="' + id + '"></div>').addClass('nmpd-wrapper');
|
|
nmpd.options = options;
|
|
/** @var display jQuery object representing the display of the numpad (typically an input field) */
|
|
var display = $(options.displayTpl).addClass('nmpd-display');
|
|
nmpd.display = display;
|
|
/** @var grid jQuery object containing the grid for the numpad: the display, the buttons, etc. */
|
|
var table = $(options.gridTpl).addClass('nmpd-grid');
|
|
nmpd.grid = table;
|
|
table.append($(options.rowTpl).append($(options.displayCellTpl).append(display).append($('<input type="hidden" class="dirty" value="0"></input>'))));
|
|
// Create rows and columns of the the grid with appropriate buttons
|
|
table.append(
|
|
$(options.rowTpl)
|
|
.append($(options.cellTpl).append($(options.buttonNumberTpl).html(7).addClass('numero')))
|
|
.append($(options.cellTpl).append($(options.buttonNumberTpl).html(8).addClass('numero')))
|
|
.append($(options.cellTpl).append($(options.buttonNumberTpl).html(9).addClass('numero')))
|
|
.append($(options.cellTpl).append($(options.buttonFunctionTpl).html(options.textDelete).addClass('del').click(function(){
|
|
nmpd.setValue(nmpd.getValue().toString().substring(0,nmpd.getValue().toString().length - 1));
|
|
})))
|
|
).append(
|
|
$(options.rowTpl)
|
|
.append($(options.cellTpl).append($(options.buttonNumberTpl).html(4).addClass('numero')))
|
|
.append($(options.cellTpl).append($(options.buttonNumberTpl).html(5).addClass('numero')))
|
|
.append($(options.cellTpl).append($(options.buttonNumberTpl).html(6).addClass('numero')))
|
|
.append($(options.cellTpl).append($(options.buttonFunctionTpl).html(options.textClear).addClass('clear').click(function(){
|
|
nmpd.setValue('');
|
|
})))
|
|
).append(
|
|
$(options.rowTpl)
|
|
.append($(options.cellTpl).append($(options.buttonNumberTpl).html(1).addClass('numero')))
|
|
.append($(options.cellTpl).append($(options.buttonNumberTpl).html(2).addClass('numero')))
|
|
.append($(options.cellTpl).append($(options.buttonNumberTpl).html(3).addClass('numero')))
|
|
.append($(options.cellTpl).append($(options.buttonFunctionTpl).html(options.textCancel).addClass('cancel').click(function(){
|
|
nmpd.close(false);
|
|
})))
|
|
).append(
|
|
$(options.rowTpl)
|
|
.append($(options.cellTpl).append($(options.buttonFunctionTpl).html('±').addClass('neg').click(function(){
|
|
nmpd.setValue(nmpd.getValue() * (-1));
|
|
})))
|
|
.append($(options.cellTpl).append($(options.buttonNumberTpl).html(0).addClass('numero')))
|
|
.append($(options.cellTpl).append($(options.buttonFunctionTpl).html(options.decimalSeparator).addClass('sep').click(function(){
|
|
nmpd.setValue(nmpd.getValue().toString() + options.decimalSeparator);
|
|
})))
|
|
.append($(options.cellTpl).append($(options.buttonFunctionTpl).html(options.textDone).addClass('done')))
|
|
);
|
|
// Create the backdrop of the numpad - an overlay for the main page
|
|
nmpd.append($(options.backgroundTpl).addClass('nmpd-overlay').click(function(){nmpd.close(false);}));
|
|
// Append the grid table to the nmpd element
|
|
nmpd.append(table);
|
|
|
|
// Hide buttons to be hidden
|
|
if (options.hidePlusMinusButton){
|
|
nmpd.find('.neg').hide();
|
|
}
|
|
if (options.hideDecimalButton){
|
|
nmpd.find('.sep').hide();
|
|
}
|
|
|
|
// Attach events
|
|
if (options.onKeypadCreate){
|
|
nmpd.on('numpad.create', options.onKeypadCreate);
|
|
}
|
|
if (options.onKeypadOpen){
|
|
nmpd.on('numpad.open', options.onKeypadOpen);
|
|
}
|
|
if (options.onKeypadClose){
|
|
nmpd.on('numpad.close', options.onKeypadClose);
|
|
}
|
|
if (options.onChange){
|
|
nmpd.on('numpad.change', options.onChange);
|
|
}
|
|
(options.appendKeypadTo ? options.appendKeypadTo : $(document.body)).append(nmpd);
|
|
|
|
// Special event for the numeric buttons
|
|
$('#'+id+' .numero').bind('click', function(){
|
|
var val;
|
|
if ($('#'+id+' .dirty').val() == '0'){
|
|
val = $(this).text();
|
|
} else {
|
|
val = nmpd.getValue() ? nmpd.getValue().toString() + $(this).text() : $(this).text();
|
|
}
|
|
nmpd.setValue(val);
|
|
});
|
|
|
|
// Finally, once the numpad is completely instantiated, trigger numpad.create
|
|
nmpd.trigger('numpad.create');
|
|
} else {
|
|
// If the numpad was already instantiated previously, just load it into the nmpd variable
|
|
//nmpd = $('#'+id);
|
|
//nmpd.display = $('#'+id+' input.nmpd-display');
|
|
}
|
|
|
|
$.data(this, 'numpad', nmpd);
|
|
|
|
// Make the target element readonly and save the numpad id in the data-numpad property. Also add the special nmpd-target CSS class.
|
|
$(this).attr("readonly", true).attr('data-numpad', id).addClass('nmpd-target');
|
|
|
|
// Register a listener to open the numpad on the event specified in the options
|
|
$(this).bind(options.openOnEvent,function(){
|
|
nmpd.open(options.target ? options.target : $(this));
|
|
});
|
|
|
|
// Define helper functions
|
|
|
|
/**
|
|
* Gets the current value displayed in the numpad
|
|
* @return string | number
|
|
*/
|
|
nmpd.getValue = function(){
|
|
return isNaN(nmpd.display.val()) ? 0 : nmpd.display.val();
|
|
};
|
|
|
|
/**
|
|
* Sets the display value of the numpad
|
|
* @param string value
|
|
* @return jQuery object nmpd
|
|
*/
|
|
nmpd.setValue = function(value){
|
|
if (nmpd.display.attr('maxLength') < value.toString().length) value = value.toString().substr(0, nmpd.display.attr('maxLength'));
|
|
nmpd.display.val(value);
|
|
nmpd.find('.dirty').val('1');
|
|
nmpd.trigger('numpad.change', [value]);
|
|
return nmpd;
|
|
};
|
|
|
|
/**
|
|
* Closes the numpad writing it's value to the given target element
|
|
* @param jQuery object target
|
|
* @return jQuery object nmpd
|
|
*/
|
|
nmpd.close = function(target){
|
|
// If a target element is given, set it's value to the dipslay value of the numpad. Otherwise just hide the numpad
|
|
if (target){
|
|
if (target.prop("tagName") == 'INPUT'){
|
|
target.val(nmpd.getValue().toString().replace('.', options.decimalSeparator));
|
|
} else {
|
|
target.html(nmpd.getValue().toString().replace('.', options.decimalSeparator));
|
|
}
|
|
}
|
|
// Hide the numpad and trigger numpad.close
|
|
nmpd.hide();
|
|
nmpd.trigger('numpad.close');
|
|
// Trigger a change event on the target element if the value has really been changed
|
|
// TODO check if the value has really been changed!
|
|
if (target && target.prop("tagName") == 'INPUT'){
|
|
target.trigger('change');
|
|
}
|
|
return nmpd;
|
|
};
|
|
|
|
/**
|
|
* Opens the numpad for a given target element optionally filling it with a given value
|
|
* @param jQuery object target
|
|
* @param string initialValue
|
|
* @return jQuery object nmpd
|
|
*/
|
|
nmpd.open = function(target, initialValue){
|
|
// Set the initial value
|
|
// Use nmpd.display.val to avoid triggering numpad.change for the initial value
|
|
if (initialValue){
|
|
nmpd.display.val(initialValue);
|
|
} else {
|
|
if (target.prop("tagName") == 'INPUT'){
|
|
nmpd.display.val(target.val());
|
|
nmpd.display.attr('maxLength', target.attr('maxLength'));
|
|
} else {
|
|
nmpd.display.val(isNaN(parseFloat(target.text())) ? '' : parseFloat(target.text()));
|
|
}
|
|
}
|
|
// Mark the numpad as not dirty initially
|
|
$('#'+id+' .dirty').val(0);
|
|
// Show the numpad and position it on the page
|
|
cursorFocus(nmpd.show().find('.cancel'));
|
|
position(nmpd.find('.nmpd-grid'), options.position, options.positionX, options.positionY);
|
|
// Register a click handler on the done button to update the target element
|
|
// Make sure all other click handlers get removed. Otherwise some unwanted sideeffects may occur if the numpad is
|
|
// opened multiple times for some reason
|
|
$('#'+id+' .done').off('click');
|
|
$('#'+id+' .done').one('click', function(){ nmpd.close(target); });
|
|
// Finally trigger numpad.open
|
|
nmpd.trigger('numpad.open');
|
|
return nmpd;
|
|
};
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Positions any given jQuery element within the page
|
|
*/
|
|
function position(element, mode, posX, posY) {
|
|
var x = 0;
|
|
var y = 0;
|
|
if (mode == 'fixed'){
|
|
element.css('position','fixed');
|
|
|
|
if (posX == 'left'){
|
|
x = 0;
|
|
} else if (posX == 'right'){
|
|
x = $(window).width() - element.outerWidth();
|
|
} else if (posX == 'center'){
|
|
x = ($(window).width() / 2) - (element.outerWidth() / 2);
|
|
} else if ($.type(posX) == 'number'){
|
|
x = posX;
|
|
}
|
|
element.css('left', x);
|
|
|
|
if (posY == 'top'){
|
|
y = 0;
|
|
} else if (posY == 'bottom'){
|
|
y = $(window).height() - element.outerHeight();
|
|
} else if (posY == 'middle'){
|
|
y = ($(window).height() / 2) - (element.outerHeight() / 2);
|
|
} else if ($.type(posY) == 'number'){
|
|
y = posY;
|
|
}
|
|
element.css('top', y);
|
|
}
|
|
return element;
|
|
}
|
|
|
|
// Default values for numpad options
|
|
$.fn.numpad.defaults = {
|
|
target: false,
|
|
openOnEvent: 'click',
|
|
backgroundTpl: '<div></div>',
|
|
gridTpl: '<table></table>',
|
|
displayTpl: '<input type="text" />',
|
|
displayCellTpl: '<td colspan="4"></td>',
|
|
rowTpl: '<tr></tr>',
|
|
cellTpl: '<td></td>',
|
|
buttonNumberTpl: '<button></button>',
|
|
buttonFunctionTpl: '<button></button>',
|
|
gridTableClass: '',
|
|
hidePlusMinusButton: false,
|
|
hideDecimalButton: false,
|
|
textDone: 'Done',
|
|
textDelete: 'Del',
|
|
textClear: 'Clear',
|
|
textCancel: 'Cancel',
|
|
decimalSeparator: ',',
|
|
precision: null,
|
|
appendKeypadTo: false,
|
|
position: 'fixed',
|
|
positionX: 'center',
|
|
positionY: 'middle',
|
|
onKeypadCreate: false,
|
|
onKeypadOpen: false,
|
|
onKeypadClose: false,
|
|
onChange: false
|
|
};
|
|
})(jQuery); |