/* This file contains the definition of the MicroStrategy namespace, its global constants, enumerations, and settings. */

/* Namespace for all MicroStrategy JavaScript. */

// Start File - consts.js //
/**
 * @namespace Global namespace for the mstr application.
 */
var mstr = {};

/* Property names. */

mstr.Props = {};
mstr.Props.Widget = {
	'ID' :'id',
	'SCRIPTCLASS' :'scriptClass',
	'FEATURES' :'features',
	'CSSCLASS' :'cssClass',
	'LISTENS_TO' :'listensTo',
	'PROPOGATES_EVENTS' :'propogatesEvents',
	'UPDATES' :'updates',
	'PATH' :'path',
	'FOR' :'for',
	'PROPOGATES' :'propogates',
	'DOCUMENT' :'document',
	'ELEMENTID' :'elementId',
	'ELEMENT' :'element',
	'ISELEMENTVALID' :'isElementValid',
	'RENDERSTATUS' :'renderStatus',
	'PARENT' :'parent',
	'LAYOUTFILE' :'layoutFile',
	'LAYOUTCLASS' :'layoutClass',
	'LAYOUTHANDLERS' :'layoutHandlers',
	'ENABLED' :'enabled',
	'VISIBLE' :'visible',
	'MESSAGE' :'message',
	'HEIGHT' :'height',
	'WIDTH' :'width',
	'TYPE' :'type',
	'VALUE' :'value',
	'VALUEFORMS' :'valueForms',
	'VALUEFORMSMAP' :'valueFormsMap',
	'BROWSEFORMS' :'browseForms',
	'BROWSEFORMSMAP' :'browseFormsMap',
	'AVAILABLEVALUES' :'availableValues',
	'ISVALUEFROMLIST' :'isValueFromList',
	'ISVALUEFROMTREE' :'isValueFromTree',
	'ANSWER' :'answer',
	'DEFAULTANSWER' :'defaultAnswer',
	'RESTRICTION' :'restriction',
	'REQUIRED' :'required',
	'PAGENUMBER' :'pageNumber',
	'CAPTION' :'caption',
	'IMAGEENABLED' :'imageEnabled',
	'IMAGEDISABLED' :'imageDisabled',
	'STATUSCODE' :'statusCode',
	'SHOWREADYSTATE' :'showReadyState',
	'CHILDREN' :'children',
	'PREINITCHILDREN' :'preInitChildren',
	'CONTENTS' :'contents',
	'INPUT' :'input',
	'SIMPLEINPUT' :'simpleInput',
	'COMPLEXINPUT' :'complexInput',
	'INPUT1' :'input1',
	'INPUT2' :'input2',
	'ALLOWFILEIMPORT' :'allowFileImport',
	'ALLOWELEMENTBROWSE' :'allowElementBrowse',

	'BUTTONBAR' :'buttonBar',
	'LINK' :'link',
	'SHOWLINK' :'showLink',
	'BOOK' :'book',
	'TOC' :'toc',
	'SUMMARY' :'summary',
	'PREVIOUSBUTTON' :'previousButton',
	'NEXTBUTTON' :'nextButton',
	'CLOSESUMMARYBUTTON' :'closeSummaryButton',
	'CSSSHADOW' :'cssShadow',

	'MIN' :'min',
	'MAX' :'max',
	'AUTOSELECT' :'autoSelect',

	'ATTRIBUTE' :'attribute',
	'FORM' :'form',

	'ISLIST' :'isList',
	'ISTREE' :'isTree',
	'DATAFORMS' :'dataForms',
	'ROOT' :'root',
	'ROOTFLATTENEDTREE' :'rootFlattenedTree',
	'LISTITEMS' :'listItems',
	'SELECTEDINDICES' :'selectedIndices',
	'BLOCKCOUNT' :'blockCount',
	'BLOCKCOUNTMAP' :'blockCountMap',
	'BLOCKBEGIN' :'blockBegin',
	'FOLDER' :'folder',
	'FOLDERSIZE' :'folderSize',
	'FOLDERNODELOCATION' :'folderNL',
	'PATHTREE' :'pathTree',
	'PATHFLATTENEDTREE' :'pathFlattenedTree',
	'SEARCHXML' :'searchXML',
	'FILTERXML' :'filterXML',
	'ATTRIBUTEID' :'attributeId',
	'OBJECTTYPES' :'objectType',
	'ALLOWUNAVAILABLEVALUE' :'allowUnavailableValue',
	'INSERTUNAVAILABLEVALUEAT' :'insertUnavailableValueAt',
	'PRESERVEVALUEONREADYSTATECHANGE' :'preserveValueOnReadyStateChange',
	'LEAFELEMENTS' :'leafElements',

	'DISPLAYFORMTEXT' :'displayFormText',
	'DISPLAYFORMICON' :'displayFormIcon',
	'DISPLAYFORMS' :'displayForms',
	'NOSELECTIONTEXT' :'noSelectionText',
	'NOSELECTIONTEXT' :'noSelectionIcon',
	'MULTISELECTIONTEXT' :'multiSelectionText',
	'MULTISELECTIONTEXT' :'multiSelectionIcon',
	'EMPTYLISTTEXT' :'emptyText',
	'ICONFORM' :'iconForm',

	'MULTISELECT' :'multiSelect',
	'COMMANDID' :'cmdId',
	'DELEGATESCOMMANDS' :'delegatesCommands',
	'APPLIESTOPATH' :'appliesToPath',
	'POPUPID' :'popupId',
	'POPUPOBJECT' :'popupObj',
	'POPORIENTATION' :'popOrientation',

	'FRAMENAME' :'frameName',
	'TIMEOUT' :'timeout',
	'PROCESSORPOOLSIZE' :'processorPoolSize',
	'ENABLEWHILEFETCHING' :'enableWhileFetching',

	'FOLDER_OBJECT' :'folderObj',
	'FOLDER_STARTINDEX' :'startIndex',
	'FETCHBLOCKSIZE ' :'fetchBlockSize',
	'BACKGROUNDLOAD' :'backgroundLoad',
	'FETCHLABEL' :'fetchLabel',

	'PAGES' :'pages',
	'PAGESIN' :'pagesIn',
	'PAGERANGE' :'pageRange',
	'CURRENTPAGE' :'currentPage',
	'TITLE' :'title',

	'ROWS' :'rows',
	'COLUMNS' :'columns',
	'SHOWCOLUMNHEADERS' :'showColumnHeaders',
	'ROWSPERCOLUMN' :'arrangementSize',
	'COLUMNSPERROW' :'arrangementSize',

	'FROM' :'from',
	'TO' :'to',
	'FROMPATH' :'fromPath',
	'SHOWPATH' :'showPath',
	'SHOWSEARCH' :'showSearch',
	'SHOWUPDOWN' :'showUpDown',
	'SHOWFETCH' :'showFetch',
	'SHOWMATCHCASE' :'showMatchCase',
	'MATCHCASEDEFAULT' :'matchCaseDefault',
	'MATCHCASECAPTION' :'matchCaseCaption',
	'SHOWANDORPICKER' :'showANDORPicker',
	'SHOWNOTPICKER' :'showNOTPicker',
	'ANDORDEFAULT' :'ANDORDefault',
	'NOTDEFAULT' :'NOTDefault',
	'FLAT' :'flat',

	'KEEPAVAILABLEITEMS' :'keepAvailableItems',
	'INSERTIONMODE' :'insertionMode',
	'SORTPOSITIONFORM' :'sortPositionForm',
	'REQUESTSCRIPTCLASS' :'requestScriptClass',

	'BROWSEABLEITEMSEXPRESSION' :'browseableItemsExp',

	'DELIMITER' :'delimiter',
	'DATATYPE' :'dataType',

	'PREFIX' :'prefix',
	'HINTPREFIX' :'hintPrefix',
	'HINTSUFFIX' :'hintSuffix',
	'HINT' :'hint',
	'SHOWHINT' :'showHint',

	'OBJECTPATH' :'objectPath',

	'METRICQUAL_OBJECTPICKER' :'objectPicker',
	'METRICQUAL_FUNCTIONPICKER' :'functionPicker',
	'METRICQUAL_CONSTANT1' :'constant1',
	'METRICQUAL_CONSTANT2' :'constant2',
	'METRICQUAL_LEVELPICKER' :'levelPicker',

	'ATTRQUAL_OBJECTPICKER' :'objectPicker',
	'ATTRQUAL_FORMPICKER' :'formPicker',
	'ATTRQUAL_FUNCTIONPICKER' :'functionPicker',
	'ATTRQUAL_CONSTANT1' :'constant1',
	'ATTRQUAL_CONSTANT2' :'constant2',

	'ALLOWQUALIFYORSELECT' :'allowQualifyOrSelect',
	'ALLOWMETRICQUALIFY' :'allowMetricQualify',
	'SHOWMETRICDIMTY' :'showMetricDimty',
	'LABELDIMTY' :'labelDimty',
	'BETWEENWORD' :'betweenWord',

	'FUNCTIONCATEGORY' :'functionCategory',
	'DISPLAYEDFORMS' :'displayedForms',
	'DEFAULTOPERATOR' :'defaultOperator',
	'DEFAULTEXPRESSIONTYPE' :'defaultExpressionType',
	'DEFAULTATTREXPRESSIONTYPE' :'defaultAttributeExprType',
	'DEFAULTMETRICQUALFUNCTION' :'defaultMetricQualFunction',
	'DEFAULTMETRICLEVEL' :'defaultMetricLevel',
	'DEFAULTATTRQUALFUNCTION' :'defaultAttrQualFunction',
	'DEFAULTATTRLISTFUNCTION' :'defaultAttrListFunction',
	'RENDERMEMO' :'renderMemo',

	'ALLOWBROWSEFOLDER' :'allowBrowseFolder',
	'ALLOWBROWSEATTRIBUTE' :'allowBrowseAttribute',
	'ALLOWBROWSEHIERARCHY' :'allowBrowseHierarchy',

	'ROOTLESSNODETEXT' :'rootlessNodeText',
	'EMPTYTREETEXT' :'emptyTreeText',

	'COLLECTFORMS' :'collectForms',

	'YEAR' :'year',
	'MONTH' :'month',
	'DAY' :'day',

	'SEARCHREQUIRED' :'searchRequired',
	'SEARCHREQUIREDTEXT' :'searchRequiredText'
};
mstr.$W = mstr.Props.Widget;

mstr.Props.Cache = {
	'CACHETYPE' :'cacheType'
};
mstr.Props.Log = {
	'LEVEL' :'level'
};
mstr.Props.MSTRFolderItem = {
	'DSSID' :'dssid',
	'NAME' :'n',
	'DISPLAYNAME' :'dispn',
	'DESC' :'desc',
	'ICON' :'tp',
	'TYPE' :'otp',
	'SUBTYPE' :'dsstp',
	'DSSTYPE' :'dsstp',
	'DATATYPE' :'dtp',
	'DSSFORMS' :'dssforms',
	'LOCKED' :'locked',
	'ISBROWSE' :'isBrowse'
};

/* Enumerations for property values. */

mstr.Enum = {};

mstr.Enum.REGEXP_RESERVED_CHARS = {
	'$' :true,
	'(' :true,
	')' :true,
	'*' :true,
	'+' :true,
	'.' :true,
	'?' :true,
	'[' :true,
	'\\' :true,
	']' :true,
	'^' :true,
	'{' :true,
	'|' :true,
	'}' :true
};

mstr.Enum.Widget = {};
mstr.Enum.Widget.PROPTYPE = {
	'SCALAR' :0,
	'LIST' :1,
	'HASH' :2
};
mstr.Enum.Widget.UPDATES = {
	'DEFAULT' :1,
	'ENABLED' :2,
	'STATE' :4,
	'VALUE' :8,
	'VALUESSUPPORTED' :16,
	'CUSTOM' :32
};
mstr.Enum.Widget.STATE = {
	'HOVER' :1,
	'SELECTED' :2
};
mstr.Enum.Widget.READYSTATE = {
	'IDLE' :1,
	'WAITING' :3,
	'ERROR' :4,
	'SUCCESS' :5,
	'CANCELLED' :6,
	'TIMEOUT' :7
};

mstr.Enum.Widget.RENDERSTATUS = {
	'NOT_STARTED' :0,
	'IN_PROGRESS' :1,
	'RENDEREDSELF' :2,
	'RENDEREDCHILDREN' :3,
	'RENDEREDALL' :4,
	'ERROR' :5
};
mstr.Enum.Widget.POPORIENTATION = {
	'CONTEXTMENU' :0,
	'VERTICAL' :1
};

mstr.Enum.Widget.NOTIFIES = {
	'NONE' :0,
	'SELF' :1,
	'LISTENERS' :2,
	'CHILDREN' :4,
	'PARENT' :8
};
mstr.Enum.Widget.AUTOWRITEBACK = {
	'OFF' :0,
	'ONBLUR' :1,
	'ONKEYPRESS' :2
};
mstr.Enum.Widget.AUTOFORMAT = {
	'OFF' :0,
	'ONBLUR' :1,
	'ONKEYPRESS' :2,
	'USEDEFAULT' :3
};
mstr.Enum.Widget.AUTOVALIDATE = {
	'OFF' :0,
	'ONBLUR' :1,
	'ONKEYPRESS' :2,
	'USEDEFAULT' :3
};

mstr.Enum.MSTRFolderItem = {};
mstr.Enum.MSTRFolderItem.TYPE = {
	'FILTER' :1,
	'TEMPLATE' :2,
	'REPORTDEFINITION' :3,
	'METRIC' :4,
	'AGGMETRIC' :7,
	'FOLDER' :8,
	'PROMPT' :10,
	'ATTRIBUTE' :12,
	'FACT' :13,
	'DIMENSION' :14,
	'HIERARCHY' :14,
	'ELEMENT' :1048576, /* WebElement displayUnitType */
	'ATTRIBUTEFORM' :21,
	'CONSOLIDATION' :47,
	'CONSOLIDATIONELEMENT' :48,
	'PROJECT' :32,
	'DIMTYUNIT' :1048578,
	'DIMTYUNITATTRIBUTE' :1048581, /* WebDependentObjectDimtyUnitAttribute */
	'DIMTYUNITDIMENSION' :1048582
/* WebDependentObjectDimtyUnitDimension */
};
mstr.Enum.MSTRFolderItem.ICON = {
	'FOLDER' :'f',
	'FOLDERSHORTCUT' :'f_sc',
	'ATTRIBUTE' :'a',
	'LOCKEDATTRIBUTE' :'la',
	'ATTRIBUTEFORM' :'fo',
	'TEMPLATE' :'t',
	'FILTER' :'fi',
	'METRIC' :'m',
	'ELEMENT' :'ae',
	'HIERARCHY' :'hi',
	'PROJECT' :'p'
};

mstr.Enum.DataForm = {};
mstr.Enum.DataForm.DATATYPE = {
	'NUMERIC' :1,
	'STRING' :2,
	'DATE' :3,
	'INTEGER' :4,
	'IMAGE' :5,
	'HTML' :6
};

mstr.Enum.Textbox = {};
mstr.Enum.Textbox.DATATYPE = {
	'UNKNOWN' :0,
	'STRING' :1,
	'NUMERIC' :2,
	'INTEGER' :4,
	'DATE' :3
};

mstr.Enum.Column = {};
mstr.Enum.Column.SHOWAS = {
	'TEXT' :0,
	'ICON' :1
};

mstr.Enum.Request = {};

mstr.Enum.Log = {};
mstr.Enum.Log.LEVEL = {
	'TRACE' :8,
	'WARNING' :2,
	'ERROR' :1
};
mstr.Enum.Log.CATEGORY = {
	'CREATE' :'Init',
	'RENDER' :'Render',
	'NOTIFY' :'Notify',
	'CACHE' :'Cache',
	'LAYOUT' :'Layout'
};

mstr.Enum.Response = {};
mstr.Enum.Response.STATUSCODE = {
	'SUCCESS' :200
};

mstr.Enum.Validation = {};
mstr.Enum.Validation.STATUSCODE = {
	'VALID' :0,
	'INVALID_DATATYPE' :1,
	'EXCEEDS_MIN_VALUE' :2,
	'EXCEEDS_MAX_VALUE' :3,
	'EXCEEDS_MIN_LENGTH' :4,
	'EXCEEDS_MAX_LENGTH' :5,
	'EXCEEDS_MIN_COUNT' :6,
	'EXCEEDS_MAX_COUNT' :7,
	'INVALID_ANSWERS' :8,
	'NO_INPUT' :9,
	'NO_PERSONAL_ANSWER_NAME' :10,
	'INCOMPLETE_CONDITION' :11,
	'TRUNCATED' :12,
	'INCOMPLETE_CONDITION_ON_AUTO_NODE' :13, // only used internally to distinguish incomplete condition 
	'INVALID_PERSONAL_ANSWER_NAME' :14,
	'DUPLICATED' :15,
	'INVALID' :999
// general error code for unknown reason/from server
};

/* Event names. */

mstr.Enum.Event = {};
mstr.Enum.Event.NAME = {
	'ANY' :'*',
	'LOAD' :'load',
	'RESIZE' :'resize',
	'AFTERRESIZE' :'afterresize',
	'CHANGEELEMENT' :'changeelement',
	'CHANGEVISIBLE' :'changevisible',
	'CHANGEENABLED' :'changeenabled',
	'CHANGESTATE' :'changestate',
	'CHANGEMESSAGE' :'changemessage',
	'CHANGEHEIGHT' :'changeheight',
	'CHANGEWIDTH' :'changewidth',
	'CHANGEFOLDER' :'changefolder',
	'CHANGEFOLDERSIZE' :'changefoldersize',
	'CHANGEBLOCKBEGIN' :'changeblockbegin',
	'CHANGEBLOCKCOUNT' :'changeblockcount',
	'CHANGEPATHTREE' :'changepathtree',
	'CHANGELISTITEMS' :'changelistitems',
	'ADDLISTITEMS' :'addlistitems',
	'REMOVELISTITEMS' :'removelistitems',
	'CHANGESELECTEDINDICES' :'changeselectedindices',
	'ADDSELECTEDINDICES' :'addselectedindices',
	'REMOVESELECTEDINDICES' :'removeselectedindices',
	'CHANGELEADSELECTEDINDEX' :'changeleadselectedindex',
	'CHANGEROOTNODE' :'changerootnode',
	'ADDTREENODES' :'addtreenodes',
	'REMOVETREENODES' :'removetreenodes',
	'CHANGENODEVALUE' :'changetreenodevalue',
	'ADDCONDITION' :'addcondition',
	'EDITNODE' :'editnode',
	'CHANGESELECTEDPATHS' :'changeselectedpaths',
	'ADDSELECTEDPATHS' :'addselectedpaths',
	'REMOVESELECTEDPATHS' :'removeselectedpaths',
	'CHANGELEADSELECTEDPATH' :'changeleadselectedpath',
	'CHANGEEDITPATH' :'changeeditpath',
	'CHANGEDATAFORMS' :'changedataforms',
	'CHANGEVALUE' :'changevalue',
	'CHANGEAVAILABLEVALUES' :'changeavailablevalues',
	'CHANGERENDERSTATUS' :'changerenderstatus',
	'MOUSEDOWNLISTITEM' :'mousedownlistitem',
	'CLICKLISTITEM' :'dblclicklistitem',
	'DBLCLICKLISTITEM' :'dblclicklistitem',
	'BEFOREEXEC' :'beforeexec',
	'MOUSEDOWNTREENODE' :'mousedowntreenode',
	'CLICKTREENODE' :'dblclicktreenode',
	'DBLCLICKTREENODE' :'dblclicktreenode',
	'POPUPOPEN' :'open',
	'POPUPCLOSE' :'close',
	'EDITOROK' :'ok',
	'EDITORAPPLY' :'apply',
	'EDITORCANCEL' :'cancel',
	'EDITORSAVE' :'save',
	'ADDREQUEST' :'addrequest',
	'REMOVEREQUEST' :'removerequest',
	'CHANGEREADYSTATE' :'changereadystate',
	'CHANGEPAGERANGE' :'changepagerange',
	'CHANGEPAGE' :'changepage',
	'LOGIN' :'login',
	'TOGGLETOC' :'toggletoc'
};
mstr.$Evt = mstr.Enum.Event.NAME;

mstr.Enum.VIEWMEDIA = {
	'STATIC' :1,
	'INTERACTIVE' :2,
	'EDITABLE' :4,
	'FLASH' :8,
	'EXPORT_EXCEL' :16,
	'EXPORT_PDF' :32,
	'EXPORT_HTML' :64,
	'EXPORT_FLASH' :128
};

/* Command names. */

mstr.Enum.Widget.COMMANDID = {
	'ADD' :'Add',
	'ADDALL' :'AddAll',
	'REMOVE' :'Remove',
	'REMOVEALL' :'RemoveAll',
	'CLEAR' :'Clear',
	'MOVEUP' :'MoveUp',
	'MOVEDOWN' :'MoveDown',
	'SELECTNEXT' :'SelectNext',
	'FETCHFIRST' :'FetchFirst',
	'FETCHPREVIOUS' :'FetchPrevious',
	'FETCHNEXT' :'FetchNext',
	'FETCHLAST' :'FetchLast',
	'FETCHBLOCK' :'FetchBlock',
	'BROWSEPATH' :'BrowsePath',
	'UPFOLDER' :'BrowseParent',
	'SEARCH' :'Search',
	'SEARCHFOLDERS' :'SearchFolders',
	'SEARCHELEMENTS' :'SearchElements',
	'BROWSE' :'Browse',
	'BROWSEFORMS' :'BrowseForms',
	'BROWSEFOLDER' :'BrowseFolder',
	'BROWSEATTRIBUTE' :'BrowseAttribute',
	'BROWSEHIERARCHYITEM' :'BrowseHierarchyItem',
	'CANCELREQUEST' :'CancelRequest',
	'OK' :'OK',
	'CANCEL' :'Cancel',
	'SAVE' :'Save',
	'OPEN' :'Open',
	'CLOSE' :'Close',
	'GROUP' :'Group',
	'UNGROUP' :'Ungroup',
	'DELETENODES' :'DeleteNodes',
	'SWAPWITHPREVIOUS' :'SwapWithPrevious',
	'SWAPWITHNEXT' :'SwapWithNext',
	'ADDCONDITION' :'AddCondition'
};
mstr.$Cmd = mstr.Enum.Widget.COMMANDID;

/* Global settings. */

mstr.Settings = {};
mstr.Settings.Http = {
	'IMAGEPATH' :'../style/mstr/images/',
	'CACHE' :true
};
mstr.Settings.Log = {
	'LEVEL' :mstr.Enum.Log.LEVEL.TRACE
};
mstr.Settings.Locale = {
	'ID' :1033,
	'DECIMALSEP' :'.',
	'THOUSANDSEP' :',',
	'LISTSEP' :',',
	'DATEINPUTFORMATS' : [], // removed DATEFORMAT, no longer used
	'TIMEINPUTFORMATS' : [],
	'NUMBERINPUTFORMATS' : [],
	'INTEGERINPUTFORMATS' : [],
	'DATEOUTPUTFORMAT' :'M/d/yyyy',
	'TIMEOUTPUTFORMAT' :'h:mm:ss a',
	'TWODIGITYEARSTART' :2029,
	'MONTHNAME_SHORT' : [],
	'MONTHNAME_FULL' : [],
	'AM_NAME' :'AM',
	'PM_NAME' :'PM'
};
mstr.Settings.Hints = {
	'NUMBER' :'##.##',
	'INTEGER' :'##',
	'STRING' :null,
	'DATE' :mstr.Settings.Locale.DATEOUTPUTFORMAT
// was DATEFORMAT
};

mstr.Settings.Scheme = {
	DEFAULT_BORDEROLOR :null,
	DEFAULT_BGCOLOR :null,
	FLAT_BORDERCOLOR :'#c0c0c0',
	FLAT_BGCOLOR :'#d4d0c8',
	HOVER_BORDERCOLOR :'#0A246A',
	HOVER_BGCOLOR :'#B6BDD2',
	SELECTED_BORDERCOLOR :'#0A246A',
	SELECTED_BGCOLOR :'#D4D5D8',
	SELECTED_HOVER_BGCOLOR :'#8592B5',
	LARGEPREVIEWS : {
		//Large Preview for major Graphs
		'pkr1' :'graphpkr1',
		'pkr2' :'graphpkr2',
		'pkr4' :'graphpkr4',
		'pkr8' :'graphpkr8',
		'pkr16' :'graphpkr16',
		'pkr32' :'graphpkr32',
		'pkr64' :'graphpkr64',
		'pkr128' :'graphpkr128',
		'pkr256' :'graphpkr256',
		'pkr512' :'graphpkr512',
		'pkr1024' :'graphpkr1024',
		'pkr2048' :'graphpkr2048',
		'pkr4096' :'graphpkr4096',
		'pkr8192' :'graphpkr8192',
		'pkr16384' :'graphpkr16384',
		'pkr32768' :'graphpkr32768',
		'pkr65536' :'graphpkr65536',
		'pkr131072' :'graphpkr131072',
		'pkr262144' :'graphpkr262144',
		'pkr524288' :'graphpkr524288',
		'pkr1048576' :'graphpkr1048576',
		'pkr2097152' :'graphpkr2097152',
		'pkr4194304' :'graphpkr4194304',
		'pkr8388608' :'graphpkr8388608',
		'pkr16777216' :'graphpkr16777216'
	}
};

mstr.Settings.Render = {
	REPAINT_EVERY_CHILDCOUNT :10
/* Determines how often we let the screen repaint itself when rendering any child views. */

};

mstr.Enum.Nodes = {};
mstr.Enum.Nodes.DATATYPE = {
    'UNKNOWN' :-1,
    'INTEGER' :1,
    'UNSIGNED' :2,
    'NUMERIC' :3,
    'DECIMAL' :4,
    'REAL' :5,
    'DOUBLE' :6,
    'FLOAT' :7,
    'CHAR' :8,
    'VARCHAR' :9,
    'LONGVARCHAR' :10,
    'BINARY' :11,
    'VARBIN' :12,
    'LONGVARBIN' :13,
    'DATE' :14,
    'TIME' :15,
    'TIMESTAMP' :16,
    'SHORT' :21,
    'LONG' :22,
    'MBCHAR' :23,
    'BOOLEAN' :24,
    'PATTERN' :25,
    'BIGDECIMAL' :30
};

/* For debugging. */

mstr.$debugging = true;
mstr.$debug = function(str) {
	if (mstr.$debugging) {
		mstr.$debugging = window.confirm(str);
	}
	;
};
mstr.$debugobj = function(obj) {
	var out = [];
	if (obj) {
		for ( var mem in obj) {
			out.push(mem + ' = ' + obj[mem]);
		}
		;
	}
	;
	return out.join('\n');
};

/* For performance diagnostics. */

mstr.timers = {};

// End File - consts.js //

//Start File - controllers.js //
mstr.controllers = {};

/**
 * Factory is a singleton that maintains a collection of model and viewer objects.
 * Factory provides methods for creating, destroying, and referencing those objects.
 */
