My Blog List

Wednesday, November 7, 2012

jQuery Plugin to Convert TextBox to a HH:MM Timefield

 

Wrote a little jQuery plugin which converts a regular HTML textbox into a time-entry field with all the necessary key bindings and masks.  

Features:

· supports time entry in 12 or 24 format

· API user can pass custom handler in case user types invalid time.

· Tested in IE8,Chrome(Desktop and Android),Safari(Desktop and iPhone),Firefox and Opera.

The source JS file, examples and documentation are located at: http://sajjansarkar.zxq.net/jquery/timefield/

Hope it is useful and please let me know if u find any bugs/have any suggestions!

Downloads,Demos and Documentation: http://sajjansarkar.zxq.net/jquery/timefield/

Usage:

$("#tbPlain").timefield();
$("#tb12Hour").timefield({ 'hourFormat': '12'});
$("#tb24Hour").timefield({ 'hourFormat': '24'});
$("#tbWithInvalidHandler").timefield({ 
		'hourFormat': '24',
		'onError'   : function(){
				alert(this.value+' is not a valid time!');
				this.style.backgroundColor='red';
			}
		});
$("#tbOnOff").timefield();
$("#btnTurnOff").click(function(){
				$("#tbOnOff").timefield('turnOffTimeField');
			});

Code:

/**
 * @author Sajjan Sarkar
 */
