/* Copyright (c) 2008 Extentech Inc. All Rights Reserved

##### Sheetster&trade; Web Application #####

This file is a part of the Sheetster&trade; Web Application

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.

If you would like to redistribute this software in a closed-source application,
dual-licensed commercial versions are available from Extentech.

For a fully supported and redistributable commercial license, 
please visit www.extentech.com or contact us at:

 sales@extentech.com
 Extentech Inc.
 1032 Irving Street #910
 San Francisco, CA 94122
 415-759-5292
 
*/
/**
	gridToolkit is a collection of general utilities for the spreadsheet.
	
	Think of them as static utility methods in java.

	@class gridToolkit general utilities for the spreadsheet.
*/
gridToolkit = Class.create();
gridToolkit.prototype = {

initialize: function(){
	this.ALPHAS = new Array("A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z","AA","AB","AC","AD","AE","AF","AG","AH","AI","AJ","AK","AL","AM","AN","AO","AP","AQ","AR","AS","AT","AU","AV","AW","AX","AY","AZ",	"BA","BB","BC","BD","BE","BF","BG","BH","BI","BJ","BK","BL","BM","BN","BO","BP","BQ","BR","BS","BT","BU","BV","BW","BX","BY","BZ",	"CA","CB","CC","CD","CE","CF","CG","CH","CI","CJ","CK","CL","CM","CN","CO","CP","CQ","CR","CS","CT","CU","CV","CW","CX","CY","CZ",	"DA","DB","DC","DD","DE","DF","DG","DH","DI","DJ","DK","DL","DM","DN","DO","DP","DQ","DR","DS","DT","DU","DV","DW","DX","DY","DZ",	"EA","EB","EC","ED","EE","EF","EG","EH","EI","EJ","EK","EL","EM","EN","EO","EP","EQ","ER","ES","ET","EU","EV","EW","EX","EY","EZ",	"FA","FB","FC","FD","FE","FF","FG","FH","FI","FJ","FK","FL","FM","FN","FO","FP","FQ","FR","FS","FT","FU","FV","FW","FX","FY","FZ",	"GA","GB","GC","GD","GE","GF","GG","GH","GI","GJ","GK","GL","GM","GN","GO","GP","GQ","GR","GS","GT","GU","GV","GW","GX","GY","GZ","HA","HB","HC","HD","HE","HF","HG","HH","HI","HJ","HK","HL","HM","HN","HO","HP","HQ","HR","HS","HT","HU","HV","HW","HX","HY","HZ",	"IA","IB","IC","ID","IE","IF","IG","IH","II","II","IK","IL","IM","IN","IO","IP","IQ","IR","IS","IT","IU","IV","IW","IX","IY","IZ",	"JA","JB","JC","JD","JE","JF","JG","JH","JI","JJ","JK","JL","JM","JN","JO","JP","JQ","JR","JS","JT","JU","JV","JW","JX","JY","JZ",	"KA","KB","KC","KD","KE","KF","KG","KH","KI","KJ","KK","KL","KM","KN","KO","KP","KQ","KR","KS","KT","KU","KV","KW","KX","KY","KZ",	"LA","LB","LC","LD","LE","LF","LG","LH","LI","LJ","LK","LL","LM","LN","LO","LP","LQ","LR","LS","LT","LU","LV","LW","LX","LY","LZ",	"MA","MB","MC","MD","ME","MF","MG","MH","MI","MJ","MK","ML","MM","MN","MO","MP","MQ","MR","MS","MT","MU","MV","MW","MX","MY","MZ",	"NA","NB","NC","ND","NE","NF","NG","NH","NI","NJ","NK","NL","NN","NN","NO","NP","NQ","NR","NS","NT","NU","NV","NW","NX","NY","NZ",	"OA","OB","OC","OD","OE","OF","OG","OH","OI","OJ","OK","OL","OM","ON","OO","OP","OQ","OR","OS","OT","OU","OV","OW","OX","OY","OZ","PA","PB","PC","PD","PE","PF","PG","PH","PI","PJ","PK","PL","PM","PN","PO","PP","PQ","PR","PS","PT","PU","PV","PW","PX","PY","PZ",	"RA","RB","RC","RD","RE","RF","RG","RH","RI","RJ","RK","RL","RM","RN","RO","RP","RQ","RR","RS","RT","RU","RV","RW","RX","RY","RZ","SA","SB","SC","SD","SE","SF","SG","SH","SI","SJ","SK","SL","SM","SN","SO","SP","SQ","SR","SS","ST","SU","SV","SW","SX","SY","SZ",	"TA","TB","TC","TD","TE","TF","TG","TH","TI","TJ","TK","TL","TM","TN","TO","TP","TQ","TR","TS","TT","TU","TV","TW","TX","TY","TZ",	"UA","UB","UC","UD","UE","UF","UG","UH","UI","UJ","UK","UL","UM","UN","UO","UP","UQ","UR","US","UT","UU","UV","UW","UX","UY","UZ",	"VA","VB","VC","VD","VE","VF","VG","VH","VI","VJ","VK","VL","VM","VN","VO","VP","VQ","VR","VS","VT","VU","VV","VW","VX","VY","VZ",	"WA","WB","WC","WD","WE","WF","WG","WH","WI","WJ","WK","WL","WM","WN","WO","WP","WQ","WR","WS","WT","WU","WV","WW","WX","WY","WZ",	"XA","XB","XC","XD","XE","XF","XG","XH","XI","XJ","XK","XL","XM","XN","XO","XP","XQ","XR","XS","XT","XU","XV","XW","XX","XY","XZ",	"YA","YB","YC","YD","YE","YF","YG","YH","YI","YJ","YK","YL","YM","YN","YO","YP","YQ","YR","YS","YT","YU","YV","YW","YX","YY","YZ",	"ZA","ZB","ZC","ZD","ZE","ZF","ZG","ZH","ZI","ZJ","ZK","ZL","ZM","ZN","ZO","ZP","ZQ","ZR","ZS","ZT","ZU","ZV","ZW","ZX","ZY","ZZ");
},


/**
	Takes an int array representing a row and column
	and formats it as a cell address.
	
	The index is one-based.
	
	[0][0] is "A1"
	[1][1] is "B2"
	[2][2] is "C3"

	@param int[] the numeric range to convert
	@return String the string representation of the range
*/
formatLocation: function(s){
	var sb = toolkit.ALPHAS[s[1]];
	sb += (parseInt(s[0])+1);
	//parent.debug("toolkit.formatLocation: " + sb);
	return sb;
},

/**
	Get the active sheet that is being worked on.  Note that this class could
	be instantiated either in the grid iframe, where we have a 'sheet' variable
	available, or outside of the iframe where we could have multiple sheets.
	
	deprecated, moving to UIMgr
**/
	getActiveSheet: function(){
		var framedoc = uiWindowing.getActiveDoc();
		if(framedoc.sheet == null){
			showStatus("Please select an active sheet.");
			return null;
		}
		return framedoc.sheet;
	},

/**
	Get the active cursor that is being worked on.  Note that this class could
	be instantiated either in the grid iframe, where we have a 'sheet' variable
	available, or outside of the iframe where we could have multiple sheets.
	
	This class is meant to be called from another Window object

	getActiveCursor: function(){
		try{
			if (cursor!=null)return cursor;
		}catch(err){
		};
		var framedoc = uiWindowing.getActiveDoc();
		if(framedoc.cursor == null){
			showError("Problem selecting active cursor.");
			return;
		}
		return framedoc.cursor;	
	},**/
	
	/**
		Takes a response from an ajax requests and identifies if it is a json
		error message being returned.  If so, show the error and return true.
	**/
	isErrorMessage: function(errorJson){
		try{
			jsonResponse = errorJson.evalJSON();
			if (jsonResponse.errormessage != null){
				parent.showError(jsonResponse.errormessage);
				return true;
			}
		}catch(ex){
			return false;
		}
		return false;
	},
	
	/**
		gets a named rule from the attached stylesheet
		
		Optional stylesheet number restricts the search for performance.
		Our automatic additions to the stylesheet are all in 0 currently
	**/
	getExternalRule: function(ruleTitle, ssNumber){
		var i, x, sht, rules;
		if(ssNumber!=null){
			sht = document.styleSheets[ssNumber];
			rules = sht.cssRules || sht.rules;
			for (i = rules.length - 1; 0 <= i; i--){
				if (rules[i].selectorText == ruleTitle){
					return rules[i];
				}
			}
		}else{
			for (x = 0; x < document.styleSheets.length; x++){
				sht = document.styleSheets[x];
				rules = sht.cssRules || sht.rules;
				
				for (i = rules.length - 1; 0 <= i; i--){
					if (rules[i].selectorText == ruleTitle){
						return rules[i];
					}
				}
			}
		}
		return null;
	},
	
	/**
		set a rule element, 
		
		This is far more efficent for setting multiple styles on an element  than
		the utlity method, changeCssRule, as it only needs to iterate the rules in the stylesheet
		once
		
		rule: an actual css rule element, get from getExternalRule()
		styles 
		
		toolkit.setExternalRule(theRule, {width: '23px'});
		($ does not work for external stylesheet rules, it will affect the wrong node)
	**/
	setExternalRule: function(rule, styles){
		if (rule != null){ 
			for (var property in styles){
				rule.style[property] = styles[property];
			}
			return true;
		}
		return false;
	},
	
	/**
	 * Change a css rule, if the rule does not exist it will be 
	 * created within the default [0] stylesheet
	 * 
	 * ruleident: the identity of the rule ".class"
	 * rule: the rule to change "border-bottom"
	 * value: new value of the rule "2px"
	 */
	changeCssRule: function(ruleident, rule, value){
		var cRule = this.getExternalRule(ruleident , 0);
		var ss = document.styleSheets[0];
		if (cRule==null){
			if (document.all&&document.getElementById){ // IE sillyness
					ss.addRule(ruleident, rule +':' + value + ';', 0);
			}else{
					ss.insertRule(ruleident + "{" + rule + ':' + value + ';' + "}", 0);
			}
		}else{
			 if(cRule.style[rule]){
				 cRule.style[rule] = value;
		     }
		}
	},

	
	/**
		Set the internal width of the data div in the cell to the width
		of the containing cell.  2 pixels are removed for padding, otherwise
		the grid can be thrown off between panes.
	**/
	setFixedWidth: function(cellDiv){
		cellDiv.setStyle({
			width: (cellDiv.parentNode.getWidth()-2) + 'px'
		});
	},
	
	/**
		Remove width constraints on the internal data div.
	**/
	removeFixedWidth: function(cellDiv){
			cellDiv.setAttribute('style', '');
	},
	
	/**
		Set the internal height of the data div in the cell to the height
		of the containing cell.
	**/
	setFixedHeight: function(cellDiv){
		cellDiv.setStyle({
			height: (cellDiv.parentNode.getWidth()-1) + 'px'
		});
	},
	
	/**
		Remove height constraints on the internal data div.
	**/
	removeFixedHeight: function(cellDiv){
			cellDiv.setAttribute('style', '');
	},

	/**
		Get the active cursor that is being worked on.  Note that this class could
		be instantiated either in the grid iframe, where we have a 'sheet' variable
		available, or outside of the iframe where we could have multiple sheets.
		
		This class is meant to be called from another Window object
	**/
	getActiveCursor: function(){
		try{
			if (cursor!=null)return cursor;
		}catch(err){
		};
		var framedoc = uiWindowing.getActiveDoc();
		if(framedoc.cursor == null){
			showError("Problem selecting active cursor.");
			return;
		}
		return framedoc.cursor;	
	},
	
	/**
		Takes a response from an ajax requests and identifies if it is a json
		error message being returned.  If so, show the error and return true.
	**/
	isErrorMessage: function(errorJson){
		try{
			jsonResponse = errorJson.evalJSON();
			if (jsonResponse.errormessage != null){
				parent.showError(jsonResponse.errormessage);
				return true;
			}
		}catch(ex){
			return false;
		}
		return false;
	},

	
	/** get the int value of the Excel-style Column alpha representation.
	
		@param String column name
		@return int the 0-based column number
	*/
	getIntVal: function(c){
		c = c.toUpperCase();
		var x = 0,y = 0;
		if(c == 'AA')return 26;
		if(c.length > 2)return -1;
		if(c.length == 2){
			var c1 = c.charAt(1);
			var c0 = c.charAt(0);
			while(c1 != this.ALPHAS[y++]){
				// loop until we hit it                        
			}
			while(c0 != this.ALPHAS[x++]){
				// loop until we hit it
			}
			x *= 26; // we hit x, now multiply by 26 -- gives us the val of first char
		} else if(c.length == 1){
			var c0 = c.charAt(0);
			while(c0 != this.ALPHAS[y++]){
				// loop until we hit it            
			}
		}
		y--;
		return x+y;
	},

	/** returns the row and column ints from
		an Excel-style address
		
		@param String the cell address (ie: "G23")
		@return int[] the row and column indexes of the cell address
	*/
	getRowColFromString: function(address){
		if(address==null)return;
		
		// strip '$' signs if any
		address = address.replace(/$/g,'');
		// alert('gridToolkit.getRowColFromString:' + address);
		
		var row = 0, col = 0, numpos = 0;
		for(var i = 0;i<address.length;i++){
			if((address.charCodeAt(i)>47)&&(address.charCodeAt(i)<58)){ // numerics are within a small Unicode range
				numpos = i;
				break;
			} 
		}
		var colval = address.substring(0,numpos);
		col = this.getIntVal(colval);
		row = parseInt(address.substring(numpos));
		var ret = new Array(row - 1, col);
		return ret;
	},


	/**
		takes a cell (ie C4) and adjusts its id so it becomes C5.
		also handles moving the internal
	**/
	incrementCellRowNum: function(theCell){
		var rc = this.getRowColFromString(theCell.getAttribute('id'));
		if (rc!=null){
			rc[0] += 1;
			var nAddr = toolkit.formatLocation(rc);  
			theCell.setAttribute('id', nAddr);
			var innerCell = theCell.firstDescendant();
			if (innerCell!=null && innerCell.getAttribute('id').indexOf('cont')!=-1){
				innerCell.setAttribute('id', 'cont' + nAddr);
				if(rc[1]==0){
					innerCell.removeClassName('rowheight' + (rc[0]));
				    innerCell.addClassName('rowheight' + (rc[0]+1));
				}
			}
		}
	},
	
	
	/**
		takes a cell (ie C4) and adjusts its id so it becomes D4.
	**/
	incrementCellColNum: function(theCell){
		var rc = this.getRowColFromString(theCell.getAttribute('id'));
		if (rc!=null){
			rc[1] += 1;
			var nAddr = toolkit.formatLocation(rc);  
			theCell.setAttribute('id', nAddr);
			var innerCell = theCell.firstDescendant();
			if (innerCell!=null && innerCell.getAttribute('id').indexOf('cont')!=-1){
				innerCell.setAttribute('id', 'cont' + nAddr);
				innerCell.removeClassName('colwidth' + this.ALPHAS[rc[1]-1]);
				innerCell.addClassName('colwidth' + this.ALPHAS[rc[1]]);
			}
		}
	},

	/**
		takes a cell (ie C4) and adjusts its id so it becomes C3.
	**/
	decrementCellRowNum: function(theCell){
		var rc = this.getRowColFromString(theCell.getAttribute('id'));
		if (rc!=null){
			rc[0] -= 1;
			var nAddr = toolkit.formatLocation(rc);  
			theCell.setAttribute('id', nAddr);
			var innerCell = theCell.firstDescendant();
			if (innerCell!=null && innerCell.getAttribute('id').indexOf('cont')!=-1){
				innerCell.setAttribute('id', 'cont' + nAddr);
				innerCell.removeClassName('rowheight' + (rc[0]+2));
				innerCell.addClassName('rowheight' + (rc[0]+1));
			}
		}
	},

	/**
		takes a cell (ie C4) and adjusts its id so it becomes C3.
	**/
	decrementCellColNum: function(theCell){
		var rc = this.getRowColFromString(theCell.getAttribute('id'));
		if (rc!=null){
			rc[1] -= 1;
			var nAddr = toolkit.formatLocation(rc);  
			theCell.setAttribute('id', nAddr);
			var innerCell = theCell.firstDescendant();
			if (innerCell!=null && innerCell.getAttribute('id').indexOf('cont')!=-1){
				innerCell.setAttribute('id', 'cont' + nAddr);
				innerCell.removeClassName('colwidth' + this.ALPHAS[rc[1]+1]);
				innerCell.addClassName('colwidth' + this.ALPHAS[rc[1]]);
			}
		}
	},
	
	/**
		Returns the cell (td element, extended by prototype) currently
		under the cursor.
		
		Pass x and y from your event, startingCell is an optional param
		that has the cursor first check the 8 cells surrounding the previously
		selected cell
	**/
	getCellUnderCursor: function(cursorX, cursorY, startingCell){
		var theCell = null;
		/* problematic ie search, works fine other way
		if(is.ie){
			//parent.debug('running IE search');
			var theElement = document.elementFromPoint(cursorX, cursorY);
			if(theElement){
				if(theElement.getAttribute('id') != 'cursor' && theElement.getAttribute('id') != 'transMultiCellSelect'){
					return Element.extend(theElement);
				}
			}
			
		}	*/
		var offsets = $('A1').cumulativeScrollOffset();
		if (startingCell){
			//parent.debug('running optimized cell search');
			// it's in a new location, search the 8 cells bordering this one for it's new spot
			var rowCol = toolkit.getRowColFromString(startingCell.id);
			var tmpAddress;
			rowCol[1]-=1;
			rowCol[0]-=1;
			try{
				if(rowCol[1]>-1 && rowCol[0]>-1 && Position.within($(toolkit.formatLocation(rowCol)), cursorX + offsets[0], cursorY + offsets[1])){
					theCell = $(toolkit.formatLocation(rowCol));
					//dlog('upperleft');
				}
				rowCol[1]+=1;
				if(!theCell && rowCol[0]>-1 && Position.within($(toolkit.formatLocation(rowCol)), cursorX + offsets[0], cursorY + offsets[1])){
					theCell = $(toolkit.formatLocation(rowCol));
					//dlog('uppermiddle');
				}
				rowCol[1]+=1;
				if(!theCell && rowCol[0]>-1 && Position.within($(toolkit.formatLocation(rowCol)), cursorX + offsets[0], cursorY + offsets[1])){
					theCell = $(toolkit.formatLocation(rowCol));
					//dlog('upperright');
				}
				rowCol[0]+=1;
				if(!theCell && Position.within($(toolkit.formatLocation(rowCol)), cursorX + offsets[0], cursorY + offsets[1])){
					theCell = $(toolkit.formatLocation(rowCol));
					//dlog('right');
				}
				rowCol[1]-=2;
				if(!theCell && rowCol[1]>-1 && Position.within($(toolkit.formatLocation(rowCol)), cursorX + offsets[0], cursorY + offsets[1])){
					theCell = $(toolkit.formatLocation(rowCol));
					//dlog('left');
				}
				rowCol[0]+=1;
				if(!theCell &&  rowCol[1]>-1 && Position.within($(toolkit.formatLocation(rowCol)), cursorX + offsets[0], cursorY + offsets[1])){
					theCell = $(toolkit.formatLocation(rowCol));
					//dlog('bottomleft');
				}
				rowCol[1]+=1;
				if(!theCell && Position.within($(toolkit.formatLocation(rowCol)), cursorX + offsets[0], cursorY + offsets[1])){
					theCell = $(toolkit.formatLocation(rowCol));				
					//dlog('lowermiddle');
				}
				rowCol[1]+=1;
				if(!theCell && Position.within($(toolkit.formatLocation(rowCol)), cursorX + offsets[0], cursorY + offsets[1])){
					theCell = $(toolkit.formatLocation(rowCol));
					//dlog('lowerright');
				}
			}catch(err){};// errored off the side of the grid, ignore
		}
		//OK  bummmer,  cursor moved too quickly and event didn't fire in the 
		// neighboring cells,  old-school it and iterate the cells.  Much slower,
		// but shouldn't hit too often.  // probably could use some positioning
		// logic here to not check *all* the cells.
		if(!theCell){
			//dLog('missed capturing cell, going slow mode');
			var theTable = $('spreadsheetTable');
			for (var i=0;i<(theTable.rows.length);i++){
				try{			
					if((theTable.rows[i]!=null)&&
					(theTable.rows[i]!=undefined)&&
					(theTable.rows[i].cells!=null)){		// guard
						for (var x=0;x<theTable.rows[i].cells.length;x++){
								if(Position.within($(theTable.rows[i].cells[x].id), cursorX + offsets[0], cursorY + offsets[1])){
									theCell = $(theTable.rows[i].cells[x].id);
									x=theTable.rows[i].cells.length;
									i=theTable.rows.length; //double-break essentially
								}
						}
					}
				}catch(err){};
			}
		}
		return theCell;	
					
	},

	/**
		Returns the target element from an event
	**/
	getEventTarget: function(evt) {
	       evt = (evt) ? evt : ((window.event) ? window.event : null);
		   if (evt) {
				var target = (evt.target) ? evt.target : evt.srcElement;
				if (target) {
				 		 return Element.extend(target);			 		
				}
		 	}
		 	return null;
	},
	
	/**
		Deletes a css rule from the collection of rules
		
		returns true if rule found, false if rule not found
	**/
	deleteCSSRule: function(ruleName){
		ruleName = ruleName.toLowerCase();
		 for (var i=0; i<document.styleSheets.length; i++) { 
	         var styleSheet=document.styleSheets[i];         
	         var ii=0;                                       
	         var cssRule=false;                              
	         do {                                            
	            if (styleSheet.cssRules) {         
	               cssRule = styleSheet.cssRules[ii];         
	            } else {                                    
	               cssRule = styleSheet.rules[ii];          
	            }                                          
	            if (cssRule)  {                              
	            	var currName = cssRule.selectorText.toLowerCase();
	            	currName = currName.substring(currName.indexOf('.'), currName.length);
	               if (currName==ruleName) { 
	                     if (styleSheet.cssRules) {       
	                        styleSheet.deleteRule(ii);      
	                     } else {                          
	                        styleSheet.removeRule(ii);    
	                     }                                  
	                     return true;                                    
	               }                                        
	            }                                           
	            ii++;                                   
	         } while (cssRule)                              
	      }
	      return false;                              
	},
	
	/**
		Greys out the screen
	**/
	greyOut: function(trueFalse) {
		
		if(is.iphone || is.ipad) // do not do for iphone...
			return;
			
	  	var dark=$('darkenScreen');
		if (trueFalse) {
    		var msx = "<br><br><br><br><br><br><br><br><br><br><div class='title' align='center'>Loading...<br/><img src='/themes/images/loading.gif' align='absmiddle'></div>";
			
			if(is.ie){
				msx += '<div class="tinyFNT" align="center">';
				msx += '<a href="http://www.mozilla.com/en-US/firefox/" target="_new">';
				msx += '<img src="/themes/images/3rdParty/ff.gif" border="0" align="center"/>';
				msx += '<br/>Slow Loading? Upgrade to Firefox 3 for Best Results</a></div>';
			}
			dark.innerHTML = msx;
	    	dark.show();                          
	  	} else {
    		dark.hide();
  		}
	}
}

