The Gravy Framework

utils.js

Summary

Collection of general utility routines

Author: Bruce Wallace (PolyGlotInc.com)
Version: 1.0


Class Summary
Args This singleton class parses ampersand-separated name=value argument pairs from the query string of the URL; It stores the name=value pairs as properties of "this" object; adapted from here.
OObject the "abstract base class" OObject has no code, only a design pattern followed by convention; Two utility functions (Class,Extends) replace the "constructor" Javascript function with a wrapper function and also set up a proper inheritence mechanism; This mechanism allows the pretty source whereby "methods" are declared inline in the Javascript function, but the source code of those methods are not copied into every object instance as would otherwise be the case with simple Javascript;

The wrapper function also sets up the "constructor" to use GLOBALS.ValidateArgs() to insure that the required parameters are passed to the constructor and throw an exception if not.

The design pattern implemented by Class/Extends corrects several problems that occur with naive Class/Superclass mechanisms often used with Javascript;

  The required "interface" for "OObject":
  (1) the Object has its "class" declared via the utility
      function GLOBALS.Class()
  (2) the "superclass" (if any) is declared via the
      utility function GLOBALS.Extends()
  (3) the "constructor" logic for the class is placed
      in a "method" named "konstructor"
  (4) the konstructor method invokes the "super" constructor
      via its class name [rather than super()]

Method Summary
static Object ArgsToString( args )
          
static Object ArrayToString( a )
          
static Object Break(msg)
          
static void Breakpoint()
          
static void busy()
           set the cursor to the hourglass icon
static void BusyDo( <String> funcname, <String> funcargs, <int> optDelayMilliSecs )
           generic utility function to queue up for future execution the specified function with the specified arguments after a specified amount of delay.
static String CallerName( <Arguments> argumentsObj )
           This utility function returns the name of the function that called this routine.
static Function Class( <Function> theClass, <StringArray> optConstructorArgDescArray )
           Declare a class and create the glue objects and code to allow "pretty" source; A mechanism is set up to insure that required constructor parameters each have a defined value; This function also defines the "name" attribute of the specified class and initializes it with the class name.
static void ClearGlobalVar( <String> varname )
           static routine to dynamically undefine/delete a global variable
static void ClearGlobalVars()
           static routine to dynamically undefine/delete ALL "...GlobalVars" variables
static void ClearPersistentVars()
           static routine to dynamically undefine/delete ALL "...PersistentVars" variables
static void DebugWindow( <String> contents )
           open debug window with contents in scrollable area
static String diamond(<boolean> optBlankFlag)
           return the HTML to draw a diamond
static boolean dollarKeyFilter( <int> keyCode, <String> valueSoFar )
           is the given character a legal addition to the given dollar string
static Object dollarStrFilter(s)
          
static String dot()
           return the HTML to draw a biggish dot
static void editElem( e, makeSelected, makeEdited, makeInvalid )
          
static void editElemID( ID, makeSelected, makeEdited, makeInvalid )
          
static String epsilon()
           return the HTML to draw a smallish "E"
static void Error(msg)
          
static Function Extends( <Function> superClass )
           This utility function takes (via "THIS") a "class" and (re)sets its superClass.
static Object filterNum( str )
          
static String format_dollar( <anyType> v, <boolean> optZeroAsBlankFlag )
           format given value as $#,###.00
static String format_dollar_not( v )
           like format_dollar except empty string returned for zero
static String FuncName(<Function> f)
           This function returns the name of a given function; It does this by converting the function to a string, then using a regular expression to extract the function name from the resulting code.
static String genHook( <String> hookID, <String> optInnerHTML )
           generate a piece of HTML, identified by the given ID, that can safely have its innerHTML replaced at runtime.
static Object GetArg( argname )
          
static Object GetGlobalVar( <String> varname )
           static routine to return the value of persistent variable with given name
static Object getHook( hookID )
          
static Object GetPersistentVar( <String> varname )
           static routine to return the value of the persistent variable with given name
static Object getTodayAsMMDDYYYY()
          
static Object getTodayAsXXDDYYYY(monthNames)
          
static void highlightElem( e, makeSelected, selectColor )
          
static Object isDate(dateStr)
           return error message on invalid date or null if valid