(function($)
{
	$.fn.timefield = function(options)
	{
		//Public Methods
		var methods = {
		     /** Initializes the timefield  */
			init : function(options)
			{
				return this.each(function()
				{
					/** Txt entry is from RIGHT TO LEFT */
					this.style.direction = "rtl";
					/** Cache for performance*/
					var $this = $(this);	
					/** bind listeners */
					$this.bind('keydown.timefield', helpers.eventHandlers.keyDownHandler);
					$this.bind('keyup.timefield', helpers.eventHandlers.keyUpHandler);
					$this.bind('blur.timefield', helpers.eventHandlers.blurHandler);
					
				});
			},
			/** removes all timefield effects**/
			turnOffTimeField : function(content)
			{
				return this.each(function()
				{
					this.style.direction = "ltr";
					var $this = $(this);
					$this.unbind('.timefield');
				});
			}
			
		};
		/** Private methods */
		var helpers = {
			eventHandlers : {
				/** On key down, check if the pressed key is valid else dont show it.
				 * 	acts as mask
				 * */
				keyDownHandler : function(event)
				{
					
					if (!helpers.utils.isValidKeyStroke(event))
					{
						event.preventDefault();
						event.stopPropagation();
					}
				},
				/**
				 * When this is called the keydown event would have already rendered the 
				 * key stroke, so apply the padding with zeros.
				 * */
				keyUpHandler : function(event)
				{
					var keyCode = event.keyCode;
					/** check if a special key was pressed */
					if ($.inArray(keyCode, constants.KEYS.SPECIAL_KEYS_TO_IGNORE) != -1)
						return;
					/** did this on key up instead of keydown as its easier coz the text has rendered */
					helpers.utils.applyPadding(helpers.utils.getEventSource(event));
				},
				/**
				 * On blur, check the validity of the enterred value based on selected settings.
				 * Could not do this on change as most browsers dont fire the change event 
				 * if the value has changed programatically.
				 * 
				 * If the time is invalid, call the specified(or default) onError handler
				 * */
				blurHandler : function(event)
				{
					helpers.utils.getEventSource(event).value = jQuery.trim( helpers.utils.getEventSource(event).value );
					if (!helpers.utils.isValidTime(helpers.utils.getEventSource(event).value))
					{
						settings.onError.call(helpers.utils.getEventSource(event));
					}
				}
			},
			utils : {
				/**
				 * This function returns true if the enterred key is a number, a colon or one of the allowed special keys.
				 * */
				isValidKeyStroke : function(event)
				{
					var keyCode = event.keyCode;
					/** check if a special key was pressed */
					if ($.inArray(keyCode, constants.KEYS.SPECIAL_KEYS_TO_IGNORE) != -1)
						return true;
					/** Only allow numbers and the colon key */
					if ((!event.shiftKey && (constants.KEYS.CHARACTER_ZERO <= keyCode 
							&& keyCode <= constants.KEYS.CHARACTER_NINE || constants.KEYS.KEYPAD_ZERO <= keyCode
							&& keyCode <= constants.KEYS.KEYPAD_NINE))
							|| (event.shiftKey && keyCode == constants.KEYS.COLON))
					{
						return true;
					}
					return false;
				},
				/**
				 * This function pads the values with zeroes and colons.
				 * */
				applyPadding : function(textBox)
				{
					var val = textBox.value;
					if (val != "")
						val = parseInt(textBox.value.replace(":", ""), 10) + "";
					if (val.length > constants.NO_OF_CHARACTERS_WITHOUT_COLON)
					{
						val = val.substring(val.length - constants.NO_OF_CHARACTERS_WITHOUT_COLON);
					}
					switch (val.length)
					{
						case 0:
							val = val;
						break;
						case 1:
							val = "00:0" + val;
						break;
						case 2:
							val = "00:" + val;
						break;
						case 3:
							val = "0" + val.substring(0, 1) + ":" + val.substring(1);
						break;
						case 4:
							val = val.substring(0, 2) + ":" + val.substring(2);
						break;
					}
					
					//$(textbox).blur();
					//$(textbox).focus();
					/**
					 * mobile browsers seem to not place the caret in the correct place
					 * after the padding of zeroes, and IE8 doesnt support
					 * setselection range but works fine without it.
					 * 
					 * The basic idea is to add an extra space after the  last digit
					 * and "select" it,ensuring that the caret is in the correct position
					 * 
					 * */
					if(textBox.setSelectionRange)
					{
						textBox.value = val+" ";
						textBox.setSelectionRange(5, 6);
					}
					else
					{
						textBox.value = val;
					}
						
					return;
				},
				/**
				 * This function returns true if the enterred time is 
				 * a valid time based on the specified(or default) settings,
				 * else returns false.
				 * */
				isValidTime : function(strTime)
				{
					if (strTime == "")
						return true;
					var regex = constants.REGEX.TWENTY_FOUR;
					switch (settings.hourFormat)
					{
						case constants.HOUR_FORMATS.TWELVE:
							regex = constants.REGEX.TWELVE;
						break;
						case constants.HOUR_FORMATS.TWENTY_FOUR:
							regex = constants.REGEX.TWENTY_FOUR;
						break;
					}
					return regex.test(strTime);
				},
				getEventSource:function(event)
				{
					return event.target || event.srcElement;
				}
			}
		};
		/**Constants*/
		var constants = {
			/** Only 2 formats supported now.*/
			HOUR_FORMATS : {
				TWELVE : "12",
				TWENTY_FOUR : "24"
			},
			KEYS : {
				CHARACTER_ZERO : 48,
				CHARACTER_NINE : 57,
				KEYPAD_ZERO : 96,
				KEYPAD_NINE : 105,
				BACKSPACE : 8,
				TAB : 9,
				ENTER : 13,
				COLON : 186,
				SPECIAL_KEYS_TO_IGNORE : 	[ 
				                         	  	8, /**BACKSPACE*/
												9, /**DELETE*/
												46, /**TAB*/
												40, /**DOWN ARROW*/
												39, /**LEFT ARROW*/
												38, /**UP ARROW*/
												37 ,/**LEFT ARROW*/
												16 /** SHIFT*/
											]
			},
			NO_OF_CHARACTERS_WITHOUT_COLON : 4,
			REGEX : {
				TWELVE : /^([0-1]?[0-2])([:][0-5]?[0-9])?$/,
				TWENTY_FOUR : /^([2][0-3]|[0-1]?[0-9])([:][0-5]?[0-9])?$/
			}
		};
		// Create some defaults, extending them with any options that were provided
		var settings = $.extend( {
			'hourFormat' : '24',
			'onError' : function()
			{
				this.value="";
			}
		}, options);
		// Method calling logic
		if (methods[options])
		{
			return methods[options].apply(this, Array.prototype.slice.call(arguments, 1));
		} else if (typeof options === 'object' || !options)
		{
			return methods.init.apply(this, arguments);
		} else
		{
			$.error('Method ' + options + ' does not exist on jQuery.timefield');
		}
	};
})(jQuery);

No comments:

Post a Comment