Common PostbackManager
Common_PostbackManager_CSharp\PostbackManager_CSharp\JavaScript\PostbackManager.js
 Copyright 2010 ESRI
 
 All rights reserved under the copyright laws of the United States
 and applicable international laws, treaties, and conventions.
 
 You may freely redistribute and use this sample code, with or
 without modification, provided you include the original copyright
 notice and use restrictions.
 
 See <a href="http://help.arcgis.com/en/sdk/10.0/usageRestrictions.htm">the use restrictions</a>.
 

// Register the namespace for the control.
Type.registerNamespace('ESRI.ADF.Samples');

// === PostbackManager ASP.NET AJAX Client Control ===


ESRI.ADF.Samples._PostbackManager = function(element) {
    /// <summary>The PostbackManager control allows developers to intercept requests made by Web ADF
    /// controls and provides a convenient method of asynchronously passing data to and from the server.
    /// </summary>
    ESRI.ADF.Samples._PostbackManager.initializeBase(this, [element]);

    // Unique ID of the web tier PostbackManager
    this._uniqueID = null;
    // Syntax for invoking an asynchronous request to the web tier PostbackManager
    this._callbackFunctionString = null;
    // Web ADF function for invoking a partial postback
    this._esriPostbackOriginal = null;
    // Web ADF function for invoking a callback
    this._esriCallbackOriginal - null;
    // Tracks whether callbacks or partial postbacks are being used for asynchronous functionality
    this._callbackMode = false;
    // Whether events will be exposed that allow attaching to asynchronous requests made by Web ADF
    // controls.  If true, all asynchronous requests are routed through the PostbackManager server
    // control.
    this._exposeAdfRequestEvents = true;
};

