My Blog List

Wednesday, November 7, 2012

Overriding Default Javascript Function Behaviors (e.g. : window.open)

Sometimes you might need to make a change to the default behavior of a Javascript function and want to apply it to all pages. An example could be to do some custom behavior replacing all new window popups with jQuery modal dialogs across an existing application. Instead of going through the code and finding all instances of window.open() and replacing with modal dialogs you could override window.open() itself and do it in one place.

Here’s a neat technique to override default functions without corrupting the global namespace.

Code:

Example 1: Override but do nothing, call window.open:

//override globally
window.open = function (open) {
		    return function (url, name, features) {	
		        // some browsers dont support the call() method, so call the method directly in such cases							        																		    
			      return open.call ?  open.call(window, url, name, features):open( url, name, features);
			};
		}(window.open);// pass in the window.open function object as argument to the anonymous function
Example 2: Override but do nothing, call showModalDialog function: (Leaving out the actual implementation of showModalDialog() for brevity)
//override globally
window.open = function (open) {
                 return function (url, name, features) {	
		    // some browsers dont support the call() method, so call the method directly in such cases							        																		    
		        return open.call ?  showModalDialog.call(window, url, name, features):showModalDialog( url, name, features);
		    };
	}(window.open);// pass in the window.open function object as argument to the anonymous function

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);