Click here to Skip to main content
15,886,059 members
Articles / Web Development / ASP.NET

Remote Scripting

Rate me:
Please Sign up or sign in to vote.
4.85/5 (86 votes)
25 Apr 20058 min read 744.5K   6.5K   196  
Use client-side JavaScript to remotely invoke methods in ASP.NET pages.
//
//  rs.js - Remote Scripting JavaScript Include File
//  
//  Rewritten from jsrsClient.js, taken from 
//    http://www.ashleyit.com/rs/jsrs/test.htm (version 2.3)
//    Copyright (C) 2001 by Brent Ashley
//  Rewrite Copyright (C) 2005 by Alvaro Mendez
//  Last Updated: Feb. 6, 2005
//
//  Include this file inside your page, like this:
//  <script language='JavaScript' src='/scripts/rs.js'></script>
//

// This is the object to use, along with the Execute function (below)
var RS = new RemoteScripting();

// Remote Scripting class.  
// This class should NOT be instanciated -- use the global RS object. 
function RemoteScripting()
{
	this.pool = new Array();
	this.poolSize = 0;
	this.maxPoolSize = 100;
	this.usePOST = false;
	this.debug = false;

	// Sniff the browser
	if (document.layers)
		this.browser = "NS";
	else if (document.all)
	{
		var agent = navigator.userAgent.toLowerCase();
		if (agent.indexOf("opera") != -1)
			this.browser = "OPR";
		else if (agent.indexOf("konqueror") != -1)
			this.browser = "KONQ";
		else
			this.browser = "IE";
	}
	else if (document.getElementById)
		this.browser = "MOZ";
	else 
		this.browser = "OTHER";
}

// Executes a remote method found on a given URL.
// Usage: RS.Execute(url, method, p1, ... pn, callback, callbackForError, c1, c2, c3)
//		  url				: url of file containing method to invoke
//		  method			: name of the Server-side method to be invoked
//		  p1...pn			: any parameters to be passed to the Server-side method
//		  callback		    : optional JavaScript function to call on successful return.
//        callbackForError	: optional JavaScript function to call on failed return.  
//                            If not passed and an error occurs, an alert box is shown.
//        c1,c2,c3			: optional parameters to pass to the callback(s) along with the 
//                            result from the remote method.
RemoteScripting.prototype.Execute = function(url, method)
{
	var call = this.getAvailableCall();	
	var args = RemoteScripting.prototype.Execute.arguments;
	var len = RemoteScripting.prototype.Execute.arguments.length;

	var methodArgs = new Array();

	for (var i = 2; i < len; i++)
	{
		if (typeof(args[i]) == 'function')
		{
			call.callback = args[i++];		  
			
			if (i < len && typeof(args[i]) == 'function')
				call.callbackForError = args[i++];
			
			var ca = 0;
			for (; i < len; i++)
				call.callbackArgs[ca++] = args[i];
			break;
		}
		
		methodArgs[i - 2] = args[i];
	}
	
	call.showIfDebugging();

	if (this.usePOST && ((this.browser == 'IE') || (this.browser == 'MOZ')))
		call.POST(url, method, methodArgs);
	else 
		call.GET(url, method, methodArgs);
	
	return this.id;
}

// Pops up a separate window containing Debug information.
// You can attach this to the F1 key for IE with onHelp = "return RS.showDebugInfo() in the body tag.
RemoteScripting.prototype.PopupDebugInfo = function()
{
	var doc = window.open().document;
	doc.open();
	doc.write('<html><body>Pool Size: ' + this.poolSize + '<br><font face = "arial" size = "2"><b>');
	for (var i = 0; i < this.pool; i++)
	{
		var call = this.pool[i];
		doc.write('<hr>' + call.id + ' : ' + (call.busy ? 'busy' : 'available') + '<br>');
		doc.write(call.container.document.location.pathname + '<br>');
		doc.write(call.container.document.location.search + '<br>');
		doc.write('<table border = "1"><tr><td>' + call.container.document.body.innerHTML + '</td></tr></table>');
	}
	doc.write('</table></body></html>');
	doc.close();
	return false;
}

// Convenience function which parses the given HTML text filled with option tags 
// and repopulates given drop-down element with them.
RemoteScripting.prototype.ReplaceOptions = function(element, optionsHTML)
{
	// Remove any existing options 
	while (element.options.length > 0)
        element.options[0] = null;

	// Create an array of each item for the dropdown
	var options = optionsHTML.split("</option>");
    var selectIndex = 0;
    var quote = (optionsHTML.indexOf("\"") > 0 ? "\"" : "'");

	// Fill 'er up
	for (var i = 0; i < options.length - 1; i++)
	{
	    aValueText = options[i].split(">");

	    option = new Option;
	    option.text = aValueText[aValueText.length - 1];
	    
	    // Account for the possibility of a value containing a >
	    for (var e = 1; e < aValueText.length - 1; e++)
			aValueText[0] += ">" + aValueText[e];

	    // Extract the value
	    var firstQuote = aValueText[0].indexOf(quote);
	    var lastQuote = aValueText[0].lastIndexOf(quote);
	    if (firstQuote > 0 && lastQuote > firstQuote + 1)
	        option.value = aValueText[0].substring(firstQuote + 1, lastQuote);

	    // Check if it's selected
	    if (aValueText[0].indexOf('selected') > 0)
	        selectIndex = i;

	    element.options[element.options.length] = option;
	}

	element.options[selectIndex].selected = true;
}