/// End of prototype object
/**
	 Register global responders that will occur on all AJAX requests
**/
var globalHandlers = {
	onCreate: function(request) {
			request.url = encodeSheetsterURI(request.url);
			request['timeoutId'] = window.setTimeout(
			function() {
				// If we have hit the timeout and the AJAX request is active, abort it and let the user know
				if (callInProgress(request.transport)) {
					request.transport.abort();
					showFailureMessage();
					// Run the onFailure method if we set one up when creating the AJAX object
					if (request.options['onFailure']) {
						request.options['onFailure'](request.transport, request.json);
					}
				}
			},
			15000 // fifteen seconds
		);
	},
	onComplete: function(request) {
		window.clearTimeout(request['timeoutId']);
	}
}

// TODO: investigate timeouts on long-running sheet loading
Ajax.Responders.register(globalHandlers);

function callInProgress (xmlhttp) {
		switch (xmlhttp.readyState) {
		case 1: case 2: case 3:
		return true;
		break;
		// Case 4 and 0
		default:
		return false;
		break;
	}
}
function showFailureMessage() {
	parent.showError("A communications error with the server occurred.  Please save your work and restart the worksheet");
}

/**
	Javascript encoding,  Need to extend the standard as escape() does not	
	handle the plus sign or a slash.  This is currently only needed for formulas, so 
	look for the /= pattern to identify
	
	Perform a normal escape on the URI, and a component escape on the last (value) section of the
	uri to handle special characters (such as +, /, etc);
	
	
**/
function encodeSheetsterURI(unescapedStr){
	var lastSlash = unescapedStr.indexOf('/=')+1;
	if (lastSlash<1){ // not a formula
		if(unescapedStr.indexOf("%") > 0){  // already encoded  don't encode if so!
			return unescapedStr;
		}else{
			return encodeURI(unescapedStr);
		}
	}
	var firstString = unescapedStr.substring(0, lastSlash);
	var lastString = unescapedStr.substring(lastSlash);
	firstString = encodeURI(firstString);
	lastString = encodeURIComponent(lastString);
	return firstString + lastString;	
}
	