ESRI.ADF.Samples._PostbackManager.prototype = {

    // === Public Functions ===

    initialize: function() {
        /// <summary>Initializes the PostbackManager control.  Fires when the control is instantiated via $create.</summary>
        ESRI.ADF.Samples._PostbackManager.callBaseMethod(this, 'initialize');

        // No more initialization needs to be done if ADF requests won't be exposed via events
        if (!this._exposeAdfRequestEvents)
            return;

        // Store the Web ADF's partial postback invocation function in a private property and override it
        // with the control's _doPostback method.
        this._esriPostBackOriginal = ESRI.ADF.System.__DoPostBack;
        __esriDoPostBack = ESRI.ADF.System.__DoPostBack = Function.createDelegate(this, this._doPostback);

        // Store the Web ADF's callback invocation function in a private property and override it with the 
        // control's _doPostback method.
        this._esriCallbackOriginal = ESRI.ADF.System._doCallback;
        ESRI.ADF.System._doCallback = Function.createDelegate(this, this._doCallback);

        // If the callback framework is being used (i.e. no ScriptManager on the page), call method to
        // modify Web ADF client controls such that their callback invocations all go through
        // PostbackManager's _doCallback function.  Also, set the private member indicating that
        // callbacks are being used
        if (!Sys.WebForms || !Sys.WebForms.PageRequestManager) {
            this._redirectAdfCallbackInvocations();
            this._callbackMode = true;
        }
    },

    dispose: function() {
        /// <summary>Disposes the PostbackManager instance.</summary>
        ESRI.ADF.Samples._PostbackManager.callBaseMethod(this, 'dispose');
    },

    doAsyncRequest: function(argument, context) {
        /// <summary>Issues an asynchronous callback or partial postback to the server</summary>
        /// <param name="argument" type="String">The argument passed back to the server control</param>
        /// <param name="context" type="String">The context of the request</param>
        argument += String.format("&ControlID={0}", this.get_id());
        ESRI.ADF.System._doCallback(this._callbackFunctionString, this.get_uniqueID(), this.get_id(), argument, context);
    },


    // === Public Properties ===

    get_uniqueID: function() {
        /// <value type="String">Unique ID of the PostbackManager server control instance that is 
        // associated with the client control instance</value>
        return this._uniqueID;
    },

    set_uniqueID: function(value) {
        this._uniqueID = value;
    },

    get_callbackFunctionString: function() {
        /// <value type="String">Syntax for invoking an asynchronous request to the PostbackManager 
        // server control</value>
        return this._callbackFunctionString;
    },

    set_callbackFunctionString: function(value) {
        this._callbackFunctionString = value;
    },

    get_exposeAdfRequestEvents: function() {
        /// <value type="Boolean">Determines whether or not to expose events that fire whenever an 
        /// asynchronous request is initiated by a Web ADF control</value>
        return this._exposeAdfRequestEvents;
    },

    set_exposeAdfRequestEvents: function(value) {
        this._exposeAdfRequestEvents = value;
    },


    // === Events ===

    add_invokingRequest: function(handler) {
        /// <summary>Raised when a Web ADF component invokes an asynchronous server request</summary>
        this.get_events().addHandler('invokingRequest', handler);
    },

    remove_invokingRequest: function(handler) {
        this.get_events().removeHandler('invokingRequest', handler);
    },

    add_requestProcessed: function(handler) {
        /// <summary>Raised when a reponse is returned to the client after a Web ADF component has
        /// issued an asynchronous server request</summary>
        this.get_events().addHandler('requestProcessed', handler);
    },

    remove_requestProcessed: function(handler) {
        this.get_events().removeHandler('requestProcessed', handler);
    },


    // === Private Functions ===

    // Fires handlers attached to the event of the passed in name
    _raiseEvent: function(name, e) {
        // Get the handler for the event.  Note that the ASP.NET AJAX framework combines multiple 
        // handlers into one function
        var handler = this.get_events().getHandler(name);
        // Make sure a handler exists
        if (handler) {
            // Create an empty arguments object if no event arguments were passed-in 
            if (e === null || typeof (e) === 'undefined') {
                e = Sys.EventArgs.Empty;
            }
            // Fire the handler
            handler(this, e);
        }
    },

    // Issues a partial postback to the PostbackManager server control instance.  Note that, when
    // partial postbacks are being used, some controls issue requests via _doCallback before executing
    // _doPostback.
    _doPostback: function(uniqueID, clientID, argument, clientCallback, context) {
        // Check whether callbacks or partial postbacks are being used for asynchronous functionality
        if (!this._callbackMode) {
            // Package arguments and raise the invokingRequest event
            var args = { 'requestArgument': argument, 'callingControlID': clientID };
            this._raiseEvent('invokingRequest', args);

            // Update the argument to be passed to the server with that passed to the invokingRequest
            // argument.  Necessary in case the argument was modified in invokingRequest handlers.
            argument = args.requestArgument;

            // Add the ID of the control initiating the request to the argument string
            if (argument.indexOf('ControlID=') == -1)
                argument += String.format('&ControlID={0}', clientID);

            // Replace the passed-in control IDs with the PostbackManager's IDs to reroute the program
            // flow through the PostbackManager when the request reaches the server.
            clientID = this.get_id();
            uniqueID = this.get_uniqueID();
        }

        // Invoke the Web ADF's postback function to issue the request
        this._esriPostBackOriginal(uniqueID, clientID, argument, clientCallback, context);
    },

    // Issues a callback to the PostbackManager server control instance
    _doCallback: function(callbackFunctionString, uniqueID, clientID, argument, context) {
        // If a control ID was not specified, try to extract it from callbackFunctionString
        if (!clientID) {
            var startIndex = callbackFunctionString.indexOf("'") + 1;
            var stopIndex = callbackFunctionString.indexOf("'", startIndex + 1);
            clientID = callbackFunctionString.substring(startIndex, stopIndex);

            clientID = clientID.replace(/\$/g, '_');
            if (!$get(clientID))
                clientID = null;
        }

        // Package arguments and raise the invokingRequest event
        var args = { 'requestArgument': argument, 'callingControlID': clientID };
        // Update the argument to be passed to the server with that passed to the invokingRequest
        // argument.  Necessary in case the argument was modified in invokingRequest handlers.
        this._raiseEvent('invokingRequest', args);
        argument = args.requestArgument;

        // Check whether a control ID was found.  If not, do not redirect the request.
        if (clientID) {

            // Add the ID of the control initiating the request to the argument string
            if (argument.indexOf('ControlID=') == -1)
                argument += String.format('&ControlID={0}', clientID);

            // Replace the passed-in control IDs and callback invocation with those of the 
            // PostbackManager to reroute the program flow through the PostbackManager when the 
            // request reaches the server.
            callbackFunctionString = this.get_callbackFunctionString();
            clientID = this.get_id();
            uniqueID = this.get_uniqueID();
        }

        // Invoke the Web ADF's postback function to issue the request
        this._esriCallbackOriginal(callbackFunctionString, uniqueID, clientID, argument, context);
    },

    // Modifies the definitions of Web ADF client controls so they call PostbackManager._doCallback 
    // when making asynchronous requests
    _redirectAdfCallbackInvocations: function() {
        var originalInvocation = 'eval(this._callbackFunctionString)';
        var newInvocation = 'ESRI.ADF.System._doCallback(this._callbackFunctionString, ' +
                'this.get_uniqueID(), this.get_element().id, argument, context)';

        if (ESRI.ADF.UI.TreeViewPlus) {
            ESRI.ADF.UI.TreeViewPlus.prototype._nodeChecked =
                this._modifyFunction(ESRI.ADF.UI.TreeViewPlus.prototype._nodeChecked,
                originalInvocation, newInvocation);
            ESRI.ADF.UI.TreeViewPlus.prototype._nodeClicked =
                this._modifyFunction(ESRI.ADF.UI.TreeViewPlus.prototype._nodeClicked,
                originalInvocation, newInvocation);
            ESRI.ADF.UI.TreeViewPlus.prototype._clearNode =
                this._modifyFunction(ESRI.ADF.UI.TreeViewPlus.prototype._clearNode,
                originalInvocation, newInvocation);
            ESRI.ADF.UI.TreeViewPlus.prototype._nextPage =
                this._modifyFunction(ESRI.ADF.UI.TreeViewPlus.prototype._nextPage,
                originalInvocation, newInvocation);
            ESRI.ADF.UI.TreeViewPlus.prototype._previousPage =
                this._modifyFunction(ESRI.ADF.UI.TreeViewPlus.prototype._previousPage,
                originalInvocation, newInvocation);
            ESRI.ADF.UI.TreeViewPlus.prototype._clearAllNodes =
                this._modifyFunction(ESRI.ADF.UI.TreeViewPlus.prototype._clearAllNodes,
                originalInvocation, newInvocation);
            ESRI.ADF.UI.TreeViewPlus.prototype._nodeLegendClicked =
                this._modifyFunction(ESRI.ADF.UI.TreeViewPlus.prototype._nodeLegendClicked,
                originalInvocation, newInvocation);
            ESRI.ADF.UI.TreeViewPlus.prototype._toggleNodeState =
                this._modifyFunction(ESRI.ADF.UI.TreeViewPlus.prototype._toggleNodeState,
                originalInvocation, newInvocation);
        }

        if (ESRI.ADF.UI.TaskResults) {
            ESRI.ADF.UI.TaskResults.prototype._rerunTask2 =
                this._modifyFunction(ESRI.ADF.UI.TaskResults.prototype._rerunTask2,
                originalInvocation, newInvocation);
        }

        if (ESRI.ADF.UI.FloatingPanel) {
            ESRI.ADF.UI.FloatingPanel.prototype.hide =
                this._modifyFunction(ESRI.ADF.UI.FloatingPanel.prototype.hide,
                originalInvocation, newInvocation);
            ESRI.ADF.UI.FloatingPanel.prototype.show =
                this._modifyFunction(ESRI.ADF.UI.FloatingPanel.prototype.show,
                originalInvocation, newInvocation);
            ESRI.ADF.UI.FloatingPanel.prototype.toggleState =
                this._modifyFunction(ESRI.ADF.UI.FloatingPanel.prototype.toggleState,
                originalInvocation, newInvocation);
        }

        if (ESRI.ADF._Tasks) {
            originalInvocation = 'eval(callbackFunctionString)';
            newInvocation = "ESRI.ADF.System._doCallback(callbackFunctionString, " +
                "null, null, argument, context)";
            startActivityIndicator = ESRI.ADF._Tasks.prototype.startActivityIndicator =
                this._modifyFunction(ESRI.ADF._Tasks.prototype.startActivityIndicator,
                originalInvocation, newInvocation);
            startJob = ESRI.ADF._Tasks.prototype.startJob =
                this._modifyFunction(ESRI.ADF._Tasks.prototype.startJob,
                originalInvocation, newInvocation);
        }

        if (typeof(esriHideContextMenu) != 'undefined' && typeof(esriContextMenuItemClicked) != 'undefined') {
            originalInvocation = 'eval(esriContextMenu.callbackFunctionString)';
            newInvocation = "ESRI.ADF.System._doCallback(esriContextMenu.callbackFunctionString, " +
                "null, null, argument, context)";
            esriHideContextMenu = this._modifyFunction(esriHideContextMenu,
                originalInvocation, newInvocation);
            esriContextMenuItemClicked = this._modifyFunction(esriContextMenuItemClicked,
                originalInvocation, newInvocation);
        }
    },

    // Replaces the specified code in the passed-in function with the passed-in new code and returns the
    // updated function
    _modifyFunction: function(originalFunction, codeToReplace, newCode) {
        // Get a string representation of the passed-in function
        var oldFunction = String(originalFunction);
        // Change the code as specified
        var newFunction = oldFunction.replace(codeToReplace, newCode);

        // Get the indexes of parentheses for parameter name retrieval
        var startIndex = newFunction.indexOf('(') + 1;
        var stopIndex = newFunction.indexOf(')');

        // Retrieve the function's parameter names 
        var parameters = [];
        if (stopIndex > startIndex) {
            var parameterString = newFunction.substring(startIndex, stopIndex);
            var parameters = parameterString.split(',');
        }

        // Extract the body of the function
        var newFunctionBody = newFunction.substring(newFunction.indexOf('{') + 1, newFunction.lastIndexOf('}'));
        
        // Return a new function instance with the extracted parameters and function body
        return new Function(parameters, newFunctionBody);
    }
}

// Register the PostbackManager, specifying inheritance from the ASP.NET AJAX client control base class
ESRI.ADF.Samples._PostbackManager.registerClass('ESRI.ADF.Samples._PostbackManager', Sys.UI.Control);