// Convenience function which parses the given HTML text filled with option tags 
// and repopulates given drop-down element with them.
// This function is like the one above with the parameters reversed so that it 
// can be passed directly into RS.Execute.
RemoteScripting.prototype.ReplaceOptions2 = function(optionsHTML, element)
{
	RS.ReplaceOptions(element, optionsHTML);
	window.status = "";	
}

// Retrieves an available RemoteScriptingCall object from the pool.
// This function is used internally and should be treated as private.
RemoteScripting.prototype.getAvailableCall = function()
{
	for (var i = 0; i < this.poolSize; i++)
	{
		var call = this.pool['C' + (i + 1)];
		if (!call.busy)
		{
			call.busy = true;      
			return this.pool[call.id];
		}
	}
	
	// If we got here, there are no existing free calls
	if (this.poolSize <= this.maxPoolSize)
	{
		var callID = "C" + (this.poolSize + 1);
		this.pool[callID] = new RemoteScriptingCall(callID);
		this.poolSize++;
		return this.pool[callID];
	}

	alert("RemoteScripting Error: Call pool is full (no more than 100 calls can be made simultaneously).");
	return null;
}


// Remote Scripting Call class.  
// This class should NOT be instanciated -- this is used by the RS object's pool.
function RemoteScriptingCall(callID)
{
	this.id = callID;
	this.busy = true;
	this.callback = null;
	this.callbackForError = null;
	this.callbackArgs = new Array();

	switch (RS.browser)
	{
		case 'IE':
			document.body.insertAdjacentHTML("afterBegin", '<span id = "SPAN' + callID + '"></span>');
			this.span = document.all("SPAN" + callID);
			var html = '<iframe style = "width:800px" name = "' + callID + '" src = "./"></iframe>';
			this.span.innerHTML = html;
			this.span.style.display = 'none';
			this.container = window.frames[callID];
			break;
			
		case 'NS':
			this.container = new Layer(100);
			this.container.name = callID;
			this.container.visibility = 'hidden';
			this.container.clip.width = 100;
			this.container.clip.height = 100;
			break;
			
		case 'MOZ':
			this.span = document.createElement('SPAN');
			this.span.id = "SPAN" + callID;
			document.body.appendChild(this.span);
			var iframe = document.createElement('IFRAME');
			iframe.id = callID;
			iframe.name = callID;
			iframe.style.width = 800;
			iframe.style.height = 200;
			this.span.appendChild(iframe);
			this.container = iframe;
			break;
			
		case 'OPR':        
			this.span = document.createElement('SPAN');
			this.span.id = "SPAN" + callID;
			document.body.appendChild(this.span);
			var iframe = document.createElement('IFRAME');
			iframe.id = callID;
			iframe.name = callID;
			iframe.style.width = 800;
			iframe.style.height = 200;
			this.span.appendChild(iframe);
			this.container = iframe;
			break;
			
		case 'KONQ':  
		default:
			this.span = document.createElement('SPAN');
			this.span.id = "SPAN" + callID;
			document.body.appendChild(this.span);
			var iframe = document.createElement('IFRAME');
			iframe.id = callID;
			iframe.name = callID;
			iframe.style.width = 800;
			iframe.style.height = 200;
			this.span.appendChild(iframe);
			this.container = iframe;
			
			// Needs to be hidden for Konqueror, otherwise it'll appear on the page
			this.span.style.display = none;
			iframe.style.display = none;
			iframe.style.visibility = hidden;
			iframe.height = 0;
			iframe.width = 0;
	}	
}

// Posts to the given url to have it invoke the given method.
// This function is used internally and should be treated as private.
RemoteScriptingCall.prototype.POST = function(url, method, args)
{
	var d = new Date();
	var unique = d.getTime() + '' + Math.floor(1000 * Math.random());
	var doc = (RS.browser == "IE") ? this.container.document : this.container.contentDocument;
	var paramSep = (url.lastIndexOf('?') < 0 ? '?' : '&');
	doc.open();
	doc.write('<html><body>');
	doc.write('<form name="rsForm" method="post" target=""');
	doc.write('action="' + url + paramSep + 'U=' + unique + '">');
	doc.write('<input type="hidden" name="RC" value="' + this.id + '">');
	
	// func and args are optional
	if (method != null)
	{
		doc.write('<input type = "hidden" name = "M" value = "' + method + '">');
		
		if (args != null)
		{
			if (typeof(args) == "string")
			{
				// single parameter
				doc.write('<input type = "hidden" name = "P0" '
					+ 'value = "[' + this.escapeParam(args) + ']">');
			}
			else 
			{
				// assume args is array of strings
				for (var i = 0; i < args.length; i++)
				{
					doc.write('<input type = "hidden" name = "P' + i + '" '
						+ 'value = "[' + this.escapeParam(args[i]) + ']">');
				}
			} // parm type
		} // args
	} // method
	
	doc.write('</form></body></html>');
	doc.close();
	doc.forms['rsForm'].submit();
}