static Object isDigit(c)
           Returns true if character c is a digit (0 ..
static Object isEmpty(s)
           Check whether string s is "empty".
static Object isFloat(s)
           Returns true if string s is an unsigned floating point (real) number.
static Object isInteger(s)
           Returns true if all characters in string s are numbers.
static Object isLetter(c)
           Returns true if character c is an English letter (A ..
static Object isSignedFloat(s)
           Returns true if string s is a signed or unsigned floating point (real) number.
static Object isZeroDollars(v)
          
static boolean keyFilter( <int> keyCode, <String> allowedChars )
           Is the given keyCode in the given list of allowed characters?
static void LoudThrow( exceptionObj )
          
static Object MissingArgException( argDesc, funcName )
          
static boolean noDuplicates( <String> s, <String> charList )
           Is there no more than one occurance of each character in the given character list in the given string?
static void notbusy()
           set the cursor to the default icon
static Object NullArgException( argDesc, funcName )
          
static Object ObjectToInitializer( o )
          
static Object ObjectToShortInitializer( o )
          
static Object ObjectToString( o )
          
static Object pleaseWait( <boolean> enableFlag, <String> msg, appId )
           create and open a window (if enabled) with the specified message
static void selectElem( e, makeSelected )
          
static void selectElemID( ID, makeSelected )
          
static void SendStatusMessage( <String> msg )
           send a message via the browser status bar
static void setBackgroundColor( ID, color )
          
static void setElemBackgroundColor( e, color )
          
static void setElemText( e, str )
          
static void setElemVisibility( e, visibleFlag )
          
static void SetGlobalVar( <String> varname, <Object> value )
           static routine to dynamically define/update a global (to window/page but reloaded with page) variable with the given name and value.
static void SetPersistentVar( <String> varname, <Object> value )
           static routine to dynamically define/update a persistent (across page loads) variable with the given name and value.
static void setText( ID, str )
          
static void setVisibility( ID, visibleFlag )
          
static boolean strFilter( s, <String> allowedChars )
           Does the given string consist of only characters in the given list of allowed characters?
static String subitem( isInValid )
           return the HTML to draw a subitem bullet
static Object timestamp()
           return the current time in milliseconds since start of epoc
static Object TooFewArgsException( funcName, required, passed )
          
static Object Trace(flag,msg)
          
static Object TraceEvt(msg)
          
static Object TraceMsg(msg)
          
static Object TraceMVC(msg)
          
static String trimStr(sInString)
           strip leading and trailing whitespace
static void UNWAIT(appId)
           close the "please wait" window if it exists otherwise no effect
static String URLDecode( <String> encoded )
           This decodes URL-encoded strings because the Javascript function "unescape" only does part of the job; adapted from here.
static void ValidateArgs( <StringArray> optArgDescArray )
           generic utility function to verify that required args (of the caller of this function) have been passed; The given array of descriptions defines how many arguments should be found with a defined value; Therefore, the caller of this routine should place its optional arguments at the end of its parameter list with no description specified in "optArgDescArray".
static void validateDate( hookID )
          
static void validateDollar( hookID )
          
static void validateFloat( hookID )
          
static void validateInteger( hookID )
          
static void WAIT(appId)
           Create an idempotent "wait a minute" window, if one doesn't exist, and squirrel away a reference to it that survives window reloads
static Object WaitWindowName(appId)
          
static void warnInvalid(theField, s)
           Notify user that contents of field theField are invalid.

// This file uses JSDoc-friendly comments [ http://jsdoc.sourceforge.net/ ]

/**
 * @file         utils.js
 * @fileoverview Collection of general utility routines
 * @author       Bruce Wallace  (PolyGlotInc.com)
 * @version      1.0
 */

/**
 * open debug window with contents in scrollable area
 * @param {String} contents what to put into this debug window
 */
function DebugWindow( contents )
{
    window.open("","","").document.write
    	("<textarea cols=150 rows=40>"+ contents + "</textarea>" );
}

function Breakpoint()
{
	//open window with current dynamic HTML displayed
	DebugWindow( document.body.parentNode.innerHTML );
	//cause Javascript debugger to kick in on undefined object reference
	//OR, comment out the next line and pre-set a breakpoint here via debugger
 //   UNDEFINED();
}

function Break(msg)
{
	if (msg.length>200) { DebugWindow( msg ); msg="(see window)"; }
	if (confirm(msg+"\nBreak here?")) Breakpoint(); else return true;
}

function Error(msg){
 if (Break("ERROR:"+msg))
 	alert("It is recommended that you close this window and try again.");
}

//global debug flags to enable verbose event tracing
var gTraceMVC = false;	//MVC setup tracing
var gTraceEvt = false;	//Event processing tracing
var gTraceMsg = false;	//message broadcast tracing

function Trace(flag,msg){ if (flag) return Break(msg); else return true; }

function TraceMVC(msg){ return Trace(gTraceMVC,msg); }
function TraceEvt(msg){ return Trace(gTraceEvt,msg); }
function TraceMsg(msg){ return Trace(gTraceMsg,msg); }

/** return the current time in milliseconds since start of epoc */
function timestamp(){ return (new Date()).getTime(); }

// ----------------------------------------------------------------------------
// Utility routines for Formatting Data
// ----------------------------------------------------------------------------

/** return the HTML to draw a biggish dot @type String */
function dot    (){ return '<font face="Symbol">&#183;</font>'; }

/** return the HTML to draw a smallish "E" @type String */
function epsilon(){ return '<font face="Symbol">&#101;</font>'; }

/** return the HTML to draw a diamond
 * @param {boolean} optBlankFlag if true, says generate blanks of same size as 1 diamond
 * @type String
 */
function diamond(optBlankFlag){ return optBlankFlag ? "&nbsp;&nbsp;" : "&diams;"; }

/** return the HTML to draw a subitem bullet @type String */
function subitem( isInValid ){ return isInValid ? "&epsilon;" : "&rArr;" }

/** Is the given keyCode in the given list of allowed characters?
 * @param {int} keyCode the charCode of the character to validate
 * @param {String} allowedChars the list of characters that are allowed
 * @return true iff keyCode is legal
 * @type boolean
 */
function keyFilter( keyCode, allowedChars ){
	return allowedChars.indexOf(String.fromCharCode(keyCode)) >= 0;
}

/** Does the given string consist of only characters in the given
 * list of allowed characters?
 * @param {int} keyCode the charCode of the character to validate
 * @param {String} allowedChars the list of characters that are allowed
 * @return true iff keyCode is legal
 * @type boolean
 */
function strFilter( s, allowedChars ){
	var q = "";
	for (var i=0; i<s.length; ++i)
	{
	  var c = s.charAt(i);
	  if (allowedChars.indexOf(c) >= 0) q += c;
	}
	return q;
}

/** Is there no more than one occurance of each
 * character in the given character list in the given string?
 * @param {String} s string to check
 * @param {String} charList list of characters that should occur only once
 * @return true if there are no duplicates
 * @type boolean
 */
function noDuplicates( s, charList )
{
	for (var i=0; i<charList.length; ++i)
	{
	  var c = charList.charAt(i);
	  var first = s.indexOf(c);
	  if (first!=-1 && first!=s.lastIndexOf(c)) return false;
	}
	return true;
}

var kDigitChars = "0123456789";
var kFloatChars = "+-.";

/** is the given character a legal addition to the given dollar string
 * @param {int} keyCode the proposed new character
 * @param {String} valueSoFar the value so far
 * @return true iff character is legal
 * @type boolean
 */
function dollarKeyFilter( keyCode, valueSoFar )
{
	return keyFilter( keyCode, kDigitChars+kFloatChars+"$," )
//	    && noDuplicates( valueSoFar+String.fromCharCode(keyCode), "$"+kFloatChars );
}
function dollarStrFilter(s){ return strFilter(s,kDigitChars+kFloatChars); }
function isZeroDollars(v){ return Math.abs(parseFloat(v))<0.01; }

/** format given value as $#,###.00
 * @type String
 * @param {anyType} v value to format
 * @param {boolean} optZeroAsBlankFlag optional flag to make zero format as blank
 * @author Bruce Wallace (PolyGlotInc.com)
 * @version 1.0
 */
function format_dollar( v, optZeroAsBlankFlag )
{
	if ( v==null || v=="null" ) return dot();
	if ( isZeroDollars(v) ) v = 0; //get rid of microcents and negative zeroes
	if ( optZeroAsBlankFlag && v==0 ) return "&nbsp;";

	// Do the equivalent to XSL::format-number($v,'$#,###.00')
	var x = parseFloat(v).toFixed(2);
	var sign = '';
	if (x < 0){ sign = '-'; x = x.substr(1); }
    var C    = x.split("");
    var n    = C.length-4;
    var s    = x.substring(n+1,n+4);

	for (var i=n; i>=0; --i)
	    s = (((C.length-i)%3==0 && i!=0) ? "," : "") + C[i] + s;

	return sign + "$" + s;
}

/** like format_dollar except empty string returned for zero @type String */
function format_dollar_not( v ){ return format_dollar( v, true ); }

/** strip leading and trailing whitespace @type String */
function trimStr(sInString) {
  sInString = sInString.replace( /^\s+/g, "" );// strip leading
  return sInString.replace( /\s+$/g, "" );// strip trailing
}

/** generate a piece of HTML, identified by the given ID,
 * that can safely have its innerHTML replaced at runtime.
 * @param {String} hookID name of the HTML to be generated
 * @param {String} optInnerHTML optional HTML to be inserted
 * into the generated HTML
 * @return generated HTML
 * @type String
 * @see #getHook
 * @author Bruce Wallace (PolyGlotInc.com)
 * @version 1.0
 */
function genHook( hookID, optInnerHTML ){
	return '<span id="'+hookID+'">'
	     + (optInnerHTML?optInnerHTML:"")
	     + '</span>';
}
function getHook( hookID ){
	var e = document.getElementById( hookID );
	if (e==null) TraceMVC("cant find hook["+hookID+"]");
	return e;
}

function setElemBackgroundColor( e, color ){
    if (!e) {Break("null setElemBackgroundColor!"); return;}
	e.style.backgroundColor = color;
}
function setBackgroundColor( ID, color ){
	setElemBackgroundColor( document.getElementById(ID), color );
}
function setElemText( e, str ){
    if (!e) {Break("null setElemText!"); return;}
	e.innerHTML = str;
}
function setText( ID, str ){
	setElemText( getHook(ID), str );
}


//TODO implement these via CSS instead
var    kInValidColor = '#FF9900';
var     kEditedColor = '#99CCCC';//'#FFCC99';
var   kSelectedColor = '#FFFFCC';
var kUnSelectedColor = '#FFFAF0';

function highlightElem( e, makeSelected, selectColor ){
	setElemBackgroundColor( e, makeSelected?selectColor:kUnSelectedColor );
}
/*ALTERNATE COLOR SCHEME...
//set the background color of the given element based on
//whether it is selected, dirty, and valid.
function editElem( e, makeSelected, makeEdited, makeInvalid ){
	var useColor = makeSelected || makeEdited || makeInvalid;
	highlightElem( e, useColor, makeInvalid ? kInValidColor :
	                            makeEdited  ? kEditedColor  :
	                                          kSelectedColor );
}
*/
//set the background color of the given element based on
//whether it is selected, dirty, and valid.
function editElem( e, makeSelected, makeEdited, makeInvalid ){
	var useColor = makeSelected || makeEdited;
	highlightElem( e, useColor, makeEdited  ? kEditedColor  :
	                                          kSelectedColor );
}
function editElemID( ID, makeSelected, makeEdited, makeInvalid ){
	editElem( getHook(ID), makeSelected, makeEdited, makeInvalid );
}
function selectElem( e, makeSelected ){
	highlightElem( e, makeSelected, kSelectedColor );
}
function selectElemID( ID, makeSelected ){
	selectElem( getHook(ID), makeSelected );
}

function setElemVisibility( e, visibleFlag )
{
    if (!e) {if (TraceMVC("null setElemVisibility!")) return;}
	e.style.visibility = visibleFlag ? "visible"  : "hidden";
	e.style.position   = visibleFlag ? "relative" : "absolute";
}
function setVisibility( ID, visibleFlag ){
  setElemVisibility( getHook(ID), visibleFlag );
}


var kMonthNames = new Array(
				"January","February","March","April","May","June","July",
				"August","September","October","November","December");
var kMonthNums = new Array(
				"01","02","03","04","05","06","07",
				"08","09","10","11","12");

function getTodayAsXXDDYYYY(monthNames)
{
	var now = new Date();
	return monthNames[ now.getMonth() ]
	     + "/" + now.getDate()
	     + "/" + now.getFullYear();
}
function getTodayAsMMDDYYYY(){
	return getTodayAsXXDDYYYY( kMonthNums );
}

// ----------------------------------------------------------------------------
// Utility routines for Exception Handling
// ----------------------------------------------------------------------------

function LoudThrow( exceptionObj )
{
	Break(exceptionObj);
	throw exceptionObj;
}

function MissingArgException( argDesc, funcName )
{
	return "Error: "+argDesc+" wasnt passed to "+funcName;
}

function NullArgException( argDesc, funcName )
{
	return "Illegal null "+argDesc+" passed to "+funcName;
}

function TooFewArgsException( funcName, required, passed )
{
	return funcName + " requires "+ required +" arguments, but was passed " + passed;
}

/**
 * This function returns the name of a given function; It does this by
 * converting the function to a string, then using a regular expression
 * to extract the function name from the resulting code.
 * @param {Function} f reference to a function
 * @return name of given function
 * @type String
 * @author Bruce Wallace (PolyGlotInc.com)
 * @version 1.0
 */
function FuncName(f)
{
    var s = f.toString().split( new RegExp("[ \(]"), 2)[1];
    if ((s == null) || (s.length == 0)) return "anonymous";
    return s;
}

/**
 * This utility function returns the name of the function that
 * called this routine.
 * @param {Arguments} argumentsObj the Javascript arguments object
 * @return the caller's function name
 * @type String
 * @see #FuncName
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 1.0
 */
function CallerName( argumentsObj ){
	return FuncName( argumentsObj.caller.callee )
}

/**
 * This utility function takes (via "THIS") a "class" and
 * (re)sets its superClass.
 * @param {Function} superClass the "superclass constructor" function
 * @return a reference to the "class object"
 * @type Function
 * @see #Class
 * @see OObject
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 1.0
 */
function Extends( superClass )
{
	this.baseClass.prototype  = superClass ? superClass.prototype : null;
	this.prototype            = new this.baseClass();
	this.prototype.name       = this.name;//default value
	this.prototype[this.name] = this.prototype.konstructor;//so people can call super
	return this;
}

/**
 * Declare a class and create the glue objects and code
 * to allow "pretty" source; A mechanism is set up to insure
 * that required constructor parameters each have a defined value;
 * This function also defines the "name" attribute of the specified
 * class and initializes it with the class name.
 * @param {Function} theClass the "class constructor" function
 * @param {StringArray} optConstructorArgDescArray optional array
 * of strings describing required parameters for this class'
 * "constructor"; Note: Optional constructor parameters should
 * not be included in this array.
 * @return the "class object" (an extended Function object)
 * @type Function
 * @see #Extends
 * @see #ValidateArgs
 * @see OObject
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 1.0
 */
function Class( theClass, optConstructorArgDescArray )
{
	// The "class" as passed in will really be an abstract base class. We
	// will squirrel that away and replace theClass with a wrapper function
	// so that method source code is not copied into each object instance
	// as would otherwise be the case.
	var baseClass = theClass;

	// create the new wrapper function to replace theClass
	var  className  = FuncName( theClass );
	self[className] = theClass = function(){
		ValidateArgs( theClass.required );
		theClass.prototype.konstructor.apply( this, arguments );
	}
	theClass.required = optConstructorArgDescArray;
	theClass.name     = className;
	theClass.baseClass= baseClass;
	theClass.Extends  = Extends;//so we can call Extends as a method
	return theClass.Extends();//needed, even tho no superclass specified
}

/**
 * @class the "abstract base class" OObject has no code, only
 * a design pattern followed by convention; Two utility
 * functions (Class,Extends) replace the "constructor"
 * Javascript function with a wrapper function and also
 * set up a proper inheritence mechanism; This mechanism
 * allows the pretty source whereby "methods" are declared
 * inline in the Javascript function, but the source code
 * of those methods are not copied into every object instance
 * as would otherwise be the case with simple Javascript;<p>
 * The wrapper function also sets up the "constructor"
 * to use {@link GLOBALS#ValidateArgs} to insure that the required
 * parameters are passed to the constructor and throw an
 * exception if not.<p>
 * The design pattern implemented by Class/Extends
 * corrects several problems that occur with naive
 * Class/Superclass mechanisms often used with Javascript;
 *<pre>
 *  The required "interface" for "OObject":
 *  (1) the Object has its "class" declared via the utility
 *      function {@link GLOBALS#Class}
 *  (2) the "superclass" (if any) is declared via the
 *      utility function {@link GLOBALS#Extends}
 *  (3) the "constructor" logic for the class is placed
 *      in a "method" named "konstructor"
 *  (4) the konstructor method invokes the "super" constructor
 *      via its class name [rather than super()]
 *</pre>
 * @throws MissingArgException thrown by constructor wrapper
 * @see GLOBALS#Class
 * @see GLOBALS#Extends
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 1.0
 */
function OObject(){/* this function exists only for JsDoc purposes.*/}


/**
 * generic utility function to verify that required args
 * (of the caller of this function) have been passed; The given
 * array of descriptions defines how many arguments should be
 * found with a defined value;  Therefore, the caller of this
 * routine should place its optional arguments at the end of its
 * parameter list with no description specified in "optArgDescArray".
 * @param {StringArray} optArgDescArray optional array of strings
 * describing required parameters; Note: Optional constructor parameters should
 * not be included in this array.
 * @throws MissingArgException
 * @see #Class
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 1.0
 */
function ValidateArgs( optArgDescArray )
{
	if (optArgDescArray==null || optArgDescArray.length==0) return;

    // arguments.caller.callee is the Function object that called us.
    // Its arity property is the number of arguments that were expected.
    //var expected = arguments.caller.callee.arity;

    // arguments.caller is the arguments object of the function that
    // called us. Its length property is the number of actual args passed.
    var passed = arguments.caller.length;
    
    // argDescArray should be an array of arg names of args that musnt be null
    var required = optArgDescArray==null ? 0 : optArgDescArray.length;
    if (passed < required)
	  LoudThrow( TooFewArgsException( CallerName(arguments), required, passed ) );

    for (var i=0; i<required; ++i)
	 //robust test for "undefined"
	 //see http://pt.withy.org/ptalk/archives/2005/06/dont_assume_undefined_is_undefined.html
	 if (arguments.caller[i] === void 0)
	  LoudThrow( MissingArgException( optArgDescArray[i], CallerName(arguments) ) );
}

// ----------------------------------------------------------------------------
// Utility routines for "please wait" indicators
// ----------------------------------------------------------------------------

function WaitWindowName(appId){
	return "gWaitWindow" + appId;
}
/** Create an idempotent "wait a minute" window, if one doesn't exist,
 * and squirrel away a reference to it that survives window reloads
 */
function WAIT(appId) {
	var winName = WaitWindowName(appId);
	if (navigator[ winName ]==null)
		navigator[ winName ]= pleaseWait( true, "Loading data.", appId );
}
/** close the "please wait" window if it exists otherwise no effect */
function UNWAIT(appId) {
	var winName = WaitWindowName(appId);
	if (navigator[ winName ]==null) return;
	    navigator[ winName ].close();
	    navigator[ winName ] = null;
}
/** create and open a window (if enabled) with the specified message
 * @param {boolean} enableFlag iff true then create window
 * @param {String} msg message to place in window
 * @return the created window or null if not enabled
 * @type Object
 */
function pleaseWait( enableFlag, msg, appId )
{
	var theWindow = null;
	if ( enableFlag )
	{
	 var xMax = screen.width, yMax = screen.height;
     var xOffset = (xMax - 220)/2, yOffset = (yMax - 100)/2;
     theWindow = window.open("", 'pleaseWait', 'width=250 height=100 toolbar=no scrollbars=no menubar=no resizable=yes top='+yOffset+' left='+xOffset+'');
     theWindow.document.write("<BODY BGCOLOR=#6eaba1>");
     theWindow.document.write("<TITLE>" +appId+ " Processing...</TITLE>");
     theWindow.document.write('<FONT FACE="Arial" SIZE=2><layer id="c"><left><B>'+msg+'<br/>Please Wait.</left></layer></font><br>');
	}
	return theWindow;
}

/** set the cursor to the hourglass icon */
function busy() {
  document.body.style.cursor='wait';
}
/** set the cursor to the default icon */
function notbusy() {
  document.body.style.cursor='default';
}

/**
 * generic utility function to queue up for future execution the specified
 * function with the specified arguments after a specified amount of delay.
 * THIS function returns immediately, and the specified function will execute
 * later (in potentially a different thread). If this is called with the same
 * function specified as was specified earlier, and that function has not
 * executed yet, then the scheduled execute time is updated, rather than the
 * function being executed twice.
 * @param {String} funcname the name of the function to call
 * @param {String} funcargs a string image of the parameters to be passed
 * @param {int} optDelayMilliSecs optional delay amount (default 5 milliSeconds)
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 1.0
 */
function BusyDo( funcname, funcargs, optDelayMilliSecs )
{
	var timedelay = optDelayMilliSecs || 5;
//	busy();
	var globalVarName = "gTimeout"+funcname;
	if (navigator[ globalVarName ]==null) navigator[globalVarName] = null;
	if (navigator[ globalVarName ]!=null) clearTimeout( navigator[globalVarName] );
	    navigator[ globalVarName ] = setTimeout( funcname+funcargs, timedelay );
}

// ----------------------------------------------------------------------------
// Utility routines for URL Handling
// ----------------------------------------------------------------------------


/** This decodes URL-encoded strings because the Javascript
 * function "unescape" only does part of the job; adapted from
 * <a target="_blank" href="http://www.albionresearch.com/misc/urlencode.php">here.</a>
 * @param {String} encoded the URL-encoded string to translate
 * @return decoded version of given encoded string
 * @type String
 * @author Bruce Wallace (PolyGlotInc.com)
 * @version 1.0
 */
function URLDecode( encoded )
{
   // Replace + with space
   // Replace %xx with equivalent character
   // Put [ERROR] in output if %xx is invalid.
   var HEXCHARS = "0123456789ABCDEFabcdef"; 
   var plaintext = "";
   var i = 0;
   while (i < encoded.length) {
       var ch = encoded.charAt(i);
	   if (ch == "+") {
	       plaintext += " ";
		   i++;
	   } else if (ch == "%") {
			if (i < (encoded.length-2) 
					&& HEXCHARS.indexOf(encoded.charAt(i+1)) != -1 
					&& HEXCHARS.indexOf(encoded.charAt(i+2)) != -1 )
			{
				plaintext += unescape( encoded.substr(i,3) );
				i += 3;
			} else {
				throw 'Bad escape combination near ...' + encoded.substr(i);
				i++;
			}
		} else {
		   plaintext += ch;
		   i++;
		}
	}
   return plaintext;
}

var gArgs = null;	//global singleton object reference
/**
 * @class This singleton class parses ampersand-separated name=value
 * argument pairs from the query string of the URL; It stores the
 * name=value pairs as properties of "this" object; adapted from
 * <a target="_blank" href="http://www.oreilly.com/catalog/jscript3/chapter/ch13.html#ch13_08.htm">here.</a>
 * @author Bruce Wallace (PolyGlotInc.com)
 * @version 1.0
 * @todo refactor this into a proper object now that I know how to
 */
function Args()	//lazy singleton factory
{
	if (gArgs!=null) return gArgs; //use singleton

	gArgs = new Object; //create singleton
	gArgs.get = function(argname){ return eval("this."+argname); };
	// todo someday see if above eval can be replaced with "this[argname]"

    var query = location.search.substring(1);  // Get query string.
    var pairs = query.split("&");              // Break at ampersand.

    for (var i=0; i<pairs.length; ++i)
    {
	  var pos = pairs[i].indexOf('=');            // Look for "name=value".
	  if (pos == -1) continue;                    // If not found, skip.
	  var argname   = pairs[i].substring(0,pos);  // Extract the name.
	  var value     = pairs[i].substring(pos+1);  // Extract the value.
	  gArgs[argname] = URLDecode(value);            // Store as a property.
    }
    return gArgs;                               // Return the singleton.
}
function GetArg( argname ) {
	return Args().get( argname );
}

/** send a message via the browser status bar
 * @param {String} msg the message to send.
 */
function SendStatusMessage( msg )
{
    window.status = msg; TraceMsg(msg);
    window.status = "";
}

// ----------------------------------------------------------------------------
// Utility routines to convert entities into strings
// ----------------------------------------------------------------------------

function ObjectToString( o )
{
	var s = "";
	for (var property in o)// iterate over all properties  
    	s += "Property [" + property + "] is [" +  o[property] +"]\n";
    return s;
}
function ObjectToInitializer( o )
{
	var s = "{\n";
	for (var property in o)// iterate over all properties
	{
	  var q = (o[property] instanceof Function) ? '' : '"';
	  s += property + ':'+q + o[property] +q+',\n';
    }
    return s.substring(0,s.length-2)+"\n}";//get rid of dangling comma
}
function ObjectToShortInitializer( o )
{
	var s = "{\n";
	for (var property in o)// iterate over all properties
	{
	  var P = o[ property ];
	  if (!(P instanceof Function))
	       s += property + ':"' + P + '",\n';
//	  else s += property + ': function,\n';
    }
    return s.substring(0,s.length-2)+"\n}";//get rid of dangling comma
}
function ArrayToString( a )
{
	var s = "";
	for (var i in a)// iterate over all array items  
    	s += "["+ i +"] is ["+  a[i] +"]\n";
    return s;
}
function ArgsToString( args )
{
     var s = "";
     for (var i=0; i<args.length; ++i)// iterate over all function args
    	s += "["+ i +"] is ["+  args[i] +"]\n";
     return s;
}

///// GLOBAL-PERSISTENCE VARIABLES ROUTINES /////

/** static routine to dynamically define/update a persistent (across page loads)
 * variable with the given name and value.
 * @param {String} varname name of "persistent" variable
 * @param {Object} value value to set variable to
 */
function SetPersistentVar( varname, value ) //persisted across page loads
{
	if (navigator.gGlobalMap==null) navigator.gGlobalMap = new Object();
	navigator.gGlobalMap[varname] = value;
}

/** static routine to return the value of the persistent variable with given name
 * @param {String} varname name of "persistent" variable
 * @return {Object} value of variable (empty object if not previously defined)
 * @type Object
 */
function GetPersistentVar( varname )
{
	if (navigator.gGlobalMap==null) navigator.gGlobalMap = new Object();
	return navigator.gGlobalMap[varname];
}

/** static routine to dynamically undefine/delete ALL "...PersistentVars" variables  */
function ClearPersistentVars(){ navigator.gGlobalMap = null; }

/** static routine to dynamically define/update a global (to window/page
 * but reloaded with page) variable with the given name and value.
 * @param {String} varname name of "persistent" variable
 * @param {Object} value value to set variable to
 */
function SetGlobalVar( varname, value )
{
	if (window.gGlobalMap==null) window.gGlobalMap = new Object();
	window.gGlobalMap[ varname ] = value;
}

/** static routine to return the value of persistent variable with given name
 * @param {String} varname name of "global" i.e. "static" variable
 * @return {Object} value of variable (empty object if not previously defined)
 * @type Object
 */
function GetGlobalVar( varname )
{
	if (window.gGlobalMap==null) window.gGlobalMap = new Object();
	return window.gGlobalMap[ varname ];
}

/** static routine to dynamically undefine/delete ALL "...GlobalVars" variables */
function ClearGlobalVars(){ window.gGlobalMap = null; }

/** static routine to dynamically undefine/delete a global variable
 * @param {String} varname name of "persistent" variable
 */
function ClearGlobalVar( varname ){ delete window.gGlobalMap[ varname ]; }

///// BASIC DATA VALIDATION ROUTINES /////

/** Check whether string s is "empty". */
function isEmpty(s)
{   return ((s == null) || (s.length == 0))
}

/** Returns true if character c is an English letter (A .. Z, a..z). */
function isLetter(c)
{   return ( ((c >= "a") && (c <= "z")) || ((c >= "A") && (c <= "Z")) )
}

/** Returns true if character c is a digit (0 .. 9). */
function isDigit(c)
{
 return (c >= "0") && (c <= "9");
}

/**
 * Returns true if all characters in string s are numbers.
 * Accepts non-signed integers only. Does not accept floating 
 * point, exponential notation, etc.
 * We don't use parseInt() because that would accept a string
 * with trailing non-numeric characters.
 */
function isInteger(s)
{
	if (isEmpty(s)) return true;
    for (i = 0; i < s.length; i++)   
        if (!isDigit(s.charAt(i))) return false;

    return true;
}

/**
 * Returns true if string s is an unsigned floating point (real) number. 
 * Does not accept exponential notation.
 */
function isFloat(s)
{
   if (isEmpty(s)) return true;
    var seenDecimalPoint = false;
    var decimalPointDelimiter = ".";

    if (s == decimalPointDelimiter) return false;

    for (i = 0; i < s.length; i++)
    {   
        var c = s.charAt(i);
        if ((c == decimalPointDelimiter) && !seenDecimalPoint) seenDecimalPoint = true;
        else if (!isDigit(c)) return false;
    }

    return true;
}


/** 
 * Returns true if string s is a signed or unsigned floating point 
 * (real) number. First character is allowed to be + or -.
 * Does not accept exponential notation.
 */
function isSignedFloat(s)
{
   if (isEmpty(s)) return true;
   var startPos = 0;
   // skip leading + or -
   if ( (s.charAt(0) == "-") || (s.charAt(0) == "+") ) startPos = 1;    
   return (isFloat(s.substring(startPos, s.length)));
}

/**
 * Notify user that contents of field theField are invalid.
 * String s describes expected contents of theField.value.
 */
function warnInvalid(theField, s)
{
	if (isEmpty(s)) return;
	theField.focus();
    theField.select();
    alert(s);
}

/** return error message on invalid date or null if valid */
function isDate(dateStr)
{
  if (isEmpty(dateStr)) return null;

  var datePat    = /^(\d{1,2})(\/|-)(\d{1,2})(\/|-)(\d{4})$/;
  var matchArray = dateStr.match(datePat); // is format OK?

  if (matchArray == null)
    return "Please enter date as either mm/dd/yyyy or mm-dd-yyyy.";

  // parse date into variables
  month = matchArray[1];
  day   = matchArray[3];
  year  = matchArray[5];

  if (month < 1 || month > 12)
    return("Month must be between 1 and 12.");

  if (day < 1 || day > 31)
    return("Day must be between 1 and 31.");

  if ((month==4 || month==6 || month==9 || month==11) && day==31)
    return("Month " + month + " doesn't have 31 days!");

  if (month == 2) { // check for february 29th
    var isleap = (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0));
    if (day > 29 || (day==29 && !isleap))
      return("February " + year + " doesn't have " + day + " days!");
  }
  return null;  // date is valid
}


function filterNum( str ) {
	var re = /\$|,/g; // remove "$" and ","
	return str.replace(re, "");
}

function validateDollar( hookID )
{
	var field = getHook( hookID );
    field.value = filterNum( field.value );
    if ( ! isSignedFloat(field.value) )
	  warnInvalid( field, "Please enter a valid dollar amount" );
}
function validateInteger( hookID )
{
	var field = getHook( hookID );
    field.value = filterNum(field.value);
    if (!isInteger(field.value))
	  warnInvalid( field, "Please enter a valid integer" );
}
function validateFloat( hookID )
{
	var field = getHook( hookID );
    //field.value = filterNum(field.value);
    if (!isSignedFloat(field.value))
	  warnInvalid( field, "Please enter a valid number" );
}
function validateDate( hookID )
{
	var field = getHook( hookID );
    warnInvalid( field, isDate(field.value) );
}

The Gravy Framework

Documentation generated by JSDoc on Fri Mar 17 06:40:21 2006