Common_CustomTasks_CSharp\ScriptTask_CSharp\ScriptTask.cs
// 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 the use restrictions. // using System.Web.UI; using System; namespace ESRI.ADF.Samples.CustomTasks { /// <summary> /// When implemented, this class automatically creates a client-side representation of your custom task. /// Any method or control included in the task can be made available on the client-side class by /// adding the ClientMember attribute to the member declaration. The client-side class is created as /// an AJAX control with a component ID matching the task's client ID, and can therefore be retrieved /// by a call to $find(task client ID). /// </summary> public abstract class ScriptTask : ESRI.ArcGIS.ADF.Web.UI.WebControls.FloatingPanelTask { #region ASP.NET WebControl Life Cycle Event Overrides - OnPreRender, Render // Declares the client-side task class and its members protected override void OnPreRender(System.EventArgs eventArgs) { base.OnPreRender(eventArgs); // Instantiate a StringBuilder to hold the initialization JavaScript System.Text.StringBuilder initializeClientTaskScript = new System.Text.StringBuilder(); // Get the type of the task (sub-class) implementing ScriptTask System.Type subClassType = this.GetType(); // Check whether the ClientTaskDefinitionExplicit custom attribute was declared on the // sub-class. If so, the attribute instance will be passed to the array. object[] definitionExplicitArray = subClassType.GetCustomAttributes(typeof(ClientTaskDefinitionExplicit), false); // Check whether the ClientTaskJavaScriptResource custom attribute was declared on the // sub-class. If so, the attribute instance will be passed to the array. object[] javaScriptResourceArray = subClassType.GetCustomAttributes(typeof(ClientTaskJavaScriptResource), false); // If neither array contains any attributes, then the client task class will need to be // created (i.e. declared) here. Set a boolean indicating whehter or not this is the case. bool createClientClass = (definitionExplicitArray.Length == 0) && (javaScriptResourceArray.Length == 0); // Check whether the ClientTaskClassName custom attribute was declared on the // sub-class. If so, the attribute instance will be passed to the array. object[] clientClassNameArray = subClassType.GetCustomAttributes(typeof(ClientTaskClassName), false); if (clientClassNameArray.Length > 0) { // Store the class name specified in the attribute declaration ClientTaskClassName clientClassNameAttribute = clientClassNameArray[0] as ClientTaskClassName; this.ClientClassName = clientClassNameAttribute.ClassName; } else { // Set the client class name to match that of the server-side task this.ClientClassName = this.GetType().ToString(); } if (createClientClass) { // The sub-class has not explicitly declared a client-side task class (default). Generate // JavaScript to declare the class and register it as an AJAX control inheriting from // ESRI.ADF.UI.FloatingPanel. string classDeclarationScript = CreateClassDeclarationScript(); // Add the class declaration to the initialization script initializeClientTaskScript.Append(classDeclarationScript); } else { // Initialize a boolean indicating whether a script resource needs to be registered for the // client-side task bool registerScriptResource = true; // Check whether the class was declared with a ClientTaskJavaScriptResource attribute if (javaScriptResourceArray.Length > 0) { // Get the path specified by the ClientTaskJavaScriptResource ClientTaskJavaScriptResource javaScriptResourceAttribute = javaScriptResourceArray[0] as ClientTaskJavaScriptResource; this.JavaScriptResourceName = javaScriptResourceAttribute.ResourceName; } else { // Create a default resource name from the sub-class's task name and the JavaScript file // extension this.JavaScriptResourceName = string.Format("{0}.js", this.GetType().ToString()); // Attempt to retrieve an object containing information about the resource. If the // resource does not exist, this will return null. System.Reflection.ManifestResourceInfo resourceInfo = subClassType.Assembly.GetManifestResourceInfo(this.JavaScriptResourceName); // VB may not prepend the namespace to the resource, so just retrieve using the basic resource name. if (resourceInfo == null) { this.JavaScriptResourceName = string.Format("{0}.js", this.GetType().Name.ToString()); resourceInfo = subClassType.Assembly.GetManifestResourceInfo(this.JavaScriptResourceName); } // Based on whether the resource was found, set a boolean indicating whether the resource // should be registered registerScriptResource = (resourceInfo != null); } // Register the client script resource, if necessary if (registerScriptResource) { if (this.ScriptManager != null) { ScriptReference scriptRef = new ScriptReference(this.JavaScriptResourceName, subClassType.Assembly.FullName); this.ScriptManager.Scripts.Add(scriptRef); } else { System.Web.UI.ScriptManager.RegisterClientScriptResource(this, subClassType, this.JavaScriptResourceName); } } } // Generate JavaScript that will add server-side members declared with the custom ClientMember // attribute to the client-side task. string addClientMembersScript = CreateAddClientMembersScript(); // Add the member-adding logic to the intialization script initializeClientTaskScript.Append(addClientMembersScript); // Enclose the initialization script in a function declaration and add the function as a handler // of the AJAX init event, so the logic executes during application initialization string classInitFunctionName = string.Format("{0}_initializeClientTaskMembers", subClassType.Name); initializeClientTaskScript.Insert(0, "function " + classInitFunctionName + "() {"); initializeClientTaskScript.Append("} Sys.Application.add_init(" + classInitFunctionName + ");"); string scriptKey = string.Format("initialize{0}", subClassType.ToString()); if (!this.Page.ClientScript.IsStartupScriptRegistered(scriptKey)) { // Register the initialization script with the client System.Web.UI.ScriptManager.RegisterStartupScript(this, this.GetType(), scriptKey, initializeClientTaskScript.ToString(), true); } } // Instantiates the client-side task protected override void Render(System.Web.UI.HtmlTextWriter writer) { base.Render(writer); string instatiationFuncName = string.Format("create{0}", this.UniqueID); // FloatingPanelTask, from which ScriptTask inherits, has already instantiated a client-side // ESRI.ADF.UI.FloatingPanel with an AJAX component ID that matches the task's client ID. // Since the client-side script task inherits from ESRI.ADF.UI.FloatingPanel, we can replace // the already instantiated FloatinPanel with a an instance of our script task. First, we // must retrieve the existing FloatingPanel and copy its initialization properties. Then, // the FloatingPanel must be disposed. Finally, we instantiate the client-side script task // via the AJAX $create function, specifying the client ID of the ScriptTask's sub-class and // the properties of the disposed FloatingPanel. // // NOTE: this step must be performed after base.Render because that is where the client-side // FloatingPanel is instantiated. string createClientTaskScript = @"function {0}() {{ var floatingPanel = $find('{1}'); var properties = {{ id:'{1}', ""transparency"":floatingPanel.get_transparency(), ""callbackFunctionString"":floatingPanel.get_callbackFunctionString(), ""expandedImage"":floatingPanel.get_expandedImage(), ""collapsedImage"":floatingPanel.get_collapsedImage(), ""dockedImage"":floatingPanel.get_dockedImage(), ""undockedImage"":floatingPanel.get_undockedImage(), ""dockedContextMenuID"":floatingPanel.get_dockedContextMenuID(), ""dockParent"":floatingPanel.get_dockParent(), ""forcePNG"":floatingPanel.get_forcePNG(), ""isDocked"":floatingPanel.get_isDocked() }}; floatingPanel.dispose(); $create({2}, properties, null, null, $get('{1}')); }} Sys.Application.add_init({0});"; createClientTaskScript = string.Format(createClientTaskScript, instatiationFuncName, this.ClientID, this.ClientClassName); System.Web.UI.ScriptManager.RegisterStartupScript(this, this.GetType(), this.ClientID + "_createClientTask", createClientTaskScript, true); string addClientFieldsScript = GetAddClientFieldsScript(); System.Web.UI.ScriptManager.RegisterStartupScript(this, this.GetType(), this.ClientID + "_addClientFields", addClientFieldsScript, true); } private string GetAddClientFieldsScript() { // Instantiate a filter that will be used to find members of the sub-class with the // ClientMember attribute. The MemberHasCustomAttribute method passed into the constructor // contains the filtering logic. System.Reflection.MemberFilter customAttributeFilter = new System.Reflection.MemberFilter(MemberHasCustomAttribute); // Find the methods and fields (instance variables) that are marked with the ClientMember // attribute. The first parameter to FindMembers specifies that instance, static, public, // and non-public fields and methods are to be searched; the second specifies the filter // to use; and the third specifies the attribute to look for. System.Reflection.MemberInfo[] clientMemberInfoArray = this.GetType().FindMembers( System.Reflection.MemberTypes.Method | System.Reflection.MemberTypes.Field, System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic, customAttributeFilter, typeof(ClientMember)); System.Reflection.MethodInfo clientMethodInfo = null; System.Reflection.FieldInfo clientFieldInfo = null; System.Text.StringBuilder addClientFieldsScript = new System.Text.StringBuilder(); addClientFieldsScript.Append("Sys.Application.add_init(function() {\n"); addClientFieldsScript.AppendFormat("var task = $find('{0}');", this.ClientID); // Iterate through the members found foreach (System.Reflection.MemberInfo memberInfo in clientMemberInfoArray) { // Check whether the current member is a method clientMethodInfo = memberInfo as System.Reflection.MethodInfo; if (clientMethodInfo == null) { // Since the member is not a method, and only methods and fields were sought in the // FindMembers call, the member must be a field. Cast the member to the FieldInfo // type, from which field-specific information can be retrieved. clientFieldInfo = memberInfo as System.Reflection.FieldInfo; // Make sure the field is a control. If it is not, skip it. if (typeof(System.Web.UI.Control).IsAssignableFrom(clientFieldInfo.FieldType)) { // Create JavaScript to add a function to the new client-side property that will return the // client-side task instance (AJAX component). This allows easy retrieval of the parent // task when only the element referenced by the property is available. addClientFieldsScript.AppendFormat("task.get_{1}().get_task = function() {{ return $find('{0}'); }};\n", this.ClientID, clientFieldInfo.Name); } } } addClientFieldsScript.Append("});\n"); return addClientFieldsScript.ToString(); } #endregion #region ICallbackEventHandler Members - GetCallbackResult // Invokes server-side methods of the ScriptTask's sub-class that have been exposed on the client via // the custom ClientMember attribute. public override string GetCallbackResult() { // Parse the message sent to the server via the callback using the Web ADF's parsing utility System.Collections.Specialized.NameValueCollection callbackArgsCollection = ESRI.ArcGIS.ADF.Web.UI.WebControls.CallbackUtility.ParseStringIntoNameValueCollection( this.CallbackEventArgument); // Make sure the callback event argument is "InvokeMethod" before executing method invocation // logic if ((callbackArgsCollection["EventArg"] != null) && (callbackArgsCollection["EventArg"] == "InvokeMethod")) { // Get the name of the method to be invoked and the parameters to pass to the method from // the callback argument string methodName = callbackArgsCollection["MethodName"]; string[] methodParameterStrings = callbackArgsCollection["Parameters"].Split('|'); // Instantiate a list to store the methods after they have been cast to the types expected // by the method. When they are first received from the client, they are all strings. System.Collections.Generic.List<object> methodParameterList = new System.Collections.Generic.List<object>(); // Iterate through the methods exposed to the client foreach (System.Reflection.MethodInfo clientMethodInfo in this.ClientMethodInfoList) { // Check whether the current method is the one to be invoked if (clientMethodInfo.Name == methodName) { int i = 0; // Iterate through the method's parameters foreach (System.Reflection.ParameterInfo parameterInfo in clientMethodInfo.GetParameters()) { // Cast the current parameter's string representation to the type expected by the // method and store it in the parameter list methodParameterList.Add(System.Convert.ChangeType(methodParameterStrings[i], parameterInfo.ParameterType)); i++; } break; } } // Convert the parameter list to an array so it can be passed to InvokeMember object[] methodParameterArray = methodParameterList.ToArray(); // Get the sub-class type of the task instance that implemented ScriptTask System.Type subClassType = this.GetType(); // Invoke the method on the sub-class. The first parameter specifies the method to be invoked; // the second specifies the type of members to look for when finding the method to invoke; the // fourth specifies the instance on which to invoke the method; and the fifth specifies the // arguments to pass to the method invocation. object result = subClassType.InvokeMember(methodName, System.Reflection.BindingFlags.InvokeMethod | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic, null, this, methodParameterArray); // Convert the method result to a string string resultString = (result == null) ? "" : result.ToString(); // Construct the JavaScript necessary to fire the method completion logic on the client-side // script task, passing the method result to the client function. string fireMethodCompleteEvent = string.Format("var task = $find('{0}'); " + "task._on{1}Complete('{2}');", this.ClientID, methodName, resultString); // Create a callback result enapsulating the JavaScript and add it to the task's callback results // so it is executed when program flow returns to the client. ESRI.ArcGIS.ADF.Web.UI.WebControls.CallbackResult methodCompleteEventCallbackResult = ESRI.ArcGIS.ADF.Web.UI.WebControls.CallbackResult.CreateJavaScript(fireMethodCompleteEvent); this.CallbackResults.Add(methodCompleteEventCallbackResult); } return base.GetCallbackResult(); } #endregion #region FloatingPanelTask Members - ExecuteTask, GetGISResourceItemDependencies // Overriden here so it does not have to be implemented by sub-classes. public override void ExecuteTask() { return; } // Overriden to return an empty list, indicating that the task has no resource dependencies. // Overriden here so it does not have to be implemented by sub-classes. public override System.Collections.Generic.List< ESRI.ArcGIS.ADF.Web.UI.WebControls.GISResourceItemDependency> GetGISResourceItemDependencies() { return new System.Collections.Generic.List<ESRI.ArcGIS.ADF.Web.UI.WebControls.GISResourceItemDependency>(); } #endregion #region Instance Properties - ClientMethodInfoList, JavaScriptResourceName, ClientClassName /// <summary> /// Stores information about all the methods made accessible on the client via the /// ClientMember attribute /// </summary> private System.Collections.Generic.List<System.Reflection.MethodInfo> ClientMethodInfoList { get { return StateManager.GetProperty("ClientMethodInfo") as System.Collections.Generic.List<System.Reflection.MethodInfo>; } set { StateManager.SetProperty("ClientMethodInfo", value); } } /// <summary> /// Stores the fully qualified name of the JavaScript file containing the client task's /// definition /// </summary> private string JavaScriptResourceName { get { return StateManager.GetProperty("JavaScriptResourceName") as string; } set { StateManager.SetProperty("JavaScriptResourceName", value); } } /// <summary> /// Stores the class name of the client task /// </summary> private string ClientClassName { get { return StateManager.GetProperty("ClientClassName") as string; } set { StateManager.SetProperty("ClientClassName", value); } } #endregion #region Instance Methods - MemberHasCustomAttribute, Script Generation Methods /// <summary> /// Method used as a delegate to check whether a class member has a particular custom /// attribute /// </summary> /// <param name="memberInfo">Contains information about the class member to be checked /// </param> /// <param name="filterAttributeObject">The custom attribute sought</param> /// <returns></returns> private static bool MemberHasCustomAttribute(System.Reflection.MemberInfo memberInfo, object filterAttributeObject) { // Get the custom attribute as a type System.Type filterAttributeType = (System.Type)filterAttributeObject; // Check whether the member referred to by the passed in MemberInfo object has the custom // attribute. object[] customAttributeArray = memberInfo.GetCustomAttributes(filterAttributeType, false); // If the member has the custom attribute, the method call above will place it in the array. // So return true or false based on whether the array has a length greater than 0. return (customAttributeArray.Length > 0); } #region Client Task Script Generation Methods /// <summary> /// Generates JavaScript to declare the client-side task such that it is registered as /// an AJAX control inheriting from ESRI.ADF.UI.FloatingPanel /// </summary> private string CreateClassDeclarationScript() { string clientTaskNamespace = ""; string namespaceRegistrationScript = ""; // Check whether the client-side task's class name contains a "." If so, then we need to // ensure that everything preceding the last "." is registered as an AJAX namespace on the // clent. if (this.ClientClassName.Contains(".")) { // Get the text preceding the last "." and construct the JavaScript needed to register // that text as an AJAX namespace. clientTaskNamespace = this.ClientClassName.Substring(0, this.ClientClassName.LastIndexOf(".")); namespaceRegistrationScript = string.Format("Type.registerNamespace('{0}');", clientTaskNamespace); } // Create JavaScript to declare the client-side task class and register it as an AJAX // control inheriting from ESRI.ADF.UI.FloatingPanel string classDeclarationScript = string.Format(@" {0} = function(element) {{ {0}.initializeBase(this, [element]); }} {0}.prototype = {{ initialize : function() {{ // Initialize the base class {0}.callBaseMethod(this, 'initialize'); }}, // Returns the object's id. Required for AJAX component registration. get_id : function () {{ return this._id; }}, // Sets the object's id. Required for AJAX component registration. set_id : function (value) {{ this._id = value; }} }}; // Register the task class, specifying inheritance from ESRI.ADF.UI.FloatingPanel. // This will give the task all the properties of FloatingPanel and will make it // an AJAX control, since FloatingPanel is an AJAX control. {0}.registerClass('{0}', ESRI.ADF.UI.FloatingPanel);", this.ClientClassName); // Return the namespace and class declaration syntax return namespaceRegistrationScript + classDeclarationScript; } /// <summary> /// Generates JavaScript to expose server-side members that have been declared with the ClientMember /// attribute to the client-side task /// </summary> private string CreateAddClientMembersScript() { // Instantiate a StringBuilder to store the JavaScript System.Text.StringBuilder clientMemberDeclarationScript = new System.Text.StringBuilder(); // Instantiate a filter that will be used to find members of the sub-class with the // ClientMember attribute. The MemberHasCustomAttribute method passed into the constructor // contains the filtering logic. System.Reflection.MemberFilter customAttributeFilter = new System.Reflection.MemberFilter(MemberHasCustomAttribute); // Get the type of the task implementing ScriptTask System.Type subClassType = this.GetType(); // Find the methods and fields (instance variables) that are marked with the ClientMember // attribute. The first parameter to FindMembers specifies that instance, static, public, // and non-public fields and methods are to be searched; the second specifies the filter // to use; and the third specifies the attribute to look for. System.Reflection.MemberInfo[] clientMemberInfoArray = subClassType.FindMembers( System.Reflection.MemberTypes.Method | System.Reflection.MemberTypes.Field, System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic, customAttributeFilter, typeof(ClientMember)); System.Reflection.MethodInfo clientMethodInfo = null; System.Reflection.FieldInfo clientFieldInfo = null; // Iterate through the members found foreach (System.Reflection.MemberInfo memberInfo in clientMemberInfoArray) { // Check whether the current member is a method clientMethodInfo = memberInfo as System.Reflection.MethodInfo; if (clientMethodInfo == null) { // Since the member is not a method, and only methods and fields were sought in the // FindMembers call, the member must be a field. Cast the member to the FieldInfo // type, from which field-specific information can be retrieved. clientFieldInfo = memberInfo as System.Reflection.FieldInfo; // Make sure the field is a control. If it is not, program flow will go to the next // loop iteration. if (typeof(System.Web.UI.Control).IsAssignableFrom(clientFieldInfo.FieldType)) { // Generate the JavaScript needed to expose the control on the client-side task. // The control will have the same name as specified in the server-side declaration // and will refere to the control's client-side DOM element. string addControlScript = CreateAddClientControlScript(clientFieldInfo); clientMemberDeclarationScript.Append(addControlScript); } } else { // The current member is a method. // If the list storing information about all the methods exposed to the client hasn't // been isntantiated, do so now if (this.ClientMethodInfoList == null) { this.ClientMethodInfoList = new System.Collections.Generic.List<System.Reflection.MethodInfo>(); } // Add the method to the list this.ClientMethodInfoList.Add(clientMethodInfo); // Generate JavaScript to expose the method on the client-side task. The method will // have the same signature as that specified in its server-side declaration and, when // called, will fire a callback that automatically passes the client-side parameters // to the equivalent server-side method. string addClientMethodScript = CreateAddClientMethodScript(clientMethodInfo); clientMemberDeclarationScript.Append(addClientMethodScript); // Generate JavaScript to add eventing for server-side method calls to the client-side // task. This will allow the specification of handlers that execute when the method // has fired on the server and program flow returns to the client. Handlers can be // added via <task>.add_on<methodName>Complete and removed via // <task>.remove_on<methodName>Complete. string addClientMethodEventScript = CreateAddMethodEventScript(clientMethodInfo); clientMemberDeclarationScript.Append(addClientMethodEventScript); } } return clientMemberDeclarationScript.ToString(); } /// <summary> /// Generates JavaScript to add the control specified to the client-side task /// </summary> /// <param name="controlMemberInfo">Contains information about the control to expose</param> private string CreateAddClientControlScript(System.Reflection.FieldInfo controlMemberInfo) { // Instantiate a StringBuilder to store the JavaScript System.Text.StringBuilder addClientControlScript = new System.Text.StringBuilder(); // Get the control instance System.Web.UI.Control control = controlMemberInfo.GetValue(this) as System.Web.UI.Control; // Create JavaScript to create a property on the client-side task that refers to the // DOM element corresponding to the passed-in control and has the same name as the // server-side field (instance variable) storing the control. string id = null; try { id = control.ID.ToString(); } catch (Exception) { control.ID = controlMemberInfo.Name; } string s = string.Format("{0}.prototype.get_{1} = function()" + "{{ return $get(this.get_id() + '_{2}'); }};\n", this.ClientClassName, controlMemberInfo.Name, control.ID); addClientControlScript.AppendFormat("{0}.prototype.get_{1} = function()" + "{{ return $get(this.get_id() + '_{2}'); }};\n", this.ClientClassName, controlMemberInfo.Name, control.ID); return addClientControlScript.ToString(); } /// <summary> /// Generates JavaScript to add the method specified to the client-side task. When called, this /// method will invoke the server-side method of the same name. /// </summary> /// <param name="clientMethodInfo">Contains information about the method to expose</param> private string CreateAddClientMethodScript(System.Reflection.MethodInfo clientMethodInfo) { // Instantiate StringBuilder to store the JavaScript System.Text.StringBuilder addClientMethodScript = new System.Text.StringBuilder(); // Declare a function on the client-side task that has the same name as that passed-in addClientMethodScript.AppendFormat("{0}.prototype.{1} = function(", this.ClientClassName, clientMethodInfo.Name); // Iterate through the method's parameters, adding the name of each to the function signature, // separated by commas foreach (System.Reflection.ParameterInfo parameterInfo in clientMethodInfo.GetParameters()) { addClientMethodScript.Append(parameterInfo.Name + ", "); } // Remove the last space and comma if the method has parameters if (clientMethodInfo.GetParameters().Length > 0) addClientMethodScript.Remove(addClientMethodScript.Length - 2, 2); // Close the function signature and add the opening curly brace addClientMethodScript.Append(") {\n"); // Add the beginning of a setTimeout statement. Note that we put the callback invocation in // a timeout with a delay of 0 to ensure proper execution of nested callbacks. Otherwise, // callbacks that are invoked via ScriptTask ClientMembers before a previous callback has // finished processing on the client would throw an error. addClientMethodScript.Append("window.setTimeout(String.format(\"var context = '{0}';\", this.get_id()) + "); // Declare the argument variable and start setting its value. The string will be in key-value // pairs in the format <key1>=<value1>&<key2>=<value2>&... The string is left incomplete here // because the names of the method parameters need to be added to it addClientMethodScript.AppendFormat("\"var argument = 'EventArg=InvokeMethod&MethodName={0}" + "&Parameters=\" + ", clientMethodInfo.Name); // Iterate through the method's parameters, adding the name of each to the argument string. // Note that these are not placed in quotes so they are evaluated at run-time. Each is // connected with " + \"|\" +" so that, at run-time, the result is a string with the value // of each parameter separated by a vertical line. foreach (System.Reflection.ParameterInfo parameterInfo in clientMethodInfo.GetParameters()) { addClientMethodScript.Append(parameterInfo.Name + " + \"|\" + "); } // If the method has parameters, remove the last separator. And add a closing double quote. // Otherwise, remove the plus sign. if (clientMethodInfo.GetParameters().Length > 0) { addClientMethodScript.Remove(addClientMethodScript.Length - 9, 9); addClientMethodScript.Append("+ \""); } else { addClientMethodScript.Remove(addClientMethodScript.Length - 4, 4); } addClientMethodScript.Append("';"); // Add an invocation of the task's callback function string. This will pass the argument // variable to the server-side ScriptTask's GetCallbackResult function, where the // server-side method specified by the MethodName parameter will be invoked. Note that, since // the statement is being passed to a timeout, we need to add an extra pair of backslashes so // the quotes surrounding the callback string are escaped properly at run-time. addClientMethodScript.Append("eval(\\\"\" + this.get_callbackFunctionString() + \"\\\");\", 0);\n"); //addClientMethodScript.AppendFormat("eval(\\\"{0}\\\");\", 0);\n", this.CallbackFunctionString); // Add the function's closing curly brace addClientMethodScript.Append("};\n"); return addClientMethodScript.ToString(); } /// <summary> /// Generates JavaScript to add eventing for the method specified to the client-side task. This /// allows for easily specifying logic to execute after a server-side method has been invoked /// and program flow has returned to the client. /// </summary> /// <param name="clientMethodInfo">Contains information about the method to expose</param> private string CreateAddMethodEventScript(System.Reflection.MethodInfo clientMethodInfo) { // Create JavaScript that will add event-related methods to the task string addMethodInvocationEventScript = @" // Adds a handler that will fire after the {1} method has executed on the server {0}.prototype.add_on{1}Complete = function(handler, before) {{ if (!this._on{1}CompleteHandlers) this._on{1}CompleteHandlers = new Array(); if (before) this._on{1}CompleteHandlers.splice(0, 0, handler); else this._on{1}CompleteHandlers.push(handler); }}; // Removes the passed-in handler {0}.prototype.remove_on{1}Complete = function(handler) {{ for (var i = 0; i < this._on{1}CompleteHandlers.length; i++) {{ if (this._on{1}CompleteHandlers[i] == handler) {{ this._on{1}CompleteHandlers.splice(i, 1); break; }} }} }}; // Called by ScriptTask after the {1} method has executed on the server. Fires // all handlers added via add_on{1}Complete. {0}.prototype._on{1}Complete = function(result) {{ // Iterate through the task's handlers array for the on{1}Complete event for (var i = 0; i < this._on{1}CompleteHandlers.length; i++) {{ // Assign the current handler to a temporary property on the task. We do // this so that, when the handler is called, the task is the caller. // Therefore, references to ""this"" in a handler will refer to the task. this._temp = this._on{1}CompleteHandlers[i]; // Execute the handler and check to see whether it returns false. If false // is returned, exit the loop, skipping execution of any other handlers. if (this._temp(result) == false) break; }} this._temp = 'undefined'; }};"; // Substitute the client-side task name and method name into the script and return it addMethodInvocationEventScript = string.Format(addMethodInvocationEventScript, this.ClientClassName, clientMethodInfo.Name); return addMethodInvocationEventScript; } #endregion #endregion #region Custom Attributes - ClientMember, ClientTaskDefinitionExplicit, ClientTaskJavaScriptResource, ClientTaskClassName /// <summary> /// Custom attribute specifying that the associated member that should be made available on the /// task's client-side representation. Can only be declared on methods and fields (instance /// members) that are controls. /// </summary> [System.AttributeUsage(System.AttributeTargets.Method | System.AttributeTargets.Field, AllowMultiple = false)] public class ClientMember : System.Attribute { } /// <summary> /// Custom attribute specifying that the client task has been explicitly declared in the /// ScriptTask implementation. When neither this attribute nor ClientTaskJavaScriptResource /// are present, the client task is declared automatically. Only valid for classes. /// </summary> [System.AttributeUsage(System.AttributeTargets.Class, AllowMultiple = false)] public class ClientTaskDefinitionExplicit : System.Attribute { } /// <summary> /// Custom attribute specifying the fully qualified name of the JavaScript file containing /// the client task declaration. When this attribute and ClientTaskDefinitionExplicit are /// not present, the client task is declared automatically. Only valid for classes. /// </summary> [System.AttributeUsage(System.AttributeTargets.Class, AllowMultiple = false)] public class ClientTaskJavaScriptResource : System.Attribute { private string _resourceName; public ClientTaskJavaScriptResource(string resourceName) { _resourceName = resourceName; } public string ResourceName { get { return _resourceName; } } } /// <summary> /// Custom attribute specifying the name of the client task class. If not declared, the /// client task class name will automatically match that of the server-side task. /// </summary> [System.AttributeUsage(System.AttributeTargets.Class, AllowMultiple = false)] public class ClientTaskClassName : System.Attribute { private string _className; public ClientTaskClassName(string className) { _className = className; } public string ClassName { get { return _className; } } } #endregion } }