// Navigates to the given url to have it invoke the given method.
// This function is used internally and should be treated as private.
RemoteScriptingCall.prototype.GET = function(url, method, args)
{
	// build URL to call
	var URL = url;
	var paramSep = (url.lastIndexOf('?') < 0 ? '?' : '&');
	
	// always send call
	URL += paramSep + "RC=" + this.id;
	
	// method and args are optional
	if (method != null)
	{
		URL += "&M=" + escape(method);
		
		if (args != null)
		{
			if (typeof(args) == "string")
			{
				// single parameter
				URL += "&P0=[" + escape(args + '') + "]";
			}
			else 
			{
				// assume args is array of strings
				for (var i = 0; i < args.length; i++)
				{
					URL += "&P" + i + "=[" + escape(args[i] + '') + "]";
				}
			} // parm type
		} // args
	} // method
	
	// unique string to defeat cache
	var d = new Date();
	URL += "&U=" + d.getTime();
	
	// make the call
	switch (RS.browser)
	{
		case 'IE':
			this.container.document.location.replace(URL);
			break;
		case 'NS':
			this.container.src = URL;
			break;
		case 'MOZ':
		case 'OPR':
		case 'KONQ':
		default:
			this.container.src = '';
			this.container.src = URL; 
			break;
	}  
}

// Sets the result of the call of the remote method.
// This function is designed to be called only when the response is received.
RemoteScriptingCall.prototype.setResult = function(result)
{
	var argsCount = this.callbackArgs.length;

	if (result == true)
	{
		if (this.callback != null)
			this.callback(this.unescape(this.getPayload()), argsCount > 0 ? this.callbackArgs[0] : null, argsCount > 1 ? this.callbackArgs[1] : null, argsCount > 2 ? this.callbackArgs[2] : null);
	}
	else
	{
		if (this.callbackForError == null)
			alert(this.unescape(this.getPayload()));
		else
			this.callbackForError(this.unescape(this.getPayload()), argsCount > 0 ? this.callbackArgs[0] : null, argsCount > 1 ? this.callbackArgs[1] : null, argsCount > 2 ? this.callbackArgs[2] : null);
	}
		
	this.callback = null;
	this.callbackForError = null;
	this.callbackArgs = new Array();
	this.busy = false;	
}

// Retrieves the payload's message sent by the response.
// This function is used internally and should be treated as private.
RemoteScriptingCall.prototype.getPayload = function()
{
	switch (RS.browser)
	{
		case 'IE':
			return this.container.document.forms['rsForm']['rsPayload'].value;
		case 'NS':
			return this.container.document.forms['rsForm'].elements['rsPayload'].value;
		case 'MOZ':
			return window.frames[this.container.name].document.forms['rsForm']['rsPayload'].value; 
		case 'OPR':
			return window.frames[this.container.name].document.forms['rsForm']['rsPayload'].value; 
		case 'KONQ':
		default:
			return window.frames[this.container.name].document.getElementById("rsPayload").value;
	}  
}

// Shows (or hides) elements on the page that assist in debugging, based on RS.debug.
// This function is used internally and should be treated as private.
RemoteScriptingCall.prototype.showIfDebugging = function()
{
	var vis = (RS.debug == true);
	switch (RS.browser)
	{
		case 'IE':
			document.all("SPAN" + this.id).style.display = (vis) ? '' : 'none';
			break;
		case 'NS':
			this.container.visibility = (vis) ? 'show' : 'hidden';
			break;
		case 'MOZ':
		case 'OPR':
		case 'KONQ':
		default:
			document.getElementById("SPAN" + this.id).style.visibility = (vis) ? '' : 'hidden';
			this.container.width = (vis) ? 250 : 0;
			this.container.height = (vis) ? 100 : 0;
			break; 
	}  
}

// Converts a string to allow it to be properly passed down as a parameter to the page.
// This function is used internally and should be treated as private.
RemoteScriptingCall.prototype.escapeParam = function(str)
{
	return str.replace(/'"'/g, '\\"');
}

// Converts a string to allow it to be properly read back from the server.
// This function is used internally and should be treated as private.
RemoteScriptingCall.prototype.unescape = function(str)
{
	return str.replace(/\\\//g, "/");
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
United States United States
I've done extensive work with C++, MFC, COM, and ATL on the Windows side. On the Web side, I've worked with VB, ASP, JavaScript, and COM+. I've also been involved with server-side Java, which includes JSP, Servlets, and EJB, and more recently with ASP.NET/C#.

Comments and Discussions