Common_CustomTasks_VBNet\ScriptTask_VBNet\ScriptTask.vb
' 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. ' Imports Microsoft.VisualBasic Imports System Imports System.Web.UI 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 MustInherit Class ScriptTask Inherits 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 Overloads Overrides Sub OnPreRender(ByVal eventArgs As System.EventArgs) MyBase.OnPreRender(eventArgs) ' Instantiate a StringBuilder to hold the initialization JavaScript Dim initializeClientTaskScript As System.Text.StringBuilder = New System.Text.StringBuilder() ' Get the type of the task (sub-class) implementing ScriptTask Dim subClassType As System.Type = Me.GetType() ' Check whether the ClientTaskDefinitionExplicit custom attribute was declared on the ' sub-class. If so, the attribute instance will be passed to the array. Dim definitionExplicitArray As Object() = subClassType.GetCustomAttributes(GetType(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. Dim javaScriptResourceArray As Object() = subClassType.GetCustomAttributes(GetType(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. Dim createClientClass As Boolean = (definitionExplicitArray.Length = 0) AndAlso (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. Dim clientClassNameArray As Object() = subClassType.GetCustomAttributes(GetType(ClientTaskClassName), False) If clientClassNameArray.Length > 0 Then ' Store the class name specified in the attribute declaration Dim clientClassNameAttribute As ClientTaskClassName = TryCast(clientClassNameArray(0), ClientTaskClassName) Me.ClientClassName = clientClassNameAttribute.ClassName Else ' Set the client class name to match that of the server-side task Me.ClientClassName = Me.GetType().ToString() End If If createClientClass Then ' 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. Dim classDeclarationScript As String = 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 Dim registerScriptResource As Boolean = True ' Check whether the class was declared with a ClientTaskJavaScriptResource attribute If javaScriptResourceArray.Length > 0 Then ' Get the path specified by the ClientTaskJavaScriptResource Dim javaScriptResourceAttribute As ClientTaskJavaScriptResource = TryCast(javaScriptResourceArray(0), ClientTaskJavaScriptResource) Me.JavaScriptResourceName = javaScriptResourceAttribute.ResourceName Else ' Create a default resource name from the sub-class's task name and the JavaScript file ' extension Me.JavaScriptResourceName = String.Format("{0}.js", Me.GetType().ToString()) ' Attempt to retrieve an object containing information about the resource. If the ' resource does not exist, this will return null. Dim resourceInfo As System.Reflection.ManifestResourceInfo = subClassType.Assembly.GetManifestResourceInfo(Me.JavaScriptResourceName) ' VB may not prepend the namespace to the resource, so just retrieve using the basic resource name. If (resourceInfo Is Nothing) Then Me.JavaScriptResourceName = String.Format("{0}.js", Me.GetType().Name.ToString()) resourceInfo = subClassType.Assembly.GetManifestResourceInfo(Me.JavaScriptResourceName) End If ' Based on whether the resource was found, set a boolean indicating whether the resource ' should be registered registerScriptResource = (Not resourceInfo Is Nothing) End If ' Register the client script resource, if necessary If registerScriptResource Then If Me.ScriptManager IsNot Nothing Then Dim scriptRef As New ScriptReference(Me.JavaScriptResourceName, subClassType.Assembly.FullName) Me.ScriptManager.Scripts.Add(scriptRef) Else System.Web.UI.ScriptManager.RegisterClientScriptResource(Me, subClassType, Me.JavaScriptResourceName) End If End If End If ' Generate JavaScript that will add server-side members declared with the custom ClientMember ' attribute to the client-side task. Dim addClientMembersScript As String = 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 Dim classInitFunctionName As String = String.Format("{0}_initializeClientTaskMembers", subClassType.Name) initializeClientTaskScript.Insert(0, "function " & classInitFunctionName & "() {") initializeClientTaskScript.Append("} Sys.Application.add_init(" & classInitFunctionName & ");") Dim scriptKey As String = String.Format("initialize{0}", subClassType.ToString()) If (Not Me.Page.ClientScript.IsStartupScriptRegistered(scriptKey)) Then ' Register the initialization script with the client System.Web.UI.ScriptManager.RegisterStartupScript(Me, Me.GetType(), scriptKey, initializeClientTaskScript.ToString(), True) End If End Sub ' Instantiates the client-side task Protected Overloads Overrides Sub Render(ByVal writer As System.Web.UI.HtmlTextWriter) MyBase.Render(writer) Dim instatiationFuncName As String = String.Format("create{0}", Me.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. Dim createClientTaskScript As String = "function {0}() {{ " & ControlChars.CrLf & " var floatingPanel = $find('{1}');" & ControlChars.CrLf & " var properties = {{ id:'{1}', ""transparency"":floatingPanel.get_transparency()," & ControlChars.CrLf & " ""callbackFunctionString"":floatingPanel.get_callbackFunctionString()," & ControlChars.CrLf & " ""expandedImage"":floatingPanel.get_expandedImage()," & ControlChars.CrLf & " ""collapsedImage"":floatingPanel.get_collapsedImage()," & ControlChars.CrLf & " ""dockedImage"":floatingPanel.get_dockedImage()," & ControlChars.CrLf & " ""undockedImage"":floatingPanel.get_undockedImage()," & ControlChars.CrLf & " ""dockedContextMenuID"":floatingPanel.get_dockedContextMenuID()," & ControlChars.CrLf & " ""dockParent"":floatingPanel.get_dockParent(), " & ControlChars.CrLf & " ""forcePNG"":floatingPanel.get_forcePNG(), " & ControlChars.CrLf & " ""isDocked"":floatingPanel.get_isDocked() }};" & ControlChars.CrLf & " floatingPanel.dispose();" & ControlChars.CrLf & " $create({2}, properties, null, null, $get('{1}'));" & ControlChars.CrLf & " }}" & ControlChars.CrLf & " Sys.Application.add_init({0});" createClientTaskScript = String.Format(createClientTaskScript, instatiationFuncName, Me.ClientID, Me.ClientClassName) System.Web.UI.ScriptManager.RegisterStartupScript(Me, Me.GetType(), Me.ClientID & "_createClientTask", createClientTaskScript, True) Dim addClientFieldsScript As String = GetAddClientFieldsScript() System.Web.UI.ScriptManager.RegisterStartupScript(Me, Me.GetType(), Me.ClientID & "_addClientFields", addClientFieldsScript, True) End Sub Private Function GetAddClientFieldsScript() As String ' 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. Dim customAttributeFilter As New System.Reflection.MemberFilter(AddressOf 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. Dim clientMemberInfoArray() As System.Reflection.MemberInfo = Me.GetType().FindMembers(System.Reflection.MemberTypes.Method Or System.Reflection.MemberTypes.Field, System.Reflection.BindingFlags.Instance Or System.Reflection.BindingFlags.Static Or System.Reflection.BindingFlags.Public Or System.Reflection.BindingFlags.NonPublic, customAttributeFilter, GetType(ClientMember)) Dim clientMethodInfo As System.Reflection.MethodInfo = Nothing Dim clientFieldInfo As System.Reflection.FieldInfo = Nothing Dim addClientFieldsScript As New System.Text.StringBuilder() addClientFieldsScript.Append("Sys.Application.add_init(function() {" & Constants.vbLf) addClientFieldsScript.AppendFormat("var task = $find('{0}');", Me.ClientID) ' Iterate through the members found For Each memberInfo As System.Reflection.MemberInfo In clientMemberInfoArray ' Check whether the current member is a method clientMethodInfo = TryCast(memberInfo, System.Reflection.MethodInfo) If clientMethodInfo Is Nothing Then ' 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 = TryCast(memberInfo, System.Reflection.FieldInfo) ' Make sure the field is a control. If it is not, skip it. If GetType(System.Web.UI.Control).IsAssignableFrom(clientFieldInfo.FieldType) Then ' 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}'); }};" & Constants.vbLf, Me.ClientID, clientFieldInfo.Name) End If End If Next memberInfo addClientFieldsScript.Append("});" & Constants.vbLf) Return addClientFieldsScript.ToString() End Function #End Region #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 Overloads Overrides Function GetCallbackResult() As String ' Parse the message sent to the server via the callback using the Web ADF's parsing utility Dim callbackArgsCollection As System.Collections.Specialized.NameValueCollection = ESRI.ArcGIS.ADF.Web.UI.WebControls.CallbackUtility.ParseStringIntoNameValueCollection(Me.CallbackEventArgument) ' Make sure the callback event argument is "InvokeMethod" before executing method invocation ' logic If (Not callbackArgsCollection("EventArg") Is Nothing) AndAlso (callbackArgsCollection("EventArg") = "InvokeMethod") Then ' Get the name of the method to be invoked and the parameters to pass to the method from ' the callback argument Dim methodName As String = callbackArgsCollection("MethodName") Dim methodParameterStrings As String() = callbackArgsCollection("Parameters").Split("|"c) ' 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. Dim methodParameterList As System.Collections.Generic.List(Of Object) = New System.Collections.Generic.List(Of Object)() ' Iterate through the methods exposed to the client For Each clientMethodInfo As System.Reflection.MethodInfo In Me.ClientMethodInfoList ' Check whether the current method is the one to be invoked If clientMethodInfo.Name = methodName Then Dim i As Integer = 0 ' Iterate through the method's parameters For Each parameterInfo As System.Reflection.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 += 1 Next parameterInfo Exit For End If Next clientMethodInfo ' Convert the parameter list to an array so it can be passed to InvokeMember Dim methodParameterArray As Object() = methodParameterList.ToArray() ' Get the sub-class type of the task instance that implemented ScriptTask Dim subClassType As System.Type = Me.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. Dim result As Object = subClassType.InvokeMember(methodName, System.Reflection.BindingFlags.InvokeMethod Or System.Reflection.BindingFlags.Public Or System.Reflection.BindingFlags.Static Or System.Reflection.BindingFlags.Instance Or System.Reflection.BindingFlags.NonPublic, Nothing, Me, methodParameterArray) ' Convert the method result to a string Dim resultString As String If (result Is Nothing) Then resultString = "" Else resultString = result.ToString() End If ' Construct the JavaScript necessary to fire the method completion logic on the client-side ' script task, passing the method result to the client function. Dim fireMethodCompleteEvent As String = String.Format("var task = $find('{0}'); " & "task._on{1}Complete('{2}');", Me.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. Dim methodCompleteEventCallbackResult As ESRI.ArcGIS.ADF.Web.UI.WebControls.CallbackResult = ESRI.ArcGIS.ADF.Web.UI.WebControls.CallbackResult.CreateJavaScript(fireMethodCompleteEvent) Me.CallbackResults.Add(methodCompleteEventCallbackResult) End If Return MyBase.GetCallbackResult() End Function #End Region #Region "FloatingPanelTask Members - ExecuteTask, GetGISResourceItemDependencies" ' Overriden here so it does not have to be implemented by sub-classes. Public Overloads Overrides Sub ExecuteTask() Return End Sub ' 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 Overloads Overrides Function GetGISResourceItemDependencies() As System.Collections.Generic.List(Of ESRI.ArcGIS.ADF.Web.UI.WebControls.GISResourceItemDependency) Return New System.Collections.Generic.List(Of ESRI.ArcGIS.ADF.Web.UI.WebControls.GISResourceItemDependency)() End Function #End Region #Region "Instance Properties - ClientMethodInfoList, JavaScriptResourceName, ClientClassName" ''' <summary> ''' Stores information about all the methods made accessible on the client via the ''' ClientMember attribute ''' </summary> Private Property ClientMethodInfoList() As System.Collections.Generic.List(Of System.Reflection.MethodInfo) Get Return TryCast(StateManager.GetProperty("ClientMethodInfo"), System.Collections.Generic.List(Of System.Reflection.MethodInfo)) End Get Set(ByVal value As System.Collections.Generic.List(Of System.Reflection.MethodInfo)) StateManager.SetProperty("ClientMethodInfo", value) End Set End Property ''' <summary> ''' Stores the fully qualified name of the JavaScript file containing the client task's ''' definition ''' </summary> Private Property JavaScriptResourceName() As String Get Return TryCast(StateManager.GetProperty("JavaScriptResourceName"), String) End Get Set(ByVal value As String) StateManager.SetProperty("JavaScriptResourceName", value) End Set End Property ''' <summary> ''' Stores the class name of the client task ''' </summary> Private Property ClientClassName() As String Get Return TryCast(StateManager.GetProperty("ClientClassName"), String) End Get Set(ByVal value As String) StateManager.SetProperty("ClientClassName", value) End Set End Property #End Region #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 Shared Function MemberHasCustomAttribute(ByVal memberInfo As System.Reflection.MemberInfo, ByVal filterAttributeObject As Object) As Boolean ' Get the custom attribute as a type Dim filterAttributeType As System.Type = CType(filterAttributeObject, System.Type) ' Check whether the member referred to by the passed in MemberInfo object has the custom ' attribute. Dim customAttributeArray As Object() = 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) End Function #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 Function CreateClassDeclarationScript() As String Dim clientTaskNamespace As String = "" Dim namespaceRegistrationScript As String = "" ' 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 Me.ClientClassName.Contains(".") Then ' Get the text preceding the last "." and construct the JavaScript needed to register ' that text as an AJAX namespace. clientTaskNamespace = Me.ClientClassName.Substring(0, Me.ClientClassName.LastIndexOf(".")) namespaceRegistrationScript = String.Format("Type.registerNamespace('{0}');", clientTaskNamespace) End If ' Create JavaScript to declare the client-side task class and register it as an AJAX ' control inheriting from ESRI.ADF.UI.FloatingPanel Dim classDeclarationScript As String = String.Format("" & ControlChars.CrLf & " {0} = function(element) {{" & ControlChars.CrLf & " {0}.initializeBase(this, [element]);" & ControlChars.CrLf & " }}" & ControlChars.CrLf & " {0}.prototype = {{ " & ControlChars.CrLf & " initialize : function() {{ " & ControlChars.CrLf & " // Initialize the base class" & ControlChars.CrLf & " {0}.callBaseMethod(this, 'initialize'); " & ControlChars.CrLf & " }}, " & ControlChars.CrLf & " // Returns the object's id. Required for AJAX component registration." & ControlChars.CrLf & " get_id : function () {{" & ControlChars.CrLf & " return this._id;" & ControlChars.CrLf & " }}, " & ControlChars.CrLf & " // Sets the object's id. Required for AJAX component registration." & ControlChars.CrLf & " set_id : function (value) {{" & ControlChars.CrLf & " this._id = value;" & ControlChars.CrLf & " }} " & ControlChars.CrLf & " }};" & ControlChars.CrLf & ControlChars.CrLf & " // Register the task class, specifying inheritance from ESRI.ADF.UI.FloatingPanel." & ControlChars.CrLf & " // This will give the task all the properties of FloatingPanel and will make it" & ControlChars.CrLf & " // an AJAX control, since FloatingPanel is an AJAX control." & ControlChars.CrLf & " {0}.registerClass('{0}', ESRI.ADF.UI.FloatingPanel);", Me.ClientClassName) ' Return the namespace and class declaration syntax Return namespaceRegistrationScript & classDeclarationScript End Function ''' <summary> ''' Generates JavaScript to expose server-side members that have been declared with the ClientMember ''' attribute to the client-side task ''' </summary> Private Function CreateAddClientMembersScript() As String ' Instantiate a StringBuilder to store the JavaScript Dim clientMemberDeclarationScript As System.Text.StringBuilder = 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. Dim customAttributeFilter As System.Reflection.MemberFilter = New System.Reflection.MemberFilter(AddressOf MemberHasCustomAttribute) ' Get the type of the task implementing ScriptTask Dim subClassType As System.Type = Me.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. Dim clientMemberInfoArray As System.Reflection.MemberInfo() = subClassType.FindMembers(System.Reflection.MemberTypes.Method Or System.Reflection.MemberTypes.Field, System.Reflection.BindingFlags.Instance Or System.Reflection.BindingFlags.Static Or System.Reflection.BindingFlags.Public Or System.Reflection.BindingFlags.NonPublic, customAttributeFilter, GetType(ClientMember)) Dim clientMethodInfo As System.Reflection.MethodInfo = Nothing Dim clientFieldInfo As System.Reflection.FieldInfo = Nothing ' Iterate through the members found For Each memberInfo As System.Reflection.MemberInfo In clientMemberInfoArray ' Check whether the current member is a method clientMethodInfo = TryCast(memberInfo, System.Reflection.MethodInfo) If clientMethodInfo Is Nothing Then ' 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 = TryCast(memberInfo, System.Reflection.FieldInfo) ' Make sure the field is a control. If it is not, program flow will go to the next ' loop iteration. If GetType(System.Web.UI.Control).IsAssignableFrom(clientFieldInfo.FieldType) Then ' 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. Dim addControlScript As String = CreateAddClientControlScript(clientFieldInfo) clientMemberDeclarationScript.Append(addControlScript) End If 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 Me.ClientMethodInfoList Is Nothing Then Me.ClientMethodInfoList = New System.Collections.Generic.List(Of System.Reflection.MethodInfo)() End If ' Add the method to the list Me.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. Dim addClientMethodScript As String = 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. Dim addClientMethodEventScript As String = CreateAddMethodEventScript(clientMethodInfo) clientMemberDeclarationScript.Append(addClientMethodEventScript) End If Next memberInfo Return clientMemberDeclarationScript.ToString() End Function ''' <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 Function CreateAddClientControlScript(ByVal controlMemberInfo As System.Reflection.FieldInfo) As String ' Instantiate a StringBuilder to store the JavaScript Dim addClientControlScript As New System.Text.StringBuilder() ' Get the control instance Dim control As System.Web.UI.Control = TryCast(controlMemberInfo.GetValue(Me), 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. Dim id As String = Nothing Try id = control.ID.ToString() Catch e1 As Exception control.ID = controlMemberInfo.Name End Try Dim s As String = String.Format("{0}.prototype.get_{1} = function()" & "{{ return $get(this.get_id() + '_{2}'); }};" & Constants.vbLf, Me.ClientClassName, controlMemberInfo.Name, control.ID) addClientControlScript.AppendFormat("{0}.prototype.get_{1} = function()" & "{{ return $get(this.get_id() + '_{2}'); }};" & Constants.vbLf, Me.ClientClassName, controlMemberInfo.Name, control.ID) Return addClientControlScript.ToString() End Function ''' <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 Function CreateAddClientMethodScript(ByVal clientMethodInfo As System.Reflection.MethodInfo) As String ' Instantiate StringBuilder to store the JavaScript Dim addClientMethodScript As 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(", Me.ClientClassName, clientMethodInfo.Name) ' Iterate through the method's parameters, adding the name of each to the function signature, ' separated by commas For Each parameterInfo As System.Reflection.ParameterInfo In clientMethodInfo.GetParameters() addClientMethodScript.Append(parameterInfo.Name & ", ") Next parameterInfo ' Remove the last space and comma if the method has parameters If clientMethodInfo.GetParameters().Length > 0 Then addClientMethodScript.Remove(addClientMethodScript.Length - 2, 2) End If ' Close the function signature and add the opening curly brace addClientMethodScript.Append(") {" & Constants.vbLf) ' 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. For Each parameterInfo As System.Reflection.ParameterInfo In clientMethodInfo.GetParameters() addClientMethodScript.Append(parameterInfo.Name & " + ""|"" + ") Next parameterInfo ' 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 Then addClientMethodScript.Remove(addClientMethodScript.Length - 9, 9) addClientMethodScript.Append("+ """) Else addClientMethodScript.Remove(addClientMethodScript.Length - 4, 4) End If 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);" & Constants.vbLf) 'addClientMethodScript.AppendFormat("eval(\\\"{0}\\\");\", 0);\n", this.CallbackFunctionString); ' Add the function's closing curly brace addClientMethodScript.Append("};" & Constants.vbLf) Return addClientMethodScript.ToString() End Function ''' <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 Function CreateAddMethodEventScript(ByVal clientMethodInfo As System.Reflection.MethodInfo) As String ' Create JavaScript that will add event-related methods to the task Dim addMethodInvocationEventScript As String = "" & ControlChars.CrLf & " // Adds a handler that will fire after the {1} method has executed on the server" & ControlChars.CrLf & " {0}.prototype.add_on{1}Complete = function(handler, before) {{" & ControlChars.CrLf & " if (!this._on{1}CompleteHandlers)" & ControlChars.CrLf & " this._on{1}CompleteHandlers = new Array();" & ControlChars.CrLf & ControlChars.CrLf & " if (before)" & ControlChars.CrLf & " this._on{1}CompleteHandlers.splice(0, 0, handler);" & ControlChars.CrLf & " else" & ControlChars.CrLf & " this._on{1}CompleteHandlers.push(handler);" & ControlChars.CrLf & " }};" & ControlChars.CrLf & ControlChars.CrLf & " // Removes the passed-in handler" & ControlChars.CrLf & " {0}.prototype.remove_on{1}Complete = function(handler) {{" & ControlChars.CrLf & " for (var i = 0; i < this._on{1}CompleteHandlers.length; i++)" & ControlChars.CrLf & " {{" & ControlChars.CrLf & " if (this._on{1}CompleteHandlers[i] == handler)" & ControlChars.CrLf & " {{" & ControlChars.CrLf & " this._on{1}CompleteHandlers.splice(i, 1);" & ControlChars.CrLf & " break;" & ControlChars.CrLf & " }}" & ControlChars.CrLf & " }}" & ControlChars.CrLf & " }};" & ControlChars.CrLf & ControlChars.CrLf & " // Called by ScriptTask after the {1} method has executed on the server. Fires" & ControlChars.CrLf & " // all handlers added via add_on{1}Complete." & ControlChars.CrLf & " {0}.prototype._on{1}Complete = function(result)" & ControlChars.CrLf & " {{" & ControlChars.CrLf & " // Iterate through the task's handlers array for the on{1}Complete event" & ControlChars.CrLf & " for (var i = 0; i < this._on{1}CompleteHandlers.length; i++)" & ControlChars.CrLf & " {{" & ControlChars.CrLf & " // Assign the current handler to a temporary property on the task. We do " & ControlChars.CrLf & " // this so that, when the handler is called, the task is the caller. " & ControlChars.CrLf & " // Therefore, references to ""this"" in a handler will refer to the task." & ControlChars.CrLf & " this._temp = this._on{1}CompleteHandlers[i];" & ControlChars.CrLf & ControlChars.CrLf & " // Execute the handler and check to see whether it returns false. If false " & ControlChars.CrLf & " // is returned, exit the loop, skipping execution of any other handlers." & ControlChars.CrLf & " if (this._temp(result) == false)" & ControlChars.CrLf & " break;" & ControlChars.CrLf & " }}" & ControlChars.CrLf & " " & ControlChars.CrLf & " this._temp = 'undefined';" & ControlChars.CrLf & " }};" ' Substitute the client-side task name and method name into the script and return it addMethodInvocationEventScript = String.Format(addMethodInvocationEventScript, Me.ClientClassName, clientMethodInfo.Name) Return addMethodInvocationEventScript End Function #End Region #End Region #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 Or System.AttributeTargets.Field, AllowMultiple:=False)> _ Public Class ClientMember Inherits System.Attribute End Class ''' <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 Inherits System.Attribute End Class ''' <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 Inherits System.Attribute Private _resourceName As String Public Sub New(ByVal resourceName As String) _resourceName = resourceName End Sub Public ReadOnly Property ResourceName() As String Get Return _resourceName End Get End Property End Class ''' <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 Inherits System.Attribute Private _className As String Public Sub New(ByVal className As String) _className = className End Sub Public ReadOnly Property ClassName() As String Get Return _className End Get End Property End Class #End Region End Class End Namespace