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

Wednesday, August 1, 2012

Generating Print Statements for Stored Procedure Arguments

 

Here’s a little utility I wrote which, given a target stored procedure name, generates PRINT statements for all the arguments. This can be included at the top of the target sproc and run to view the actual values passed at runtime.

I was in two minds whether to post this at all, as it has very little use beyond trying to log runtime values for a target sproc which is called internally inside another sproc. For all other cases the SQL SERVER profiler should suffice. But, since we do have nested calls in our application and I thought maybe some one else does too, I am posting it.

A word on TSQL_SPs Trace Template:

Now I know we can profile nested sproc calls using the TSQL_SPs Trace Template, but Profiler does not capture the actual values passed in to the nested call if they are dynamic.

For example:

case 1: This gets profiled correctly

CREATE PROCEDURE OUTER_SPROC
(
	:
	:
	EXEC INNER_SPROC 'THIS','IS','HARD-CODED'
	:
	:
)
Profiler output:
EXEC INNER_SPROC 'THIS','IS','HARD-CODED'

case 2: This does not

CREATE PROCEDURE OUTER_SPROC
(
	DECLARE @VAR1 VARCHAR
	DECLARE @VAR2 VARCHAR
	DECLARE @VAR3 VARCHAR
	
	SET @VAR1 = 'THIS'
	SET @VAR1 = 'IS'
	SET @VAR1 = 'DYNAMIC'
	:
	EXEC INNER_SPROC @VAR1,@VAR2,@VAR3
	:
	:
)

EXEC INNER_SPROC @VAR1,@VAR2,@VAR3




And so, my little utility

USE [UtilsDB]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Author:		Sajjan Sarkar
-- Create date: 
-- Description:	Utility to generate print statements for a target sproc
-- =============================================
ALTER PROCEDURE [dbo].[usp_Utils_PrintArgs]
    (
      @DBName VARCHAR(100) = '' ,
      @SprocName VARCHAR(100) = ''
    )
AS 
    BEGIN
	-- SET NOCOUNT ON added to prevent extra result sets from
	-- interfering with SELECT statements.
        SET NOCOUNT ON ;
	
        DECLARE @execGetParms VARCHAR(500)
        DECLARE @ArgName VARCHAR(100)
        DECLARE @ArgType VARCHAR(100)
        DECLARE @PrintString VARCHAR(8000)
         
        -- this string will contain the print statements
        SET @PrintString = ''
		-- sp_procedure_params_rowset returns the arguments  for a sproc. this has to be a dynamic 
		-- SQL so that we can  run it for sprocs across databases
        SET @execGetParms = 'exec [' + @DBName + ']..sp_procedure_params_rowset N''' + @SprocName + ''',1,NULL,NULL'
  	
		-- temp table to consume the results of the sp_procedure_params_rowset call
        CREATE TABLE #tmpArgs
            (
              PROCEDURE_CATALOG VARCHAR(100) ,
              PROCEDURE_SCHEMA VARCHAR(100) ,
              PROCEDURE_NAME VARCHAR(100) ,
              PARAMETER_NAME VARCHAR(100) ,
              ORDINAL_POSITION INT ,
              PARAMETER_TYPE INT ,
              PARAMETER_HASDEFAULT INT ,
              PARAMETER_DEFAULT VARCHAR(100) ,
              IS_NULLABLE INT ,
              DATA_TYPE INT ,
              CHARACTER_MAXIMUM_LENGTH INT ,
              CHARACTER_OCTET_LENGTH INT ,
              NUMERIC_PRECISION INT ,
              NUMERIC_SCALE INT ,
              DESCRIPTION VARCHAR(100) ,
              TYPE_NAME VARCHAR(100) ,
              LOCAL_TYPE_NAME VARCHAR(100)
            )
		
        INSERT  INTO #tmpArgs
                ( PROCEDURE_CATALOG ,
                  PROCEDURE_SCHEMA ,
                  PROCEDURE_NAME ,
                  PARAMETER_NAME ,
                  ORDINAL_POSITION ,
                  PARAMETER_TYPE ,
                  PARAMETER_HASDEFAULT ,
                  PARAMETER_DEFAULT ,
                  IS_NULLABLE ,
                  DATA_TYPE ,
                  CHARACTER_MAXIMUM_LENGTH ,
                  CHARACTER_OCTET_LENGTH ,
                  NUMERIC_PRECISION ,
                  NUMERIC_SCALE ,
                  DESCRIPTION ,
                  TYPE_NAME ,
                  LOCAL_TYPE_NAME
		        )
                EXEC ( @execGetParms
                    )
        --  loop thru each arg and build the string          
        DECLARE cursorDB CURSOR READ_ONLY
        FOR
            SELECT  PARAMETER_NAME ,
                    TYPE_NAME
            FROM    #tmpArgs AS TA
            ORDER BY ORDINAL_POSITION ASC 
                   
        OPEN cursorDB
        
        FETCH NEXT FROM cursorDB INTO @ArgName, @ArgType
        WHILE ( @@fetch_status <> -1 ) 
            BEGIN
                IF ( @@fetch_status <> -2 ) 
                    BEGIN   
                        IF @ArgName != '@RETURN_VALUE'  -- ignore the default @RETURN_VALUE arg
                            BEGIN
								-- cast to string depending on type of arg
                                SET @PrintString = @PrintString + ' PRINT ''' + @ArgName + ':''+ CASE WHEN ' + @ArgName + ' IS NULL THEN ''NULL'' ELSE '
                                    + CASE WHEN @ArgType IN ( 'int', 'bit', 'numeric' ) THEN ' CAST(' + @ArgName + ' AS VARCHAR(100)) '
                                           WHEN @ArgType LIKE 'date%' THEN ' CAST(' + @ArgName + ' AS VARCHAR)'
                                           ELSE @ArgName
                                      END + ' END ' + CHAR(13)
                            END                
                       
                    END 
                FETCH NEXT FROM cursorDB INTO @ArgName, @ArgType
            END--WHILE (@@fetch_status <> -1)                   
                    
        CLOSE cursorDB
        DEALLOCATE cursorDB
	
		-- print output
        PRINT @PrintString
        
        DROP TABLE #tmpArgs
	             
    END



Example Usage:


exec UtilsDB..usp_Utils_PrintArgs 'MyTargetDB' ,'INNER_SPROC'



output

PRINT '@VAR1:'+ CASE WHEN @VAR1 IS NULL THEN 'NULL' ELSE @VAR1 END 
PRINT '@VAR2:'+ CASE WHEN @VAR2 IS NULL THEN 'NULL' ELSE @VAR2 END 
PRINT '@VAR2:'+ CASE WHEN @VAR2 IS NULL THEN 'NULL' ELSE @VAR2 END 

It also handles casting non-string arguments to their string forms, so if your arguments were String,INT and DATETIME

PRINT '@VAR1:'+ CASE WHEN @VAR1 IS NULL THEN 'NULL' ELSE @VAR1 END 
PRINT '@VAR2:'+ CASE WHEN @VAR2 IS NULL THEN 'NULL' ELSE CAST(@VAR2 AS VARCHAR(100)) END 
PRINT '@VAR3:'+ CASE WHEN @VAR3 IS NULL THEN 'NULL' ELSE CAST(@VAR3 AS VARCHAR) END 

Now just paste the PRINT statements into INNER_SPROC and run the outer one :)