mstr.controllers.Factory = ( function() {

	var F = {};

	F.PATHDELIM = '/';
	F._objs = {};
	F._IDCOUNTER = 1;

	/**
	 * Adds a given object to an internal collection of objects after
	 * validating the object and its ID.
	 * @param obj The object to be added.
	 * @return The unique ID string for the given object.
	 */
	F.add = function F_add(obj) {
		if (obj) {
			var id = this.validateId(obj);
			this._objs[id] = obj;
			return id;
		}
		;
		return null;
	};

	/** 
	 * Retrieves an object from the Factory's internal collection given an ID.
	 * @param id The unique identifier of the requested object in the collection.
	 * @return The requested object, if found; null otherwise.
	 */
	F.obj = function F_obj(id) {
		// Look for it in the mstr controller first...
		if (id in this._objs) {
			return this._objs[id];
		}

		// If it's not found, look for it in the bones controller
		if (typeof (microstrategy) != "undefined") {
			return microstrategy.bone(id);
		}

		// Otherwise, return null
		return null;
	};

	/** 
	 * Retrieves an object from the Factory's internal collection given a path
	 * form another object.
	 * @param p The path of the requested object, relative to the context object.
	 * @param contextObj The context object.
	 * @return The requested object, if found; null otherwise.
	 */
	F.getPath = function F_getPath(p, contextObj) {
		var v;

		// Before examining the given context object: 
		// Is the given value path enclosed by quotes? 
		var match = (p != null) && p.match(/^\"(.*)\"$/);
		if (match) { // Yes, use the literal string inside the quotes as our command value.
			v = match[1];
			// Cast as integer, if possible (this allows us to support booleans too).
			if (String(parseInt(v)) == v)
				v = parseInt(v);
			return v;
		}
		;

		// The given value is not a string literal in quotes.  It is a property path to be evaluated.
		var obj = contextObj;
		if (obj && p && p.split) {
			var ps = p.split(this.PATHDELIM);
			for ( var i = 0, len = ps.length; i < len; i++) {
				var n = ps[i];

				// First, check for an object ID (use a regular expression to look for the "#" prefix).
				var matchId = n.match(/^\#(.*)/);
				v = matchId && this.obj(matchId[1]) || null;

				// Not found? Check the context's members.
				if (v == null)
					v = obj[n];

				// Not found? Check the context's properties table, if any.
				if (v == undefined) {
					v = obj.props && obj.props[n];
				}
				;

				// Not found? Try using the context's get() method, if any.
				if (v == undefined) {
					v = obj.get && obj.get(n);
				}
				;

				// Not found? Look for parens (possibly with 1 arg inside). 
				if (v == undefined) {
					var match = n.match(/(.+)\((.*?)\)/);
					if (match) {
						// Found parens.  Try a method call on the object.

						// First parse out the method name and validate it exists.
						var methodName, argString;
						methodName = match[1];
						if (obj[methodName]) {
							// Method is defined on the object.  Now parse the arguments string, if any.
							var args;
							args = this.resolveArgsString(match[2], contextObj);

							// Call the specified method with the given argument values.
							v = obj[methodName].apply(obj, args || []);
						}
						;
					}
					;
				}
				;

				obj = v;
				if (obj == null)
					break;
			}
			;
		}
		;
		return obj;
	};

	/**
	 * Resolves a given string into an array (possibly null or empty) of arguments
	 * for a function call.
	 */
	F.resolveArgsString = function F_rsvArgsStr(str, contextObj) {
		// Parse the argument(s) string into individual args in an array.
		var args = [];

		if (str == null || str == '')
			return args;

		// First, replace periods with forward-slashes.  Periods are used to delimit path
		// parts inside of parenthesis, simply to make path parsing simpler.        
		str = str.replace(/\./gm, '/');

		// The args might be a single json object argument, or
		// a comma-delim array of non-json arguments.
		// Check for json: is the entire argument string enclosed in curly braces?
		var json = str.match(/\{(.*)\}/);
		if (json) {
			// Yes, curly braces found.  Assume the entire string defines 1 json object.
			var hash = args[0] = {};

			// Assume the contents inside the curlys are "name:value-path" pairs, comma-delimited.
			var jsonPairs = (json[1].split(',')) || [];

			// Evaluate each value-path (if any), and store results in a hash.
			var nameValuePair;
			for ( var i = 0, len = jsonPairs.length; i < len; i++) {
				nameValuePair = jsonPairs[i].split(':');
				if (nameValuePair[0] == '' || nameValuePair.length < 2)
					continue;
				hash[nameValuePair[0]] = this.getPath(nameValuePair[1],
						contextObj);
			}
			;
		} else {
			// No curly braces found.  Assume the arg string
			// is 0, 1 or more value-paths, comma-delimited.
			var paths = str.split(',');

			// Evaluate each path (if any), and store results in array.
			for ( var i = 0, len = paths.length; i < len; i++) {
				args[i] = this.getPath(paths[i], contextObj);
			}
			;
		}
		;

		return args;
	};

	/** 
	 * Sets the value of a property at a given path relative to a given object.
	 * @param p The path of the property to be set, relative to the context object.
	 * @param contextObj The context object.
	 * @param v The new value for the specified property.
	 */
	F.setPath = function F_setPath(p, contextObj, v, bSilent) {
		// Find the "parent" object whose direct property we are setting.
		var i = p.lastIndexOf(this.PATHDELIM);
		if (i > 0) {
			contextObj = this.getPath(p.substring(0, i), contextObj);
		}
		;

		// Set the object's property.
		if (contextObj) {
			// Convert property to integer, if possible.
			p = p.substr(i + 1);
			if (String(parseInt(p)) == p) {
				p = parseInt(p);
			}
			;

			// Set the property value on the object.  What technique should we use for this?
			// That depends on the optional bSilent argument...            
			if (bSilent) {
				// Avoid using a set method (so as not to raise an event).  Try
				// setting the property directly on the object's property table first;
				// otherwise, try a set method.
				if (contextObj.props) {
					contextObj.props[p] = v;
				} else if (contextObj.set) {
					contextObj.set(p, v);
				} else {
					contextObj[p] = v;
				}
				;
			} else {
				// Try a set method first; if not defined, then set the property 
				// directly on the object.
				if (contextObj.set) {
					contextObj.set(p, v);
				} else if (contextObj.props) {
					contextObj.props[p] = v;
				} else {
					contextObj[p] = v;
				}
				;
			}
			;
		}
		;
	};

	/**
	 * Create a javascript class instance corresponding to a given
	 * hashtable of properties.
	 * @param json The hashtable of properties.
	 * @return The javascript class instance if successful; null otherwise.
	 */
	F.create = function F_create(json) {
		var sc = json && json[mstr.$W.SCRIPTCLASS];
		if (sc) {
			return eval('new ' + sc + '(json)');
		}
		;
		return null;
	};

	/**
	 * Helper method for retrieving a property value of an object, whether
	 * the object has been initialized or not.
	 * @param obj The object whose property we wish to read.
	 * @param p The name of the property we wish to read.
	 * @return The value of the requested property.
	 */
	F.safeget = function F_safeget(obj, p) {
		if (!obj || p == null)
			return undefined;

		var v = obj[p];
		if (v == undefined) {
			v = obj.props && obj.props[p];
		}
		;
		if (v == undefined) {
			v = obj.get && obj.get(p);
		}
		;
		return v;
	};

	/**
	 * Helper method for setting a property value of an object, whether
	 * the object has been initialized or not.
	 * @param obj The object whose property we wish to write.
	 * @param p The name of the property we wish to write.
	 * @param v The value of the requested property.
	 */
	F.safeset = function F_safeset(obj, p, v) {
		if (!obj || p == null)
			return;

		if (obj.set) {
			return obj.set(p, v);
		} else if (obj.props) {
			obj.props[p] = v;
		} else {
			obj[p] = v;
		}
		;
	};

	/**
	 * Creates a new javascript object based on given json data,
	 * adds the object to the factory, and sets the object's element & document
	 * properties.
	 * @param el The HTML placeholder element for the new javascript object.
	 * @param hWin The HTML window that contains the given HTML placeholder element.
	 * @param json The properties hashtable for the new javascript object.  If null,
	 * this method will use the JSON in the "value" property of the HTML placeholder element.
	 * @return The new javascript object if successful, null otherwise.
	 */
	F.registerElement = function F_registerElement(el, hWin, json) {
		// Register this window, if we haven't already.
		this.registerWindow(hWin);

		// Do we have an element to register too?
		if (el) {
			// If no corresponding json string is given, try reading it from within the element.
			if (!json) {
				var s = el.value;
				if (s)
					eval('json = ' + s);
			}
			;

			// Try to instantiate a javascript class instance from the json.
			var obj = this.registerJSON(json, el.ownerDocument);

			// Did the json string evaluate into an object we can instantiate?
			if (obj) {
				obj.props && (obj.props[mstr.$W.ELEMENT] = el);
				obj.init && obj.init();
			}
			;
			return obj;
		}
		;
	};

	/**
	 * @description This method attach event listeners for global events in this window, such as mouse
	 * events and window resize events.  This allows views, such as popups or auto-resizing views,
	 * to be notified of those events and respond, if desired.
	 */
	F.registerWindow = function F_registerWindow(hWin) {
		// Do we have a valid HTML Document that has not been registered already?
		var doc = hWin && hWin.document;
		if (!doc || doc.mstrRegistered)
			return;

		// Define a generic event handlers to transmit events from the document to
		// the Event Manager.
		var fg = function(e) {
			try {
				var evt = new mstr.lang.Event(e, hWin, hWin.document);
				mstr.controllers.EventManager.notifyWindowListeners(evt);
			} catch (localerr) {
			}
			;
		};

		var gt = function(e) {
			var t = mstr.$E.target(e, hWin);
			var tagName = null;
			if (t && t.tagName) {
				tagName = t.tagName.toLowerCase();
			}
			return tagName;
		};

		var fo = function(e) {
			var tagName = gt(e);
			return (tagName == 'object' || tagName == 'embed'
					|| tagName == 'textarea' || tagName == 'select' || tagName == 'input'
					|| tagName == 'html' //#430384 - add tag <HTML> to the following list for Safari/Chrome after fix to #328162 
					|| tagName == 'option'  //#430380 - enable clicking on <OPTION> tag for Safari/Chrome 
				   );
		};

		var fImg = function(e, b) {
			if (gt(e) == 'img'
					|| (mstr.$E.target(e, hWin)
							&& mstr.$E.target(e, hWin).getAttribute && mstr.$E
							.target(e, hWin).getAttribute("dg"))) {
				doc.dragging = b;
			}
		};

		var fu = function(e) {
			//TQMS#347108 - related change to avoid browser flashing when closing a dialog editor in IE6
			fImg(e, false);
			fg(e);
		};

		// Define an event handler to return false for mousedown in Firefox (to prevent text selection during drag).
		var fd = function(e) {
			//TQMS#347108 - only set change when target is a draggable to avoid browser flashing when closing a SELECT dropdown in IE6
			fImg(e, true);
			fg(e);
			if (mstr.utils.ISFF || mstr.utils.ISWK){
				return fo(e);
			}
		};

		var fm = function(e) {
			fImg(e, true);
			fg(e); // Define an event handler to return false for mousemove in IE (to prevent no drop cursor when dragging images).
			if (mstr.utils.ISIE4) {
                //359520. Text boxes, etc need to return true.
                return fo(e) ? true : !doc.dragging;
			}
		};

		// Attach handler for global mouseup and mousedown events anywhere in this document.
		doc.onmousedown = mstr.utils.Objects.compositeFunc(doc.onmousedown, fd);
		doc.onmouseup = mstr.utils.Objects.compositeFunc(doc.onmouseup, fu);
		doc.onmousemove = mstr.utils.Objects.compositeFunc(doc.onmousemove, fm);

		// Return false for onselectstart to avoid select during drag and drop (only works in IE).
		/*if (mstr.utils.ISIE4)
		{
		    doc.body.onselectstart = mstr.utils.Objects.compositeFunc(doc.body.onselectstart, function(e) { 
		        var t = mstr.$E.target(e, hWin);
		        return (t.tagName.toLowerCase() == 'textarea' || (t.tagName.toLowerCase() == 'input' && t.type == 'text'));
		    });
		}*/

		// Attach handler for unload or resize events of this document.  Note that for IE and
		// Opera, we must attach to the document body, but for Firefox, we use the window.
		var el = mstr.utils.ISW3C ? hWin : doc.body;
		el.onunload = mstr.utils.Objects.compositeFunc(el.onunload, fg);
		el.onresize = mstr.utils.Objects.compositeFunc(el.onresize, fg);
		if (mstr.utils.ISIE6) {
			el = doc.documentElement;
			el.onscroll = mstr.utils.Objects.compositeFunc(el.onscroll, fg);
		} else {
			el.onscroll = mstr.utils.Objects.compositeFunc(el.onscroll, fg);
		}
		// Mark HTML Document as registered, so it will not be re-registered again later.
		doc.mstrRegistered = true;
	};

	/**
	 * Creates a new javascript object based on given json data, and adds
	 * the object to the factory.  Also sets the object's document property, if given.
	 * @param json The properties table for the new object to be created.
	 * @param doc The HTML document that contains the new javascript object.
	 * @return The new javascript object if successful, null otherwise.
	 */
	F.registerJSON = function F_registerJSON(json, doc) {
		var obj = this.create(json);
		if (obj) {
			var id = this.add(obj);
			if ((id != null) && obj.props) {
				if (doc && obj.props)
					obj.props[mstr.$W.DOCUMENT] = doc;
				if (obj.isHTTPProcessor)
					mstr.http.Governor.addProc(obj);
			}
		}
		return obj;
	};

	/**
	 * Generates an unused unique ID for a factory object.
	 * @return A new unique ID string.
	 */
	F.nextFreeId = function F_nextFreeId() {
		return 'id_mstr' + this._IDCOUNTER++;
	};

	/**
	 * Given an object, if it has no ID, supply it with a new unique one.
	 * @param obj The object whose ID will be validated.
	 * @return The unique ID string for the given object.
	 */
	F.validateId = function F_validateId(obj) {

		if (!obj)
			return;
		// Performance optimization: Read and write from obj.props,
		// rather than using .getId() and .setId(), to reduce the number
		// of function calls on page load for a heavy page.
		// var id = obj.getId ? obj.getId() : obj[mstr.$W.ID];
		var id = obj.props ? obj.props[mstr.$W.ID] : obj[mstr.$W.ID];
		if (id == null) {
			id = this.nextFreeId();
			// obj.setId ? obj.setId(id) : (obj[mstr.$W.ID] = id);
			if (obj.props) {
				obj.props[mstr.$W.ID] = id;
			} else {
				obj[mstr.$W.ID] = id;
				this._objs[id] = obj;
			}
			;
		}
		;
		return id;
	};

	/**
	 * This method "plugs in" a given plugin into a given obj.  When we "plug in" object P
	 * into object O, we copy all the methods of object P into object O (by reference), EXCEPT
	 * for P's init() method.  We then apply P's init() method (if any) to object O for a one-time
	 * initialization.
	 */
	F.plugin = function F_plugin(/*Object*/obj, /*Object*/plugin) {
		if (!obj || !plugin)
			return;
		for ( var n in plugin) {
			if (n != 'init')
				obj[n] = plugin[n];
		}
		if (plugin.init)
			plugin.init.apply(obj, []);
	};

	return F;

})();

/* Shortcuts to access factory. */

mstr.$obj = function(id) {
	return mstr.controllers.Factory.obj(id);
};
mstr.$path = function(p, contextObj) {
	return mstr.controllers.Factory.getPath(p, contextObj);
};
mstr.$register = function(id, hWin) {
	if (!hWin)
		hWin = window;
	var retVal = mstr.controllers.Factory.registerElement(hWin.document
			.getElementById(id), hWin);
	return retVal;
};

mstr.$deferRegister = function(textareaId, hWin) {
	if (!mstr.deferredRegisters)
		mstr.deferredRegisters = [];
	mstr.deferredRegisters.push( {
		'textareaId' :textareaId,
		'hWin' :hWin
	});

	if (!window.mstrDefersRegisters) {
		window.mstrDefersRegisters = true;
		if (typeof (window.onload) == 'function') {
			var f = window.onload;
			window.onload = function(e) {
				f && f();
				mstr.$doDeferredRegisters();
			};
		} else {
			window.onload = function() {
				mstr.$doDeferredRegisters();
			};
		}
		;
	}
	;
};

mstr.$doDeferredRegisters = function() {
	var arr = mstr.deferredRegisters, info;
	if (!arr || !arr.shift)
		return;

	while (info = arr.shift()) {
		mstr.$register(info.textareaId, info.hWin);
	}
	;
};

/**
 * EventManager is a singleton with methods and members
 * for managing propogation of events among objects.
 */
mstr.controllers.EventManager = ( function() {

	var E = {};

	E._listeners = {};

	/**
	 * Registers a given object as a listener to another given object
	 * for a specific event name.  Registration provides a callback function name with
	 * which the listener can be notified of the event.
	 * @param listener The object requesting notification of an event.
	 * @param provider The source of the event.
	 * @param eventName The name of the event.
	 * @param callbackMethodName The name of the listener's method which should
	 * be called when the requested event is raised.
	 */
	E.attachEventListener = function E_attachEventListener(listener, provider,
			eventName, callbackMethodName) {
		if (!listener || !provider || !eventName || !callbackMethodName)
			return;

		// Determine the IDs of the listener & provider, and register them.
		this.attachEventListenerByIds(mstr.controllers.Factory
				.validateId(listener), mstr.controllers.Factory
				.validateId(provider), eventName, callbackMethodName);
	};

	/**
	 * Registers a given object as a listener to another object whose
	 * ID is not known, but whose path relative to the listener object is known.
	 * @param listener The object requesting notification of an event.
	 * @param providerPath The path of the source of the event, relative to listener.
	 * @param eventName The name of the event.
	 * @param callbackMethodName The name of the listener's method which should
	 * be called when the requested event is raised.
	 */
	E.attachEventListenerByPath = function E_attachEventListenerByPath(
			listener, providerPath, eventName, callbackMethodName) {
		var provider = mstr.controllers.Factory.getPath(providerPath, listener);
		this.attachEventListener(listener, provider, eventName,
				callbackMethodName);
	};

	E.attachEventListenerByIds = function E_attachEventListenerByIds(
			listenerId, providerId, eventName, callbackMethodName) {
		// Initialize a table for this event-provider-listener combo, if not ready.
		if (!this._listeners[eventName]) {
			this._listeners[eventName] = {};
		}
		;
		if (!this._listeners[eventName][providerId]) {
			this._listeners[eventName][providerId] = {};
		}
		;

		// Add entry for listener's callback to the event-provider-listener table.
		this._listeners[eventName][providerId][listenerId] = callbackMethodName;
	};

	E.attachWindowEventListener = function E_attachWindowEventListener(
			listener, eventName, callbackMethodName) {
		this.attachEventListenerByIds(mstr.controllers.Factory
				.validateId(listener), 'win', eventName, callbackMethodName);
	};

	/**
	 * Unregisters a given object as a listener to another given object
	 * for a specific event name.  
	 * @param listener The object requesting notification of an event.
	 * @param provider The source of the event.
	 * @param eventName The name of the event.
	 * be called when the requested event is raised.
	 */
	E.detachEventListener = function E_detachEventListener(listener, provider,
			eventName) {
		if (!eventName)
			return;

		// Performance optimization: Read and write from obj.props,
		// rather than using .getId() and .setId(), to reduce the number
		// of function calls on page load for a heavy page.

		// Determine the ID of the listener.
		// var idL = listener && listener.getId && listener.getId();
		var idL = listener && listener.props && listener.props['id'];
		if (idL == null)
			return;

		// Determine the ID of the provider.
		// var idP = provider && provider.getId && provider.getId();
		var idP = provider && provider.props && provider.props['id'];
		if (idP == null)
			return;

		this.detachEventListenerById(idL, idP, eventName);
	};

	E.detachEventListenerById = function E_detachEventListenerById(listenerId,
			providerId, eventName) {
		if (this._listeners[eventName]
				&& this._listeners[eventName][providerId]) {
			delete this._listeners[eventName][providerId][listenerId];
		}
		;
	};

	E.detachWindowEventListenerById = function E_detachWindowEventListenerById(
			listenerId, eventName) {
		if (!eventName)
			return;
		this.detachEventListenerById(listenerId, 'win', eventName);
	}

	E.detachWindowEventListener = function E_detachWindowEventListener(
			listener, eventName) {
		if (!eventName)
			return;

		// Performance optimization: Read and write from obj.props,
		// rather than using .getId() and .setId(), to reduce the number
		// of function calls on page load for a heavy page.

		// Determine the ID of the listener.
		// var idL = listener && listener.getId && listener.getId();
		var idL = listener && listener.props && listener.props['id'];
		if (idL == null)
			return;

		this.detachEventListenerById(idL, 'win', eventName);
	};

	/**
	 * Notifies the listeners of a given object that the object has
	 * raised a given event.
	 * @param provider The source of the event.
	 * @param evt The event object.
	 */
	E.notifyListeners = function E_notifyListeners(provider, evt) {
		// Performance optimization: Read and write from obj.props,
		// rather than using .getId() and .setId(), to reduce the number
		// of function calls on page load for a heavy page.
		this.notifyListenersOfId(
		/* provider && provider.getId && provider.getId(), */
		provider && provider.props && provider.props['id'], evt);
	};

	E.notifyWindowListeners = function E_notifyWindowListeners(evt) {
		this.notifyListenersOfId('win', evt);
	};

	E.notifyListenersOfId = function E_notifyListenersOfId(providerId, evt) {
		var l = this.getListeners(providerId, evt && evt.name);
		if (l) {
			for ( var id in l) {
				var obj = mstr.$obj(id);
				if (obj) {
					var callbackMethodName = l[id];
					obj[callbackMethodName] && obj[callbackMethodName](evt);
				}
				;
			}
			;
		}
		;
	};

	/**
	 * Retrieves a table of listeners & callbacks for a given
	 * event name and provider id.
	 * @param providerId The id of the provider of the event.
	 * @param evtName The event name.
	 */
	E.getListeners = function E_getListeners(providerId, eventName) {
		if ((providerId != null) && (eventName != null)) {
			return this._listeners[eventName]
					&& this._listeners[eventName][providerId];
		}
		;
	};

	return E;

})();

/**
 * when a modal editor is popup/uppopup, add/remove its id to a flag array;
 * 
 * this is currently for Floating Editor.
 */
mstr.controllers.ModalEditors = ( function() {

    var ME = {};
    ME._currentModalEditor = new Array();

    //Add to current modal editor list:
    ME.add = function(elem) {
        this._currentModalEditor[this._currentModalEditor.length] = elem.props['id'];
        return this._currentModalEditor;
    };

    //Remove from current modal editor list:
    ME.remove = function(elem) {
        for ( var i = 0; i < this._currentModalEditor.length; i++) {
            if (this._currentModalEditor[i] == elem.props['id']) {
                this._currentModalEditor.length = i;
                break;
            }
        }
        return this._currentModalEditor;
    };

    //Return number of current visible modal editors  
    ME.size = function() {
        return this._currentModalEditor.length;
    };

    return ME;

})();

//End File - controllers.js //

//Start File - utils.js //

mstr.utils = {};

/* Package constants. */

mstr.utils.ISOPERA = !!(navigator && navigator.userAgent && (navigator.userAgent.indexOf('Opera') > -1));
mstr.utils.ISIE4 = !!(self && self.document && self.document.all && !mstr.utils.ISOPERA);
mstr.utils.ISIE6 = !!(navigator && navigator.userAgent && (navigator.userAgent.indexOf('MSIE 6.0') > -1));
mstr.utils.ISIE7 = !!(navigator && navigator.userAgent && (navigator.userAgent.indexOf('MSIE 7.0') > -1));
mstr.utils.ISIE8 = !!(navigator && navigator.userAgent && (navigator.userAgent.indexOf('MSIE 8.0') > -1) && document.documentMode && document.documentMode == 8); //IE8 standard mode
mstr.utils.ISANYIE8 = !!document.documentMode;	// till now, only IE 8 has documentMode	
mstr.utils.ISFF2 = !!(navigator && navigator.userAgent && (navigator.userAgent.indexOf('Firefox/2') > -1));
mstr.utils.ISFF3 = !!(navigator && navigator.userAgent && (navigator.userAgent.indexOf('Firefox/3') > -1));
mstr.utils.ISFF3_0 = !!(navigator && navigator.userAgent && (navigator.userAgent.indexOf('Firefox/3.0') > -1));
mstr.utils.ISFF3_5 = !!(navigator && navigator.userAgent && (navigator.userAgent.indexOf('Firefox/3.5') > -1));
mstr.utils.ISFF = !!(navigator && navigator.userAgent && (navigator.userAgent.indexOf('Firefox/') > -1));
mstr.utils.ISSAFARI = !!(navigator && navigator.userAgent && (navigator.userAgent.indexOf('Safari') > -1));
mstr.utils.ISSAFARI3 = mstr.utils.ISSAFARI && !!(navigator.userAgent.indexOf('Version/3') > -1);
mstr.utils.ISCHROME = !!(navigator && navigator.userAgent && (navigator.userAgent.indexOf('Chrome') > -1));
mstr.utils.ISWK = !!(navigator && navigator.userAgent && (navigator.userAgent.indexOf('AppleWebKit') > -1));
mstr.utils.ISW3C = !!(self && self.document && self.document.getElementById && (!mstr.utils.ISIE4 || mstr.utils.ISIE8));

//A flag to indicate whether 'about:blank' is in Restricted Sites in IE browsers
mstr.utils.aboutBlankRestricted = null;

mstr.utils.Objects = ( function() {

	var O = {};

	O.extendsClass = function O_extendsClass(subClass, baseClass) {
		subClass.prototype = new baseClass();
		subClass.prototype.constructor = subClass;
	};

	O.compositeFunc = function Objects_compositeFunc(f1, f2) {
		if (!f1 && !f2)
			return null;
		if (!f1)
			return f2;
		if (!f2)
			return f1;
		return ( function() {
			var retVal1 = f1(arguments[0], arguments[1], arguments[2]);
			var retVal2 = f2(arguments[0], arguments[1], arguments[2]);
			return retVal1 && retVal2;
		});
	};

	/**
	 * Returns the popup which contains a given view.
	 * Given a view scriptClass instance, finds the nearest ancestor which has
	 * a non-null "opener" property.
	 * @param obj The view instance whose ancestors are to be searched.
	 * @param bInclusive If true, the given view instance will itself be checked.
	 */
	O.findParentPopup = function Objects_findParentPopup(obj, bInclusive) {
		if (!obj)
			return null;

		var p = bInclusive ? obj : obj.get && obj.get('parent');

		while (p && p.get) {
			if (p.get('opener')) {
				return p;
			}
			;
			p = p.get('parent');
		}
		;

		return null;
	};

	return O;

})();

mstr.$O = mstr.utils.Objects;


/**
 * LayoutRenderer is responsible for rendering the HTML DOM of a layout-driven
 * view.  Given a properties table from the view, and a layout Xml string, LayoutRenderer
 * will generate the corresponding HTML DOM in a <span> container.
 */
mstr.utils.LayoutRenderer = ( function() {

	var L = {};
	
	/**
	 * check whether About:Blank page is in Restricted Sites list in IE browsers
	 */
	L._checkAboutBlankRestricted = function() {
		//if flag is not set yet for IEs, then do it.
		if (mstr.utils.aboutBlankRestricted == null && mstr.utils.ISIE4) {
			
			// By default, assume about:blank is restricted.
			mstr.utils.aboutBlankRestricted = true;

			// To test it, insert an temporary <DIV> with an child <DIV> inserted as 'innerHTML' before this <DIV> is in DOM Tree.
			var div = document.createElement('div');
			div.innerHTML = "<div onclick='mstr.utils.aboutBlankRestricted = false; return false;'>&nbsp;</div>";
			document.body.appendChild(div);

			// Manually call the handler to see if it is defined.
			div.firstChild.click();

			// If the handler worked, it would've update aboutBlankRestricted.

			//now remove the temp <DIV>
			document.body.removeChild(div);
		}
	};
	
	/**
	 * Generates the HTML DOM of a given layout-driven view, given the properties
	 * of the view and a layout XML string.
	 * @param json The properties table of the view to be rendered.
	 * @param layoutXml The layout XML string.
	 */
	L.render = function L_render(json, layoutXml) {
		if (!json || !layoutXml)
			return;

		//check 'about:blank' in Restrickted Sites or not.
		this._checkAboutBlankRestricted();
		
		
		// Apply the layout file to the object's properties table.
		var result = {};
		result.html = this._generateHTMLStr(json, layoutXml);

		// Find the tokens for rendering child objects; record each & assign them ids.
		var result2 = this._markRenderTokens(result.html);
		result.html = result2.html;
		result.childPaths = result2.childPaths;

		// Convert the resultant HTML string into an HTML DOM.
		if (result.html) {
			var doc = json[mstr.$W.DOCUMENT]
					|| (json[mstr.$W.ELEMENT] && json[mstr.$W.ELEMENT].ownerDocument);
			result.el = this._generateHTMLDom(doc, result.html);
		}
		;
		result.renderStatus = result.el ? mstr.Enum.Widget.RENDERSTATUS.RENDEREDSELF
				: mstr.Enum.Widget.RENDERSTATUS.ERROR;

		return result;
	};

	/** 
	 * Find the tokens for rendering child objects; record each & assign them ids.
	 * The tokens are of the format "<mstrlayout:wRender name="<path>"><innerHTML></mstrlayout>".
	 * They are replaced with "<mstrlayout:wRender name="<path>" id="<new_id>"><innerHTML></mstrlayout>",
	 * where <new_id> is generated by the Factory controller, and will be used to find this token node
	 * in the HTML DOM after this rendering is completed.
	 * @param sXML The XML string.
	 * @return An object with an html member, which is the modified XML string, and
	 * a childPaths member, which is array of path names for the wRender nodes which were found.
	 */
	L._markRenderTokens = function L_markRenderTokens(sXML) {
		var paths = [];

		// Non-greedy search for wRender tags that may have any arbitrary innerHTML (except for a </mstrlayout:wRender> tag).
		var REGEXP = new RegExp(
				'<mstrlayout:wRender name=\\"([^"]+)\\"[^>]*>(.*?)</mstrlayout:wRender>',
				'gm');
		var replaceFunc = function($0, $1, $2) {
			var id = mstr.controllers.Factory.nextFreeId();
			paths.push( {
				'name' :$1,
				'id' :id
			});
			return '<span class="mstrPlaceholder" name="' + $1 + '" id="' + id
					+ '">' + $2 + '</span>'
		};

		var s = sXML.replace(REGEXP, replaceFunc);
		return {
			'html' :s,
			'childPaths' :paths
		};
	};

	/**
	 * Generate an HTML string by merging a given layout XML string to a given
	 * JSON hashtable of properties.
	 * @param json The JSON hashtable of properties.
	 * @param sXML The layout XML string.
	 */
	L._generateHTMLStr = function L_generateHTMLStr(json, sXML) {
		return this._replaceIterationTokens(json, this._replaceAttributeTokens(
				json, sXML));
	};

	/**
	 * Replaces the {@...} string tokens in given XML with the value of 
	 * the property path "...", resolved from the given json object.
	 * @param json The JSON hashtable of properties.
	 * @param sXML The layout XML string.
	 */
	L._replaceAttributeTokens = function L_rATokens(json, sXML) {
		// Retrieve the HTML info, which includes the HTML pieces in string
		// array, plus a lookup of attribute tokens for the HTML pieces.
		var htmlInfo = this._getLayoutHTMLInfo(json['layoutClass'], sXML);
		if (!htmlInfo || !htmlInfo.arr)
			return;

		// Replace the attribute token pieces of the HTML with dynamic content.
		// First, create caches of commonly used vars for performance.
		var arr = htmlInfo.arr;
		var tokens = htmlInfo.tokens;
		var pathCache = {};

		// Loop thru tokens in this HTML. For each token...
		for ( var i = 0, len = mstr.$A.len(tokens); i < len; i++) {
			// What property path does this token point to?
			var path = tokens[i].path;

			// Do we not have the value of the property path in our cache?
			var v = pathCache[path];
			if (v == null) {
				// Value not cached. Read the value from the given json.
				if (path.indexOf(mstr.controllers.Factory.PATHDELIM) < 0) {
					v = json[path];
				} else {
					v = mstr.controllers.Factory.getPath(path, json);
				}
				;
				if (v == null)
					v = '';

				// Cache the value into our cache for performance.
				pathCache[path] = v;
			}
			;

			// Plug in the value of the property path into the HTML piece.
			arr[tokens[i].index] = v;
		}
		;

		// Now that HTML pieces have been resolved, concat them and return string.
		return arr.join('');
	};

	/**
	 * Searches for layout handlers whose event names include a variable token, and when found,
	 * replaces those tokens with the current value of the token resolved relative to the given view.
	 */
	L.replaceLayoutHandlerTokens = function L_replaceLayoutHandlerTokens(hInfo,
			view) {
		var handlers = hInfo && hInfo.info, seq = hInfo && hInfo.sequence;
		if (!handlers || !seq || !view)
			return hInfo;

		var REGEXP = /\-\-([^\-]*)\-\-/m; // Matches "--foo--"
		var KEYS_TO_UPDATE = [ 'name', 'prop', 'eventName' ];
		var matches, pathCache = {}, path, v, hInfoClone, infoToUpdate, n, nEncoded, nResolved;

		// Walk the array of handlers in sequence...
		for ( var j = 0, lenSeq = mstr.$A.len(seq); j < lenSeq; j++) {
			// Read the unencoded name of the next handler.
			n = seq[j];
			if (n == null)
				continue;

			// Compute the hashkey for it by encode any tokens in it.
			nEncoded = n.replace(/\-\-/g, '_doubledash_');

			// Look up the handler's info in the hashtable under the hashkey.            
			if (!handlers[nEncoded] || !handlers[nEncoded].prop)
				continue;

			// Does this handler's property fit the {@foo} syntax?
			if (matches = handlers[nEncoded].prop.match(REGEXP)) {
				// Yes, we found a token for the property of this handler.
				// Is this the first token we've found in this hashtable so far?
				if (!hInfoClone) {
					// Create a clone of the handler hashtable, so we don't pollute the original
					// by replacing the token (the original may have come from a global cache).
					hInfoClone = {
						'info' :mstr.$H.clone(hInfo.info),
						'sequence' : [].concat(hInfo.sequence)
					};
				}
				;

				// Now resolve the token's path into a value relative to the given view.
				// Do we not have the value of the property path in our cache?
				path = matches[1];
				v = pathCache[path];
				if (v == null) {
					// Value not cached. Read the value from the given view scriptclass instance.
					if (path.indexOf(mstr.controllers.Factory.PATHDELIM) < 0) {
						v = view.get(path);
					} else {
						v = mstr.controllers.Factory.getPath(path, view);
					}
					;
					if (v == null)
						v = '';

					// Cache the value into our cache for performance.
					pathCache[path] = v;
				}
				;

				// Replace the layout handler's name, event name & property with the resolved value
				// in the clone of the hashtable of handlers.
				infoToUpdate = mstr.$H.clone(hInfoClone.info[nEncoded]);
				for ( var i = 0, len = KEYS_TO_UPDATE.length; i < len; i++) {
					infoToUpdate[KEYS_TO_UPDATE[i]] = infoToUpdate[KEYS_TO_UPDATE[i]]
							.replace('--' + path + '--', v);
				}
				;
				// Now move the update information under a new hashtable key that reflects the
				// resolved token's value.
				nResolved = n.replace('--' + path + '--', v);
				hInfoClone.info[nResolved] = infoToUpdate;
				delete hInfoClone.info[nEncoded];
				hInfoClone.sequence[j] = nResolved;
			}
			;
		}
		;

		return hInfoClone || hInfo;
	};

	L._getLayoutHTMLInfo = function L_getLHTMLInfo(layoutClassName, sXML) {
		// Compute the cache key for the given layout.
		var cacheKey = layoutClassName.replace(/\./g, '_');

		// Do we have information under that cache key?
		var htmlInfo = this.layoutHTMLCache && this.layoutHTMLCache[cacheKey];

		if (!htmlInfo) {
			// Cached info not found.  Generate info by parsing layout.
			htmlInfo = this._parseLayoutHTML(sXML);

			// Write information into cache.
			if (!this.layoutHTMLCache)
				this.layoutHTMLCache = {};
			this.layoutHTMLCache[cacheKey] = htmlInfo;
		}
		;

		return htmlInfo;
	};

	/**
	 * Splits a given layout XML/HTML string into an array of string pieces, at
	 * the dynamic attribute tokens (if any).
	 */
	L._parseLayoutHTML = function L_parseLayoutHTML(sXML) {

		sXML = this._trimLayoutNodes(sXML);

		var REGEXP = /\{\@([^}]*)\}/m; // Matches "{@foo}"
		var html = [];
		var tokens = [];
		var counter = 0;
		var matches;
		while (matches = sXML.match(REGEXP)) {
			html[counter] = sXML.substring(0, matches.index);
			tokens.push( {
				'path' :matches[1],
				'index' :counter + 1
			});
			counter += 2;
			sXML = sXML.substr(matches.index + matches[0].length);
		}
		;
		html[counter] = sXML;

		return {
			'arr' :html,
			'tokens' :tokens
		};
	};

	/**
	 * Replaces the {@...} string tokens in given XML with the value of 
	 * the property path "...", resolved from the given json object.
	 * @param json The JSON hashtable of properties.
	 * @param sXML The layout XML string.
	 */
	L._replaceAttributeTokensOrig = function L_rATokensOrig(json, sXML) {
		var pathCache = {};
		var replaceFunc = function($0, $1) {
			var v = pathCache[$1];
			if (v == null) {
				if ($1.indexOf(mstr.controllers.Factory.PATHDELIM) < 0) {
					v = json[$1];
				} else {
					var v = mstr.controllers.Factory.getPath($1, json);
				}
				;
				if (v == null)
					v = '';
				pathCache[$1] = v;
			}
			;
			return v;
		};
		return this._trimLayoutNodes(sXML).replace(/\{\@([^}]*)\}/gm,
				replaceFunc);

	};

	/**
	 * Find all <mstrlayout:wIterate> nodes, and replace them with multiple copies
	 * of their innerHTML.
	 */
	L._replaceIterationTokens = function L_rITokens(json, sXML) {
		// Non-greedy search for wIterate tags that may have any innerHTML (except </mstrlayout:wIterate> tag).
		var REGEXP = new RegExp(
				'<mstrlayout:wIterate name="([^"]+)" id="([^"]+)"[^>]*>(.*?)<\/mstrlayout:wIterate>',
				'm');

		var out = [];
		var matches;
		while (matches = sXML.match(REGEXP)) {
			out.push(sXML.substring(0, matches.index));
			var iteratedXML = this._iterateContent(json, matches[0],
					matches[1], matches[2], matches[3]);
			if (iteratedXML) {
				out.push(iteratedXML);
			}
			;
			sXML = sXML.substr(matches.index + matches[0].length);
		}
		;
		out.push(sXML);
		return out.join('');
	};

	/**
	 * Generate multiple copies of given HTML string, according to given parameters.
	 */
	L._iterateContent = function L_iterateContent(json, sXML, name, id,
			innerHTML) {
		// Given info on a single wgcIterate node, generate a string of multiple copies of its innerHTML.

		if (!name)
			return '';

		if (!id) {
			id = name + '__#';
		}
		;

		if (!innerHTML) {
			innerHTML = '<mstrlayout:wRender name="' + id + '"></mstrlayout:wRender>';
		}
		;

		var p = mstr.controllers.Factory.getPath(name, json);
		var len = mstr.$A.len(p);
		if (len) {
			// Non-greedy search for wRender tags that may have any innerHTML (except </mstrlayout:wRender> tag).
			var REGEXP = new RegExp(
					'<mstrlayout:wRender name="' + id + '"[^>]*>(.*?)</mstrlayout:wRender>',
					'gm');

			var iteratedXML = [];
			for ( var i = 0; i < len; i++) {
				iteratedXML.push(innerHTML.replace(REGEXP,
						'<mstrlayout:wRender name="' + name
								+ mstr.controllers.Factory.PATHDELIM + i + '">'
								+ '$1' + '</mstrlayout:wRender>'));
			}
			;
			return iteratedXML.join('');
		}
		;
		return '';
	};

	/**
	 * Remove the <mstrlayout:layout..></mstrlayout:layout> nodes surrounding
	 * a given xml string.  Assumes the nodes are present, and that the first
	 * node contains no ">" char in its attributes.
	 * @param sXML The layout XML string.
	 */
	L._trimLayoutNodes = function L_trimLayoutNodes(sXML) {
		var i = sXML.indexOf('>');
		return sXML.substring(i + 1, sXML.length - 20); //20 = length of </mstrlayout:layout>
	};

	/**
	 * Generates a container SPAN for the real JUIL DOM node to replace.
	 * @param doc the global dom
	 * @return The newly generate SPAN node, if successful; null otherwise.
	 */
	L._generateHTMLDom = function L_generateHTMLDom(doc, innerHTML) {
		var elContainer = doc.createElement('span');
		
		//TQMS#385175 - check whether 'about:blank' is in Restricted Sites in IE browsers;
		if (mstr.utils.aboutBlankRestricted) {
			//if so, need to add new element to DOMtree before setting its attribute.
			//so temporarily append it to <BODY>
			doc.body.appendChild(elContainer);
			
			//set attribute
			elContainer.innerHTML = innerHTML;
			
			//now remove it from <BODY>
			doc.body.removeChild(elContainer);
		} else {
			//set attribute
			elContainer.innerHTML = innerHTML;
		}
		return elContainer;
	};


	/**
	 * Retrieves a collection of HTML handlers for a given layout.
	 * First looks up the collection in a cache.  If not found in cache,
	 * parses the layout to generate the collection, and caches it.
	 */
	L.getLayoutHandlers = function L_getLayoutHandlers(layoutClassName, sXML) {
		// Compute the cache key for the given layout.
		var cacheKey = layoutClassName.replace(/\./g, '_');

		// Do we have information under that cache key?
		var hInfo = this.layoutHandlerCache
				&& this.layoutHandlerCache[cacheKey];

		if (!hInfo) {
			// Cached info not found.  Generate info by parsing layout.
			hInfo = this._parseLayoutHandlers(sXML);

			// Write information into cache.
			if (!this.layoutHandlerCache)
				this.layoutHandlerCache = {};
			this.layoutHandlerCache[cacheKey] = hInfo;
		}
		;

		return hInfo;
	};

	/**
	 * Build a collection of HTML handlers found in a given layout XML string.
	 * The handlers are the attributes of the <mstrlayout:layout..> root node.
	 * Assumes the root node is present, and that it contains no ">" char in
	 * its attributes, and that the attribute names are "on_<eventname>".
	 * @param sXML The layout XML string.
	 */
	L._parseLayoutHandlers = function L_parseLayoutHandlers(sXML) {
		// Generate a hashtable of layout handlers found in given layout XML:
		// key = event name, value = {script string, script function}
		var i = sXML.indexOf('>');
		sXML = sXML.substring(0, i);

		var handlers = {}, handlerKey;
		var seq = [];
		var matches;
		while (matches = sXML.match(/([^\s=]+)=\"([^\"]+)\"/m)) {
			// We found a handler of format <name>="<value>".
			var hInfo = {};
			hInfo.name = matches[1];
			hInfo.str = matches[2];

			// Parse name into format "on_<src>_<method>_<prop>", where
			// "<src>_" is optional. Assumes "<prop>" has no underscore chars.
			// Enhancement: window events are now supported, in the format
			// "on_win_<eventName>".
			var nameParts = matches[1].split('_');
			nameParts.shift();
			hInfo.prop = nameParts.pop();
			hInfo.method = nameParts.pop();
			if (hInfo.method == 'win') {
				hInfo.src = hInfo.method;
				hInfo.eventName = hInfo.prop;
			} else {
				hInfo.src = nameParts.pop();
				hInfo.eventName = [ hInfo.method, hInfo.prop ].join('_');
			}
			;

			// Add handler info to hashtable keyed by handler name.
			handlerKey = hInfo.name.replace(/\--/g, '_doubledash_');
			handlers[handlerKey] = hInfo;

			// Also add handler name to ordered array, so we know what order
			// to execute these in for a re-render.
			seq.push(hInfo.name);

			// Continue parsing the XML for next handler...
			sXML = sXML.substr(matches.index + matches[0].length);
		}
		;
		return {
			'info' :handlers,
			'sequence' :seq
		};
	};

	return L;

})();

mstr.utils.Animations = ( function() {

	var A = {};

	/**
	 * A private lookup table of on-going animations, keyed by animation name.
	 * Each key value is another hashtable, which is keyed by animation request id.
	 */
	A._lookup = {};

	/**
	 * Adds the given information for an animation request in a local hashtable
	 * of currently on-going animations.
	 * @return Unique identifier (string) under which the information has been stored.
	 */
	A.add = function A_add(aniName, el, info, callback) {
		if (!info || !el)
			return;

		// The given HTML element will need an ID so we can keep track of it.
		// Does it already have an ID?  If not, assign it one.
		var id = el.id;
		if (!id)
			id = el.id = mstr.controllers.Factory.nextFreeId();

		// Do we have a hashtable for the given name? If not, create one.
		if (!this._lookup[aniName])
			this._lookup[aniName] = {};

		// Add the given info in a hashtable for that name.
		info.id = id;
		info.el = el;
		info.callback = callback;
		this._lookup[aniName][id] = info;

		// Return the hash key under which this information was stored.
		return id;
	};

	/**
	 * Remove information for an animation request from the local hashtable.
	 * @param aniName The animation name, such as "scroll", "resize", "fade", etc.
	 * @param id The unique identifier of the animation request to be removed.
	 */
	A.remove = function A_remove(aniName, id) {
		if (this._lookup[aniName]) {
			delete this._lookup[aniName][id];
		}
		;
	};

	A.doAnimationRequest = function A_doAnimationRequest(aniName, id) {
		var bContinue = false;

		// Do we have a method for performing the requested animation name?
		if (this['do_animation_' + aniName]) {
			// Yes. Call the method and it will tell us if it is completed or still in progress.
			bContinue = this['do_animation_' + aniName](id);
		}
		;

		// Is the animation completed? If so, call afterAnimation handler to wrap up request.
		if (!bContinue)
			this.afterAnimationRequest(aniName, id);
	};

	/**
	 * Removes the specific animation request, and calls whatever callback function
	 * the request supplied (if any).  Typically called once the animation is finished or cancelled.
	 */
	A.afterAnimationRequest = function A_afterAnimationRequest(aniName, id) {
		// Did this animation request specify a callback?  If so, call it now...
		var info = this._lookup[aniName] && this._lookup[aniName][id];

		if (info && info.callback) {
			info.callback();
		}
		;

		// Remove this request from the local lookup hashtable.
		this.remove(aniName, id);
	};

	/**
	 * Checks if a given HTML element is currently undergoing the specified animation name.
	 */
	A.isAnimating = function A_isAnimating(el, aniName) {
		return !!(el && this._lookup[aniName] && this._lookup[aniName][el.id]);
	};

	/**
	 * Requests scrolling animation for a given HTML element.
	 * @param el The HTML element to be scrolled.
	 * @param x The scrollLeft to which the given HTML should be scrolled (if missing, scrollLeft remains unchanged).
	 * @param y The scrollTop to which the given HTML should be scrolled (if missing, scrollTop remains unchanged).
	 * @param steps The number of steps requested for animation (if missing, default = 10).
	 */
	A.scroll = function A_scroll(el, x, y, steps, callback) {
		if (!el)
			return false;
		steps = parseInt(steps);
		if (!steps || steps < 0)
			steps = 10;

		// Record the animation request's information into a local hashtable.
		var info = {
			'endX' :x,
			'endY' :y,
			'steps' :steps,
			'stepCounter' :0
		};
		if (x != null) {
			info.startX = el.scrollLeft;
			info.stepX = (x - info.startX) / steps;
		}
		;
		if (y != null) {
			info.startY = el.scrollTop;
			info.stepY = (y - info.startY) / steps;
		}
		;
		var id = this.add('scroll', el, info, callback);

		// Start the animation.
		return this.doAnimationRequest('scroll', id);
	};

	/**
	 * Implements the "scroll" animation by incrementally scrolling an HTML element.
	 * @param id The unique id of the animation request.
	 */
	A.do_animation_scroll = function A_do_animation_scroll(id) {
		var info = this._lookup['scroll'] && this._lookup['scroll'][id];

		if (!info || !info.el)
			return;

		info.stepCounter++;

		// Do horizontal scrolling, if requested.
		if (info.endX != null) {
			info.el.scrollLeft = info.endX - info.stepX
					* (info.steps - info.stepCounter);
		}
		;

		// Do vertical scrolling, if requested.
		if (info.endY != null) {
			info.el.scrollTop = info.endY - info.stepY
					* (info.steps - info.stepCounter);
		}
		;

		// Is the animation not yet completed?
		if (info.stepCounter < info.steps) {
			// Set a timeout to continue the animation after a pause.
			var timeInterval = 100 - parseInt(95 * Math.sin(Math.PI
					* info.stepCounter / info.steps));
			self.setTimeout(
					"mstr.utils.Animations.doAnimationRequest('scroll', '" + id
							+ "')", timeInterval);
			return true;
		}
		;
		return false;
	};

	/**
	 * Requests resizing animation for a given HTML element.
	 * @param el The HTML element to be resized.
	 * @param x The width to which the given HTML should be resized (if missing, width remains unchanged).
	 * @param y The height to which the given HTML should be resized (if missing, height remains unchanged).
	 * @param steps The number of steps requested for animation (if missing, default = 10).
	 */
	A.resize = function A_resize(el, x, y, steps, callback) {
		if (!el)
			return false;
		steps = parseInt(steps);
		if (!steps || steps < 0)
			steps = 10;

		// Record the animation request's information into a local hashtable.
		var info = {
			'endX' :x,
			'endY' :y,
			'steps' :steps,
			'stepCounter' :0
		};
		if (x != null) {
			info.startX = el.offsetWidth;
			info.stepX = (x - info.startX) / steps;
		}
		;
		if (y != null) {
			info.startY = el.offsetHeight;
			info.stepY = (y - info.startY) / steps;
		}
		;
		var id = this.add('resize', el, info, callback);

		// Start the animation.
		return this.doAnimationRequest('resize', id);
	};

	/**
	 * Implements the "scroll" animation by incrementally scrolling an HTML element.
	 * @param id The unique id of the animation request.
	 */
	A.do_animation_resize = function A_do_animation_resize(id) {
		var info = this._lookup['resize'] && this._lookup['resize'][id];

		if (!info || !info.el)
			return;

		info.stepCounter++;

		// Do horizontal resizing, if requested.
		if (info.endX != null) {
			info.el.style.width = (info.endX - info.stepX
					* (info.steps - info.stepCounter)) + 'px'
		}
		;

		// Do vertical resizing, if requested.
		if (info.endY != null) {
			info.el.style.height = (info.endY - info.stepY
					* (info.steps - info.stepCounter)) + 'px';
		}
		;

		// Is the animation not yet completed?
		if (info.stepCounter < info.steps) {
			// Set a timeout to continue the animation after a pause.
			var timeInterval = 100 - parseInt(95 * Math.sin(Math.PI
					* info.stepCounter / info.steps));
			self.setTimeout(
					"mstr.utils.Animations.doAnimationRequest('resize', '" + id
							+ "')", timeInterval);
			return true;
		}
		;
		return false;
	};

	/**
	 * Requests opacity animation for a given HTML element (e.g., fade in or fade out).
	 * @param el The HTML element to be animated.
	 * @param opStart The starting opacity for the animated element.
	 * @param opStop The ending opacity for the animated element.
	 * @param steps The number of steps requested for animation (if missing, default = 15).
	 */
	A.alpha = function A_alpha(el, opStart, opStop, steps, callback) {
		if (!el)
			return false;
		steps = parseInt(steps);
		if (!steps || steps < 0)
			steps = 15;

		// Record the animation request's information into a local hashtable.
		var info = {
			'opStart' :opStart,
			'opStop' :opStop,
			'steps' :steps,
			'opDelta' :(opStop - opStart) / steps,
			'stepCounter' :0
		};
		var id = this.add('alpha', el, info, callback);

		// Start the animation.
		return this.doAnimationRequest('alpha', id);
	};

	/**
	 * Implements the "alpha" animation by incrementally adjusting the opacity of an HTML element.
	 * @param id The unique id of the animation request.
	 */
	A.do_animation_alpha = function A_do_animation_alpha(id) {
		var info = this._lookup['alpha'] && this._lookup['alpha'][id];

		if (!info || !info.el)
			return;

		info.stepCounter++;

		var op = info.opStop - info.opDelta * (info.steps - info.stepCounter);
		if (mstr.utils.ISIE4) {
			info.el.style.filter = 'progid:DXImageTransform.Microsoft.Alpha(opacity=' + op + ')';
		} else {
			info.el.style.MozOpacity = op / 100;
		}
		;

		// Is the animation not yet completed?
		if (info.stepCounter < info.steps) {
			// Set a timeout to continue the animation after a pause.
			var timeInterval = 100 - parseInt(95 * Math.sin(Math.PI
					* info.stepCounter / info.steps));
			self.setTimeout(
					"mstr.utils.Animations.doAnimationRequest('alpha', '" + id
							+ "')", timeInterval);
			return true;
		}
		;
		return false;
	};

	/**
	 * Requests move animation for a given HTML element.
	 * @param el The HTML element to be resized.
	 * @param x The new x position (if missing or NaN, width remains unchanged).
	 * @param y The new y position (if missing or NaN, height remains unchanged).
	 * @param steps The number of steps requested for animation (if missing, default = 10).
	 */
	A.move = function A_move(el, x, y, steps, callback) {
		if (!el)
			return false;
		steps = parseInt(steps);
		if (!steps || steps < 0)
			steps = 10;

		// Record the animation request's information into a local hashtable.
		var info = {
			'endX' :x,
			'endY' :y,
			'steps' :steps,
			'stepCounter' :0
		};
		if (x != null && !isNaN(x)) {
			info.startX = mstr.utils.BoxModel.getElementLeft(el);
			info.stepX = (x - info.startX) / steps;
		}
		;
		if (y != null && !isNaN(y)) {
			info.startY = mstr.utils.BoxModel.getElementTop(el);
			info.stepY = (y - info.startY) / steps;
		}
		;
		var id = this.add('move', el, info, callback);

		// Start the animation.
		return this.doAnimationRequest('move', id);
	};

	/**
	 * Implements the "move" animation by incrementally adjusting an HTML elements top and left style properties.
	 * @param id The unique id of the animation request.
	 */
	A.do_animation_move = function A_do_animation_move(id) {
		var info = this._lookup['move'] && this._lookup['move'][id];

		if (!info || !info.el)
			return;

		info.stepCounter++;

		// Do horizontal move, if requested.
		if (info.endX != null) {
			info.el.style.left = (info.endX - info.stepX
					* (info.steps - info.stepCounter)) + 'px'
		}
		;

		// Do vertical move, if requested.
		if (info.endY != null) {
			info.el.style.top = (info.endY - info.stepY
					* (info.steps - info.stepCounter)) + 'px';
		}
		;

		// Is the animation not yet completed?
		if (info.stepCounter < info.steps) {
			// Set a timeout to continue the animation after a pause.
			var timeInterval = 100 - parseInt(95 * Math.sin(Math.PI
					* info.stepCounter / info.steps));
			self.setTimeout(
					"mstr.utils.Animations.doAnimationRequest('move', '" + id
							+ "')", timeInterval);
			return true;
		}
		;
		return false;
	};

	return A;

})();

mstr.utils.BoxModel = ( function() {

	var BM = {};

	BM.getBox = function BM_getBox(el) {
		var box = {};
		box.height = mstr.utils.BoxModel.getElementOuterHeight(el);
		box.width = mstr.utils.BoxModel.getElementOuterWidth(el);

		box.left = mstr.utils.BoxModel.getElementSumOffsetLeft(el);
		box.right = box.left + box.width;
		box.top = mstr.utils.BoxModel.getElementSumOffsetTop(el);
		box.bottom = box.top + box.height;

		return box;
	};

	BM.getElementOuterWidth = function BM_getElementOuterWidth(el) {
		return this.getElementDimension(el, 'offsetWidth');
	};

	BM.getElementOuterHeight = function BM_getElementOuterHeight(el) {
		var h = this.getElementDimension(el, 'offsetHeight');
		/* Hack for inline element, like span, which return offsetHeight = 0 in Firefox. */
		if (mstr.utils.ISFF && !h && el && el.nodeName && (el.nodeName.toLowerCase() == 'span')
			&& mstr.utils.CSS.getStyleValue(el, 'display') !='block') 
		{
			var childNodes = el.childNodes;
			var hChild = 0;
			for ( var i = 0; i < childNodes.length; i++) {
				hChild = this
						.getElementDimension(childNodes[i], 'offsetHeight');
				h = Math.max(h, hChild);
			}
			;
		}
		;
		return h;
	};

	BM.getElementInnerWidth = function BM_getElementInnerWidth(el) {
		return this.getElementDimension(el, 'clientWidth');
	};

	BM.getElementInnerHeight = function BM_getElementInnerHeight(el) {
		return this.getElementDimension(el, 'clientHeight');
	};

	BM.getElementDimension = function BM_getElementDimension(el, dimension) {
	    var x = parseInt(el && el[dimension]);
		return (isNaN(x)) ? 0 : x;
	};

	/**
	 * Return the computed top value of an object
	 * @param el   the object
	 * @return Integer  the top value of the object
	 */
	BM.getElementTop = function BM_getObjTop(el) {
		if (el) {
			if (mstr.utils.ISIE4) {
				return el.style.pixelTop;
			} else if (mstr.utils.ISW3C) {
				return parseInt(document.defaultView.getComputedStyle(el, "")
						.getPropertyValue("top"));
			} else { // else is NN4
				return el.top;
			}
		}
		;

		return 0;
	};

	/**
	 * Get the width of the element based on its actual text content of childen nodes.
	 * This function is introduced to get the fit width of ListView popup for IE6.
	 * when elem.style.width = auto, IE6 cannot give the width of elem's container elem which might be too wide to fit the content
	 * @param elem  - the object
	 *        tagName - child html tag name
	 *        attName - attribute name of the child tag, eg, 'class'
	 *        attValue - attribute value
	 */
	BM.getElementContentWidth = function BM_getElementContentWidth(elem,
			tagName, attName, attValue) {
		var fontSize = 8; //default;
		if (mstr.utils.ISIE6)
			fontSize = parseInt(elem.currentStyle.fontSize);
		//if (mstr.utils.ISFF) fontSize = parseInt(document.defaultView.getComputedStyle(elem, '').fontSize);

		var childWidth = 0;
		var elemWidth = null; //auto        

		var objs = microstrategy.findChildrenWithAtt(elem, tagName, attName,
				attValue); //'DIV', 'className', 'mstrListViewItemName');
		for ( var i in objs) {
			childWidth = (objs[i].innerHTML.length * fontSize);
			elemWidth = Math.max(elemWidth, childWidth);
		}
		return (elemWidth && elemWidth > 0) ? elemWidth : null;
	}

	/**
	 * Return the computed left value of an object
	 * @param el   the object
	 * @return Integer  the left value of the object
	 */
	BM.getElementLeft = function BM_getElementLeft(el) {
		if (el) {
			if (mstr.utils.ISIE4) {
				return el.style.pixelLeft;
			} else if (mstr.utils.ISW3C) {
				return parseInt(document.defaultView.getComputedStyle(el, "")
						.getPropertyValue("left"));
			} else { // else is NN4
				return el.left;
			}
		}
		;

		return 0;
	};

	/**
	 * Starting with a given element, sum up the offsetTop values from the element
	 * up to but not including a given ancestor element.
	 * @param el The starting element whose offsetTop will be measured.
	 * @param [elLimit] ending element, whose offsetTop will not be included in the sum.
	 * @return Integer sum of the offsetTops of the specified range of elements.
	 */
	BM.getElementSumOffsetTop = function BM_getElementSumOffsetTop(el, elLimit) {
		/* 
		    WARNING: offsetParent can skip parentNodes, by design.  Therefore, elAncestor may
		    be skipped, and function will not stop at elAncestor, thus yielding an incorrectly high result. 
		 */
		var y = 0;
		for ( var i = 0; el && (el != elLimit); i++) {
			y += el.offsetTop;
            try {
	            el = el.offsetParent; //IE sometime throw "htmlfile: Unspecified error." exception
	        } catch (e) {
	        	el = document.body;	
	        }
		}
		;
		return y;
	};

	/**
	 * Starting with a given element, sum up the offsetLeft values from the element
	 * up to but not including a given ancestor element.
	 * @param el The starting element whose offsetLeft will be measured.
	 * @param elLimit Optional ending element, whose offsetLeft will not be included in the sum.
	 * @return Integer sum of the offsetLefts of the specified range of elements.
	 */
	BM.getElementSumOffsetLeft = function BM_getElementSumOffsetLeft(el,
			elLimit) {
		/* 
		    WARNING: offsetParent can skip parentNodes, by design.  Therefore, elAncestor may
		    be skipped, and function will not stop at elAncestor, thus yielding an incorrectly high result. 
		 */
		var y = 0;
		for ( var i = 0; el && (el != elLimit); i++) {
			y += el.offsetLeft;
            try {
	            el = el.offsetParent;	//IE sometime throw "htmlfile: Unspecified error." exception
	        } catch (e) {
	        	el = document.body;	
	        }
		}
		;
		return y;
	};

	/**
	 * This method does the same job as getElementSumOffsetLeft, but uses a workaround
	 * to avoid the problem of skipping ancestor nodes.
	 * @param el The starting element whose offsetLeft will be measured.
	 * @param elLimit Optional ending element, whose offsetLeft will not be included in the sum.
	 * @return Integer sum of the offsetLefts of the specified range of elements.
	 */
	BM.getElementSumOffsetLeftAccurately = function BM_getElementSumOffsetLeftAccurately(
			el, elLimit) {
		return this.getElementSumOffsetLeft(el)
				- this.getElementSumOffsetLeft(elLimit);
	};

	/**
	 * This method does the same job as getElementSumOffsetTop, but uses a workaround
	 * to avoid the problem of skipping ancestor nodes.
	 * @param el The starting element whose offsetTop will be measured.
	 * @param elLimit Optional ending element, whose offsetTop will not be included in the sum.
	 * @return Integer sum of the offsetTops of the specified range of elements.
	 */
	BM.getElementSumOffsetTopAccurately = function BM_getElementSumOffsetTopAccurately(
			el, elLimit) {
		return this.getElementSumOffsetTop(el)
				- this.getElementSumOffsetTop(elLimit);
	};

	BM.computeDistanceFromAncestor = function BM_computeDistanceFromAncestor(
			el, elAncestor) {
		//The "distance" of an element from its ancestor is computed as:
		//the SUM of offsetTop/Left of the element and its offsetParents up to but NOT including the given ancestor; 
		//MINUS any scrollTop/Left's of the element's parentNodes up to and including ancestor itself.
		//NOTE: parentNodes are consecutive but offsetParents are not.  Therefore, we must examine the
		//scrollTop/Left's of parentNodes, not offsetParents, because otherwise we might skip some.

		var doc = el && el.ownerDocument, body = doc && doc.body, html = doc
				&& doc.documentElement;

		//If no ancestor is specified, we assume it is the body of the given element's document.
		if (!elAncestor)
			elAncestor = body;

		//initialize return values
		var dist = {};
		dist.offsetX = 0;
		dist.offsetY = 0;
		dist.scrollX = 0;
		dist.scrollY = 0;

		// Walk the offset parents of the given eleemnt, up to the given ancestor.
		var elOffset = el;
		while (elOffset && (elOffset != elAncestor)) {
			// Sum up the offsetLeft & offsetTop of offset ancestor (including the element itself, but
			// excluding the ancestor.
			dist.offsetX += elOffset.offsetLeft;
			dist.offsetY += elOffset.offsetTop;

			// Determine: should we subtract scrollLeft/Top for ancestors?  By default, yes, we should, except:
			// if we are an absolutely positioned element whose offsetParent is the entire document (either the BODY,
			// HTML tag, or null).  In that exception case, we should not subtract for scrolling.
			var p = elOffset.offsetParent, bSubtractScroll = true;
			var s = elOffset.style;
			if (s.left || s.top || s.position.toLowerCase() == 'absolute') {
				bSubtractScroll = false;
			}
			
			// Did we determine that we should subtact for scrolling?
			if (bSubtractScroll) {
				// Subtract scrolling, starting with the parentNode of the current element, walking up all the parent
				// node ancestors up to and including the current element's offsetParent.
				var elScroll = elOffset.parentNode;
				while (elScroll) {
					var x = elScroll.scrollLeft;
					if (x != null) {
						dist.scrollX += x;
						dist.scrollY += elScroll.scrollTop;
					}
					if (elScroll == p)
						break;
					elScroll = elScroll.parentNode;
				}
			}
			elOffset = p;
		}

		dist.left = dist.offsetX - dist.scrollX;
		dist.top = dist.offsetY - dist.scrollY;
		dist.right = dist.left + mstr.utils.BoxModel.getElementOuterWidth(el)
				- 1;
		dist.right = Math.max(dist.right, dist.left);
		dist.bottom = dist.top + mstr.utils.BoxModel.getElementOuterHeight(el)
				- 1;
		dist.bottom = Math.max(dist.bottom, dist.top);

		return dist;
	};

	BM.getBrowserWindowHeight = function BM_getBrowserWindowHeight(htmlDoc) {
		if (!htmlDoc)
			return 0;

		// Transitional DOCTYPEs should use documentElement.clientHeight;
		// default DOCTYPEs should use document.body.clientHeight.

		if (mstr.utils.ISIE4) {
			// For IE6, a default DOCTYPE would return zero for documentElement.clientHeight, 
			// so we'll just do a null-check on documentElement.clientHeight to check for
			// the default DOCTYPE.
			return (htmlDoc.documentElement && htmlDoc.documentElement.clientHeight)
					|| (htmlDoc.body && htmlDoc.body.clientHeight) || 0;
		} else {
			// For Firefox1&2, Transitional DOCTYPEs have a document.doctype object with
			// publicId & systemId string properties.  Default DOCTYPEs have doctype = null.
			// Not tested for other browsers yet (notably, Opera & Safari).
			var isTrans = htmlDoc.doctype ? (String(htmlDoc.doctype.publicId)
					.toLowerCase().indexOf('transitional') > -1) : false;
			return isTrans ? htmlDoc.documentElement
					&& htmlDoc.documentElement.clientHeight : htmlDoc.body
					&& htmlDoc.body.clientHeight;
		}
		;
	};

	BM.getBrowserWindowWidth = function BM_getBrowserWindowWidth(htmlDoc) {
		if (!htmlDoc)
			return 0;

		// Transitional DOCTYPEs should use documentElement.clientWidth;
		// default DOCTYPEs should use document.body.clientWidth.

		if (mstr.utils.ISIE4) {
			// For IE6, a default DOCTYPE would return zero for documentElement.clientWidth, 
			// so we'll just do a null-check on documentElement.clientWidth to check for
			// the default DOCTYPE.
			return (htmlDoc.documentElement && htmlDoc.documentElement.clientWidth)
					|| (htmlDoc.body && htmlDoc.body.clientWidth) || 0;
		} else {
			// For Firefox1&2, Transitional DOCTYPEs have a document.doctype object with
			// publicId & systemId string properties.  Default DOCTYPEs have doctype = null.
			// Not tested for other browsers yet (notably, Opera & Safari).
			var isTrans = htmlDoc.doctype ? (String(htmlDoc.doctype.publicId)
					.toLowerCase().indexOf('transitional') > -1) : false;
			return isTrans ? htmlDoc.documentElement
					&& htmlDoc.documentElement.clientWidth : htmlDoc.body
					&& htmlDoc.body.clientWidth;

		}
		;
	};

	/**
	 * Measures the left, right, top and bottom of the current viewable area of the browser window, in pixels.
	 * @return A rectangle object with "left", "right", "top", and "bottom" integer properties.
	 */
	BM.getBrowserWindowRect = function EM_getBrowserWindowRect(htmlDoc) {
		var r = {};

		r.width = this.getBrowserWindowWidth(htmlDoc);
		r.height = this.getBrowserWindowHeight(htmlDoc);

		r.scrollX = (htmlDoc && htmlDoc.documentElement && htmlDoc.documentElement.scrollLeft)
				|| (htmlDoc && htmlDoc.body && htmlDoc.body.scrollLeft) || 0;
		r.scrollY = (htmlDoc && htmlDoc.documentElement && htmlDoc.documentElement.scrollTop)
				|| (htmlDoc && htmlDoc.body && htmlDoc.body.scrollTop) || 0;

		r.left = r.scrollX;
		r.right = r.left + r.width;
		r.top = r.scrollY;
		r.bottom = r.scrollY + r.height;
		return r;
	};

	BM.isElementWithinScrollWindow = function EM_isElWithinScrollWin(
			elScrolling, el, bufferSize) {
		if (elScrolling && el) {
			var elTop = this.getElementSumOffsetTop(el, elScrolling);
			var elBottom = elTop + el.offsetHeight;

			var scrollTop = elScrolling.scrollTop;
			var scrollBottom = scrollTop + elScrolling.clientHeight;

			return mstr.utils.Math.rangesIntersect( {
				'min' :elTop,
				'max' :elBottom
			}, {
				'min' :scrollTop,
				'max' :scrollBottom
			}, bufferSize);
		}
		;
		return false;
	};

	/**
	 * Performs a scroll operation on a given HTMLElement. 
	 * @param {HTMLElement} elScrolling The HTMLElement to be scrolled.
	 * @param {HTMLElement} el The HTMLElement that should be scrolled to.
	 */
	BM.scrollToElement = function BM_scrollToElement(elScrolling, el) {
		if (!elScrolling || !el)
			return;
		var scrollTop = elScrolling.scrollTop;
		if (scrollTop == null)
			return;
		var scrollHeight = this.getElementInnerHeight(elScrolling);
		if (!scrollHeight)
			return;

		var elY = el.offsetTop;
		if (mstr.utils.ISW3C)
			elY -= elScrolling.offsetTop;
		if (scrollTop > elY) {
			elScrolling.scrollTop = elY + 1;
		} else {
			var h = Math.min(this.getElementOuterHeight(el), scrollHeight);
			if ((elY + h) > (scrollTop + scrollHeight)) {
				var newScrollTop = Math.max(elY + h - scrollHeight, 0);
				elScrolling.scrollTop = newScrollTop;
			}
		}
	};

	BM.centerElementOnObject = function BM_centerElementOnObject(target, el) {
		if (!el || !target)
			return;

		el.style.left = Math.max(((this.getElementOuterWidth(target) / 2) - (this
						.getElementOuterWidth(el) / 2)), 0)
				+ "px";
		el.style.top = Math.max(((this.getElementOuterHeight(target) / 2) - (this
						.getElementOuterHeight(el) / 2)), 0)
				+ "px";
	};

	/*
	 * Given a rectangle with "left","top","width" and "height", this method
	 * will compute an adjust left & top that will allow the rectangle to best fit
	 * with a second rectangle that has "width" and "height" properties (its left & top
	 * are assumed to be zero).
	 */
	BM.fitRectWithinRect = function BM_fitRinR(rect1, rect2) {
		var WINDOW_BUFFER = 3, x = rect1.left, y = rect1.top;

		// Check if it bleeds to the right.
		// If so, move the HTML element left just enough to avoid bleeding right.
		x = Math.min(x, rect2.width - rect1.width - WINDOW_BUFFER);
		//Check if it bleeds to the left, if so don't let it; better to bleed right than left.
		x = Math.max(WINDOW_BUFFER, x);

		// Check if it bleeds to the bottom. If so, move it up just enough to avoid bleeding bottom.
		y = Math.min(y, rect2.height - rect1.height - WINDOW_BUFFER);
		//Check if it bleeds to the top, if so don't let it; better to bleed bottom than top.
		y = Math.max(WINDOW_BUFFER, y);
		return {
			left :x,
			top :y,
			width :rect1.width,
			height :rect1.height
		};
	};

	/*
	 * This method computes the "client coordinates" of a given HTML element.
	 * "Client coordinates" are relative to the browser window, meaning that (0,0) is
	 * the top-left corner of the browser window, but not necessarily the top-left corner
	 * of the document.  If the end-user has scrolled the browser, then the top-left corner
	 * of the document will be above & to the left of the browser window's top-left corner.
	 * Therefore, a negative client coordinate indicates a position that is above/left of
	 * the current scroll position.
	 */
	BM.getClientCoords = function BM_getcc(el) {
		var doc = el.ownerDocument, body = doc && doc.body, html = doc
				&& doc.documentElement;
		if (el == body || el == html)
			el = null;

		return el ? this.computeDistanceFromAncestor(el, html) : {
			left :0,
			top :0
		};
	};

	/*
	 * This method sets the  "client coordinates" of a given HTML element.
	 * Dom doesn't allow us to set the client coordinates directly, only the offset coordinates.
	 * So this method converts the given client coords to offset coords.
	 * "Client coordinates" are relative to the browser window, meaning that (0,0) is
	 * the top-left corner of the browser window, but not necessarily the top-left corner
	 * of the document.  If the end-user has scrolled the browser, then the top-left corner
	 * of the document will be above & to the left of the browser window's top-left corner.
	 * Therefore, a negative client coordinate indicates a position that is above/left of
	 * the current scroll position.
	 */
	BM.setClientCoords = function BM_setcc(el, left, top) {
		var sty = el && el.style;
		if (!sty)
			return;

		// Measure the client coordinates of el's offset parent...
		var coords = this.getClientCoords(el.offsetParent);
		// ...and set el's top & left relative to the offset parent's coordinates.
		sty.left = (left - coords.left) + "px";
		sty.top = (top - coords.top) + "px";

	};

	/**
	 * check if an HTML element, like DIV, has scrollbar(s) present
	 * 
	 * @param el - HTML element to check
	 * @param which - direction : optional,
	 * 				if set, value could be one of these: 'x','y','h','v','hv'
	 * 				if not set, it will check either scrollbar
	 */
	BM.hasScrollbar = function BM_hasScrollbar(el, which) {
		var overflow = {
			x :mstr.utils.CSS.getStyleValue(el, 'overflowX'),
			y :mstr.utils.CSS.getStyleValue(el, 'overflowY')
		};

		switch (which) {
		case 'x'://horizontal scrollbar
			if (overflow.x == 'hidden')
				return false;

			return overflow.x == 'scroll' || el.scrollWidth > el.clientWidth;
			break;

		case 'y'://vertical scrollbar
			if (overflow.y == 'hidden')
				return false;

			return overflow.y == 'scroll' || el.scrollHeight > el.clientHeight;
			break;

		case 'xy': //both scrollbars
			if (overflow.x == 'hidden' || overflow.y == 'hidden')
				return false;
			return overflow.x == 'scroll' && overflow.y == 'scroll'
					|| el.scrollHeight > el.clientHeight
					&& el.scrollWidth > el.clientWidth;
			break;

		default: //any scrollbar
			if (overflow.x == 'hidden' && overflow.y == 'hidden')
				return false;

			return overflow.x == 'scroll' || overflow.y == 'scroll'
					|| el.scrollHeight > el.clientHeight
					|| el.scrollWidth > el.clientWidth;
			break;
		}

	};

	BM.prototype = new Object();
	return BM;

})();

/**
 * Dom is a singleton with methods for manipulating the HTML DOM.
 */
 mstr.utils.Dom = (function(){
 
     /**
      * @constructor
      */
    var D = {};
    
    /** 
     * Replaces a given node in a DOM with a given node.
     * Assumes the replacement node has no parent node yet.
     * The node that is replaced will be discarded.
     * @param target The node to be replaced.
     * @param replacement The node to replace the given target.
     */
    D.replaceNode = function D_replaceNode(target, replacement)
    {
        if (!target || !replacement) return;
        if (target.swapNode)
        {
            target.swapNode(replacement);
        }
        else
        {
            var p = target.parentNode;
            if (p)
            {
	            p.insertBefore(replacement, target);
	            p.removeChild(target);
	        };
        };
    };

    /**
     * Inserts a given DOM node as a child of a given parent node.
     * If another child is given, the node is inserted immediately before
     * that child; otherwise, it is appended.
     * @param el The node to be inserted.
     * @param parent The parent node under which the node will be inserted.
     * @param before The optional child node before which the node will be inserted.
     * @return The newly inserted node, if successful; null otherwise.
     */
    D.insertNode = function D_insertNode(el, parent, before)
    {
        if (el && parent)
        {
            if (before)
            {
                return parent.insertBefore(el, before);
            }
            else
            {
                return parent.appendChild(el);
            };
            
        };
        return null;
    };
    
    D.findAncestor = function D_findAncestor(el, propertyName, propertyValue, elLimit, bInclusive)
    {
        if (typeof(bInclusive) == 'undefined') bInclusive = true;
        var el2 = (!!bInclusive) ? el : el && el.parentNode;
        if (propertyValue != null)
        {
            propertyValue = String(propertyValue).toLowerCase();
            while (el2)
            {
                if (String(el2[propertyName]).toLowerCase() == propertyValue)
                {
                    return el2;
                }
                if (el2 == elLimit) break;
                el2 = el2.parentNode;
            }
        }
        else
        {
            while (el2)
            {
                if (el2[propertyName] != null)
                {
                    return el2;
                }
                if (el2 == elLimit) break;
                el2 = el2.parentNode;
            }
        }
        return null;
    };

    /**
     * Find the closest relative or absolutely positioned ancestor of a given element.  This function assumes
     * that either the position, left or top will be set in the inline style attribute.  It makes no allowance
     * for properties set in css classes.
     */
    D.findPositionedAncestor = function D_findPositionedAncestor(el, inclusive)
    {
        var testFn = function (d) {
            var s = d.style;
            if (!s) return false;

            var pos = s.position.toLowerCase();
            return ((pos == 'relative' || pos == 'absolute') || s.left || s.top) ;
        };
        
        
        if (inclusive && testFn(el)) return el;
        
    	// Optimization: In IE, use offsetParent rather than parentNode.  This will skip some ancestor nodes
    	// who have no effect on the result.  In Firefox, however, we can't, because offsetParent will skip 
    	// some absolutely positioned ancestors, so we continue using parentNode there to be safe.
    	var prop = mstr.utils.ISIE4 ? 'offsetParent' : 'parentNode';
        var p = el[prop];
        while (p)
        {
            if (testFn(p)) return p;
            p = p[prop];
        }
    
        return el.ownerDocument.body;
    };

    /**
     * Returns true if a given HTML element contains another given HTML element.
     * @param elContainer The container HTML element.
     * @param elDescendant The descendant HTML element.
     * @param bInclusive Optional argument, specifies if we should check for elContainer = elDescendant (default = true).
     */
    D.containsElement = function D_containsElement(elContainer, elDescendant, bInclusive)
    {
        if (elContainer && elDescendant)
        {
            // For inclusive search, first check if the 2 given elements match.
            if ((bInclusive != false) && (elContainer == elDescendant)) return true;

            // Next, loop up the chain of parent nodes of the descendant element, 
            // stopping if/when we find the container element.
            if (elDescendant.ownerDocument == elContainer.ownerDocument)
            {
                var elAnc = elDescendant.parentNode;
                while (elAnc)
                {
                    if (elAnc == elContainer) return true;
                    elAnc = elAnc.parentNode;
                };
            };
        };
        return false; 
    };
    
    D.clearChild = function D_clearChild(elParent, childIndex)
    {
        if (childIndex > -1)
        {
            return elParent && elParent.childNodes && elParent.childNodes[childIndex] 
                && elParent.removeChild(elParent.childNodes[childIndex]);
        };
    };
    
    D.clearChilds = function D_clearChilds(elParent)
    {
	var c = elParent && elParent.childNodes;
	for (var i = mstr.$A.len(c); i > 0; i--)
	{
		elParent.removeChild(c[i - 1]);
	};
    };    
        

    D.clearBrowserHighlights = function Dom_clearBrowserHighlights(hWin)
    {
        hWin = hWin || window;
        if (mstr.utils.ISIE4)
        {
            if (hWin && hWin.document && hWin.document.selection && (hWin.document.selection.empty)) {
            	try {
	                hWin.document.selection.empty();
	            } catch (ex){
		        	// TQMS# 337503: It seems that for an unknown reason the empty property sometimes can generate Runtime error, so swallow any errors.
	            };
            }
        }
        else if (mstr.utils.ISW3C)
        {
            if (hWin && hWin.getSelection && hWin.getSelection())
                if (hWin.getSelection().removeAllRanges) hWin.getSelection().removeAllRanges();
        };
    };
    
    /**
     * Appends a hidden input, with a given name & value, to a given HTML FORM.
     * @param elForm The HTML FORM element.
     * @param name The name of the hidden input to be appended.
     * @param value The string value of the hidden input to be appended.
     */    
    D.addFormHiddenInput = function D_addFormHiddenInput(elForm, name, value)
    {
        if (elForm && elForm.ownerDocument && name)
        {
            var el = elForm.ownerDocument.createElement('input');
            if (el)
            {
                el.type = 'hidden';
                el.name = String(name);
                el.value = String(value);
                elForm.appendChild(el);
            };
        };
    };

    /**
     * Removes inputs, with a given name, from a given HTML FORM.
     * @param elForm The HTML FORM element.
     * @param strNames Comma-delimited string of input names to be removed.
     */    
	D.removeFormInputNames = function D_rmvFormInputNames(elForm, strNames)
	{
		var c = elForm && elForm.childNodes,
			len = (c && c.length) || 0,
			el, n;
			
		if (len)
		{
			var arrNames = strNames && strNames.split && strNames.split(','),
				hash = arrNames && arrNames.length && mstr.$A.toHash(arrNames);

			for (var i = len - 1; i > -1; i--)
			{
				el = c[i];
				n = el && el.name;
				if (n && hash[n]) elForm.removeChild(el);
			}	
		}
	}

    /**
     * Move an element to a given coordinate.
     * @param el  the HTML element to be moved.
     * @param iX  the x coordinate of the new location.
     * @param iY  the y coordinate of the new location.
     * @param newLocation  the {x, y} coordinate of the new location.
     */
    D.moveElementTo = function D_moveElementTo(el, iX, iY){
       if (el != null) {
            var x = parseInt(iX);
            var y = parseInt(iY);
            if (!isNaN(x) && !isNaN(y)) {
               if (mstr.utils.ISIE4 || mstr.utils.ISW3C) {
                   el.style.left = x + "px";
                   el.style.top = y + "px";
               } else {
                   document.layers[el.id].left = x;
                   document.layers[el.id].top = y;
               }
            }
       }
    };
    
    /**
     * This method adds an iframe for popup masking.
     * @param target The element which the iframe will be masking (used for sizing).
     * @param container The element to append the iframe to.
     */
    D.insertPopupMask = function D_insertPopupMask(target, container, x, y) {
    	//#362189 -MSTR Office: <SELECT> tag shows through dialog and floating editor; 
    	//		  we have to render iframe for all IE browsers.
        if (mstr.utils.ISW3C) return null;
        
        // Create an iFrame to mask any select boxes.
        var mask = container.ownerDocument.createElement('iframe');
        mask.src = '../html/Empty.html';
            
        // Size the iFrame.
        mask.style.width = mstr.utils.BoxModel.getElementOuterWidth(target) + 'px';
        mask.style.height = mstr.utils.BoxModel.getElementOuterHeight(target) + 'px';
        
        // Position the iFrame.
        if (x) mask.style.left = x;
        if (y) mask.style.top = y;
        
        // Display the mask.
        mask.style.display = 'block';
            
        // Append the iframe to the container.
        container.appendChild(mask);
        
        return mask;
    };

    
    /**
     * Sets the element's background image as class. Replaces the occurrence of 'mstrBGIcon_XXXX' with the new className created with suffix
     * @elem dom element for which the background image is applied
     * @src suffix name for the className represented by .mstrBGIcon_<suffix> in widgets.css
     * @see #getBGIconClass
     * 
     * Note: TQMS issue 339210: Get a 'this page contains both secure and nonsecure items' pop-up warning when Web is deployed under HTTPS for the Prompt page in Orion in IE
     * Work around is to refer a background image through class instead of using a inline style for the <code>elem</code>
     */
    D.setBGIconClass = function D_setBGIconClass(elem, suffix) {
    	var className = D.getBGIconClass(suffix);
    	if(className != null) {
    		//order is important since suffix might be added to non mstrBGIcon_xxxx classes like "Hover' : "mstrBGIcon_down mstrListBlockItem" becomes "mstrBGIcon_down mstrListBlockItemHover"
    		elem.className = className + " " + elem.className.replace(/mstrBGIcon_[^\s]*/, ""); 
    	}
    };
    
    /**
     * Gets the class name as defined in widgets.css. The format of such a class is .mstrBGIcon_<suffix)
     * @suffix suffix name for the className mstrBGIcon_XXXXXX defined in widgets.css
     * @see #setBGIconClass
     * 
     * Note: TQMS issue 339210: Get a 'this page contains both secure and nonsecure items' pop-up warning when Web is deployed under HTTPS for the Prompt page in Orion in IE
     * Work around is to refer a background image through class instead of using a inline style for the <code>elem</code>
     */
    D.getBGIconClass = function D_getBGIconClass(suffix) {
    	if(!suffix) return null;
    	return 'mstrBGIcon_' + suffix; //defined in widgets.css
    };
    
    return D;
    
 })();
 
 mstr.$D = mstr.utils.Dom;
 
 mstr.utils.Events = (function(){

    Events.mouseX = 0;
    Events.mouseY = 0;
    
    Events.target = function Events_target(e, hWin)
    {
        hWin = hWin || window;
        if (mstr.utils.ISIE4)
            return hWin && hWin.event && hWin.event.srcElement;
        else if (mstr.utils.ISW3C)
            return e && e.target;
        return null;    
    };
        
    Events.shiftKey = function Events_shiftKey(e, hWin)
    {
        hWin = hWin || window;
        e = e || hWin.event;
        return !!(e && e.shiftKey);
    };
    
    Events.ctrlKey = function Events_ctrlKey(e, hWin)
    {
        hWin = hWin || window;
        e = e || hWin.event;
        return !!(e && e.ctrlKey);
    };
    
    Events.getMousePosition = function Events_getMousePosition(e, hWin)
    {
        /* TO DO: (low) context menus need this method cleaned up */
        hWin = hWin || window;
        if (mstr.utils.ISIE4)
        {
            mstr.utils.Events.mouseX = hWin.event.clientX;
            mstr.utils.Events.mouseY = hWin.event.clientY;
        }
        else if (mstr.utils.ISW3C)
        {
            mstr.utils.Events.mouseX = e.pageX;
            mstr.utils.Events.mouseY = e.pageY;
        }
        return { "x" : mstr.utils.Events.mouseX, "y" : mstr.utils.Events.mouseY };
    };
    
    Events.button = function Events_button(e, hWin)
    {
        //Returns 1 for left-click, 2 for right-click.
        hWin = hWin || window;
        if (mstr.utils.ISIE4)
        {
            return (hWin && hWin.event && hWin.event.button) || 1;
        }
        else if (mstr.utils.ISW3C)
        {
            return (!e || (e.button < 2)) ? 1 : 2;
        }
        else
        {
            return 1;
        };
    };
    
    
    Events.keyCode = function Events_keyCode(e, hWin)
    {
	    e = e || (hWin && hWin.event);
	    return e && e.keyCode;

    };
    
    Events.keyChar = function Events_keyChar(e, hWin)
    {
        var code = hWin.event ? hWin.event.keyCode : (e && e.which);
        return code && String.fromCharCode(code);
    };

    Events.cancel = function Events_cancel(e, hWin)
    {
        if (e && e.preventDefault)
        {
            e.preventDefault();
        }
        else
        {
            e = e || (hWin && hWin.event);
            if (e) e.returnValue = false;
        };
    };

    
            
    function Events(){};
    
    return Events;
 })();  
 
 mstr.$E = mstr.utils.Events;
 
 
/**
 * Math is a singleton which houses methods for common mathematical operations.
 * @class
 */
 mstr.utils.Math = (function(){
 
    var M = {};
    
    /** 
     * Returns true if a given data type is a numeric data type (example:
     * integer, decimal, float, etc). Otherwise returns false.
     * @memberOf mstr.utils.Math
    **/
    M.isDataTypeNumeric = function M_isDataTypeNumeric(/*Integer*/ dataType)
    {
        var en = mstr.Enum.Nodes.DATATYPE;
        switch (parseInt(dataType))
        {
            case en.INTEGER:
            case en.NUMERIC:
            case en.DECIMAL:
            case en.REAL:
            case en.DOUBLE:
            case en.FLOAT:
            // case en.BIGDECIMAL:  // BigDecimal is not numeric; its surrounded by "#"s
                return true;
            default:
                return false;
        }  
    };
    
    /** 
     * Returns true if a given data type is mstr.Enum.Modes.DATATYPE.BIGDECIMAL.
     * Otherwise returns false;
     * @memberOf mstr.utils.Math
    **/
    M.isDataTypeBigDecimal = function M_isDataTypeBigDec(/*Integer*/ dataType)
    {
        return dataType == mstr.Enum.Nodes.DATATYPE.BIGDECIMAL;
    };
    

    /** 
     * Returns true if a given data type is mstr.Enum.Modes.DATATYPE.INTEGER.
     * Otherwise returns false;
     * @memberOf mstr.utils.Math
    **/
    M.isDataTypeInteger = function M_isDataTypeInteger(/*Integer*/ dataType)
    {
        return dataType == mstr.Enum.Nodes.DATATYPE.INTEGER;
    };

    /** 
     * Returns true if a given data type is a date or time or date and time
     * data type (e.g., date, time, timestamp). Otherwise returns false.
     * @memberOf mstr.utils.Math
    **/
    M.isDataTypeDateTime = function M_isDataTypeDateTime(/*Integer*/ dataType)
    {
        var en = mstr.Enum.Nodes.DATATYPE;
        switch (parseInt(dataType))
        {
            case en.DATE:
            case en.TIME:
            case en.TIMESTAMP:
                return true;
            default:
                return false;
        }   
    };

    /** 
     * Returns true if a given data type is a textual datatype (e.g., char,
     * varchar, longvarchar). Otherwise returns false.
     * @memberOf mstr.utils.Math
    **/
    M.isDataTypeString = function M_isDataTypeString(/*Integer*/ dataType)
    {
        var en = mstr.Enum.Nodes.DATATYPE;
        switch (parseInt(dataType))
        {
            case en.CHAR:
            case en.VARCHAR:
            case en.LONGVARCHAR:
                return true;
            default:
                return false;
        }   
    };
    
    M.canCastDataType = function M_canCastDataType(dtpFrom, dtpTo) {
        if (dtpFrom == dtpTo) return true;
        
        if (this.isDataTypeInteger(dtpTo)) {
            return this.isDataTypeInteger(dtpFrom);
        } else if (this.isDataTypeNumeric(dtpTo)) {
            return this.isDataTypeNumeric(dtpFrom);
        } else if (this.isDataTypeDateTime(dtpTo)) {
            return this.isDataTypeDateTime(dtpFrom);
        } else if (this.isDataTypeBigDecimal(dtpTo)) {
            return this.isDataTypeBigDecimal(dtpFrom);
        } else { // casting to string data type, anything can be cast that
            return true;
        }
    };
    
    /**
     * Given two data types, this method returns a data type which
     * includes both of the given data types.  If either of the input
     * params are null, then that param is ignored.  If both are null,
     * then UNKNOWN is returned.
     * Note: this method's usage is limited and typically is only called
     * with the following data types: enDT.INTEGER, enDT.NUMERIC, enDT.DATE,
     * en.TIME, en.TIMESTAMP, enDT.VARCHAR.
     * Therefore this method may not work well with any generic data types, but that's ok as
     * long as it works well for the limited usage we intend for it.
     * @memberOf mstr.utils.Math
     */
    M.getCommonDataType = function M_getCommonDataType(dtp1, dtp2) {
        var enDT = mstr.Enum.Nodes.DATATYPE;
        // Check for nulls.
        if (!dtp2) return dtp1 || enDT.UNKNOWN;
        if (!dtp1) return dtp2 || enDT.UNKNOWN;
        // Check for exact match.
        if (dtp1 == dtp2) return dtp1;
        // Can one data type be cast into the other?
        if (this.canCastDataType(dtp2, dtp1)) return dtp1;
        if (this.canCastDataType(dtp1, dtp2)) return dtp2;
        // No data types can be cast as the other, so by default, use a string data type.
        return enDT.VARCHAR;
    };
    
    /**
     * Attempts to convert a given string to either a numeric or string data type.
     * If conversion fails, the original string is returned.
     * @memberOf mstr.utils.Math
     */
    M.convertStringToDataType = function M_cvtStrToDataType(s, dataType)
    {
        var result = s;
        
        switch (dataType)
        {
            case mstr.Enum.Nodes.DATATYPE.INTEGER:
                // For integer, try converting non-null string to integer.
                if (s != null)
                {
                    result = parseInt(s);
                    // If conversion failed, reset result to given string.
                    if (isNaN(result)) result = s;
                };
                break;
            case mstr.Enum.Nodes.DATATYPE.NUMERIC:
            case mstr.Enum.Nodes.DATATYPE.DECIMAL:
            case mstr.Enum.Nodes.DATATYPE.REAL:
            case mstr.Enum.Nodes.DATATYPE.DOUBLE:
            case mstr.Enum.Nodes.DATATYPE.FLOAT:
            case mstr.Enum.Nodes.DATATYPE.BIGDECIMAL:
                    // For non-integer numeric cases, try a number conversion if the given string is non-null.
                    if (s != null)
                    {
                        result = Number(s);
                        // If conversion failed, reset result to given string.
                        if (isNaN(result)) result = s;  
                    };
                    break; 
            case mstr.Enum.Nodes.DATATYPE.CHAR:
            case mstr.Enum.Nodes.DATATYPE.VARCHAR:
            case mstr.Enum.Nodes.DATATYPE.LONGVARCHAR:
                // For strings, convert a null to an empty string.
                if (s == null)  result = '';
        };
        
        return result;

    };
    
    /**
     * Converts a given hashtable of indices into an array of range objects.
     * @param indices Hashtable keyed by index number (the hash values are ignored).
     * @return An array of range objects, sorted in ascending order of the range.min values.
     * @memberOf mstr.utils.Math
     */
    M.convertIndicesToRanges = function M_convertIndicesToRanges(indices, sorted)
    {
        // If the given array of indices is not sorted, clone it and sort the clone.
        // Using a clone preserves the order of the given array, just to be safe.
        var arr = indices;
        if (!sorted) arr = mstr.$A.sortNumericArray(indices.concat());
        
        // Walk the array, grouping contiguous blocks of indices into ranges.
        var ranges = [];
        var range = {'min': arr[0]};
        for (var i = 1, len = mstr.$A.len(arr); i < len; i++)
        {
            // Did we find a break in the continuity of integers?
            var diff = arr[i] - arr[i-1];
            if (diff != 0 && diff != 1)
            {
                // Close current range.
                range.max = arr[i-1];
                ranges.push(range);
                
                // Start new range.
                range = {'min' : arr[i]};
            };
        };
        
        // Close final range.
        range.max = arr[len - 1];
        ranges.push(range);
        
        return ranges;        
    };

    /** 
     * Determines if two ranges of numerical values intersect.
     * @param range1 The first range, an object with min and max numerical members.
     * @param range2 The second range, an object with min and max numerical members.
     * @param bufferSize An optional padding that allows for near-misses to be considered hits.
     * @memberOf mstr.utils.Math
     */ 
    M.rangesIntersect = function M_rangesIntersect(range1, range2, bufferSize)
    {
        bufferSize = bufferSize || 0;
        if (range1 && range2)
        {
            range2.min -= bufferSize;
            range2.max += bufferSize;
            if (range1.min < range2.min)
            {
                return range1.max >= range2.min;
            }
            else if (range1.min == range2.min)
            {
                return true;
            }
            else if (range1.min < range2.max)
            {
                return true;
            };
        };
        return false;
    };
    
    /**
     * Determines if a given number falls within a given min and max value, inclusive.
     * If either min or max = null, it is ignored.
     * @param x The number to be inspected.
     * @param min Optional minimum number value allowed.
     * @param max Optional maximum number value allowed.
     * @return -1 if the number is below the range; 1 if the number is above the range;
     * or 0 if the number is within the range.
     * @memberOf mstr.utils.Math
     */
    M.inNumericRange = function M_inNumericRange(x, min, max)
    {
        if (Number(min) == 0 && (x + "").indexOf("-") > -1) return -1;
        x = Number(x);
        if ((min != null) && (x < Number(min))) return -1;
        if ((max != null) && (x > Number(max))) return 1;
        return 0;
    };
    
    /**
     * 
     * @memberOf mstr.utils.Math
     */
    M.roundUpDiv = function M_roundUpDiv(n, d)
    {
        return parseInt(n / d) + ((n % d) ? 1 : 0);
    };
    
    return M;
    
 })();

 /**
  * A singleton containing methods for operating on Strings.
  * @class
  */
 mstr.utils.String = (function(){
 
    var S = {};
    
    /**
     * Given a string, removes blank spaces at the start & end of the string.
     * @memberOf mstr.utils.String
     */
    S.trim = function S_trim(s) {
        return this.trimDelimited(s, null);
    };
    
    /**
     * Given a string of delimited values, removes blank spaces (i) at the
     * start of the string, (ii) at the end of the string, and (iii) immediately
     * following the delimiters.
     * @memberOf mstr.utils.String
     */
    S.trimDelimited = function S_trimDelimited(s, delim)
    {
        if (s && typeof(s) == 'string')
        {
            s = s.replace(/^\s+/, ''            /* remove blanks at start */
                    ).replace(/\s+$/, '');      /* remove blanks at end */

            // Do we have a delimiter? If so, remove blanks after each instance (if any).
            if (delim)
            {
                s = s.replace(
                            new RegExp("\\" + delim + "\\s+", 'gm'),
                            delim
                            );
            }
        }
        return s;                                   
    };
    
    /**
     * Given a string of delimited values, inserts a single blank space
     * after each delimiter which is not followed by a blank space.
     * @memberOf mstr.utils.String
     */
    S.padDelimited = function S_padDelimited(s, delim)
    {
        if (delim && s && typeof(s) == 'string')
        {
            return s.replace(
                        new RegExp("\\" + delim + "(\\S+?)", "gm"),
                        delim + " $1"
                    );
        }
        return s;
    };
    
    /**
    * Return whether a string is null or contains only white spaces
     * @memberOf mstr.utils.String
    */
    S.isEmpty = function S_isEmpty(s)
    {
        if(!s) return true;
        return String(s).replace(/\s*/g,'') == '' ? true : false;
    };

    /**
     * This function splits a given string into an array, much like the
     * native String.split() method.  However, the second argument of this
     * method can be either a delimiter or an array of delimiters or null.  If it is an array,
     * then the method will search the given string for each delimiter, starting
     * with the first array item.  If found, that delimiter will be used; if not found,
     * the next array item will be tried.  If no array item is found, the entire
     * string is returned as a single-item array.  Likewise, if the second argument
     * (the delimeter) is omitted, the entire string is returned
     * as a single-item array. 
     * @memberOf mstr.utils.String
     **/
    S.smartSplit = function S_smartSplit(/*String*/ s, /*String|Array?*/ delims, /*Boolean?*/ bTrimDelimited) 
    {
        if (delims && !this.isEmpty(s)) {
            if (typeof(delims) == 'string') delims = [delims];
            s = String(s);  // Cast numbers into string so we can use indexOf() and split().
            // For each given delimiter...
            for (var i = 0, len = delims.length; i < len; i++) {
                // Is the delimiter used in the given string?
                if (s.indexOf(delims[i]) > -1) {
                    // Yes, so parse by this delimiter.
                    // First, if requested, trim the string pieces first before splitting.
                    if (bTrimDelimited) s = mstr.utils.String.trimDelimited(s, delims[i]);
                    return s.split(delims[i]);
                }
            }
        }
        return [s];
        
    };
    /**
     * For IE, when we set a string to innerHTML, it will normalize the string immediately. Even if I have white-space css property set to 'pre'.
     * for example, div.innerHTML = '<a style="white-space:pre">xxxx       yyyyy</a>'.
     * When the string is assigned to innerHTML in IE, it first normalize the multiple empty spaces into one empty space, then assigned to innerHTML. So, even we have white-space:pre,
     * we do not have the string containing multiple empty spaces any more.
     * So, we have to encode this string to prevent it from IE normalization. One way is to change every empty space into &nbsp;, which will be shown as an empty space, but will not be
     * normalized by IE. But if we replace every empty space to &nbsp;, the whole string is not wrappable any more. 
     * Therefore, we are encoding every two adjacent empty spaces into one empty space and one &nbsp;. Then the string does not have any adjacent empty spaces, IE normalization will not 
     * affect it. Word wrapping still can happen, since the existing of the regular empty space. And visually it is still the same as the original string. 
     */
    S.preserveWhiteSpace4HTML = function S_preWS(s) {
    	return s.replace(/\u0020\u0020/g, ' &nbsp;');
    }
    S.escape4HTMLText = function S_escape4HTMLText(v) {
        var QUOTE = /\"/gm;
        var QUOTE_ENCODED = '&quot;';
        var AMP = /\&/gm;
        var AMP_ENCODED = '&amp;';
        var LESSTHAN = /\</gm;
        var LESSTHAN_ENCODED = '&lt;';
        var GREATERTHAN = /\>/gm;
        var GREATERTHAN_ENCODED = '&gt;';
        return v.replace(AMP, AMP_ENCODED
                            ).replace(QUOTE, QUOTE_ENCODED
                            ).replace(LESSTHAN, LESSTHAN_ENCODED
                            ).replace(GREATERTHAN, GREATERTHAN_ENCODED);
    }
    
    return S;
    
 })();

/**
 * LocaleParser is a single which houses method for manipulating localized numbers/dates.
 * @class
 */
 mstr.utils.LocaleParser = (function(){
 
    var LP = {};
    
    /**
     * BROWSER_DECIMALSEP is the decimal separator recognized by the javascript engine
     * on this browser.  It is not necessarily the same as the mstr.Settings.Locale.DECIMALSEP
     * recognized by MSTRWeb server.  The browser's decimal sep depends on the client machine's
     * OS, not on any MSTRWeb setting.
     * @memberOf mstr.utils.LocaleParser
     */
    LP.BROWSER_DECIMALSEP = String(1/10).match(/\D/) || '.';
    
    /**
     * This method checks if the value of a given string corresponds to one of
     * a given list of data types.  If so, it returns that data type.  If no match
     * is made, the method returns null.  The data types are matched in the order
     * they are listed.  The optional boolean flag bFormat allows the caller to request
     * that this method format the given string into the output format accepted by
     * MSTRWeb server.
     * @return If no match is found, returns null.  If a match is found, the return value
     * depends on the bFormat param.  If bFormat is false (or missing), returns the data type 
     * (Integer) which matched.  If bFormat is true, returns a javascript Object with two
     * properties:  "dataType" = the data type (Integer) that matched; "formatted" = the
     * given string converted into the MSTRWeb server output format.
     * @memberOf mstr.utils.LocaleParser
     */
    LP.whichDataType = function LP_whichDtp(/*String*/ s, /*Array*/ dtps, /*Boolean?*/ bFormat) {
        // We need a hashtable of validation methods; the hashtable maps
        // data type to a validation method for that data type.
        var lookup = this._getMethodLookup();
        // Now walk the array of given data types.
        // For each data type...
        for (var i = 0, len = dtps && dtps.length || 0; i < len; i++) {
            // What is the validation method's name for this data type?
            var dtp = dtps[i],
                methodName = lookup[dtp];
            // Call the validation method, with the bFormat flag.  If bFormat,
            // the validation method is expect to return a converted String if there is a match;
            // null otherwise.
            // Else if bFormat is false, the method does not compute the converted String and
            // instead just returns true|false depending on whether there is a match.
            var match = methodName && this[methodName](s, bFormat);
            if ((match !== null) && (match !== false)) {
                return bFormat ? 
                        {dataType: dtp, formatted: match} : 
                        dtp;
            }
        }
        return null;
    }

    /**
     * This method retrieves a lookup of validation method names.  The lookup is a hashtable,
     * keyed by data type.  The data type is mapped to the name (string) of a LocaleParser method
     * that validates that data type.  This method returns true when passed in a string whose value
     * matches that data type; otherwise, the method returns false.
     * The lookup is built the first time the method is called.
     * @memberOf mstr.utils.LocaleParser
     */     
    LP._getMethodLookup = function LP_getMethodLookup() {
        if (!this.dtpChecker) {
            var h = this.dtpChecker = {},
                enDT = mstr.Enum.Nodes.DATATYPE;
            h[enDT.INTEGER] = 'isInteger';
            h[enDT.NUMERIC] = h[enDT.DECIMAL] = h[enDT.REAL] = h[enDT.DOUBLE] = h[enDT.FLOAT] = 'isNumeric';
            h[enDT.CHAR] = h[enDT.VARCHAR] = h[enDT.LONGVARCHAR] = 'isString';
            h[enDT.DATE] = h[enDT.TIME] = h[enDT.TIMESTAMP] ='isDateAndOrTime';
            h[enDT.BIGDECIMAL] = 'isBigDecimal';
        }
        return this.dtpChecker;
    };
    
    LP.REGEXPS = {
        DATES: {},  // To store regular expressions built from date formats.
        TIMES: {}   // To store regular expressions built from time formats.
    };
    
    LP.CACHE = {
        PARSEDATE: {
            CONTAINS: {},
            EQUALS: {}
        },
        PARSETIME: {
            CONTAINS: {},
            EQUALS: {}
        }
    };
    
    
    /**
     * Validation method for integer.  Returns true if the given string matches one of MSTR Web's input patterns (or
     * output pattern) for integers.
     * If bFormat, the method returns a converted String if there is a match; null otherwise.
     * Else if bFormat is false, the method instead just returns true|false depending on whether there is a match.
     * @memberOf mstr.utils.LocaleParser
     */
    LP.isInteger = function LP_isInteger(/*String*/ s, /*Boolean?*/ bFormat) {
        if (s == null) return false;
        if (typeof(s) != 'string') s = String(s);
        // Cheat: instead of looking at MSTR Web patterns, assume all the patterns are just digits
        // with thousand separators.  So to check if its an integer, first created a formatted string as follows:
        // (1) remove all the thousand separators (if any), and
        // (2)remove any padding that follows the leading minus sign (if present).
        // Then to check if its valid:
        // (1) copy the string and remove the minus sign (if any) from the copy,
        // (2) check if the copy has at least one digit char in it, and
        // (3) check if the copy has no non-digit chars in it.
        // Note: we don't check if the number of grouped digits after each thousands sep is correct (future enhancement).
        var formattedStr = mstr.utils.String.trim(s
                                    ).replace(new RegExp("\\" + mstr.Settings.Locale.THOUSANDSEP, "g"), ""
                                    ).replace(/^\-(\s)*/, "-"),
            strWithoutMinus = formattedStr.replace(/^\-/, ""),
            isValid = strWithoutMinus.match(/\d/) && !strWithoutMinus.match(/\D/);
        if (bFormat) {
            return isValid ? formattedStr : null;
        } else {
            return !!isValid;
        }
    };

    /**
     * Validation method for numbers.  Returns true if the given string matches one of MSTR Web's input patterns (or
     * output pattern) for numbers.
     * If bFormat, the method returns a converted String if there is a match; null otherwise.
     * Else if bFormat is false, the method instead just returns true|false depending on whether there is a match.
     * Note that the formatted string (if requested) may end with a decimal separator.  If we strip out
     * a trailing decimal sep here, we end up with a usability issue:  Since numeric textboxes call
     * this method after keystrokes, the end-user may type a decimal slowly and be surprised to see
     * the decimal sep disappear if they pause after entering it.  This can be annoying, so for that
     * reason we don't strip out trailing decimal seps until we are submitting an answer to MSTRWeb sever.
     * @memberOf mstr.utils.LocaleParser
     */
    LP.isNumeric = function LP_isNumeric(/*String*/ s, /*Boolean?*/ bFormat) {
        if (s == null) return false;
        if (typeof(s) != 'string') s = String(s);
        s = mstr.utils.String.trim(s);
        // Cheat: instead of looking at MSTR Web patterns, support all patterns that are just digits
        // with maybe some thousand separators and at most one decimal sep. 
        // So to check if its a numeric, first created a formatted string as follows:
        // (1) remove all the thousand separators (if any), and
        // (2)remove any padding that follows the leading minus sign (if present), and
        // (3) allow a "%" suffix but remove any white space before it.
        // Then to check if its valid:
        // (1) copy the string and remove the minus prefix & "%" suffix (if any) from the copy,
        // (2) remove a single decimal separator from the copy,
        // (3) check if the copy has at least one digit char in it, and
        // (4) check if the copy has no non-digit chars in it.
        // Note: we don't check if the number of grouped digits after each thousands sep is correct (future enhancement).
        var formattedStr = mstr.utils.String.trim(s
                                    ).replace(new RegExp("\\" + mstr.Settings.Locale.THOUSANDSEP, "g"), ""
                                    ).replace(/^\-(\s)*/, "-"
                                    ).replace(/\s+\%$/, "%"),
            strWithoutMinusPercent = formattedStr.replace(/^\-/, ""
                                    ).replace(/\%$/, ""
                                    ).replace(new RegExp("\\" + mstr.Settings.Locale.DECIMALSEP), ""),
            isValid = strWithoutMinusPercent.match(/\d/) && !strWithoutMinusPercent.match(/\D/);
        if (bFormat) {
            return isValid ? formattedStr : null;
        } else {
            return !!isValid;
        }
    };
    
    /**
     * Validation method for big decimals.  Returns true if the given string matches one of MSTR Web's input patterns (or
     * output pattern) for numbers.  Also returns true for a string if the string is surrounded by a single # prefix & # suffix.
     * Padding adjecent to the pound signs is ignored.  The trimmed contents inside the #s are ignored for now.
     * (TO DO: Future enhancement: validate the big decimal pattern inside the #s.)
     * If bFormat, the method returns a converted String if there is a match; null otherwise.  (The formatted string
     * does includes the # prefix+suffix, if given; otherwise, the prefix+suffix is not included in the formatted string.)
     * Else if bFormat is false, the method instead just returns true|false depending on whether there is a match.
     * @memberOf mstr.utils.LocaleParser
     */
    LP.isBigDecimal = function LP_isBigDecimal(/*String*/ s, /*Boolean?*/ bFormat) {
        if (s == null) return false;
        
        // Does the trimmed string begin and end with pound signs? If so, removed them and padding adjacent to them.
        var matchPounds = s.match(/^\s*\#(.*)\#\s*$/);
        if (matchPounds) {
            // Found pounds signs; remove padding adjacent to pounds.
            s = matchPounds[1];
            // Pounds indicate a special big decimal pattern syntax. We don't have the
            // logic here to validate the pattern (future enhancement) but for now we do
            // so minimal checking:
            // (1) if the pattern is empty, it's invalid
            // (2) if the pattern contains any letters besides a single "e" (or "E"), it's invalid ("e" is an exponential).
            if (mstr.utils.String.isEmpty(s)
                || s.replace(/E/i, "").match(/[a-zA-Z]/)) {
                // Validation failed.
                return bFormat ? null : false;
            } else {
                // Validation successful.
                return bFormat ? "#" + mstr.utils.String.trim(s) + "#" : true;
            }
        } else {
            // No pound signs found, proceed with standard numeric validation.
            var numMatch = this.isNumeric(s, bFormat);
            if (bFormat) {
                // Return either a formatted String (if matched), or null.
                if (numMatch) {
                    return matchPounds ? 
                            "#" + numMatch + "#" : 
                            numMatch;
                } else {
                    return null;
                }
            } else {
                // Return a Boolean.
                return numMatch;
            }
        }
    };
    
    
    /**
     * Validation method for dates.  Returns true if the given string matches one of MSTR Web's input patterns (or
     * output pattern) for dates.
     * If bFormat, the method returns a converted String if there is a match; null otherwise.
     * Else if bFormat is false, the method instead just returns true|false depending on whether there is a match.
     * If the optional Boolean param "bContains" is true, then
     * a string which contains a valid date substring will be considered valid; otherwise
     * only a string which equals a valid date string will be considered valid.
     * @memberOf mstr.utils.LocaleParser
     */
    LP.isDate = function LP_isDate(/*String*/ s, /*Boolean?*/ bFormat, /*Boolean?*/ bContains) {
        // Try parsing the string according to our acceptable patterns.
        var dateInfo = this.parseDate(s, bContains);
        // If parsing succeeded, and the numbers are in the right range, validation successful.
        var isValid = !!(dateInfo && this.doesDateExist(dateInfo.month, dateInfo.day, dateInfo.year));
        if (bFormat) {
            if (isValid) {
                var formatted = this.formatDateInfo(dateInfo, mstr.Settings.Locale.DATEOUTPUTFORMAT);
                if (bContains) {
                    return {match: dateInfo.match, formatted: formatted};
                } else {
                    return formatted;
                }
            } else {
                return null;
            }

        } else {
            return !!isValid;
        }
    };
    
    /**
     * This method applies a format String to a dateInfo object to produce a formatted date String.
     * The given dateInfo object is assumed to have the following properties:  "month" (1-12),
     * "day" (Integer), "year" (four digit Integer).  The format String is typically the out format
     * specified by MSTRWeb server.  In theory, this method should handle any such format String,
     * but to optimize this implementation, we make certain assumptions about the format String, namely:
     * the string may contain: "MMMM", "MMM", "MM", "M", "dd", "d", "yyyy", "yy"; all other chars
     * are taken as literals.
     * @memberOf mstr.utils.LocaleParser
     */
    LP.formatDateInfo = function LP_formatDateInfo(/*Object*/ dateInfo, /*String*/ format) {
        if (!format) return '';
        var SL = mstr.Settings.Locale,
 			s = format.replace(/dd/g, this.formatInteger(dateInfo.day, 2)
                        ).replace(/d/g, Number(dateInfo.day)
                        ).replace(/yyyy/g, Number(dateInfo.year)
 						).replace(/yy/g, this.formatInteger(Number(dateInfo.year) % 100, 2)
					    ).replace(/MMM/g, "~~~~"
					    ).replace(/MM/g, "@@"
					    ).replace(/M/g, "^"
					    ).replace(/MMMM/g, SL.MONTHNAME_FULL[Number(dateInfo.month) - 1]
					    ).replace(/\~\~\~\~/g, SL.MONTHNAME_SHORT[Number(dateInfo.month) - 1]
					    ).replace(/\@\@/g, this.formatInteger(dateInfo.month, 2)
					    ).replace(/\^/g, Number(dateInfo.month)
                        );
        return s;
    };
    
    /**
     * This method converts a given integer into a string whose length is at least
     * equal to the given minimum length.  For example, if the integer 5 is passed
     * in with a given min length = 2, then this method returns "05".
     * @memberOf mstr.utils.LocaleParser
     */
    LP.formatInteger = function LP_fmtInt(/*Integer*/ num, /*Integer*/ minLen) {
        var s = String(num),
            missing = Math.max(minLen - s.length, 0);
        if (missing > 0) {
            var arr = [s];
            for (var i = 1; i <= missing; i++) {
                arr.push("0");
            }
            s = arr.reverse().join('');
        }
        return s;
    };
    
    /**
     * Parses a given date string to determine the month, day and year values
     * it represents.  If the optional Boolean param "bContains" is true, then
     * a string which contains a valid date substring will be considered valid; otherwise
     * only a string which equals a valid date string will be considered valid.
     * @memberOf mstr.utils.LocaleParser
     */
    LP.parseDate = function LP_parseDate(/*String*/ s, /*Boolean?*/ bContains) {
        if (s == null) return false;
        if (typeof(s) != 'string') s = String(s);
        s = mstr.utils.String.trim(s);
        
        // Check our cache.  The cache is a hashtable; hash key = the given string (trimmed);
        // hash value = the parse result object (if date found) or a null (if date not found).
        var cache = LP.CACHE.PARSEDATE[bContains ? "CONTAINS" : "EQUALS"],
            cachedResult = cache[s];
        if (cachedResult || (cachedResult === null)) return cachedResult;       
        // Cached result not found.  Must parse date and store parsing result in cache.
        var parseResult = null;

        // Build an array of all acceptable date formats.
        var SL = mstr.Settings.Locale,
            formats = [].concat(SL.DATEINPUTFORMATS);
        formats.unshift(SL.DATEOUTPUTFORMAT);

        // Now compare the given string to each acceptable format.
        for (var i = 0, len = formats.length; i < len; i++) {       
            // Convert the format to a regular expression for javascript comparison.    
            var reInfo = this._buildRegExp4DateFormat(formats[i]),
            // Does the given string match the regular expression?
                result = reInfo && s.match(bContains ? reInfo.reContains : reInfo.reEquals);
            if (result) {
                // Yes, matched. Fetch parts: month, day and year.
                parseResult = {
                    match: result[0],
                    year: reInfo.yearIndex && this.fourDigitYear(result[reInfo.yearIndex]),
                    day: reInfo.dayIndex && parseInt(Number(result[reInfo.dayIndex])),
                    month: reInfo.monthIndex && this.numericMonth(result[reInfo.monthIndex])
                };
                break;
            }
        } // end for loop walking the acceptable formats
        // Store parse result in cache for later re-use.
        cache[s] = parseResult;
        return parseResult;
    };

    /**
     * Validation method for times.  Returns true if the given string matches one of MSTR Web's input patterns (or
     * output pattern) for times.
     * If bFormat, the method returns a converted String if there is a match; null otherwise.
     * Else if bFormat is false, the method instead just returns true|false depending on whether there is a match.
     * If the optional Boolean param "bContains" is true, then
     * a string which contains a valid time substring will be considered valid; otherwise
     * only a string which equals a valid time string will be considered valid.
     * @memberOf mstr.utils.LocaleParser
     */
    LP.isTime = function LP_parseTime(/*String*/ s, /*Boolean?*/ bFormat, /*Boolean?*/ bContains) {
        // Try parsing the string according to our acceptable patterns.
        var timeInfo = this.parseTime(s, bContains);
        // If parsing succeeded, and the numbers are in the right range, validation successful.
        var isValid = !!(timeInfo && this.doesTimeExist(timeInfo.hour, timeInfo.min, timeInfo.sec));
        if (bFormat) {
            if (isValid) {
                var formatted = this.formatTimeInfo(timeInfo, mstr.Settings.Locale.TIMEOUTPUTFORMAT);
                if (bContains) {
                    return {match: timeInfo.match, formatted: formatted};
                } else {
                    return formatted;
                }
            } else {
                return null;
            }
        } else {
            return !!isValid;
        }
    };

    /**
     * Parses a given time string to determine the hour, minute and second values
     * it represents.  If the optional Boolean param "bContains" is true, then
     * a string which contains a valid time substring will be considered valid; otherwise
     * only a string which equals a valid time string will be considered valid.
     * @memberOf mstr.utils.LocaleParser
     */
    LP.parseTime = function LP_parseTime(/*String*/ s, /*Boolean?*/ bContains) {
        if (s == null) return false;
        if (typeof(s) != 'string') s = String(s);
        s = mstr.utils.String.trim(s);
        
        // Check our cache.  The cache is a hashtable; hash key = the given string (trimmed);
        // hash value = the parse result object (if date found) or a null (if date not found).
        var cache = LP.CACHE.PARSETIME[bContains ? "CONTAINS" : "EQUALS"],
            cachedResult = cache[s];
        if (cachedResult || (cachedResult === null)) return cachedResult;       
        // Cached result not found.  Must parse date and store parsing result in cache.
        var parseResult = null;

        // Build an array of all acceptable date formats.
        var SL = mstr.Settings.Locale,
            formats = [].concat(SL.TIMEINPUTFORMATS);
        formats.unshift(SL.TIMEOUTPUTFORMAT);

        // Now compare the given string to each acceptable format.
        for (var i = 0, len = formats.length; i < len; i++) {       
            // Convert the format to a regular expression for javascript comparison.    
            var reInfo = this._buildRegExp4TimeFormat(formats[i]),
            // Does the given string match the regular expression?
                result = reInfo && s.match(bContains ? reInfo.reContains : reInfo.reEquals);
            if (result) {
                // Yes, matched. Fetch pieces: hour, min, sec, ampm, zone, etc.
                var ampm = reInfo.ampmIndex && result[reInfo.ampmIndex];
                parseResult = {
                    match: result[0],
                    // By convention, return a capital Hour (0-23), not a little hour (1-12)
                    hour: reInfo.hourIndex && this.capitalHour(result[reInfo.hourIndex], ampm),
                    min: reInfo.minIndex && parseInt(Number(result[reInfo.minIndex])),
                    sec: reInfo.secIndex && parseInt(Number(result[reInfo.secIndex]))
                };
                break;
            }
        } // end for loop walking the acceptable formats
        // Store parse result in cache for later re-use.
        cache[s] = parseResult;
        return parseResult;
    };
        
    /**
     * This method applies a format String to a timeInfo object to produce a formatted time String.
     * The given timeInfo object is assumed to have the following properties:  "hour" (0-23),
     * "min" (0-59), "sec" (0-59, possibly missing).  The format String is typically the out format
     * specified by MSTRWeb server.  In theory, this method should handle any such format String,
     * but to optimize this implementation, we make certain assumptions about the format String, namely:
     * the string may contain: "HH", "H", "hh", "h", "mm", "m", "ss", "s", "a"; all other chars
     * are taken as literals.
     * @memberOf mstr.utils.LocaleParser
     */
    LP.formatTimeInfo = function LP_formatTimeInfo(/*Object*/ timeInfo, /*String*/ format) {
        if (!format) return '';
        var SL = mstr.Settings.Locale,
            twelveHour = !(Number(timeInfo.hour) % 12) ? 12 : (Number(timeInfo.hour) % 12),
            s = format.replace(/HH/g, this.formatInteger(timeInfo.hour, 2)
                        ).replace(/H/g, Number(timeInfo.hour)
                        ).replace(/hh/g, this.formatInteger(twelveHour, 2)
                        ).replace(/h/g, twelveHour
                        ).replace(/mm/g, this.formatInteger(Number(timeInfo.min) || 0, 2)
                        ).replace(/m/g, Number(timeInfo.min) || 0
                        ).replace(/ss/g, this.formatInteger(Number(timeInfo.sec) || 0, 2)
                        ).replace(/s/g, Number(timeInfo.sec) || 0
                        ).replace(/a/g, (Number(timeInfo.hour) < 12) ? SL.AM_NAME : SL.PM_NAME
                        );
        return s;
    };

    /**
     * Returns true if the given time falls within the valid number range of hours, minutes
     * and seconds.  Seconds are optional, so if omitted, the time can still be considered valid.
     * @memberOf mstr.utils.LocaleParser
     */
    LP.doesTimeExist = function LP_doesTimeExst(/*Integer*/ capitalHour, /*Integer*/ min, /*Integer?*/ sec) {
        var h = parseInt(capitalHour);
        if (h >= 0 && h <= 23) {
            var m = parseInt(min);
            if (m >= 0 && m <= 59) {
                if (!sec) return true;
                var s = parseInt(sec);
                if (s >= 0 && s <= 59) return true;
            }
        }
        return false;
    };
    
    /**
     * This method converts a given hour to a capital Hour.  "Capital Hour" means
     * an integer from 0-23.  The given hour can be either in capital Hour units
     * or in little hour units (1-12); but if given in little hour units, then the
     * ampm argument must be set to either "AM" or "PM" or their localized equivalents
     * (mstr.Settings.Locale.AM_NAME and .PM_NAME).  The ampm param is case-insensitive.
     * @memberOf mstr.utils.LocaleParser
     */
    LP.capitalHour = function LP_24Hr(/*String*/ hour, /*String?*/ ampm) {
        // Assume that if no am/pm info is given, the hour is a capital Hour,
        // meaning 0-23.  Otherwise, if am/pm is given, the hour is a little hour,
        // meaning, 1-12.
        hour = parseInt(Number(hour));
        var SL = mstr.Settings.Locale;
        if (ampm && (ampm.match(SL.AM_NAME) || ampm.match(/AM/i))) {
            // We have AM info, so hour is a little hour.
            return hour % 12;
        } else if (ampm && (ampm.match(SL.PM_NAME) || ampm.match(/PM/i))) {
            // We have PM info, so hour is a little hour.
            return 12 + (hour % 12);
        } else {
            // Missing or invalid ampm param.
            // We don't have am/pm info, so hour is a capital Hour.
            return hour;
        }
    };

    /**
     * Validation to check if a given string is either (1) a date only, (2) a time only, or
     * (3) a date + time.
     * The bFormat param determines whether this method returns a string (bFormat = true)
     * or boolean (bFormat = false or missing).
     * @memberOf mstr.utils.LocaleParser
     */
    LP.isDateAndOrTime = function LP_isDateAndOrTime(/*String*/ s, /*Boolean?*/ bFormat) {
        return this.isDateTime(s, bFormat)
                || this.isDate(s, bFormat)
                || this.isTime(s, bFormat);
    };
    
    /**
     * Validation method for timestamps. Succeeds if the given string contains a
     * valid date and a valid time.  Note that if other extraneous chars are also present
     * in the string along valid date + time substrings, validation still succeeds.
     * The bFormat param determines whether this method returns a string (bFormat = true)
     * or boolean (bFormat = false or missing).
     * If bFormat is true, the method will return the formatted
     * date and time, if found; null otherwise.  If bFormat is false,
     * the method will return true if the date and time are found; false otherwise.
     * If bFormat is true and a match is made, the formatted date + formatted time strings
     * are concat'd together by a space (in whichever order they were found); other extraneous
     * pieces of the string are discarded.
     * @memberOf mstr.utils.LocaleParser
     */
    LP.isDateTime = function LP_isDateTime(/*String*/ s, /*Boolean?*/ bFormat) {
        // Try to parse the date and time info from the given string.
        // If parsing succeeded, and the numbers are in the right range, validation successful.
        var dateTimeInfo = this.parseDateAndOrTime(s),
            dateInfo = dateTimeInfo && dateTimeInfo.date,
            timeInfo = dateTimeInfo && dateTimeInfo.time,
            isValid = !!dateInfo 
                        && !!timeInfo
                        && this.doesDateExist(dateInfo.month, dateInfo.day, dateInfo.year)
                        && this.doesTimeExist(timeInfo.hour, timeInfo.min, timeInfo.sec);
        // Are we returning a formatted String, or a Boolean?
        if (bFormat) {
            // Return formatted Strings concat'd together, if matched; otherwise return null.
            if (isValid) {
                // Format the strings and concat. Which came first, date or time?
                var formattedDate = this.formatDateInfo(dateInfo, mstr.Settings.Locale.DATEOUTPUTFORMAT),
                    formattedTime = this.formatTimeInfo(timeInfo, mstr.Settings.Locale.TIMEOUTPUTFORMAT),
                    dateIndex = s.indexOf(dateInfo.match),
                    timeIndex = s.indexOf(timeInfo.match);
                return (timeIndex < dateIndex) ?
                        formattedTime + ' ' + formattedDate :
                        formattedDate + ' ' + formattedTime;
                    
            } else {
                return null;
            }
        } else {
            // Return Boolean.
            return !!isValid;
        }
    };
        
    /**
     * Parses a given string for date and time substring information.  If both date and time are not
     * found, parser returns null; otherwise, parser returns a composite object with two properties, "date" and "time", each of
     * which correspond to the return values from calling the methods "parseDate" and "parseTime" respectively.
     * If only date info is found, the "time" property is set to null; if only time info is found, the "date" property
     * is set to null.
     * @memberOf mstr.utils.LocaleParser
     */
    LP.parseDateAndOrTime = function LP_parseDateAndOrTime(/*String*/ s) {
        // First look for a date substring.
        var dateInfo = this.parseDate(s, true);
        // Now look for a time substring; search in the string after removing the date (if found), so that we
        // don't mistake some of the date as part of a time.
        var sWithoutDate = mstr.utils.String.trim((dateInfo && dateInfo.match) ? 
                                s.replace(dateInfo.match, "") :
                                s),
            timeInfo = this.parseTime(sWithoutDate, false); // by passing false, we are asking for exactly match
            
        // If we didn't find a time nor a date, we are done.
        if (!dateInfo && !timeInfo) {
            return null;
        } else {
            // We found a date or a time, or both. Return the results of both parsings.
            return {
                date: dateInfo,
                time: timeInfo
                };
        }
    };

    /**
     * Validation method for string.  Returns true if the given argument is a string or number.
     * Empty string are considered valid strings here; that allows us to avoid raising
     * validation errors when users carelessly enter empty strings, such as when accidentally
     * using leading/trailing delimiters or double-delimiters in a delimited list.  However,
     * since validation won't catch such empty strings, we must make sure that other
     * code outside validation (such as the code that builds prompt answer XML) checks for emptys.
     * @memberOf mstr.utils.LocaleParser
     */
    LP.isString = function LP_isInteger(/*String*/ s, /*Boolean?*/ bFormat) {
        var type = (s != null) ? typeof(s) : null,
            isValid = (type == 'string');
        if (bFormat) {
            return isValid ? String(s) : null;
        } else {
            return !!isValid;
        }
    };

    /**
     * Compares a given non-null big decimal value with a minimum and maximum. If the value is below the minimum, returns -1.
     * If the value is above the maximum, returns 1.  Otherwise, returns 0.  Note that the value, min & max
     * are all assumed to use the current locale's separators (decimal sep & thousand sep) and therefore maybe
     * Strings rather than Numbers.
     * @memberOf mstr.utils.LocaleParser
     */
    LP.inBigDecimalRange = function LP_inBigDecimalRange(/*String|Number*/ val, /*String|Number?*/ min, /*String|Number?*/ max) {
        if ((max != null) && (this.compareBigDecimal(val, max) > 0)) return 1;
        if ((min != null) && (this.compareBigDecimal(val, min) < 0)) return -1;
        return 0;
    };

    /**
     * Compares two non-null localized big decimal strings. If val1 is lesser, returns a negative.
     * If val1 is greater, returns a postive. Otherwise returns zero.
     * @memberOf mstr.utils.LocaleParser
     */ 
    LP.compareBigDecimal = function LP_compareBigDec(/*String*/ val1, /*String*/ val2) {
        // The browser has its own indepedent decimal separator, separate from our mstr.Settings.Locale.
        // We must convert our Locale's decimal separator to the browser's before we can do a number
        // comparison in native javascript.
        var num1 = isNaN(val1) ? this.convertToBrowserBigDecimal(String(val1)) : val1,
            num2 = isNaN(val2) ? this.convertToBrowserBigDecimal(String(val2)) : val2;
        return num1 - num2;
    };
    
    /**
     * This method takes a string representing a big decimal in the locale of mstr.Settings.Locale, and attempts to
     * converts the string to an actual Number object instance in javascript.  Note that this method can only succeed
     * when the given string is either a numeric string, or a numeric string surround by pound signs.  Non-numeric
     * big decimal strings are not supported yet. (TO DO: enhance this method to support big decimal non-numeric formats.)
     * @memberOf mstr.utils.LocaleParser
     */
    LP.convertToBrowserBigDecimal = function LP_2BrswrBigDec(/*String*/ val) {
        if (val && val.replace) val = val.replace(/^\s*\#/, '').replace(/\#\s*$/, '');
        return this.convertToBrowserNumber(val);
    };
    
    
    /**
     * Compares a given non-null value with a minimum and maximum. If the value is below the minimum, returns -1.
     * If the value is above the maximum, returns 1.  Otherwise, returns 0.  Note that the value, min & max
     * are all assumed to use the current locale's separators (decimal sep & thousand sep) and therefore maybe
     * Strings rather than Numbers.
     * @memberOf mstr.utils.LocaleParser
     */
    LP.inNumericRange = function LP_inNumericRange(/*String|Number*/ val, /*String|Number?*/ min, /*String|Number?*/ max) {
        if ((max != null) && (this.compareNumber(val, max) > 0)) return 1;
        if ((min != null) && (this.compareNumber(val, min) < 0)) return -1;
        return 0;
    };

    /**
     * Compares two non-null localized number strings. If val1 is lesser, returns a negative.
     * If val1 is greater, returns a postive. Otherwise returns zero.
     * @memberOf mstr.utils.LocaleParser
     */ 
    LP.compareNumber = function LP_compareNumber(/*String*/ val1, /*String*/ val2) {
        // The browser has its own indepedent decimal separator, separate from our mstr.Settings.Locale.
        // We must convert our Locale's decimal separator to the browser's before we can do a number
        // comparison in native javascript.
        var num1 = isNaN(val1) ? this.convertToBrowserNumber(String(val1)) : val1,
            num2 = isNaN(val2) ? this.convertToBrowserNumber(String(val2)) : val2;
        return num1 - num2;
    };
    
    /**
     * This method takes a string representing a number in the locale of mstr.Settings.Locale, and converts the
     * string to an actual Number object instance in javascript.  To do this, we must first remove the number's
     * thousand separators (if any), and replace its decimal separator with that of the browser.  Assumes the
     * given string is already formatted correctly so that calling Number(...) on it with the correct decimal sep
     * will work; note that this means the string is not padded at the ends, nor after a minus sign.
     * @memberOf mstr.utils.LocaleParser
     */
    LP.convertToBrowserNumber = function (/*String*/ val) {
        return Number(val.replace(new RegExp("\\" + mstr.Settings.Locale.THOUSANDSEP, "g"), ""
                            ).replace(new RegExp("\\" + mstr.Settings.Locale.DECIMALSEP), this.BROWSER_DECIMALSEP));
    };
    
    /**
     * Compares a given value with a minimum and maximum. If the value is below the minimum, returns -1.
     * If the value is above the maximum, returns 1.  Otherwise, returns 0.  Note that the value, min & max
     * are all assumed to use the current locale's formats for date & time.
     * @memberOf mstr.utils.LocaleParser
     */
    LP.inDateTimeRange = function LP_inDateTimeRange(val, min, max) {
        // Parse the value's date and/or time.
        var valInfo = this.parseDateAndOrTime(val),
            valDateInfo = valInfo && valInfo.date,
            valTimeInfo = valInfo && valInfo.time;
        // If both date & time failed to parse, exit.
        if (!valDateInfo && !valTimeInfo) return 0;
        
        // Minimum comparison.
        if (min != null) {
            // Parse the minimum's date and/or time.
            var minInfo = this.parseDateAndOrTime(min);
            // Compare the minimum's date, if any.
            if (valDateInfo && minInfo.date) {
                var minDiff = this.compareDate(valDateInfo, minInfo.date);
                if (minDiff < 0) {
                    // The given date is less than the minimum.
                    return -1;
                } else if (minDiff == 0)  {
                    // The given date is the same as the minimum date.
                    // Compare the minimum's time, if any.
                    if (valTimeInfo && minInfo.time
                        && (this.compareTime(valTimeInfo, minInfo.time) < 0))
                    {
                        // The given date's time is less than the minimum.
                        return -1;
                    } // end if compareTime with minInfo
                } // end else if minDiff == 0
            } // end if valDateInfo && minInfo.date
        } // end if min

        // Maximum comparison.
        if (max != null) {
            // Parse the maximum's date and/or time.
            var maxInfo = this.parseDateAndOrTime(max);
            // Compare the maximum's date, if any.
            if (valDateInfo && maxInfo.date) {
                var maxDiff = this.compareDate(valDateInfo, maxInfo.date);
                if (maxDiff > 0) {
                    // The given date is greater than the maximum.
                    return 1;
                } else if (maxDiff == 0) {
                    // The given date is the same as the maximum date.
                    // Compare the maximum's time, if any.
                    if (valTimeInfo && maxInfo.time
                        && (this.compareTime(valTimeInfo, maxInfo.time) > 0))
                    {
                        // The given date's time is greater than the maximum.
                        return 1;
                    } // end if compareTime of max
                } // end else if maxDiff == 0
            } // end if valDateInfo && maxInfo.date
        } // end if max
        return 0;
    };

    /**
     * Compares two non-null localized date strings. If val1 is lesser, returns a negative.
     * If val1 is greater, returns a postive. Otherwise returns zero.
     * The caller may also pass in Objects, rather than Strings, for either value, where the
     * Object passed in the Object returned by calling LocaleParser.parseDate(..) with the
     * localized date string.
     * @memberOf mstr.utils.LocaleParser
     */ 
    LP.compareDate = function LP_compareDate(/*String|Object*/ val1, /*String|Object*/ val2) {
        if (val1 == val2) return 0;
        var date1 = typeof(val1) == 'string' ? this.parseDate(val1) : val1,
            date2 = typeof(val2) == 'string' ? this.parseDate(val2) : val2;
        if (!date1) return -1;
        if (!date2) return 1;
        return (date1.year - date2.year)
                    || (date1.month - date2.month)
                    || (date1.day - date2.day)
                    || 0;
    };
    
    /**
     * Compares two non-null localized time strings. If val1 is lesser, returns a negative.
     * If val1 is greater, returns a postive. Otherwise returns zero.
     * The caller may also pass in Objects, rather than Strings, for either value, where the
     * Object passed in the Object returned by calling LocaleParser.parseTime(..) with the
     * localized time string.
     * @memberOf mstr.utils.LocaleParser
     */ 
    LP.compareTime = function LP_compareTime(/*String*/ val1, /*String*/ val2) {
        if (val1 == val2) return 0;
        var time1 = typeof(val1) == 'string' ? this.parseTime(val1) : val1,
            time2 = typeof(val2) == 'string' ? this.parseTime(val2) : val2;
        if (!time1) return -1;
        if (!time2) return 1;
        return (time1.hour - time2.hour)
                    || (time1.minute - time2.minute)
                    || (time1.sec - time2.sec)
                    || 0;
    };

    /**
     * Compares two non-null localized datetime strings. If val1 is lesser, returns a negative.
     * If val1 is greater, returns a postive. Otherwise returns zero.
     * @memberOf mstr.utils.LocaleParser
     */ 
    LP.compareDateTime = function LP_compareDateTime(/*String*/ val1, /*String*/ val2) {
        return this.compareDate(val1, val2) || this.compareTime(val1, val2);
    };

    /**
     * Converts a given date format string from MSTRWeb server to a javascript
     * regular expression that will be used to match that string against a
     * user-given value.
     * This method makes many assumptions about the format strings:
     * 1) The string has chars "M", "d" and "y" (case-sensitive).
     * 2) The "d"s are grouped together consecutively, as are the "M"s and "y"s.
     * 3) There are either 1 or 2 "d"s, 1 to 4 "M"s, and either 2 or 4 "y"s.
     * @memberOf mstr.utils.LocaleParser
     */
    LP._buildRegExp4DateFormat = function LP_re4DateFmt(/*String*/ formatStr) {
    
        if (!formatStr) return null;
        // Lookup local cached result. 
        var reInfo = LP.REGEXPS.DATES[formatStr];
        if (!reInfo) {
            // Cache not found, build result and cache it.
            if (!LP.REGEXPS.MONTHNAME_FULL) {
                LP.REGEXPS.MONTHNAME_FULL = mstr.Settings.Locale.MONTHNAME_FULL.join("|");
                LP.REGEXPS.MONTHNAME_SHORT = mstr.Settings.Locale.MONTHNAME_SHORT.join("|");
            }
            // The result is an object with several properties...
            reInfo = LP.REGEXPS.DATES[formatStr] = {};
            // The "formatStr" prop has the original format string.
            reInfo.formatStr = formatStr;
            // The "re" prop has a regular expression derived from the format string.
            // This requires several string replacements:
            // 1) First, we need to escape any string chars which are not:
            // "M", "d", "y", or white space.
            // 2) Replace "dd" with 2 digits, to be captured as a whole.
            // 3) Replace "d" with 1 or 2 digits, to be captured as a whole.
            // Note that 2 & 3 must be done before the rest, because the digit placeholder
            // is "\d" in RE syntax, which might be confused with "d" for day in Date syntax.
            // 4) Replace 4 "y"s with 4 digits, to be captured as a whole.
            // 5) Replace 2 "y"s with 2 digits, to be captured as a whole.
            // 6) Replace 4 "M"s with a placeholder for a list of full month names
            // (this placeholder should avoid "M"s)
            // 7) Replace 3 "M"s with a placeholder for a list of short month names
            // (this placeholder should avoid "M"s and should avoid being confused
            // with the parts of the list of full month names).
            // 8) Replace 2 "M"s with 2 digits, to be captured as a whole.
            // 9) Replace "M" with 1 or 2 digits, to be captured as a whole.
            // 10) Replace the 4 "M"s placeholder with the actual list of full month names.
            // 11) Replace the 3 "M"s placeholder with the actual list of short month names.
            var reStr = reInfo.reStr = formatStr.replace(/([^M|d|y|\s])/g, "\\$1"
                            ).replace(/dd/g, "~~~~" // temporary place holder so the following "d" replacement wont touch "dd"s
                            ).replace(/d/g, "(\\d{1,2})"
                            ).replace(/\~\~\~\~/g, "(\\d\\d)"   // replace temporary placeholder for "dd"s
                            ).replace(/yyyy/g, "(\\d\\d\\d\\d)"
                            ).replace(/yy/g, "(\\d\\d)"
                            ).replace(/MMMM/g, "@@@@"
                            ).replace(/MMM/g, "@@@"
                            ).replace(/MM/g, "(\\d\\d)"
                            ).replace(/M/g, "(\\d{1,2})"
                            ).replace("@@@@","(" + LP.REGEXPS.MONTHNAME_FULL + ")"
                            ).replace("@@@", "(" + LP.REGEXPS.MONTHNAME_SHORT + ")");
            // Now instantiate the reg expr objects and cache them; one object for
            // an "equals" match, another for a "contains" match.
            reInfo.reEquals = new RegExp("^" + reStr + "$");
            reInfo.reContains = new RegExp(reStr);
            // When we apply this reg expr and a match is found, the
            // results of match() will yield the month, day and year
            // parts that matched the pattern.  But they'll be sorted in
            // the order they are found.  So we record their order in order
            // to identify them later when examining the results of a match.
            var indices = [
                {key: "monthIndex", index: formatStr.indexOf("M")},
                {key: "dayIndex", index: formatStr.indexOf("d")},
                {key: "yearIndex", index: formatStr.indexOf("y")}
                ];
            indices.sort(function(a, b) { return a.index - b.index });
            var counter = 1;
            for (var i = 0; i < 3; i++) {
                reInfo[indices[i].key] = indices[i].index > -1 ?
                                            counter++ : null;
            }   
        }
        return reInfo;
    };

    /**
     * Converts a given time format string from MSTRWeb server to a javascript
     * regular expression that will be used to match that string against a
     * user-given value.
     * This method makes many assumptions about the format strings:
     * 1) The string may have chars "H", "h", "m", "s", "a", "z" and "Z" (case-sensitive).
     * 2) These chars above are grouped together consecutively.
     * 3) Some chars are assumed to occur a max # of times (e.g., no more than 2 "H"s, "s"s, "m"s, etc).
     * 4) A single quote (') is used to enclose a set of literal chars.
     * 5) Two single quotes ('') are used to represent a single quote.
     * @memberOf mstr.utils.LocaleParser
     */
    LP._buildRegExp4TimeFormat = function LP_re4TimeFmt(/*String*/ formatStr) {
    
        if (!formatStr) return null;
        // Lookup local cached result. 
        var reInfo = LP.REGEXPS.TIMES[formatStr],
            SL = mstr.Settings.Locale;
        if (!reInfo) {
            // Cache not found, build result and cache it.
            if (!LP.REGEXPSTR_AMPM) {
                
                LP.REGEXPSTR_AMPM = [SL.AM_NAME, 
                                    SL.PM_NAME,
                                    String(SL.AM_NAME).toLowerCase(),
                                    String(SL.PM_NAME).toLowerCase()
                                    ].join("|");
            }
            // The result is an object with several properties...
            reInfo = LP.REGEXPS.TIMES[formatStr] = {};
            // The "formatStr" prop has the original format string.
            reInfo.formatStr = formatStr;
            // The "re" prop has a regular expression derived from the format string.
            // This requires several string replacements:
            // 1) First, we temporarily replace every double-single-quote with
            // a placeholder so it will be ignored in the next step.
            var reStr = formatStr.replace(/\'\'/g, '"');
            // 2) Next, we temporarily remove every literal that is enclosed
            // in single quotes, so it won't be modified by the following steps.
            // We'll reinsert these literals back in after those steps are done.
            var literals = reStr.match(/\'(.+?)\'/g);
            reStr.replace(/\'(.+?)\'/g, '*');
            // 3) we need to escape any string chars which are not:
            // "H", "h", "m", "s", "z", "Z", "a", or white space.
            // 4) Replace these special chars with correct placeholders.
            reStr = reStr.replace(/([^H|h|m|s|z|Z|a|\s])/g, "\\$1"
                            ).replace(/HH|hh/g, "(\\d\\d)"
                            ).replace(/H|h/g, "(\\d{1,2})"
                            ).replace(/mm/g, "(\\d\\d)"
                            ).replace(/m/g, "(\\d{1,2})"
                            ).replace(/ss/g, "(\\d\\d)"
                            ).replace(/s/g, "(\\d{1,2})"
                            ).replace(/a/gi, "(" + LP.REGEXPSTR_AMPM + ")"
                            ).replace(/z|Z/g, "(.+?)");
            // 5) Now we are ready to reinsert our literals back in.
            for (var i = 1, len = literals && literals.length || 0; i < len; i++) {
                reStr = reStr.replace(/\*/, literals[i]);
            }
            // 6) And we can undo our first step; replacing the double single quotes.
            reStr = reStr.replace(/\"/g, "'");
            
            // Now instantiate the reg expr object and cache it.
            // Now instantiate the reg expr objects and cache them; one object for
            // an "equals" match, another for a "contains" match.
            reInfo.reEquals = new RegExp("^" + reStr + "$");
            reInfo.reContains = new RegExp(reStr);
            // When we apply this reg expr and a match is found, the
            // results of match() will yield the month, day and year
            // parts that matched the pattern.  But they'll be sorted in
            // the order they are found.  So we record their order in order
            // to identify them later when examining the results of a match.
            var indices = [
                {key: "hourIndex", index: formatStr.search(/h|H/)},
                {key: "minIndex", index: formatStr.indexOf("m")},
                {key: "secIndex", index: formatStr.indexOf("s")},
                {key: "ampmIndex", index: formatStr.indexOf("a")},
                {key: "zoneIndex", index: formatStr.search(/z|Z/)}
                ];
            indices.sort(function(a, b) { return a.index - b.index });
            var counter = 1;
            for (var i = 0; i < 5; i++) {
                reInfo[indices[i].key] = indices[i].index > -1 ? 
                                            counter++ : null;
            }   
        }
        return reInfo;
    };

    /**
     * Returns true if the given date is an actual day on the calendar.
     * For example February 30th of any year would return false.
     * Warning: Duplicates old "Calendar.isDateValid" method.
     * @memberOf mstr.utils.LocaleParser
     */
    LP.doesDateExist = function LP_doesDateExist(/*Integer*/ month, /*Integer*/ day, /*Integer*/ year) {
        var m = parseInt(month);
        if (m < 0 || m > 12) return false;
        var d = parseInt(day);
        if (day < 0) return false;
        var date = new Date(year, month-1, 1);
        if (day < 0 || day> this.getDaysOfMonth(date)) {   
            return false;
        }
        return true;
    };

    /* Duplicates Calendar.isLeapYear. */
    LP.isLeapYear = function LP_isLeapYear (year){    
       if (((year % 4)==0) && ((year % 100)!=0) || ((year % 400)==0))
          return (true);
       else
          return (false);
    }      
    
    /* Duplicates Calendar.getDaysOfMonth */
    LP.getDaysOfMonth = function LP_getDaysOfMonth (date){
       month = date.getMonth() + 1;
       year = date.getFullYear();
       switch(month){
            case 1:
            case 3:
            case 5:
            case 7:
            case 8:
            case 10:
            case 12:
                return 31;
            break;
            case 4:
            case 6:
            case 9:
            case 11:
                return 30;
            break;
            case 2:
                  if (this.isLeapYear(year)) return 29;
                  else return 28;            
       }
    }
        
    /**
     * Given a year integer that is between 0 and 99 inclusive,
     * this method will return a corresponding 4-digit year.
     * @memberOf mstr.utils.LocaleParser
     */
    LP.fourDigitYear = function LP_4dYear(/*String|Integer*/ year) {
        year = parseInt(Number(year));
        if (!isNaN(year)) {
            var twoDigitStart = (mstr.Settings.Locale.TWODIGITYEARSTART  % 100) || 0;   
            if (year >= 0 && year <= twoDigitStart) 
                year = 2000 + year; 
            else if (year > twoDigitStart && year < 100)
                year = 1900 + year;
        }
        return year;
    };
    
    /**
     * Returns the 1-based (NOT 0-based) index of a given month name.  If not found, returns 0.
     * The name is searched for in the list of localized month names/abbrevs.
     * @memberOf mstr.utils.LocaleParser
     */
    LP.numericMonth = function LP_numMonth(/*String|Number*/ month) {
        // Were we given an integer (either as a String or a Number)? Then no work required.
        var monthInt = parseInt(Number(month));
        if (!isNaN(monthInt)) return monthInt;
        
        // We were given a non-integer string. Must be a month name (long or short).
        // Search for it in our list of month names.
        var len = month && month.length || 0,
            index = -1;
        if (len) {
            // If the name is 3 chars long or shorter, start by looking in the abbreviations.
            if (len <= 3) {
                index = mstr.utils.Arrays.find(mstr.Settings.Locale.MONTHNAME_SHORT, month);
            }
            // If still not found, look for it in the full month names list.
            if (index == -1) {
                index = mstr.utils.Arrays.find(mstr.Settings.Locale.MONTHNAME_FULL, month);
            }
        }
        return index + 1;
    };
    

    
    return LP;
 })();
  
/**
 * Arrays is a singleton which houses methods for manipulating javascript arrays.
 * @class
 */ 
 mstr.utils.Arrays = (function(){
 
    var A = {};
    
    A.isArray = function A_isArray(obj)
    {
        return !!(obj && (typeof(obj) == 'object') && (typeof(obj.length) == 'number'));
    };

    A.toArray = function A_toArray(obj)
    {
        if ((obj != null) && !this.isArray(obj)) 
        {
            return [obj];
        }
        else
        {
            return obj;
        };
    };
    
    /**
     * Constructs an array from the keys of a given hash table.
     * @param hash The hashtable.
     * @param bParseInt If true, the hashtable keys are converted to integers before being stored in the array.
     * @return An array, possibly empty.
     */
    A.fromHashKeys = function A_fromHashKeys(hash, bParseInt)
    {
        var arr = [];
        if (bParseInt)
        {
            for (var key in hash)
            {
                arr.push(parseInt(key));
            };
        }
        else
        {
            for (var key in hash)
            {
                arr.push(key);
            };
        };
        return arr;
    };

    /**
     * Constructs an array from the values of a given hash table.
     * @param hash The hashtable.
     * @return An array, possibly empty.
     */
    A.fromHashValues = function A_fromHashValues(hash)
    {
        var arr = [];
        for (var key in hash)
        {
            arr.push(hash[key]);
        };
        return arr;
    };
    
    A.toHash = function A_toHash(arr)
    {
        var h = {};
        for (var i = 0, len = this.len(arr); i < len; i++)
        {
            h[arr[i]] = true;
        };
        return h;
    };
    
    A.toHashProperties = function A_toHashProperties(arr, props)
    {
        var propsMax = (props) ? props.length : 0;
        var h = {};
        for (var i = 0, len = this.len(arr); i < len; i++)
        {
            var n = (i < propsMax) ? props[i] : i;
            h[n] = arr[i];
        }
        return h;
    };
    
    /**
     * Returns the length of a given array object if successful, zero otherwise.
     */
    A.len = function A_len(arr)
    {   
        return (arr && arr.length) || 0;
    };
    
    /**
     * Returns a copy of a given array, with the given items inserted at a given index.
     * @param arr The original array to be copied and grown.  If null, an empty array is assumed.
     * @param items An array of items to be inserted.
     * @param index The index where items are to be inserted. If undefined or negative, the items
     * are appended at the end of the array.
     * @return A new copy of the given array, with newly inserted items.
     */
    A.insert = function A_insert(arr, items, index)
    {
        if (!arr) arr = [];
        if ((typeof(index) == 'undefined') || (index >= arr.length) || (index < 0))
        {
            return [].concat(arr).concat(items);
        }
        else if (index == 0)
        {
            return [].concat(items).concat(arr);            
        }
        else if (index > 0)
        {
            return arr.slice(0, index).concat(items.concat(arr.slice(index, arr.length)));
        };  
    };
    
    /**
     * Removes items from a given array at given indices.
     * @param arr The array from which we will remove items.
     * @indices The array of indices which will be removed.
     * @sorted Optional flag; if true, indicates the indices array is already sorted in ascending order.
     * @return The given array, after the given indices have been removed.
     */
    A.remove = function A_remove(arr, indices, sorted)
    {
        if (arr && arr.length && indices && indices.length)
        {
            // Sort the given indices in ascending order, so we dont have to readjust
            // subsequent indices after each removal.
            var sortedIndices = sorted ? indices : mstr.$A.sortNumericArray(indices);

            // Generate an array of ranges from the indices.
            var ranges = mstr.utils.Math.convertIndicesToRanges(sortedIndices, true);
            
            // Remove the ranges from the array.
            return this.removeRanges(arr, ranges);
        };
        return arr;
    };

    A.removeRange = function A_removeRange(arr, start, count)
    {
        var len = (arr && arr.length) || 0;
        if (len && count)
        {
            // TQMS # 331736: Firefox3 crashes if you use Array.splice() on an array
            // with some undefined items.  It seems Mozilla might be working on a fix
            // but meanwhile, let's try to avoid using splice() here in Firefox.
            if (mstr.utils.ISFF) {
                // Build a new array from the given array's contents.
                start = Math.max(0, parseInt(start) || 0);
                var a = start ? arr.slice(0, start) : [],
                    start2 = start + count;
                if (start2 < len) a = a.concat(arr.slice(start2));
                return a;
            } else {
                // For other browsers, use Array.splice.
                arr.splice(Math.max(0, start), count);
            }
        };
        return arr;
    };
    
    /**
     * Removes ranges of indices from an array.
     * Assumes ranges are sorted in ascending order.
     * @param arr The array from which items will be removed.
     * @param ranges An array of range objects.  Each range object specifies the
     * first (range.min) and last (range.max) index of a set of array item
     * to be remove.d
     * @return The array, after the requested ranges have been removed.
     */
    A.removeRanges = function A_removeRanges(arr, ranges)
    {
        if (arr && arr.length && ranges && ranges.length)
        {
            // Walk the ranges in descending order and remove each range from the array.
            for (var i = ranges.length - 1; i > -1; i--)
            {
                var range = ranges[i];
                if (range && range.max >= 0)
                {
                    var min = Math.max(range.min, 0);
                    arr = A.safeRemove(arr, min, range.max - min + 1);
                };
            };
        };
        return arr;
    };
    
    A.safeRemove = function A_safeRemove(arr, start, deleteCount)
    {
        try 
        {
            arr.splice(start, deleteCount);
            return arr;
        } catch (ex) 
        {
            var a = [].concat(arr);
            a.splice(start, deleteCount);
            return a;
        }
    }
    
    A.safeSplice = function A_safeSplice(arr, start, deleteCount, values)
    {
        try 
        {
            arr.splice(start, deleteCount, values);
            return arr;
        } catch (ex) 
        {
            var a = [].concat(arr);
            a.splice(start, deleteCount, values);
            return a;
        }
    }
    
    /**
     * Searches for the given items in an array and removes whichever are found.
     * The item search is done by object-pointer comparison.
     */
    A.removeItems = function A_removeItems(arr, items)
    {
        if (arr && arr.length)
        {
            for (var i = 0, len = this.len(items); i < len; i++)
            {
                var j = this.find(arr, items[i]);
                if (j > -1)
                {
                    arr.splice(j, 1);
                };
            };
        };
        return arr;
    };
    
    /**
     * Searches for the given items in an array and removes whichever are found.
     * The item search is done by comparing the item value of the given formName.
     */
    A.removeItemsByForm = function A_removeItemsByForm(arr, items, formName)
    {
        var searchResult = this.findByFormMultiple(arr, items, formName, false);
        if (searchResult && searchResult.foundCount && searchResult.indices)
        {
            return this.remove(arr, searchResult.indices);
        };
        return arr;
    };
    
    /**
     * Searches a given array for a given item.  If item is an object, matching is done by reference.
     * @param arr The array to be searched.
     * @param item The item to be searched for.
     * @return The index (0-based) of item in arr, if found; -1 otherwise.
     */
    A.find = function A_find(arr, item)
    {
        if (arr.indexOf) return arr.indexOf(item);
        
        for (var i = 0, len = this.len(arr); i < len; i++)
        {
            if (arr[i] == item) return i;
        };
        return -1;
    };

    /**
     * Searches a given array for a given list of item.  If an item is an object, matching is done by reference.
     * @param arr The array to be searched.
     * @param items The items to be searched for.
     * @param includePlaceholders Optional flag; if true, the resulting array of indices
     * will include "-1" placeholder for items not found; otherwise, the resulting
     * array of indices will omit placeholders for those items.
     * @return An object with a boolean flag which = true if all items were found, and array of the indices for
     * each of the items searched for.
     */
    A.findMultiple = function A_findMultiple(arr, items, includePlaceholders)
    {
        var result = {
                        'foundCount' : 0,
                        'foundAll' : false,
                        'indices' : []
                    };

        if (arr && arr.length)
        {
            var len = this.len(items);
            if (len)
            {
                result.foundAll = true;
                for (var i = 0; i < len; i++)
                {
                    var j = this.find(arr, items[i]);
                    if (j > -1)
                    {
                        result.indices[includePlaceholders ? i : result.foundCount] = j;                        
                        result.foundCount++;
                    }
                    else
                    {
                        if (includePlaceholders) result.indices[i] = j;
                        result.foundAll = false;
                    };
                };
            };
        };
        return result;
    };

    /**
     * Searches a given array for an item with a given form value.
     * Uses a given "getter" function name to inspect each item.
     * @param arr The array to be searched.
     * @param getterName The name of the getter function used to read each item's forms.
     * @param formValue The value of the form to be searched for.
     * @param formName The name of the form to be searched by.
     * @return The index (0-based) of item in arr, if found; -1 otherwise.
     */
    A.findByGetter = function A_findByForm(arr, getterName, formValue, formName)
    {
        for (var i = 0, len = this.len(arr); i < len; i++)
        {
            if (arr[i] && arr[i][getterName](formName) == formValue) return i;
        };
        return -1;
    };

    /**
     * Searches a given array for an item with a given form value.
     * @param arr The array to be searched.
     * @param formValue The value of the form to be searched for.
     * @param formName The name of the form to be searched by.
     * @param ignoreCase indicates whether case should be ignore when searching.
     * @return The index (0-based) of item in arr, if found; -1 otherwise.
     */
    A.findByForm = function A_findByForm(arr, formValue, formName, ignoreCase)
    {
        for (var i = 0, len = this.len(arr); i < len; i++)
        {
            var fv = arr[i][formName];
            if (ignoreCase && typeof(fv) == 'string' &&  typeof(formValue) == 'string') {
                if (fv.toLowerCase() == formValue.toLowerCase()) return i;
            } else {
                if (fv == formValue) return i;
            }
        };
        return -1;
    };
    
    /**
     * Searches a given array for an item whose form value matches a 
     * given regular expression.
     * @param arr The array to be searched.
     * @param regExpr The regular expression for matching items.
     * @param formName The name of the form to be searched by.
     * @param start Optional index of the first item to be searched (default = 0).
     * If necessary, the search will wrap to the beginning of the array.
     * @return The index (0-based) of item in arr, if found; -1 otherwise.
     */
    A.matchByForm = function A_matchByForm(arr, regExp, formName, start)
    {
        var len = this.len(arr),
            start = (start && (start > -1)) ? start : 0,
            v;
                    
        for (var i = start; i < len; i++)
        {
            if (regExp.test(String(arr[i][formName]))) return i;
        };
        
        // We haven't found a match yet.  Did we not start at the beginning?
        if (start > 0)
        {
            // Wrap the search to the beginning.
            for (var j = 0; j < start; j++)
            {
                if (regExp.test(String(arr[j][formName]))) return j;
            };
        };
        return -1;        
    };

    /**
     * Searches a given array for a given list of items by matching a given form.
     * @param arr The array to be searched.
     * @param items The items to be searched for.
     * @param includePlaceholders Optional flag; if true, the resulting array of indices
     * will include "-1" placeholder for items not found; otherwise, the resulting
     * array of indices will omit placeholders for those items.
     * @return An object with a boolean flag which = true if all items were found, and array of the indices for
     * each of the items searched for.
     */
    A.findByFormMultiple = function A_findByFormMultiple(arr, items, formName, includePlaceholders)
    {
        // Initialize our result object.
        var result = {
                        'foundCount' : 0,
                        'foundAll' : false,
                        'indices' : []
                    };

        // Cache commonly used reference for performance.
        var indices = result.indices;

        // Do we have an array to search in?
        var lenArr = this.len(arr);
        if (lenArr)
        {
            // Map out the values of the valueForm into a hashtable for quick lookup.
            var lookup = mstr.utils.Hash.generateItemArrayLookup(arr, formName); 
            
            // Now loop thru the items we are searching for.  For each item...
            result.foundAll = true;
            var lenItems = this.len(items);
            for (var i = 0; i < lenItems; i++)
            {
                // What is the form value for this item?
                var formValue = items[i][formName];

                // What is the index of that value in our lookup?
                var index = lookup[formValue];
                
                // Did we find an index in the lookup?
                if (index != null)
                {
                    // Value was found in lookup.
                    indices[includePlaceholders ? i : result.foundCount] = index;
                    result.foundCount++;
                    
                    // Optimization: Quit searching if every value in lookup has been once.
                    if (result.foundCount >= lenArr) break;
                }
                else
                {
                    // Value was not found in lookup.
                    if (includePlaceholders) indices[i] = -1;
                    result.foundAll = false;
                };
            };            
        };
        
        return result;
    };
    
    /**
     * Sorts a given array of numbers in ascending order.  Array.sort() will sort a numeric array
     * by ASCII codes, so we use this method specifically for arrays with only numeric values.
     * Calls a "heap sort" algorithm, which is faster than native javascript Array.sort(). The
     * "Quick Sort" algorithm can be even faster in some cases, but uses massive recursion and can
     * generate stack overflows in javascript, so it is best avoided for general use.
     * @param arr The array of numbers to be sorted.
     */
    A.sortNumericArray = function A_sortNumericArray(arr)
    {
        if (!arr) return null;
        
        // Dont use arr.sort(this.intSorter), a heapSort is significantly faster.

        // Make a copy of the array to be sorted, to avoid modifying original array.
        var arrCopy = arr.concat();
        
        // Is it an odd-numbered array?  If so, remove last item...
        var len = this.len(arrCopy);
        var last = (len % 2) ? arrCopy.pop() : null;
        
        // Sort the even-numbered array with a heap algorithm.
        this.heapSort(arrCopy);
        
        // Was it an odd-numbered array? If so, reinsert the last item in the sorted index...
        if (last != null)
        {
            // First, do a binary search to find out where the item belongs.
            var searchResult = this.binarySearch(arrCopy, last, 0, len - 2);
            
            // Then insert the last item into that location.
            arrCopy = this.insert(arrCopy, [last], searchResult.index);
        };
        
        return arrCopy;
    };

    /**
     * Search for a given value in a numerical array sorted in ascending order.
     * @return An object with a found boolean property.  If found, the object also has an index property
     * with the found value's index in the array.  Otherwise, the index property points the index where
     * the value would belong if inserted into the sorted array.
     */
    A.binarySearch = function A_binarySearch(arr, value, left, right)
    {
        while (left <= right)
        {
            var mid = parseInt((left + right) / 2);
            var midValue = arr[mid];
            if (value > midValue)
            {
                left = mid + 1;
            }
            else if (value < midValue)
            {
                right = mid - 1;
            }
            else
            {
                return {'found' : true, 'index' : mid};
            };
        };
        return {'found' : false, 'index' : left};
    };
    
    /**
     * Implements the "Heap Sort" algorithm in javascript for sorting an array of numbers.
     */
    A.heapSort = function A_heapSort(arr)
    {
        var len = this.len(arr);
       
        for (var i = parseInt(len / 2 - 1); i >= 0; i--) 
        {
            this._siftDown(arr, i, len);
        };
        
        var temp;
        for (var i = len - 1; i >= 1; i--) 
        {
            temp = arr[0];
            arr[0] = arr[i];
            arr[i] = temp;
            
            this._siftDown(arr, 0, i - 1);
        };
        
        return arr;
    };

    /**
     * Helper function, called by the heapSort method, to implement the "Heap Sort" algorithm.
     */
    A._siftDown =  function A_shiftDown(arr, root, bottom) 
    {
        var done = false, maxChild, temp;

        while(!done && (root*2 <= bottom)) 
        {
            if (root*2 == bottom)
                maxChild = root * 2;
            else if (arr[root * 2] > arr[root * 2 + 1])
                maxChild = root * 2;
            else
                maxChild = root * 2 + 1;
            
            if(arr[root] < arr[maxChild]) 
            {
                temp = arr[root];
                arr[root] = arr[maxChild];
                arr[maxChild] = temp;
                root = maxChild;
            } 
            else 
            {
                done = true;
            };
        };
    };
    
    /**
     * This helper function is used inside Array.sort() calls to correctly sort an array of integers.
     * Without any sort function, Array.sort() will sort a numeric array by ASCII codes, therefore
     * 10 would be considered less than 2.
     * @param x The first of two numbers to be compared.
     * @param y The second of two numbers to be compared.
     * @return Postive number if x > y; negative number if x < y; 0 if x = y.
     */
    A.intSorter = function A_intSorter(x, y)
    {
        return x - y;
    };

    /**
     * This helper function is used inside Array.sort() calls to correctly sort an array of ranges.
     * @param range1 The first of two ranges to be compared.
     * @param range2 The second of two ranges to be compared.
     * @return Postive number if range1.min > range2.min; negative number if range1.min < range2.min; 0 if range1.min = range2.min.
     */
    A.rangeSorter = function A_rangeSorter(range1, range2)
    {
        return range1.min - range2.min;
    };

    /**
     * This is a wrapper for the native Array.join() method, but it checks for
     * a null array, empty array, and null delimiter.  
     * Note the result is always a string, possibly empty.
     */
    A.smartJoin = function(/*Array*/ arr, /*String*/ delim) {
        var len = arr && arr.length || 0;
        if (!len) return '';
        if (delim == null) delim = '';
        return (arr.join && arr.join(delim)) || '';
    };

    /**
     * This method walks the items of a given array, calling a given function for
     * each item.  The function is passed the array item as its single argument.  If
     * the function returns true, then the item is appended to the result array.
     * Note that the original array is not modified.
     * Note that the result is always an array, possibly empty.
     */
    A.filter = function(/*Array*/ arr, /*Object*/ obj, /*String*/ methodName) {
        var result = [],
            len = arr && arr.length || 0;
        if (len && obj && methodName) {
            for (var i = 0; i < len; i++) {
                if (obj[methodName](arr[i]))
                    result.push(arr[i]);
            }
        }
        return result;
    };
    
    /**
     * This method removes all instances of a given value in a given array.  Note that the
     * exactly-equals operator (===) is used for comparison, because if we use the standard
     * equals operator (==), then the number 0 and the empty string "" are considered a match.
     */
    A.removeInstances = function (/*Array*/ arr, /*String|Number*/ val) {
        var len = (arr && arr.length) || 0;
        for (var i = len-1; i > -1; i--) {
            if (arr[i] === val) {
                arr.splice(i, 1);
            }
        }
        return arr;
    };
    
    return A;
    
 })();
 
/* Shortcut for accessing Arrays utility. */

 mstr.$A = mstr.utils.Arrays;

/**
 * Hash is a singleton which houses methods for manipulating javascript hashtables.
 */ 
 mstr.utils.Hash = (function(){
 
     /**
      * @constructor
      */
    var H = {};

    /**
     * Counts the number of non-null keys in a hash.
     */
    H.len = function H_len(hash)
    {
        var count = 0;
        if (hash)
        {
            for (var key in hash)
            {
                if (key != null) count++;
            };
        };
        return count;
    };
    
    H.lenGreaterThan = function H_lenGreaterThan(obj, x)
    {
        if (obj)
        {
            var len = 0;
            for (var key in obj)
            {
                len++;
                if (len > x) return true;
            };
        };
        return false;
    };

    H.isEmpty = function H_isEmpty(obj)
    {
        if (obj)
        {
            for (var key in obj)
            {
                return false;
            };
        };
        return true;
    };
    
    /**
     * Applies a set of default values to a given data hashtable if that hashtable
     * has undefined properties.
     * @param vs The hashtable of data values.
     * @param ds The set of default data values.
     * @return The given hashtable of data values with the requested defaults (if any) appended.
     */
    H.applydefault = function H_applydefault(hash, d)
    {
        hash = hash || {};

        for (var k in d)
        {
            if (hash[k] == null)        
            {
                hash[k] = mstr.utils.Hash.deepClone(d[k]);
            }
        }     
        return hash;
    };
    
    H.overwrite = function H_overwrite(hash, keys)
    {
        var changed;
        if (hash)
        {
            for (var k in keys)
            {
                if (hash[k] != keys[k])
                {
                    if (!changed) changed = {}; 
                    changed[k] = keys[k];
                    hash[k] = keys[k];
                }
            }
        }
        return changed;
    };

    H.remove = function H_remove(hash, keys)
    {
        var changed;
        if (hash)
        {
            for (var k in keys)
            {
                if (hash[k] != null)
                {
                    if (!changed) changed = {}; 
                    changed[k] = hash[k];
                    delete hash[k];
                };
            };
        };
        return changed;
    };
    
    /**
     * Removes all the property-value pairs from this object, if any.
     * @return Hash of values that were removed, keyed by property name.
     */
    H.clear = function H_clear(hash)
    {
         var changed;
         if (hash)
         {
             for (var k in hash)
             {
                 if (hash[k] != null)
                 {
                     if (!changed) changed = {}; 
                     changed[k] = hash[k];
                     delete hash[k];
                 }
             }
         }
         return changed;
    };

    H.clone = function H_clone(obj)
    {
        if (!obj) return undefined;

        var c = {};
        for (var n in obj)
        {
            c[n] = obj[n];
        }
        return c;
    };
    
    H.deepClone = function H_deepClone(item)
    {
        if (!item) return item;
        
        if (mstr.$A.isArray(item))
        {
            var cnt = item.length;
            var a = new Array(cnt);
            for (var i = 0; i < cnt; i++)
            {
                a[i] = mstr.utils.Hash.deepClone(item[i]);
            }
            return a;
        } 
        
        if (typeof(item) == 'object')
        {
            var o = {};
            for (var n in item)
            {
                o[n] = mstr.utils.Hash.deepClone(item[n]);
            }
            return o;
        } 
        
            return item;
        
    };
    
    H.firstKey = function H_firstKey(obj)
    {
        if (obj)
        {
            for (var key in obj)
            {
                if (key != null) return key;
            };
        };
    };
    
    H.firstItem = function H_firstItem(obj)
    {
        if (obj)
        {
            for (var key in obj)
            {
                if (key != null && obj[key] != null) return obj[key];
            };
        };
    };
        
    H.equals = function H_equals(obj1, obj2)
    {
        if (obj1 == obj2) return true;
        if (!obj1 || !obj2) return false;
        for (var key in obj1)
        {
            if (obj1[key] != obj2[key])
            {
                return false;
            };
        };
        return this.len(obj1) == this.len(obj2);
    };
    
    /**
     * Generates a hashtable, keyed by the integers from a given array of ranges.
     * @param ranges An array of range objects, each with min & max integers.
     * @return A hashtable whose keys are the integers from the given ranges.
     */
    H.fromRanges = function H_fromRanges(ranges)
    {
        var hash = {};
        
        // Walk the given array of ranges, if any.
        var len = mstr.utils.Arrays.len(ranges);
        for (var i = 0; i < len; i++)
        {
            // For each range, loop thru the integers in the range...
            var min = ranges[i].min;
            var max = ranges[i].max;
            for (var j = min; j <= max; j++)
            {
                // Store a dummy hash value using the integer as the key.
                hash[j] = true;
            };
        };
        return hash;
    };
    
    
    /**
    * Takes an object whose keys are numeric values and returns the largest key
    */
    H.maxNumber = function H_maxNumber(hash){
        if (!hash) return null;
        var max = null;
         
        for (var key in hash)
        {
          if (Number(key) > max) max = Number(key);
        };
         
        return max;
    
    }
    
    
    /**
    * Takes an object whose keys are numeric values and returns the smallest key
    */
    H.minNumber = function H_minNumber(hash){
        if (!hash) return null;
        var min = null;
         
        for (var key in hash)
        {
          if (min==null) {
            min = key;
          }  
          if (Number(key) <= min) min = Number(key);
        };
         
        return min;
    
    }
    
    /**
     * Generates a hashtable for quick lookup of items in a given array.
     * The hashtable is keyed by the values of a given form of the items.
     * The hashtable value is a pointer to the index of the item in the array.
     */
    H.generateItemArrayLookup = function H_genItemArrayLookup(arr, key)
    {
        var lookup = {}; 
        for (var i = 0, len = mstr.$A.len(arr); i < len; i++)
        {
            lookup[arr[i][key]] = i;
        };
        return lookup;
    };
    
    return H;
    
 })();  
 
/* Shortcut for accessing Hash utility. */

 mstr.$H = mstr.utils.Hash;
 
 /**
  * manipulation of inline/external styles of an HTML Element
  * 
  */
 mstr.utils.CSS = (function(){
     
        var CSS = {};
        
        /**
         * return specified style, like 'height', etc
         * @param el - html element
         * @param prop - css property name 
         */
        CSS.getStyleValue = function CSS_getStyleValue(elem, prop)
        {
            if (elem.currentStyle){ //IE
                prop = prop == 'float' ? 'styleFloat' : prop;
                value = elem.currentStyle[prop];
            }
            else if (document.defaultView && document.defaultView.getComputedStyle){ //FF
                prop = prop == 'float' ? 'cssFloat' : prop; 
                var styles = document.defaultView.getComputedStyle(elem, null); 
                value = styles ? styles[prop] : null;
            }
            
            return value;           
        };
        
        CSS.setStyleValue = function CSS_setStyleValue(elem, prop, value)
        {
            if (mstr.utils.$A.isArray(prop) && mstr.utils.$A.isArray(value)) {
                for (var i in prop) elem.style[prop[i]] = value[i];
            }
            else {
                elem.style[prop] = value;
            }
        };
        
        return CSS;     
 })();


 mstr.utils.Color = ( function() {

 	var C = {};

 	C.hexChars = '0123456789ABCDEF';

 	/**
 	 * Converts a decimal value to a hexidecimal value.
 	 * @param {Integer} n The decimal value.
 	 * @type String
 	 * @return The hexidecimal value (without the hash).
 	 * @refactoring This method is only called in one place (rgb2hex) so we should move it into that method.
 	 */
 	C.toHex = function C_toHex(n) {
 		n = n || 0;
 		n = Math.max(Math.min(isNaN(n) ? 0 : parseInt(n, 10), 255), 0);

 		return mstr.utils.Color.hexChars.charAt((n - n % 16) / 16)
 				+ mstr.utils.Color.hexChars.charAt(n % 16);
 	};

 	C.toDec = function C_toDec(h) {
 		return mstr.utils.Color.hexChars.indexOf(h.toUpperCase());
 	};

 	/**
 	 * Converts Mozilla's color string 'rgb(0, 120, 255)' to an Integer array of red, green and blue. 
 	 * @param {String} color The Mozilla color string to convert.
 	 * @type Integer[]
 	 * @return An Integer array of red, green and blue values.
 	 */
 	C.rgbStr2rgb = function C_rgbStr2rgb(color) {
 		var rgb = [ 0, 0, 0 ];
 		color = color.replace(/ /g, ''); //get rid of the possible blank space
 		var i = color.indexOf('rgb');
 		if (i >= 0) {
 			color = color.substring(i + 4, color.length - 1);
 			rgb = color.split(',');
 		}
 		return rgb;
 	}

 	/**
 	 * Converts an RGB color string value like 'rgb(0, 120, 255)' to a hexidecimal color string value.
 	 * @param {String} color The RBG color value.
 	 * @type String
 	 * @return The hexidecimal color value.
 	 */
 	C.rgbStr2hex = function C_rgbStr2hex(color) {
 		//only when color is in rgb(r,g,b) format
 		if (color.indexOf('rgb') >= 0) {
 			var rgb = mstr.utils.Color.rgbStr2rgb(color);
 			return "#" + mstr.utils.Color.rgb2hex(rgb[0], rgb[1], rgb[2]);
 		}
 		return color;
 	}

 	C.rgb2hex = function C_rgb2hex(r, g, b) {
 		return mstr.utils.Color.toHex(r) + mstr.utils.Color.toHex(g)
 				+ mstr.utils.Color.toHex(b);
 	};

 	C.rgb2hsv = function C_rgb2hsv(r, g, b) {
 		var rgb = [ r, g, b ];
 		rgb.sort( function(a, b) {
 			return a - b
 		});

 		var h, s, v = 0;
 		var min = rgb[0];
 		var max = rgb[2];

 		v = max / 255.0;
 		s = (max != 0) ? (parseFloat(max) - parseFloat(min)) / parseFloat(max)
 				: 0;

 		if (s == 0) {
 			h = 0;
 		} else {
 			var d = max - min;
 			var red = (max - r) / d;
 			var green = (max - g) / d;
 			var blue = (max - b) / d;

 			if (r == max)
 				h = blue - green;
 			else if (g == max)
 				h = 2.0 + red - blue;
 			else
 				h = 4.0 + green - red;

 			h = h / 6.0;
 			if (h < 0)
 				h = h + 1.0;
 		}

 		return [ Math.round(h * 360), Math.round(s * 100), Math.round(v * 100) ];
 	};

 	C.hex2rgb = function C_hex2rgb(s) {
 		var color = (s.charAt(0) == '#') ? s.substr(1) : s;
 		var rgb = [ 0, 0, 0 ];
 		rgb[0] = (mstr.utils.Color.toDec(color.substr(0, 1)) * 16)
 				+ (mstr.utils.Color.toDec(color.substr(1, 1)));
 		rgb[1] = (mstr.utils.Color.toDec(color.substr(2, 1)) * 16)
 				+ (mstr.utils.Color.toDec(color.substr(3, 1)));
 		rgb[2] = (mstr.utils.Color.toDec(color.substr(4, 1)) * 16)
 				+ (mstr.utils.Color.toDec(color.substr(5, 1)));

 		return rgb;
 	};

 	C.hex2hsv = function C_hex2hsv(s) {
 		var rgb = mstr.utils.Color.hex2rgb(s);
 		return mstr.utils.Color.rgb2hsv(rgb);
 	};

 	C.hsv2rgb = function C_hsv2rgb(h, s, v) {
 		h = h / 360;
 		s = s / 100;
 		v = v / 100;

 		var r, g, b;
 		if (s == 0) {
 			r = v * 255;
 			g = v * 255;
 			b = v * 255;
 		} else {
 			var th = h * 6;
 			if (th == 6)
 				th = 0;

 			var i = Math.floor(th);
 			var p = v * (1 - s);
 			var q = v * (1 - s * (th - i));
 			var t = v * (1 - s * (1 - (th - i)));

 			var tr, tg, tb;

 			switch (i) {
 			case 0:
 				tr = v;
 				tg = t;
 				tb = p;
 				break;

 			case 1:
 				tr = q;
 				tg = v;
 				tb = p;
 				break;

 			case 2:
 				tr = p;
 				tg = v;
 				tb = t;
 				break;

 			case 3:
 				tr = p;
 				tg = q;
 				tb = v;
 				break;

 			case 4:
 				tr = t;
 				tg = p;
 				tb = v;
 				break;

 			default:
 				tr = v;
 				tg = p;
 				tb = q;
 				break;
 			}

 			r = tr * 255
 			g = tg * 255
 			b = tb * 255
 		}
 		return [ Math.round(r), Math.round(g), Math.round(b) ];

 	};

 	C.hsv2hex = function C_hsv2hex(h, s, v) {
 		var rgb = mstr.utils.Color.hsv2rgb(h, s, v);
 		return mstr.utils.Color.rgb2hex(rgb[0], rgb[1], rgb[2]);
 	};

 	C.getContrastingColor = function C_getContrastingColor(hex, colors) {
 		var rgb = mstr.utils.Color.hex2rgb(hex);
 		return (((rgb[0] * 299) + (rgb[1] * 587) + (rgb[2] * 114)) / 1000 < 125) ? colors[0]
 				: colors[1];
 	};

 	/**
 	 * Calculates the light color component for 3d borders.
 	 * @param {Integer[]} rgbColor An array of rgb component colors.
 	 * @param Integer luminosityColor The luminosity value for the supplied color.
 	 * @type String
 	 * @return The hexidecimal value for the light color component.
 	 * @refactoring This method is only called twice and it's always in conjunction with handed into {@link calculateLuminosity} and {@link calculateDarkColor}.  We should refactor 
 	 *      all three methods into one and move it to mstr.utils.Color.
 	 */
 	C.get3DBorderColor = function C_get3DBorderColor(bgRGB, stroke) {
 		var lum = parseInt(bgRGB[0]) * 0.3 + parseInt(bgRGB[1]) * 0.59
 				+ parseInt(bgRGB[2]) * 0.11;

 		var r = parseInt(bgRGB[0]);
 		var g = parseInt(bgRGB[1]);
 		var b = parseInt(bgRGB[2]);

 		switch (stroke) {
 		case 'light': //light border color
 			if (lum > 150) {
 				r = r * 0.9;
 				g = g * 0.9;
 				b = b * 0.9;
 			} else {
 				r = r * 0.6 + 102;
 				g = g * 0.6 + 102;
 				b = b * 0.6 + 102;
 			}
 			break;
 		case 'dark': //dark border color
 			if (lum > 10) {
 				r = r * 0.4;
 				g = g * 0.4;
 				b = b * 0.4;
 			} else {
 				r = r * 0.8 + 30;
 				g = g * 0.8 + 30;
 				b = b * 0.8 + 30;
 			}
 		}

 		return "#"
 				+ mstr.utils.Color.rgb2hex(Math.round(r), Math.round(g), Math
 						.round(b));
 	}

 	return C;

 })();


 mstr.utils.Controls = (function(){
 
    var C = {};
    
    C.getSelectionRange = function C_getSelectionRange(field)
    {
        var result = { start: -1, end: -1 };
        if ("selectionStart" in field)
        {
            result = {start: field.selectionStart, end: field.selectionEnd};
        } 
        else if (document.selection)
        {
            var range = document.selection.createRange();
            if (range.parentElement() == field)
            {
                var s = range.duplicate();
                s.moveEnd("textedit", 1);
                var e = range.duplicate();
                e.moveStart("textedit", -1);
                result = { start: field.value.length - s.text.length, end: e.text.length };
            }
        }
        return result;
    };
    
    C.setSelectionRange = function C_setSelectionRange(field, start, end)
    {
        if (end === undefined) end = start;
        
        if ("selectionStart" in field)
        {
            try {
                field.setSelectionRange(start, end);
            } catch (e) {}
            field.focus(); // to make behaviour consistent with IE
        }
        else if(document.selection)
        {
            var range = field.createTextRange();
            range.collapse(true);
            range.moveStart("character", start);
            range.moveEnd("character", end - start);
            try{
            	range.select();
            }catch(err){
            	//Do nothing, Please refer to http://support.microsoft.com/kb/286126 for more information regarding 
            	//what could be the causes of range.select() to bubble an error.
            }
        }
    };
    
    return C;
    
 })();
 
 
 /**
  * ListViewHelper contains methods commonly used ListModels.
  */ 
 mstr.utils.ListModelHelper = (function(){
 
    var LMH = {};
    
    /**
     * Determines if a given list item can be added to the selected ListModel.
     * This method does not take into account item-independent settings, such as max.
     */
    LMH.canAdd = function LMH_canAdd(model, item)
    {
        // TO DO (med/low): how do we make this method extendable by customers?
        
        if (!item) return false;

        // Do we have a form to use for add validation?
        var n = model.props['allowedItemFormName'];
        if (n != null)
        {
            // Do we have a property enumerating the allowed form values?
            if (model.props['allowedItemFormValues'])
            {
                // Yes, we do have the allowed values, so let's use them.
                // Do we have a hashtable of addable value for the form?
                if (!model.props['allowedItemFormValuesHash'])
                {
                    // Not initialized yet; initialize it now.
                    model.props['allowedItemFormValuesHash'] =
                            mstr.$A.toHash(model.props['allowedItemFormValues']
                                            && model.props['allowedItemFormValues'].split(',')
                            );
                };
                return !!model.props['allowedItemFormValuesHash'][item[n]];
            }
            else if (model.props['prohibitedItemFormValues'])
            {
                // No allowed values listed.  But we do have a list of the prohibited form values?           
                // Do we have a hashtable of nonaddable values for the form?
                if (!model.props['prohibitedItemFormValuesHash'])
                {
                    // Not initialized yet; initialize it now.
                    model.props['prohibitedItemFormValuesHash'] =
                            mstr.$A.toHash(model.props['prohibitedItemFormValues']
                                            && model.props['prohibitedItemFormValues'].split(',')
                            );
                };
                return !model.props['prohibitedItemFormValuesHash'][item[n]]; 
            };           
        };
        
        // No validation defined for this cart, so assume all items are addable.                
        return true;
    };    
    
    /**
     * Determines which items (from a given hash of keys) can be added to the given ListModel.
     * This method returns null if none of the items can be added.
     */
    LMH.verifySelectionKeys = function LMH_verifySelectionKeys(model, keys)
    {
        var items = model.getItems();

        for (var i in keys) 
        {
            if (!mstr.utils.ListModelHelper.canAdd(model, items[i])) {
                delete keys[i];
            };
        };
        
        return (mstr.utils.Hash.isEmpty(keys)) ? null : keys;
    };
    
    
    return LMH;
 })();
    

 /**
  * ListViewHelper contains methods commonly used by views on ListModels, such as
  * ListView and ListPulldownView.
  */ 
 mstr.utils.ListViewHelper = (function(){
 
    var LVH = {};

    /**
     * Retrieves a reference to the item renderer object for a given view.
     * The item renderer is a javascript object responsible of rendering data items in
     * the view's GUI.  For example, a ListView uses an item renderer to render list items;
     * a TreeView uses an item renderer to render tree node items; 
     * a Pulldown uses an item renderer to render the selected items from a ListModel or TreeModel.
     * The itemRenderer methods assumed by the view are view-specific; so the TreeModel will expect
     * different methods than the ListModel.
     * The renderer is specified by class name in the view's itemRendererClass property.  
     * If this property is undefined, the view uses the itemRendererClass from its current
     * layout, if any.
     * @param view The view whose item renderer object we wish to retrieve.
     * @return A javascript renderer object, if successful; null, otherwise.
     * @refactoring Replace the eval statement with "rc = window[rc]".
     */
    LVH.getItemRenderer = function LVH_getIR(view)
    {
        // First, check properties for an item renderer.
        var rc = view.props['itemRendererClass'];
        if (rc)
        {
            // Do we have an object, or the FQCN of an object?
            if (typeof(rc) == 'object')
            {
                // We have an item renderer object, so use it.
                return rc;
            }
            else if (typeof(rc) == 'string')
            {
                // We have an FQCN string, resolve it into an object.
                try { eval('rc = ' + rc); }
                catch(localerr){};
                view.props['itemRendererClass'] = rc;
                
                // If we now have a valid object, use it.
                if (rc) return rc;
            };
        };

        // No item renderer obtained from properties, so try 
        // looking in the layout.        
        var hInfo = view._layoutHandlers 
                    && view._layoutHandlers.info
                    && view._layoutHandlers.info['itemRendererClass'];
        if (!hInfo) return;
        
        // Has the layout's itemRenderer FQCN already been resolved into an object?
        if (!hInfo.obj)
        {
            // Not yet.  Resolve it now.
            var fqcn = hInfo && hInfo.str;
            if (fqcn)
            {
                try { eval('hInfo.obj = ' + fqcn); }
                catch(localerr){};
                hInfo.str = null;                
            };
        };
        return hInfo.obj;
    };

    /**
     * Determines the id of the "Browse" command, if any, that we can perform on a given list item
     * of a given view.  Typically used by views to render an item as browseable (e.g., hyperlinked).
     * This method will first consult the allowBrowseXXX flags of the view to determine
     * what type of browsing is generally allowed by the view.  Then it will consult the view's model
     * to determine if the model can perform the allowed browsing on a specific list item.
     * @param view The view which presents the given list item.
     * @param item The list item to be inspected.
     * @return One of the enumerated Command Ids: BrowseFolder, BrowseAttribute, or DataExplore.
     */
    LVH.getItemBrowseCommand = function LVH_getItemBrowseCommand(view, item)
    {
        if (!item) return null;
        
        var m = view.props.model;
        if (!m) return null;

        // First check if the given item is a folder/project...        
        if (view.props['allowBrowseFolder'] && m.canBrowseFolder(item))
        {
            return mstr.$Cmd.BROWSEFOLDER;
        };
        
        //Not browseable, get out of here.
        if (!view.props['allowBrowseHierarchy'] && !view.props['allowBrowseAttribute']){ 
            return null;
        }
        
        // Is the given item an attribute?
        var bAttr = (parseInt(item['tp']) == mstr.Enum.MSTRFolderItem.TYPE.ATTRIBUTE);
        var bAttrInHierarchy = false;
        if (bAttr)
        {
            // Is the current container within a hierarchy?
            var con = m.getContainer();
            if (con)
            {
                if (con.isWithinHierarchy == null)
                {
                    con.isWithinHierarchy = this.isWithinHierarchy(con);
                };
                bAttrInHierarchy = !!con.isWithinHierarchy;
            };
        };

        // If we have an attribute not in a hierarchy, see if we can do unfiltered browsing on it.
        if (bAttr && !bAttrInHierarchy)
        {
            if (view.props['allowBrowseAttribute'] && m.canBrowseAttribute(item))
            {
                return mstr.$Cmd.BROWSEATTRIBUTE;
            };
        }
        else 
        {
            // Otherwise, it is either not an attribute, or an attribute in a hierarchy.
            // Either way, see if we can do hierarchy browsing on it.
            if (view.props['allowBrowseHierarchy'] && m.canBrowseHierarchy(item))
            {
                return mstr.$Cmd.BROWSEHIERARCHYITEM;
            };
        };
        return null;
    };
    
    /**
     * Determine the blockCount that a model should request for a given request task.
     * The blockCount is looked up in the "mstr.Settings.TaskBlockCountMap" global.
     * @param taskId The identifier of the task.
     */
    LVH.getFetchBlockForTask = function LVH_getFetchBlockForTask(taskId)
    {
        var map = mstr.Settings.TaskBlockCountMap;
        if (map)
        {
            var bc = parseInt(map[taskId]);
            if (isNaN(bc)) bc = parseInt(map['*']);
            if (!isNaN(bc)) return bc;
        };
        return -1;
    };
    
    /**
     * Given a node, typically from the container tree of a list model, determine if
     * that node is either a hierarchy or the descendant of one.
     */ 
    LVH.isWithinHierarchy = function LVH_isWithinHierarchy(node)
    {
        while (node)
        {
            if (node.isWithinHierarchy) return true;
            
            switch(node.value && parseInt(node.value['tp']))
            {
                case mstr.Enum.MSTRFolderItem.TYPE.HIERARCHY:
                    return true;
            };
            node = node.parent;
        };
        return false;
    };
    
    /**
     * Given a node, typically from the container tree of a list model, if
     * that node within a hierarchy, returns the id of the hierarchy.
     */ 
    LVH.getHierarchyId = function LVH_getHierarchyId(node)
    {
         while (node)
        {
            switch(node.value && parseInt(node.value['tp']))
            {
                case mstr.Enum.MSTRFolderItem.TYPE.HIERARCHY:
                    return node.value['dssid'];
            };
            node = node.parent;
        };
        return null;
    };

    return LVH;
    
 })();
 
//End File - utils.js //  

// Start file - models.js //

/**
  * @namespace Contains event classes.
  */
 mstr.lang = {};
 
 mstr.lang.Event = (function(){
 
     /**
      * The target of the event.
      * @name mstr.lang.Event#target
      * @type HTMLElement
      * @return The target of the Event.
      * @function
      */
    Event.prototype.target = function Event_target()
    {
        return mstr.$E.target(this.e, this.win);
    };

    /**
     * Indicates whether the shift key was pressed during the Event.
     * @name mstr.lang.Event#shift
     * @type Boolean
     * @return True if the shift key was pressed.
     * @function
     */
    Event.prototype.shift = function Event_shift()
    {
        return mstr.$E.shiftKey(this.e, this.win);
    };
    
    /**
     * Indicates whether the ctrl key was pressed during the Event.
     * @name mstr.lang.Event#ctrl
     * @type Boolean
     * @return True if the ctrl key was pressed.
     * @function
     */
    Event.prototype.ctrl = function Event_ctrl()
    {
        return mstr.$E.ctrlKey(this.e, this.win);
    };
    
    /**
     * Indicates whether the click event was from the right mouse button.
     * @name mstr.lang.Event#rightClick
     * @type Boolean
     * @return True if the right mouse button was pressed.
     * @function
     */
    Event.prototype.rightClick = function Event_rightClick()
    {
        return 2 == mstr.$E.button(this.e, this.win);
    };

    /**
     * Returns a clone of this Event.
     * @name mstr.lang.Event#clone
     * @type mstr.lang.Event
     * @return The cloned Event.
     * @function
     */
    Event.prototype.clone = function Event_clone()
    {
        return new mstr.lang.Event(this.e,
                                    this.win,
                                    this.src,
                                    this.name,
                                    this.memo,
                                    this.originalSrc);
    };

    /**
     * This is the constructor for mstr.lang.Event
     * @name mstr.lang.Event
     * @constructor
     * @class This class wraps the native HTMLEvent object.
     * @param {HTMLElement} e The native HTMLElement to wrap.
     * @param {HTMLWindow} [hWin] The containing window.
     * @param {Object} [src] The source of the HTMLEvent.
     * @param {String} [name] The name (or type) of event.
     * @param {Object} [memo] An associative array containing any information you wish to cache with the Event object.
     * @param {Object} [originalSrc] The original source of the HTMLEvent.
     * @refactoring I searched and I can't find any uses of this constructor where the name, memo or originalSrc are passed in so maybe we don't need the parameters.
     */
    function Event(e, hWin, src, name, memo, originalSrc)
    {
        /**#@+
         * @memberOf mstr.lang.Event#
         */
        
        /**
         * The HTMLWindow that contains the event.
         * @name win
         * @type Window
         */
        this.win = hWin || window;

        /**
         * The HTMLEvent that this class wraps.
         * @name e
         * @type HTMLEvent
         */
        this.e = e || (this.win && this.win.event);

        /**
         * The source of the HTMLEvent.
         * @name src
         * @type Object
         */
        this.src = src;

        /**
         * The name or type of the original HTMLEvent.
         * @name name
         * @type String
         */
        this.name = (name != null) ? name : (this.e && this.e.type);

        /**
         * An associative array containg any information that the developer wants to tie to the Event.
         * @name memo
         * @type Object
         */
        this.memo = memo;
        
        /**
         * The original source of the HTMLEvent.
         * @name originalSrc
         * @type HTMLElement
         */
        this.originalSrc = originalSrc || src;

        if (mstr.utils.ISIE4)
        {
            /**
             * The x coordinate of the cursor for mouse events.
             * @name x
             * @type Integer
             */
            this.x = this.e ?
                        this.e.clientX 
                        + ((this.win 
                            && this.win.document.documentElement
                            && this.win.document.documentElement.scrollLeft
                            ) 
                            || 0
                          )
                        :
                        null; 
            /**
             * The y coordinate of the cursor for mouse events.
             * @name y
             * @type Integer
             */
            this.y = this.e ?
                        this.e.clientY 
                        + ((this.win 
                            && this.win.document.documentElement
                            && this.win.document.documentElement.scrollTop
                            ) 
                            || 0
                          )
                        :
                        null; 
        }
        else if (mstr.utils.ISW3C)
        {
            this.x = this.e ? this.e.pageX : null;
            this.y = this.e ? this.e.pageY : null;
        }
        
        /**#@-*/
    } 
    return Event;
    
 })();

 mstr.lang.CustomEvent = (function(){
 
    mstr.$O.extendsClass(CustomEvent, mstr.lang.Event);
    
    /**
     * @name mstr.lang.CustomEvent
     * @class This class wraps the native HTMLEvent object.
     * @param {Object} [src] The source of the HTMLEvent.
     * @param {String} [name] The name (or type) of event.
     * @param {Object} [memo] An associative array containing any information you wish to cache with the Event object.
     * @param {HTMLElement} e The native HTMLElement to wrap.
     * @param {HTMLWindow} [hWin] The containing window.
     * @param {Object} [originalSrc] The original source of the HTMLEvent.
     * @extends mstr.lang.Event
     * @refactoring This class doesn't seem to add anything to the superclass {@link mstr.lang.Event} but it does get instantiated every time {@link mstr.models.BaseModel} or
     *      {@link mstr.views.BaseView} raises an event.  I don't know why we need this class.  It also has the same parameters as the superclass, but in a different order.
     */
    function CustomEvent(src, name, memo, e, hWin, originalSrc)
    {
        mstr.lang.Event.apply(this, [e, hWin, src, name, memo, originalSrc]);
    }
    return CustomEvent;

 })();
 
 /**
  * @namespace A namespace for Data Models.
  */
 mstr.models = {};    
 
/**
 * @class IModel is the generic interface for interacting with a Model.  The Model maintains a
 * some properties data internally, and provides commands for manipulating that data.  IModel provides
 * methods accessing those commands.  Additionally, the Model is expected to raise events
 * when its data is modified by commands; the Model's listeners should be notified of these events.
 * Therefore, IModel provides methods for adding/removing event listeners to the Model.
 */
 mstr.models.IModel = {
    /** Retrieve a property value from model.     */
    'get' : null,
    /** Set a property value in model. */
    'set' : null,
    /** Insert items into an array property value. */
    'listadd' : null,
    /** Remove items from an array property value. */
    'listremove' : null,
    /** Insert items into a hash property value. */
    'hashadd' : null,
    /** Remove items from a hash property value. */
    'hashremove' : null,
    /** Attaches an object as a listener for a given event from the model. */
    'attachEventListener' : null,
    /** Attaches an object as a listener for a given event from the model. */
    'removeEventListener' : null,
    /** Execute a command on the model. */
    'execCommand' : null,
    /** Query whether a command is current enabled or disabled on the model. */
    'queryCommandEnabled' : null
 };

/**
 * @class BaseModel is the base class for all models.  It implements IModel.
 */ 
 mstr.models.BaseModel = (function(){
    
    /**
     * @memberOf mstr.models.BaseModel
     * A lookup table used to store default property values for this instance.
     */
    BM.prototype.DEFAULTS = {
        'readyState': mstr.Enum.Widget.READYSTATE.IDLE
    };

    /**
     * @memberOf mstr.models.BaseModel
     * A lookup table used to identify which events should be broadcast to listeners.
     */
    BM.prototype.BROADCASTS = {
        '_DEFAULT_' : true
    };

    /**
     * Read a given property name from the model's properties table.
     * @param {String} n The name of the property.
     * @memberOf mstr.models.BaseModel
     */            
    BM.prototype.get = function BM_get(n)
    {
        return this.props[n];
    };
    
    /**
     * Writes a given property value to the object's properties table.
     * If the given value matches the current value in the table, this method aborts.
     * Otherwise, the method raises a "set<n>" event, according to this.NOTIFIES.
     * @param n The name of the property.
     * @param v The value of the property.
     * @return True if successful, false otherwise.
     * @memberOf mstr.models.BaseModel
     */      
           
    BM.prototype.set = function BM_set(n, v)
    {        
        // Read the current value from the table of properties/defaults.
        var vOld = this.props[n];
        // Is the newly given value different?
        if (vOld != v)
        {
            // Yes, update properties table.  If it's null then delete the property
            if (v == null)
            {
                delete this.props[n];
            } else 
            {
                this.props[n] = v;
            }
            // Raise an event corresponding to modified value.
            this.raiseEvent('set_' + n, {'name' : n, 'value' : v, 'valueWas' : vOld});
            return true;
        }
        return false;
    };

    /**
     * @memberOf mstr.models.BaseModel
     */
    BM.prototype.setPropertyByPath = function BM_setPropertyByPath(p, v, bSilent)
    {
        mstr.controllers.Factory.setPath(p, this, v, bSilent);
    };
    
    /**
     * @memberOf mstr.models.BaseModel
     */
    BM.prototype.getId = function BM_getId()
    {
        return this.props[mstr.$W.ID];
    };
    
    /**
     * @memberOf mstr.models.BaseModel
     */
    BM.prototype.setId = function BM_setId(id)
    {
        this.props[mstr.$W.ID] = id;
    };
    
    /**
     * Inserts given items into an array property at a given start index.
     * Raises an "listadd<n>" event, according to this.NOTIFIES.
     * @param n The name of the array property value in which to insert the items.
     * @param items The items to insert.
     * @param start The starting index at which the items will be inserted.
     * @return The insertion index, if successful; null otherwise.
     * @memberOf mstr.models.BaseModel
     */
    BM.prototype.listadd = function BM_listadd(n, items, start)
    {
        // Do we have a valid list of items to insert?    
        var len = mstr.$A.len(items);
        if (len)
        {
            // Update the array value.
            this.props[n] = mstr.$A.insert(this.props[n], items, start);
            // Raise an event corresponding to inserted values.
            // Make sure the event has a meaningful start value (no negatives, please).
            if (start == null || start < 0) start = mstr.$A.len(this.props[n]) - len;
            this.raiseEvent('listadd_' + n, {'name' : n, 'start' : start, 'count' : len});
            return start;
        }
        return null;
    };

    /**
     * Removes ranges of items from an array property at given indices.
     * Raises a "listremove<n>" event, according to this.NOTIFIES.
     * @param n The name of the array property value from which to remove items.
     * @param ranges An array of ranges of indicies of the items to be removed, possibly unsorted.
     * @return The sorted array of removed index ranges if successful, null otherwise.
     * @memberOf mstr.models.BaseModel
     */
    BM.prototype.listremove = function BM_listremove(n, ranges, sorted)
    {
        // Do we have a valid list of index ranges to remove?    
        var len = mstr.$A.len(ranges);
        if (len)
        {
            // Performance optimization: Sort the array of indices...
            var rangesSorted  = sorted ? ranges : ranges.concat().sort(mstr.$A.rangeSorter);
            
            // Update the array value.
            this.props[n] = mstr.$A.removeRanges(this.props[n], rangesSorted);

            // Raise an event corresponding to removed values.
            this.raiseEvent('listremove_' + n, {'name' : n, 'ranges' : rangesSorted});
            
            return rangesSorted;
        }
        return null;
    };
    
    /**
     * Removes all items from an array property (without resetting the property value to
     * an entirely new empty array object).  
     * Raises a "listremove_<n>" event.
     * @param n The name of the array property value from which to remove items.
     * @return The range of removed indices if successful, null otherwise.
     * @memberOf mstr.models.BaseModel
     */
    BM.prototype.listclear = function BM_listclear(n)
    {
        var arr = this.props[n],
        len = arr && arr.length;
        if (len)
        {
            arr.splice(0, len);
            var rangeRemoved = {min: 0, max: len -1};
            this.raiseEvent(
                new mstr.lang.CustomEvent(
                this, 
                'listremove_' + n, 
                {'name': n, 'ranges': [rangeRemoved]})
            );
            return rangeRemoved;
        }
        return null;
    };
    

    /**
     * Inserts given items into a hash property at given keys.  Given a property named "n"
     * (which is assumed to store a hashtable) and a hashtable "items", each item in "items"
     * will be inserted into the "n" hashtable under its "items" key.  Previous values under
     * those keys in "n" will be overwritten.
     * Raises an "hashadd<n>" event, according to this.NOTIFIES.
     * @param n The name of the hash property value in which to insert the items.
     * @param items A hashtable of items to insert.
     * @return True if successful, false otherwise.
     * @memberOf mstr.models.BaseModel
     */
    BM.prototype.hashadd = function BM_hashadd(n, items)
    {
        // Update the hash values.
        var changed = mstr.$H.overwrite(this.props[n], items);
        // Were any values changed?
        if (changed)
        {
            // Raise an event corresponding to inserted values.
            this.raiseEvent('hashadd_' + n, {'name' : n, 'at' : changed});
            return true;
        }
        return false;
    };

    /**
     * Removes items from a hash property at given keys.
     * Raises an "hashremove<n>" event, according to this.NOTIFIES.
     * @param n The name of the array property value from which to remove items.
     * @param keys A collection of keys of the items to be removed.
     * @return True if successful, false otherwise.
     * @memberOf mstr.models.BaseModel
     */
    BM.prototype.hashremove = function BM_hashremove(n, keys)
    {
        // Update the hash values.
        var changed = mstr.$H.remove(this.props[n], keys);
        // Were any values changed?
        if (changed)
        {
            // Raise an event corresponding to inserted values.
            this.raiseEvent('hashremove_' + n, {'name' : n, 'at' : changed});
            return true;
        }
        return false;
    };
    
    /**
     * Removes all items from a hash property.
     * Raises an "hashremove<n>" event.
     * @param n The name of the array property value from which to remove items.
     * @return True if successful, false otherwise.
     * @memberOf mstr.models.BaseModel
     */
    BM.prototype.hashclear = function BM_hashclear(n)
    {
        var v = this.props[n],
        changedKeys = v && mstr.utils.Hash.clear(v);
        if (changedKeys)
        {
            this.raiseEvent(
                new mstr.lang.CustomEvent(
                this, 
                'hashremove_' + n, 
                {'name': n, 'at': changedKeys})
                );
            return true;
        }
        return false;
    };
    
    /**
     * @memberOf mstr.models.BaseModel
     */
    BM.prototype.attachEventListener = function BM_aEL(listener, eventName, callbackMethodName)
    {
        mstr.controllers.EventManager.attachEventListener(
                listener, this, eventName, callbackMethodName);
    };

    /**
     * @memberOf mstr.models.BaseModel
     */
    BM.prototype.detachEventListener = function BM_dEL(listener, eventName)
    {
        mstr.controllers.EventManager.detachEventListener(
                listener, this, eventName);
    };
    
    /**
     * Creates an event with the given name & memo, and notifies the
     * appropriate listeners of the event.
     * @param name The name of the event to be broadcast.
     * @param memo The memo of the event to be broadcast.
     * @memberOf mstr.models.BaseModel
     */
    BM.prototype.raiseEvent = function BM_raiseEvent(name, memo)
    {
        if (this._shouldBroadcastEvent(name))
        {
            var evt = new mstr.lang.CustomEvent(this, name, memo);

            // First, notify any handler you may have yourself.
            if (this['on_' + name]) this['on_' + name](evt);
            
            // Second, notify listeners registered for this event, if any.
            this.notifyListeners(evt);
        }
    };

    /**
     * Looks up an event name in this.NOTIFIES to determine whether or not
     * that event should be broadcast to listeners.
     * @param n The name of the event.
     * @return Bitwise sum of the members of mstr.Enum.Widget.NOTIFIES.
     * @memberOf mstr.models.BaseModel
     */    
    BM.prototype._shouldBroadcastEvent = function BM_shouldBroadcastEvent(n)
    {
        var b = this.BROADCASTS[n];
        if (b == undefined) 
        {
            b = this.BROADCASTS._DEFAULT_;
        }
        return b;
    };
    
    /**
     * Returns the current RMC model l.  .
     * @memberOf mstr.models.BaseModel
     */
    BM.prototype.getRMCModel = function BM_getRMCModel()
    {
        // First, do we have a model cached locally, to avoid regenerating it every time?
        return this.props['rmc'];
    };

    /**
     * Generates and returns an RMC model l.  .
     * @memberOf mstr.models.BaseModel
     */
    BM.prototype.setRMCModel = function BM_setRMCModel(model, modelClass, props)
    {
        this.props['rmc'] = model;
        
        if(model) {
            // the current model should now serve as the parent of the rmc model. 
            model.props.parent = this;        
       }
    }

    /**
     * Given an event, notify the appropriate listeners of the event.
     * @memberOf mstr.models.BaseModel
     */
    BM.prototype.notifyListeners = function BM_notifyListeners(evt)
    {
        mstr.controllers.EventManager.notifyListeners(this, evt);
    };          

    /**
     * Executes a given command name in a generic fashion by checking for a
     * method on this instance named after the command.  
     * @param n The name of the command.
     * @param v1, v2, v3... Optional value arguments for the command.
     * @memberOf mstr.models.BaseModel
     */
//    BM.prototype.execCommand = function BM_execCommand(n, v)
    BM.prototype.execCommand = function BM_execCommand(n)
    {
        // Do we have a method named after the given command name?
        if (this['exec' + n])
        {
            // Yes, call that method: exec + n(v1, v2, v3...)
            var params = [],
                len = arguments.length;
            for (var i = 1; i < len; i++)
            {
                params[i-1] = arguments[i];
            }
            return this['exec' + n].apply(this, params);
            // // If v is a non-null non-array, wrap it in an array so we can use "apply" method.
            // if ((v != null && v != undefined) && (typeof(v) != 'object' || !v.length)) v = [v];
            // return this['exec' + n].apply(this, v || [ ]);
        }
        
        // Is the given command name prefixed with "Set_"?  If so, 
        if (n.substring(0,4) == 'Set_')
        {
            // Yes, try calling the set() method using the remained of
            // the command name as the propery string.
            n = n.substring(4);
            if (n != '') return this.set(n, arguments[1]);
        }
    }

    /**
     * Queries the enabled status of a given command name in a generic fashion by
     * checking for a method on this instance named after the command.
     * @param n The name of the command.
     * @param v Optional value for the command.  Some commands may be value-sensitive.
     * @memberOf mstr.models.BaseModel
     */
    BM.prototype.queryCommandEnabled = function BM_queryCommandEnabled(n, v)
    {
        // First, check for a special command name format: "Set_xxxx", which is the
        // special reserved syntax for calling the set() method.  This method is always
        // enabled.
        if (n.substring(0,4) == 'Set_')
        {
            return true;
        }
        
        if (this['queryEnabled' + n])
        {
            return this['queryEnabled' + n](v);
        }
        else
        {
            return !!this.props['CommandEnabled' + n];
        }
    };
    
    /**
     * Submits a request to the http Governor, if any.
     * @param reqInputs The hashtable of name-value pairs that define the request.
     * @param memo An optional memo object, not submitted to the Governor, which
     * is used internally to store information about this request.
     * @return The id supplied by the Governor to track the request, if successful.
     * @memberOf mstr.models.BaseModel
     */
    BM.prototype.submitRequest = function BM_submitRequest(reqInputs, memo)
    {
        // Do we have a Governor to submit requests to?
        var g = mstr.http && mstr.http.Governor;
        if (!g) return;
        
        // Do we have a handler to receive callbacks from the Governor?
        if (!this.onRequestCallback) return;
        
        // Do we have a request currently pending?  If so, and it matches the new
        // request we are submitting, we might not need to submit the new request.
        var ctxt = this.props['requestContext'];
        if (ctxt && ctxt.id)
        {
            // We have a request pending. Let's determine if it matches the new request we are submitting.
            // If it matches, and it's still in progress, don't resubmit request again.
            var bMatch = false;

            // Is it still in progress (no errors/timeouts)?
            switch (this.props['readyState'])
            {
                case mstr.Enum.Widget.READYSTATE.WAITING:
                case mstr.Enum.Widget.READYSTATE.SUCCESS:
                    // Does it match the inputs of our new request?
                    if ((ctxt.inputs.url == reqInputs.url)
                        && mstr.$H.equals(ctxt.inputs.params, reqInputs.params))
                    {
                        // It does match, so need to submit request again.
                        ctxt.memo = memo;
                        return ctxt.id;
                    }
                    break;
            }
            
            // No match found, so cancel pending request first before submitting another request.
            this.cancelRequest(ctxt.id);
        }
        
        // Submit the given request input parameters; receive a request id in return.
        var id = g.submitRequest(reqInputs, this.getId(), 'onRequestCallback');
        
        // Store the resultant id in our context object, as well as the given optional memo.
        if (!ctxt) ctxt = this.props['requestContext'] = {};
        ctxt.id = id;
        ctxt.inputs = reqInputs;
        ctxt.memo = memo;
        ctxt.response = null;
        
        // Call our callback handler to initialize our readyState.
        this.onRequestCallback(g.requests.lookup[id]);        
        
        return id;
    };
    
    /**
     * Notifies the HTTP request governor that this model no longer wants callbacks for
     * readyState changes in a given request.  Effectively cancels the request for this view,
     * but does not cancel the request for the entire governor if there are other views still
     * listening for callbacks from that request.  If successful, the readyState of this view
     * will result in CANCELLED.
     * @memberOf mstr.models.BaseModel
     */
    BM.prototype.cancelRequest = function BM_cancelRequest(reqId)
    {
        if (this.successTimer) {
            self.clearTimeout(this.successTimer);
            delete this.successTimer;
        }
        return mstr.http.Governor.cancelRequest(reqId, this.getId());
    };

    /**
     * Calls cancelRequest for this view with the id of the current request context, if any.
     * @return True if request successfully cancelled; false otherwise.
     * @memberOf mstr.models.BaseModel
     */
    BM.prototype.execCancelRequest = function BM_execCancelReq()
    {
        var ctxt = this.props['requestContext'];
        if (ctxt && ctxt.id) 
        {
            return this.cancelRequest(ctxt.id);
        }
        return false;
    };
        
    /**
     * This handler is called by the Governor when a submitted request changes
     * its readyState.
     * @param req The request object which was generated for this model.
     * @memberOf mstr.models.BaseModel
     */
    BM.prototype.onRequestCallback = function BM_onRequestCallback(req)
    {
        // Validate the given request object.  Did we make this request?
        if (!req || !this.props['requestContext']
            || (this.props['requestContext'].id != req.id))
        {
            return;
        }
        
        // Attach a pointer to the response data, so we can read it later.
        this.props['requestContext'].response = req.response;
        
        // Update our readyState to match the given request's readyState.
        this.set('readyState', req.readyState);
        
        // Is the request completed successfully?
        if (req.readyState == mstr.Enum.Widget.READYSTATE.SUCCESS)
        {        

            // Does this model have a method for reading the response data?
            if (this.loadRequestResponse)
            {
                // Set a timer to read the data very soon.  This allows views
                // to repaint themselves before the data crunching starts. The actual
                // implementation of loadRequestResponse is model-specific, and should
                // be implemented in the subclasses of BaseModel.
                var id = this.getId(),
                    me = this;
                this.successTimer = self.setTimeout(function () {
									                    mstr.$obj(id).loadRequestResponse(); 
									                    delete me.successTimer;
								                    }, 1);
            }
            else
            {
                // This model does not know how to read the response, so just
                // reset our request context & our readyState, then exit.
                this.props['requestContext'] = {};
                this.set('readyState', mstr.Enum.Widget.READYSTATE.IDLE);        
            }
        }
        
    };

    /**
     * Instantiate any object-type properties (or array-type properties
     * with object values) that may be sub-models nested in this model.
     * @param excludedProps An array of property names that should be excluded from initialization.
     * @memberOf mstr.models.BaseModel
     */
    BM.prototype.init = function BM_init(excludedProps)
    {
        var props = this.props;
        var doc = props[mstr.$W.DOCUMENT];
        
        var ex = (excludedProps) ? ('|' + excludedProps.join('|') + '|') : '';
        
        // Examine each property value..
        for (var n in props)
        {
            // Skip it if this property has been excluded.
            if (ex.indexOf('|' + n + '|') != -1) continue;
            
            var v = props[n];
            if (v == null || n == 'parent') continue;
            
            var obj = null;
            
            // Determine the type of the value.
            if (mstr.$A.isArray(v))
            {
                // Setup a counter variable to count the number of consecutive non-class objects encountered.
                var nonInstantiable = 0;
                
                // An array property value.  Examine each array item's type.
                for (var i = 0, len = mstr.$A.len(v); i < len; i++)
                {
                    var v2 = v[i];
                    if (v2 == null) continue;
                    
                    if (v2.getId)
                    {
                        // A javascript class instance.
                        // Doesn't need to be instantiated, but make sure its parent
                        // property is set correctly.
                        v2.props && (v2.props[mstr.$W.PARENT] = this);

                        // Reset the consecutive nonInstantiable counter.
                        nonInstantiable = 0
                    }
                    else if (typeof(v2) == 'object')
                    {
                        // An object item, not a javascript object instance.
                        // Try to instantiate it.
                        // Performance optimization: check for scriptClass property before making call.
                        obj = v2[mstr.$W.SCRIPTCLASS] && mstr.controllers.Factory.registerJSON(v2, doc);
                        // If successful, replace array item with new instance.
                        if (obj)
                        {
                            v[i] = obj;
                            obj.props && (obj.props[mstr.$W.PARENT] = this);
                            obj.init && obj.init();
                            
                            // Reset the consecutive nonInstantiable counter.
                            nonInstantiable = 0
                        }
                        else
                        {
                            // Optimization: if not successful, increment the nonInstantiable counter.
                            nonInstantiable++;
                            
                            // If we have found more than 2 consecutive non-instantiable object then assume
                            // the remainder will be non-instantiable as well.
                            if (nonInstantiable > 2) break;
                        }
                    }
                }
            
            }
            else if (v.getId)
            {
                // A javascript class instance.
                // Doesn't need to be instantiated, but make sure its parent
                // property is set correctly.
                v.props && (v.props[mstr.$W.PARENT] = this);
            }
            else if (typeof(v) == 'object')
            {
                // An object property value, not a javascript object instance.
                // Try to instantiate it.
                // Performance optimization: check for scriptClass property before making call.
                obj = v[mstr.$W.SCRIPTCLASS] && mstr.controllers.Factory.registerJSON(v, doc);
                // If successful, replace property value with new instance.
                if (obj)
                {
                    props[n] = obj;
                    obj.props && (obj.props[mstr.$W.PARENT] = this);
                    obj.init && obj.init();
                }
            }
        }
    };
         
   /** 
    *  This method creates a model of the specified scriptClass with the properties passed in.
     * @memberOf mstr.models.BaseModel
    */
    BM.prototype._createNewModel = function BM_createNewModel(properties)
    {
        // Extract the scriptClass from the properties.  If it's not there, then
        // return because we don't know what type of class to instantiate.
        var scriptClass = properties['scriptClass'];
        if (!scriptClass) return null;
        
        // Instantiate a new model.
        var m = new mstr.models[scriptClass](properties);            
                                    
        if (!m) return null;

        // Add new model to the factory, so we can attach event listeners to it.
        mstr.controllers.Factory.add(m);            

        // Initialize the new model.
        var zz = m.init && m.init();

        return m;
    };
   
                
    /**
     * This function searches the model tree upwards to find first ancestor with the given property.
     * 
     * For example, FastTileView scriptClass property: 
     *                  (n) = ('popup/scriptClass')
     * 
     * @param n - property to search
     *                  A full property name which may include the path (e.g. popup/scriptClass) 
     * @param inclusive - flag to start with current object or its parent 
     *                  true - start searching from current object
     *                  false - start searching from current object's parent 
     * @memberOf mstr.models.BaseModel
     */
    BM.prototype.getAncestorByProperty = function BM_getAncestorByProperty(n, inclusive) {
        var p = (inclusive) ? this : this.get('parent');
        while (p) {
            if (mstr.controllers.Factory.getPath(n, p)) return p;

            p = p.get('parent');
        }
        
        return null;
    };
    
    /**
     * This function searches the model tree upwards to find first ancestor matching given property value.
     * 
     * For example, FastTileView scriptClass property name/value pair: 
     *                  (n,v) = ('popup/scriptClass', 'mstr.views.FastTileView')
     * 
     * @param n - property to search
     *                  A full property name which may include the path (e.g. popup/scriptClass) 
     * @param v - property value to match
     *                  value must be passed as a regular expression as /expression/, eg, /mstr.views.FastTileView/
     * @param inclusive - flag to start with current object or its parent 
     *                  true - start searching from current object
     *                  false - start searching from current object's parent 
     * @memberOf mstr.models.BaseModel
     */
    BM.prototype.getAncestorByPropertyValue = function BM_getAncestorByPropertyValue(n, v, inclusive) {
        if (!(v instanceof RegExp)) v = new RegExp(v);
        
        var p = this.getAncestorByProperty(n, inclusive);
        while (p) {
            var t = mstr.controllers.Factory.getPath(n, p);
            if (t && v.test(t)) return p;
                        
            p = p.getAncestorByProperty(n, inclusive);
        }
        
        return null;
    };

    /**
     * @constructor
     */
    function BM(props)
    {
        this.props = mstr.utils.Hash.applydefault(props, this.DEFAULTS);
    }
    
    return BM;
    
 })();
 
 mstr.models.BooleanTaskModel = ( function() {
	mstr.$O.extendsClass(BTM, mstr.models.BaseModel);

	BTM.prototype.execSubmit = function BTM_execFetchBlock(reqInputs) {
		this.submitRequest(reqInputs, {});
	};

	BTM.prototype.loadRequestResponse = function BTM_loadRequestResponse() {
		// Do we a context object from which to read the response?
		var ctxt = this.props['requestContext'];

		var res = ctxt && ctxt.response;
		if (!res || !res.data) {
			this.set('readyState', mstr.Enum.Widget.READYSTATE.ERROR);
			return;
		}
		this.set("readyState", mstr.Enum.Widget.READYSTATE.SUCCESS);
	};

	function BTM(props) {
		mstr.models.BaseModel.apply(this, [ props ]);
	}

	return BTM;

})();
 

 
 
 // End file - models.js //
 
 
// Start file - http.js //
 mstr.http = {};
 
/**
 * The Governor is a singleton, essentially an HTTP controller.
 * It manages a pool of "processors" which individually submit HTTP
 * requests and receive HTTP responses.  Models communicate with the Governor,
 * submitting requests for information, and the Governor manages these requests
 * in a local queue, notifying models when their requests have been processed.
 */
 mstr.http.Governor = (function(){
 
    var G = {};
    
    /**
     * A local pool of processor instances, keyed by id.
     */
    G.procs = {};
    /**
     * A local table of requests pending and completed.
     */
    G.requests = {
                    'pending': [],  /* Ordered array of request ids still waiting. */
                    'lookup' : {}   /* Lookup table for fetching request data. */
                };
    
    /**
     * Checks if we have a free processor, and if so, assigns to it the next pending
     * request, if any.
     */
    G.go = function G_go()
    {        
        var r = this._nextRequest();
        if (!r) return;
        var p = this._freeProc(r);
        if (!p) {
            var c = r && r.inputs && r.inputs.params && r.inputs.params.processorClass;
            if (c) {
                p = new c();
                mstr.controllers.Factory.add(p);
                this.addProc(p, true);
            }
        }
        if (!p) {
            this.requests.pending.unshift(r.id);   
            return;   
        }
        this._assignRequestToProc(r, p);        
    };

    /**
     * Retrieves the next request object listed in the pending queue.
     */
    G._nextRequest = function G_nextRequest()
    {
        var id = this.requests.pending.shift();
        return id && this.requests.lookup[id];
    };
    
    /**
     * Searches the processor pool for a processor that is not currently handling a request.
     */
    G._freeProc = function G_freeProc(/*Request*/ r)
    {
        for (var id in this.procs)
        {
            var p = this.procs[id];
            if (!p) continue;
            switch(p.props['readyState'])
            {
                case mstr.Enum.Widget.READYSTATE.IDLE:
                case mstr.Enum.Widget.READYSTATE.SUCCESS:
                case mstr.Enum.Widget.READYSTATE.ERROR:
                case mstr.Enum.Widget.READYSTATE.CANCELLED:
                case mstr.Enum.Widget.READYSTATE.TIMEOUT:
                     var c = r && r.inputs && r.inputs.params && r.inputs.params.processorClass;
                     
                     if (c) {
                    	if (p.constructor == c) {
                    		return p;
                    	}
                     } else if(p.constructor != mstr.http.PUProcessor) {
                    	return p;
                	 }
            };
        };
    };

    /**
     * Adds a given processor instance to the local processor pool.
     * Assigns the processor an id (if needed), and 
     * @param p The processor.
     * @param addOnly  Just add the proc without invoking go method.
     */
    G.addProc = function G_addProc(p, addOnly)
    {
        if (!p) return;

        // Adds proc to pool hash under id key.
        var id = mstr.controllers.Factory.validateId(p);
        this.procs[id] = p;

        // Adds governor as a listener to processor's readystate change events.
        mstr.controllers.EventManager.attachEventListener(
                                            this,
                                            p,
                                            'set_readyState', 
                                            'on_proc_set_readyState');
        
        // Process next pending request, if any.
        if(!addOnly) this.go();
    };

    /**
     * Submits a request for information to the governor, who queues it up for whenever
     * a processor is free to handle the request.
     * @param reqInputs A hashtable of inputs that define the request.
     * @param callbackObjectId The id of the object making this request.
     * @param callbackMethodName The name of the method on the callback object that should
     * be called by the Governor whenever the request's readyState changes.
     * @return An id for the given request.  The id can be used to subsequently cancel the request.
     */
    G.submitRequest = function G_submitRequest(reqInputs, callbackObjectId, callbackMethodName)
    {
        var id;
        // add session info into task input. When request comes from different tabs in the same winodw, we may have different session state.
        var ss = microstrategy && microstrategy.sessionState;
        if (ss && reqInputs && reqInputs.params) {
        	reqInputs.params.sessionState = ss;
        }
        // Lookup other requests to see if they match the given request inputs.  
        // Did we find a match?
        var lookupItem = this.searchForRequestInputs(reqInputs);
        if (lookupItem)
        {
            // Match found, so reuse that id.
            id = lookupItem.id; 
               
            // Add this callback to that requests' callbacks table.
            lookupItem.callbacks[callbackObjectId] = callbackMethodName;            
        }
        else
        {
            // No match found, create a new item for our queue.         
            // Generate a unique id, which we need to keep track of the request.
            var id = mstr.controllers.Factory.nextFreeId();
    
            // Store the request inputs and the callback info in Governor's lookup table.
            var lookupItem = {
                                'id' : id,
                                'inputs' : reqInputs,
                                'callbacks' : {},
                                'readyState' : mstr.Enum.Widget.READYSTATE.WAITING, 
                                'response' : null,
                                'proc' : null
                            };
            this.requests.lookup[id] = lookupItem;

            // Add this callback to that requests' callbacks table.
            lookupItem.callbacks[callbackObjectId] = callbackMethodName;            
            
            // Add request id to pending queue.
            this.requests.pending.push(id);
        
            // Tries to process the next request, if any processor is free.
            // This is done via timeout so that we may return the request id to the caller
            // BEFORE the request can be processed.
            window.setTimeout("mstr.http.Governor.go()", 10);
        };
                
        return id;
    };
    
    /**
     * Searches the requests lookup table for a request with the given set of inputs parameters.
     * @param reqInputs A hashtable of name-value pairs, which define the inputs for a request.
     * @return The matching request item, if found; null, otherwise.
     */
    G.searchForRequestInputs = function G_searchReqInputs(reqInputs)
    {
    // Null requests, or requests to import files, are not cached.  Import requests
    // should not cache because the file contents may have changed since the last
    // import (or the file path itself may have changed as well).
    if (!reqInputs || reqInputs.htmlForm) return null;

        var queueItems = this.requests.lookup;
        for (var id in queueItems)
        {
            switch(queueItems[id]['readyState'])
            {
                case mstr.Enum.Widget.READYSTATE.TIMEOUT:
                case mstr.Enum.Widget.READYSTATE.ERROR:
                case mstr.Enum.Widget.READYSTATE.CANCELLED:
                    continue;
            };
            var qInputs = queueItems[id]['inputs'];
            if ((qInputs.url == reqInputs.url)
                    && mstr.utils.Hash.equals(qInputs.params, reqInputs.params))
            {
                return queueItems[id];
            };            
        };
        return null;
    };
    
    /**
     * Asks that a given object stop receiving updates about a given request id.
     * If no other objects are listening for callbacks, the request is cancelled.
     * @param reqId The id of the request.
     * @param callbackObjectId The id of the object who no longer wants to be called back.
     */
    G.cancelRequest = function G_cancelRequest(reqId, callbackObjectId)
    {
        if (!reqId) return;
        
        // Find the callback objects table for the given request id.
        var callbacks = this.requests.lookup[reqId]
                        && this.requests.lookup[reqId].callbacks;
        
        // Remove the given callback object from the callback objects table.                
        if (callbacks) delete callbacks[callbackObjectId];
        
        // Are any callbacks left for this request id?
        var bFound = false;
        if (callbacks)
        {
            for (var cid in callbacks)
            {
                bFound = true;
                break;
            };
        };
        if (!bFound)
        {
            // No callbacks left for this request, remove the request entirely.
            this.removeRequest(reqId);
        };
    };
    
    /**
     * Removes the given request id from the queue of pending requests and from
     * the lookup table of request information.
     */
    G.removeRequest = function G_removeRequest(reqId)
    {
        this.requests.pending = mstr.$A.removeItems(this.requests.pending, [reqId]);
        delete this.requests.lookup[reqId];
    };
    
    /**
     * Asks a given processor to handle a given request.
     */
    G._assignRequestToProc = function G_assignRequestToProc(req, proc)
    {
        // Assign the request and the processor pointers to each other (for communication).
        req.proc = proc;
        proc.props['req'] = req;
        
        // Feed the processor the input params for the request.
        proc.setInputs(req.inputs);
        
        // Begin the HTTP submission.
        proc.submit();
    };
    
    /**
     * This method is called by processor upon a change in their readyState.
     * This method updates the readyState of the request object that the processor is handling,
     * and triggers the appropriate callbacks for that request.
     * @param evt The CustomEvent object raised by the processor.
     */
    G.on_proc_set_readyState = function G_on_proc_set_readyState(evt)
    {
        // Determine which processor raised event...
        var proc = evt && evt.src;
        
        // And the new readyState of that processor...
        var readyState = proc.get('readyState');

        // Is there a request associated with the processor?
        var req = proc.props['req'];
        if (req)
        {
            // Has the request readyState changed?
            if (req.readyState != readyState)
            {
                // Update request's readyState.
                req.readyState = readyState;
                
                switch(readyState)
                {
                    case mstr.Enum.Widget.READYSTATE.SUCCESS:
                        // Record the time that this request took.
                        req.totalTime = proc.props['lastSuccess'] - proc.props['lastSubmit'];
                        mstr.timers.lastRequest = req.totalTime;
                        // Continue to next case (ERROR)...
                    case mstr.Enum.Widget.READYSTATE.ERROR:
                        // If response to request is available, record response.
                        var res = req.response = proc.getResponse();
                        // Try to instantiate the response data into a javascript class.
                        if (res && res.data)
                        {
                            var jsc = mstr.controllers.Factory.create(res.data);
                            // Do we have a javascript class instance?
                            if (jsc)
                            {
                                // Initialize the instance, and replace the data JSON with the instance.
                                jsc.init && jsc.init();
                                res.data = jsc;
                            };
                        };
                };
                
                // Call request's callbacks, if any.
                this._notifyRequestCallbacks(req);
                
                // Should we cache the result of this request object, or clear it?
                switch(readyState)
                {
                    case mstr.Enum.Widget.READYSTATE.SUCCESS:
                        if (!mstr.Settings.Http.CACHE)
                        {
                            req.response = null;
                            this.removeRequest(req.id);
                        };
                        break;
                };

            };
        };
                
        // Is the processor free to handle another request?
        switch(readyState)
        {
            case mstr.Enum.Widget.READYSTATE.SUCCESS:
            case mstr.Enum.Widget.READYSTATE.ERROR:
            case mstr.Enum.Widget.READYSTATE.TIMEOUT:
            case mstr.Enum.Widget.READYSTATE.CANCELLED:
                // Yes, release previous request.
                proc.props['req'] = null;
                proc.props['readyState'] = mstr.Enum.Widget.READYSTATE.IDLE;
                if (req) req.proc = null;

                // Process next pending request if any.               
                this.go();
                break;
        };
    };

    /**
     * Call the callbacks listed for a given request object.
     * Each callback method will be sent 3 args: 
     * the original request inputs, the readyState of the request, and
     * the respones if the request is finished.
     * @param req A request item from the local requests lookup table.
     */
    G._notifyRequestCallbacks = function G_notifyRequestCallbacks(req)
    {
        if (!req || !req.callbacks) return;
        
        // Look in callbacks table for ids & method names to call.
        for (var id in req.callbacks)
        {
            // For each id, lookup object in Factory...
            var obj = mstr.controllers.Factory.obj(id);
            if (!obj) continue;
            
            // Try to call the recorded method name for the object...
            var f = req.callbacks[id];
            if (obj[f])
            {
                obj[f](req);
            };
        };
    };   

    /**
     * This handler is called by a the FRAME/IFRAME window of a FrameProcessor when
     * that window is loaded.  It notifies the Governor that some (I)FRAME has returned
     * with a service response (or error).  The Governor must then determine which
     * FrameProcessor instance that (I)FRAME corresponds to, and notify that FrameProcessor
     * so it process its response accordingly.
     * @param hWin The window object of the FRAME/IFRAME that has returned with a response.
     */
    G.onFrameProcLoad = function(hWin)
    {
        if (hWin && hWin.name)
        {
            var frameProc = this._findFrameProcByFrameName(hWin.name);
            frameProc && frameProc.update && frameProc.update(hWin);
        };
    };
     
    /**
     * Searches the Governor's processor pool for a processor whose "frameName" property
     * matches a given string.  This method is used to associate a FRAME/IFRAME window
     * with a FrameProcessor instance.
     * @n The "frameName" value we are searching for.
     */
    G._findFrameProcByFrameName = function G_findFrameProcBFN(n)
    {
        for (var id in this.procs)
        {
            var p = this.procs[id];
            if (p && p.props && (p.props['frameName'] == n))
            {
                return p;
            };
        };
        return null;
    };
    
    // Register Governor with Factory so it can be an event listener to its processors.
    G.props = {};
    G.getId = function() { return this.props['id']; };
    G.setId = function(id) { this.props['id'] = id; };
    mstr.controllers.Factory.add(G); 
            
    return G;
    
 })();
 
 /**
 * XHRProcessor is a representation of an xmlHttpRequest object, used to communicate
 * via HTTP with the MSTR Web server.  It serves as a ActiveX/binary alternative to
 * using an IFRAME or FRAME.
 */
mstr.http.XHRProcessor = (function(){

    mstr.$O.extendsClass(XHR, mstr.models.BaseModel);
    

    
    /** Identifies this object as a proc for Governor at the time of registration. */
    XHR.prototype.isHTTPProcessor = true;
    
    XHR.prototype.setInputs = function XHR_setInputs(inputs)
    {
        this.props['inputs'] = inputs;
        this.props['readyState'] = mstr.Enum.Widget.READYSTATE.IDLE;
        this.props['response'] = null;
    };
    
    /**
     * Asks the XHRProcessor to submit its request via HTTP.
     */    
    XHR.prototype.submit = function XHR_submit()
    {
        // Reset some properties.
        var ps = this.props;
        ps['response'] = null;
        ps['cancelled'] = true;
        ps['readyState'] = mstr.Enum.Widget.READYSTATE.WAITING;
        ps['lastSubmit'] = new Date();
        
        // Do we have a binary xmlHttpRequest object instantiated yet?
        var xhr = this._xhr;
        if (!xhr)
        {
            // No, instantiate the binary object now.  The exact syntax varies
            // depending upon the browser, so use null-checking to pick a syntax.
            xhr = this._xhr = self.XMLHttpRequest ?
                                    new XMLHttpRequest() : 
                                    ( self.ActiveXObject ?
                                        new ActiveXObject("Microsoft.XMLHTTP") :
                                        null
                                    );
        }
        
        // Do we have an instance of the binary object?
        if (xhr)
        {
            // Yes, construct a URL for the HTTP request.
            var inputs = ps['inputs'],
                params = inputs && inputs.params,
                arr = [];

            this._addEnv(params);
            
            var url = this._getURL(params);
            
            for (var key in microstrategy.persistParams) {
            	params[key] = microstrategy.persistParams[key];
            }
            
            this._makeArray(params, arr);
            
            
            // Add timestamp to make sure IE doesn't cache the request.
            arr.push('_xts_=' + new Date().getTime());
            
            /* TQMS issue 337255: "Error: The system cannot locate the resource specified." 
                 error is displayed when you try to edit a comment and the new comments string is large enough (around 1400 chars).
                 This issue only happens in IE. And this is a problem in the XHR calling process: 
                 the URL string (URL path + query string) is too long and exceeds the limit (set by IE, 2083 BYTES) so that causes the error 
                 in IE when using GET method to pass the data to server side. 
                 The fix includes the changes only in this method and basically it is: use POST method instead of using GET method. 
            */
            var paramString = arr.join('&');
            
            if(microstrategy.sessionId && !url.match(/jsessionid=/)){
                url += ";jsessionid=" + microstrategy.sessionId;
            }
            
            // In IE6 we can't reuse the xhr unless we call abort and reset the onreadystatechange handler.
            if (mstr.utils.ISIE4) xhr.abort();
            
            var xhrCallback = new Function(
                                "",
                                "mstr.$obj('" 
                                + this.getId() 
                                + "')._onreadystatechange()"
                            ); 
                            
            // TQMS 366224, 364614 and 364620 etc. Such issues result from a bug in FF3.5: 'readystatechange' event doesn't notify its 
            // event handler when ready state is 4 (COMPLETE). So, its callback xhr.onreadystatechange doesn't get called in this situation.
            // However, FF has some other event handlers - xhr.onload which get notified correctly when the ready state is 4 (COMPLETE).
            if(mstr.utils.ISFF3_5){
               xhr.onload = xhr.onerror = xhrCallback; 
            }   
            else {
               xhr.onreadystatechange = xhrCallback;
            }   
                
            //if(typeof console != 'undefined') console.log(paramString);
            // Submit the async request.
            xhr.open("POST", url, true);
            // set the content type of the POSTed data.
            xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");            
            // set the content length of the POSTed data.
            xhr.setRequestHeader("Content-Length", paramString.length);            
            // pass the query string into the send method. 
            xhr.send(paramString);
        }
        else 
        {
            // No, we failed to obtain an instance of the binary object.  Raise an error.
            this.set('readyState', mstr.Enum.Widget.READYSTATE.ERROR);
        }
    };
    
    /* 
     * This enumeration of the xmlHttpRequest binary object's "readyState" 
     * property is defined by the xmlHttpRequest spec, which differs from our
     * MicroStrategy "readyState" property's enumeration. 
     */
    XHR.READYSTATE = {
        UNINITIALIZED: 0,
        LOADING: 1,
        LOADED: 2,
        INTERACTIVE: 3,
        COMPLETE: 4
    };
    
    XHR.prototype._addEnv = function XHR_addEnv(params) {
        // Add envelope parameters for xhr.
	    if (params) {
	        params['taskEnv'] = 'juil_xhr';
	    };
    };
    
    /**
     * Assemblem the request parameters into an array
     */
    XHR.prototype._makeArray = function XHR_makeArray(params, arr) {
       for (var n in params)
        {
            arr.push(n + '=' + encodeURIComponent(params[n]));
        }
    };
    
    XHR.prototype._getURL = function XHR_getURL(params) {
       return this.props['taskURL'];
    };
    
    /**
     * Process the response.
     */
    XHR.prototype._processResponse = function XHR_processResponse(txt) {
       var r;
       if (txt)
       {
           try { eval("r = " + txt) }
           catch(localerr) {}
       }
       // Store the eval'd JSON into the "response" property.
       this.props['response'] = {'data': r};    
    };
    
    
    /**
     * This is the callback which is notified whenever the binary object's "readyState"
     * property changes.  This callback will map that readyState to a MicroStrategy readyState
     * and update the readyState property of this processor instance accordingly. Additinally,
     * if the callback indicates success, then this method will retrieve the response string
     * (presumably a JSON string), eval it into a javascript Object, and store it in the "response"
     * property of this instance.
     */
    XHR.prototype._onreadystatechange = function XHR_onreadystatechange()
    {
        var xhr = this._xhr,
            xhr_rs = xhr && xhr.readyState,
            enXHR = XHR.READYSTATE,
            enRS = mstr.Enum.Widget.READYSTATE;
                    
        switch (xhr_rs)
        {
            case enXHR.UNINITIALIZED:
                rs = enRS.IDLE;
                break;
                
            case enXHR.LOADING:
            case enXHR.LOADED:
            case enXHR.INTERACTIVE:
                // I believe these statuses occur as we set the inputs/URL of the xhr binary object.
                // Essentially, they indicate that we are submitting the request.
                rs = enRS.WAITING;
                break;
                
            case enXHR.COMPLETE:
                // This status indicates that an error or success response has returned. 
                rs = enRS.ERROR;
                // Examine the status property to check for success.
                if (xhr.status == 200)
                {
                    // Does the responseText contain a string? If so, eval it into JSON.
                    var txt = xhr.responseText;
                    
                    this._processResponse(txt);
                    
                    // We have a successful response.
                    rs = enRS.SUCCESS;
                    this.props['lastSuccess'] = new Date();
                    
                // Check for server errors and store the status in the response.
                } else if (xhr.status >= 400) {
                    var res = { 
                        'status' : xhr.status,
                        'statusText' : xhr.statusText
                    };
                    
                    // Try to extract the error message from the response headers (looking for task error response).
                    var headers = xhr.getAllResponseHeaders().split("\n");
                    
                    if(headers){
                        var headerJSON = {};
                        for(var i=0;i<headers.length-1;i++){
                            var header = headers[i];
                            header = header.split(": ",2);
                            headerJSON[header[0]]=header[1];
                        }

                        if (headerJSON['X-MSTR-TaskFailureMsg']) {
                            res.data = headerJSON['X-MSTR-TaskFailureMsg'];
                        }
                        if (headerJSON['X-MSTR-TaskErrorCode']) {
                            res.taskErrorCode = parseInt(headerJSON['X-MSTR-TaskErrorCode'])+0x100000000
                        }
                    }

                    this.props['response'] = res;
                }
                // Browser quirk: Now that we are done with this response, Firefox requires 
                // that we reset our binary object by calling abort.  To avoid having this
                // raise ready state changes, we'll temporarily detach the callback.
                if (!mstr.utils.ISIE4 && xhr.abort)
                {
//                    var temp = xhr.onreadystatechange;
                    xhr.abort();
//                    if (temp) xhr.onreadystatechange = temp;
                }
                break;
                
            default:
                // Unknown scenario, treat it as error.
                rs = enRS.ERROR;
                break;
        }
        this.set('readyState', rs);
    };
    
    /**
     * Informs the XHRProcessor that its request has been cancelled.
     */    
    XHR.prototype.cancel = function XHR_cancel()
    {
        if (this.props['readyState'] == mstr.Enum.Widget.READYSTATE.WAITING)
        {
            this.props['cancelled'] = true;
            if (this._xhr && this._xhr.abort) this._xhr.abort();
            this.set('readyState', mstr.Enum.Widget.READYSTATE.CANCELLED);
        }
    };
    
    /** 
     * Retrieves the "response" property of this instance, which is a JSON object 
     * eval'd from the responseText string of the xmlHttpRequest object, if any.
     */
    XHR.prototype.getResponse = function XHR_getResponse()
    {
        return this.props['response'];
    };
    
    function XHR(props)
    {
        mstr.models.BaseModel.apply(this, [props]);
    }
    
    return XHR;
    
})(); 

 /**
 * PUProcessor is also a representation of an xmlHttpRequest object, it extends the XHRProcessor
 * to handle partial updates that were originally sent as iframe request. It differs with the XHRProcessor 
 * in request parameter assembling and response handling. Those differences are reflected in the implementation 
 * below.
 */
mstr.http.PUProcessor = (function(){
    mstr.$O.extendsClass(PUP, mstr.http.XHRProcessor);
    
    /**
     *  No need to do anything here.
     */
    PUP.prototype._addEnv = function PUP_addEnv(params) {
    };
    
    /**
     * It's possible to have request parameters with idential name but multiple values. 
     * If this happens we still have a single name key with an array of values as its value.
     */
    PUP.prototype._makeArray = function PUP_makeArray(params, arr) {
       for (var n in params)
       {   
           if(typeof params[n] == 'object') {
               for (var j = 0; j < params[n].length; j++){
                    arr.push(n + '=' + encodeURIComponent(params[n][j]));
               }
           } else {
             arr.push(n + '=' + encodeURIComponent(params[n]));
           }
       }
    };
    
    PUP.prototype._getURL = function PUP_getURL(params) {
       var url = params['action'];
       params['xhr'] = 'true';
       delete params['action'];
       delete params['processorClass'];
       delete params['target'];
       return url;
    };
    
    /**
     * If the result assembled through xhr.jsp is correct, it should come in form of 
     * error html{status:... title:...} So we have to disect this streamed response into
     * parts, construct the update result on the latter half and add the error html 
     * as a value to an error object in the end.
     * If for whatever reason the evaluation fails then we should just send down the text
     * itself for further handling
     */
    PUP.prototype._processResponse = function PUP_processResponse(txt) {
       var r;
       if (txt) {
           var errorEnd = txt.indexOf('{status');
           if(errorEnd != -1) {
	           try { 
	                 eval("r = " + txt.substring(errorEnd));
 	                 if(r) r.components.push({id:"mstrWeb_error", content:txt.substring(0, errorEnd).replace(/\r\n/g, "")});
	           }
	           catch(localerr) {}
           }
           
       }
       // Store the eval'd JSON into the "response" property.
       if(r) this.props['response'] = {'data': r};
       else this.props['response'] = {'text': txt};    
    };
    
    function PUP(props)
    {
        mstr.http.XHRProcessor.apply(this, [props]);
    }
    
    return PUP;
    
})(); 

 
 // End file - http.js //
 
// Start file - behaviors.js // 
mstr.behaviors = {};
 
mstr.behaviors.featureResolver = (function(){
    
    var FR = {};
    
    FR.absorbFeatures = function FR_absorbFeatures(features){
        this.features = features;
    }
    
    FR.resolveFeatSet = function FR_resolveFeatSet(featSet,def){
        if(featSet){
            var res = true;
            var featArr = featSet.split(';');
            for(var f in featArr){
                res = res && this.featAvailable(featArr[f]);
            }
            return res;
        }else{
            return def;
 
 
 
        }
    }
    
    FR.featAvailable = function FR_featAvailable(feature){
        var resolvers = [];
        var reportBone;
        //return true;
        if(microstrategy.EXECUTION_SCOPE == microstrategy.RWD_EXECUTION){
           resolvers.push(microstrategy.getViewerBone());
        }else if(microstrategy.EXECUTION_SCOPE == microstrategy.REPORT_EXECUTION){
           reportBone = mstr.$obj('UniqueReportID');
           resolvers.push('ReportFrame',reportBone);
        } 
         
        resolvers.push(FR);
        
        //retrieve all the objects that can resolve the features
        var resIndex;
        var resolverObjs = new Array();
        for(resIndex in resolvers){
           var resolver = typeof(resolvers[resIndex]) == 'string' ? mstr.$obj(resolvers[resIndex]) : resolvers[resIndex];
           if(resolver){
               resolverObjs.push(resolver);
           }
        }
        
        if(feature.charAt(0) == 'e' && feature.charAt(1) == ':'){
            //Is an expression, evaluate and return its result
            var m=microstrategy;
            var _bone_ = reportBone;
            if(!_bone_ && feature.indexOf('_bone_')>0){return false;}
            return eval(feature.substring(2,feature.length));
        }
        
        var feat = feature, show, undef, currBone, neg;
        
        if(feature.charAt(0)=="!"){
           feat = feature.substring(1,feature.length);
           neg = true;
        }

		show = false;
		undef = true;
		for(resIndex in resolverObjs){
		    currBone = resolverObjs[resIndex];
		    if(currBone && currBone.features){
		        undef = undef && currBone.features[feat] == undefined;
		        show = show || (currBone.features[feat] != undefined && currBone.isFeatureAvailable(feat));
            }
		}
        
        show = neg ? !(undef || show) : undef || show;
        return show;
    }


    FR.isFeatureAvailable = function FR_isFeatureAvailable(name){
        var __result = true;
        if ((this.features != null) && typeof(this.features[name]) != undefined)
            __result = this.features[name];

        return __result;
    }
   
    
    return FR;

})(); 

// End file - behaviors.js // 
 
