Common_TaskResults_VBNet\OnDemandTaskResults\OnDemandTaskResults.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 Namespace OnDemandTaskResults_VBNet ' Specify the OnDemandTaskResults JavaScript file as a client script resource. This will register the script ' on the client. LoadOrder is specified to ensure that this custom script is loaded after the Web ADF's ' TaskResults control's script is registered, as the custom script requires the availability of the client ' tier TaskResults control. <System.Web.UI.ToolboxData("<{0}:OnDemandTaskResults runat=""server"" Width=""200px"" Height=""200px"" " & ControlChars.CrLf & " BackColor=""#ffffff"" Font-Names=""Verdana"" Font-Size=""8pt"" ForeColor=""#000000""" & ControlChars.CrLf & " ShowAttributesOnDemand=""True"" ActivityIndicatorText=""Retrieving Data""> </{0}:OnDemandTaskResults>"), AjaxControlToolkit.ClientScriptResource("OnDemandTaskResults_VBNet.OnDemandTaskResults", "OnDemandTaskResults.js", LoadOrder:=3)> _ Public Class OnDemandTaskResults Inherits ESRI.ArcGIS.ADF.Web.UI.WebControls.TaskResults #Region "Public Properties - ShowAttributesOnDemand, ActivityIndicatorText" ''' <summary> ''' Determines whether to retrieve feature attributes individually when a feature node is expanded (true) ''' or all at once when the task results are first generated (false) ''' </summary> Public Property ShowAttributesOnDemand() As Boolean Get If (Me.StateManager.GetProperty("ShowAttributesOnDemand") Is Nothing) Then Return True Else Return CBool(Me.StateManager.GetProperty("ShowAttributesOnDemand")) End If End Get Set(ByVal value As Boolean) Me.StateManager.SetProperty("ShowAttributesOnDemand", Value) End Set End Property ''' <summary> ''' Determines the text shown while data is being retrieved for task results ''' </summary> Public Property ActivityIndicatorText() As String Get If (Me.StateManager.GetProperty("ActivityIndicatorText") Is Nothing) Then Return "Retrieving Data" Else Return CStr(Me.StateManager.GetProperty("ActivityIndicatorText")) End If End Get Set(ByVal value As String) Me.StateManager.SetProperty("ActivityIndicatorText", Value) End Set End Property #End Region #Region "Private Properties - NodeDataCache, GraphicsAttributeCache, ProcessedNodes" ''' <summary> ''' Stores the text (markup) for nodes containing feature data. The text for a node is retrieved ''' from the cache when that node is first expanded. Node ID is used as each entry's key. ''' </summary> Private ReadOnly Property NodeDataCache() As System.Collections.Generic.Dictionary(Of String, String) Get ' Attempt to retrieve the node data from state 'INSTANT VB NOTE: The local variable nodeDataCache was renamed since Visual Basic will not allow local variables with the same name as their method: Dim nodeDataCache_Renamed As System.Collections.Generic.Dictionary(Of String, String) = TryCast(Me.StateManager.GetProperty("NodeCache"), System.Collections.Generic.Dictionary(Of String, String)) ' If the node data is not yet stored, create a new dictionary for the data and store it If nodeDataCache_Renamed Is Nothing Then nodeDataCache_Renamed = New System.Collections.Generic.Dictionary(Of String, String)() Me.StateManager.SetProperty("NodeCache", nodeDataCache_Renamed) End If Return nodeDataCache_Renamed End Get End Property ''' <summary> ''' Stores the attribute data for the Graphics associated with GraphicsLayerNodes. The attributes for ''' a graphic feature is retrieved from the cache when that feature's MapTips is first shown or when ''' its task results node is first expanded. ''' </summary> Private ReadOnly Property GraphicsAttributeCache() As System.Collections.Generic.Dictionary(Of String, String) Get ' Attempt to retrieve the node data from state 'INSTANT VB NOTE: The local variable graphicsAttributeCache was renamed since Visual Basic will not allow local variables with the same name as their method: Dim graphicsAttributeCache_Renamed As System.Collections.Generic.Dictionary(Of String, String) = TryCast(Me.StateManager.GetProperty("GraphicsAttributeCache"), System.Collections.Generic.Dictionary(Of String, String)) ' If the node data is not yet stored, create a new dictionary for the data and store it If graphicsAttributeCache_Renamed Is Nothing Then graphicsAttributeCache_Renamed = New System.Collections.Generic.Dictionary(Of String, String)() Me.StateManager.SetProperty("GraphicsAttributeCache", graphicsAttributeCache_Renamed) End If Return graphicsAttributeCache_Renamed End Get End Property ''' <summary> ''' Stores the IDs of nodes for which attribute data has been retrieved ''' </summary> Private ReadOnly Property ProcessedNodes() As System.Collections.Generic.List(Of String) Get ' Attempt to retrieve the node data from state 'INSTANT VB NOTE: The local variable processedNodes was renamed since Visual Basic will not allow local variables with the same name as their method: Dim processedNodes_Renamed As System.Collections.Generic.List(Of String) = TryCast(Me.StateManager.GetProperty("ProcessedNodes"), System.Collections.Generic.List(Of String)) ' If the node data is not yet stored, create a new dictionary for the data and store it If processedNodes_Renamed Is Nothing Then processedNodes_Renamed = New System.Collections.Generic.List(Of String)() Me.StateManager.SetProperty("ProcessedNodes", processedNodes_Renamed) End If Return processedNodes_Renamed End Get End Property #End Region #Region "ASP.NET WebControl Life Cycle Event Handlers - CreateChildControls" Protected Overrides Sub CreateChildControls() MyBase.CreateChildControls() For Each menuItem As ESRI.ArcGIS.ADF.Web.UI.WebControls.ContextMenuItem In Me.FeatureContextMenu.Items If menuItem.Text.ToLower() = "remove" Then Me.FeatureContextMenu.Items.Remove(menuItem) Exit For End If Next menuItem For Each menuItem As ESRI.ArcGIS.ADF.Web.UI.WebControls.ContextMenuItem In Me.GraphicsLayerContextMenu.Items If menuItem.Text.ToLower() = "remove" Then Me.GraphicsLayerContextMenu.Items.Remove(menuItem) Exit For End If Next menuItem ' Wire event handlers to fire when a node is added, removed, or expanded AddHandler NodeAdded, AddressOf OnDemandTaskResults_NodeAdded AddHandler NodeExpanded, AddressOf OnDemandTaskResults_NodeExpanded AddHandler NodeRemoved, AddressOf OnDemandTaskResults_NodeRemoved End Sub #End Region #Region "Web ADF Control Event Handlers - NodeAdded, NodeExpanded, NodeRemoved" ' Fires when a node is added. Replaces any feature data with an activity indicator and ' stores that data for on-demand retrieval. Private Sub OnDemandTaskResults_NodeAdded(ByVal sender As Object, ByVal args As ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNodeEventArgs) ' If attributes are not to be retrieved on-demand, exit the method If (Not Me.ShowAttributesOnDemand) OrElse Not (TypeOf args.Node Is ESRI.ArcGIS.ADF.Web.UI.WebControls.TaskResultNode) Then Return End If ' Attempt to get GraphicsLayerNodes that are descendants of the added node Dim graphicsLayerNodes As System.Collections.Generic.List(Of ESRI.ArcGIS.ADF.Web.UI.WebControls.GraphicsLayerNode) = Me.FindChildGraphicsLayerNodes(args.Node) ' Make sure a GraphicsLayerNode was found If graphicsLayerNodes.Count = 0 Then Return End If ' Get the control that is adding the result node Dim callingControl As System.Web.UI.Control = Me.GetCallingControl(Me.Page) ' Cast the control to IAttributesOnDemandProvider Dim attributesOnDemandProvider As OnDemandTaskResults_VBNet.IAttributesOnDemandProvider = TryCast(callingControl, OnDemandTaskResults_VBNet.IAttributesOnDemandProvider) ' Set a boolean indicating whether the task results control needs to cache attributes. This will ' be the case if the control that added the results does not implement IAttributesOnDemandProvider. Dim cacheAttributesInWebTier As Boolean = (attributesOnDemandProvider Is Nothing) ' If the control that has added results implements IAttributesOnDemandProvider, add the ID of ' the control as an attribute on the task result node. If Not attributesOnDemandProvider Is Nothing Then args.Node.Attributes.Add("AttributesProviderID", callingControl.ID) End If ' Stores the properties that will be used to initialize client tier graphics layer nodes Dim graphicsLayerNodeList As System.Collections.Generic.List(Of System.Collections.Generic.Dictionary(Of String, Object)) = New System.Collections.Generic.List(Of System.Collections.Generic.Dictionary(Of String, Object))() For Each graphicsLayerNode As ESRI.ArcGIS.ADF.Web.UI.WebControls.GraphicsLayerNode In graphicsLayerNodes ' Get the feature nodes contained by the current graphics layer node Dim featureNodes As System.Collections.Generic.List(Of ESRI.ArcGIS.ADF.Web.UI.WebControls.FeatureNode) = Me.FindChildFeatureNodes(graphicsLayerNode) ' Get the markup for the activity indicator that will be shown when retrieving attributes for ' a MapTip or feature node Dim activityIndicatorMarkup As String = Me.CreateActivityIndicatorMarkup() ' Replace the markup for any feature nodes contained in the passed-in node with the ' activity indicator markup. This method will store the replaced feature data markup ' for retrieval the first time a node is expanded. Me.CreateOnDemandNodes(args.Node, activityIndicatorMarkup, cacheAttributesInWebTier) ' Update the task results MapTips so that attributes are retrieved on-demand (i.e. ' when a MapTip is first expanded) Me.CreateOnDemandMapTips(graphicsLayerNode, activityIndicatorMarkup, cacheAttributesInWebTier) ' Create a dictionary to hold the graphics layer node's properties and add the node ID and the ' IDs of child feature nodes Dim graphicsLayerNodeProperties As System.Collections.Generic.Dictionary(Of String, Object) = New System.Collections.Generic.Dictionary(Of String, Object)() graphicsLayerNodeProperties.Add("nodeID", graphicsLayerNode.NodeID) graphicsLayerNodeProperties.Add("featureNodes", Me.GetCurrentPageNodeIDs(graphicsLayerNode)) ' If the node has more than one page of child nodes, add properties storing page information If graphicsLayerNode.NumberOfPages > 1 Then graphicsLayerNodeProperties.Add("pagingTextFormatString", graphicsLayerNode.PagingTextFormatString) graphicsLayerNodeProperties.Add("pageSize", graphicsLayerNode.PageSize) graphicsLayerNodeProperties.Add("pageCount", graphicsLayerNode.NumberOfPages) graphicsLayerNodeProperties.Add("nodeCount", graphicsLayerNode.Nodes.Count) End If ' Add the properties to the list of graphics layer node properties graphicsLayerNodeList.Add(graphicsLayerNodeProperties) Next graphicsLayerNode ' Create a dictionary to hold the node ID of the task results node and the client tier properties of child ' graphics layer nodes. This information is used to initialize the task results node and its child nodes ' on the client. Dim taskResultNodeProperties As System.Collections.Generic.Dictionary(Of String, Object) = New System.Collections.Generic.Dictionary(Of String, Object)() taskResultNodeProperties.Add("nodeID", args.Node.NodeID) taskResultNodeProperties.Add("graphicsLayerNodes", graphicsLayerNodeList) Dim jsSerializer As System.Web.Script.Serialization.JavaScriptSerializer = New System.Web.Script.Serialization.JavaScriptSerializer() ' Construct script to call the client-side task result node initialization routine, passing ' it the task results JSON constructed above. Wrap the script in a callback result for ' processing on the client Dim nodeInitScript As String = "" & ControlChars.CrLf & " var taskResults = $find('{0}');" & ControlChars.CrLf & " taskResults.initClientNodes({1});" nodeInitScript = String.Format(nodeInitScript, Me.ClientID, jsSerializer.Serialize(taskResultNodeProperties)) Me.CallbackResults.Add(ESRI.ArcGIS.ADF.Web.UI.WebControls.CallbackResult.CreateJavaScript(nodeInitScript)) End Sub ' Fires when a node is expanded. Updates the node text and associated graphic feature with ' stored attribute data. Private Sub OnDemandTaskResults_NodeExpanded(ByVal sender As Object, ByVal args As ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNodeEventArgs) ' Make sure the expanded node is a feature node If Not (TypeOf args.Node Is ESRI.ArcGIS.ADF.Web.UI.WebControls.FeatureNode) Then Return End If ' If the node data cache contains data for the graphic feature, then update the feature ' and its node from the web tier cache. Otherwise, the control that created the result ' must implement IAttributesOnDemandProvider, so we update the result with information ' retrieved from that control. If Me.NodeDataCache.ContainsKey(args.Node.Attributes("GraphicID")) Then Me.UpdateResultFromCache(TryCast(args.Node, ESRI.ArcGIS.ADF.Web.UI.WebControls.FeatureNode), Nothing) ElseIf (Not Me.ProcessedNodes.Contains(args.Node.NodeID)) Then Me.UpdateResultFromProvider(TryCast(args.Node, ESRI.ArcGIS.ADF.Web.UI.WebControls.FeatureNode), Nothing) End If ' === USED ONLY FOR DEMONSTRATION - REMOVE FOR PRODUCTION PURPOSES == ' Suspend the current thread to allow the retrieving data activity indicator to display System.Threading.Thread.Sleep(1000) End Sub ' Fires when a node is removed. Removes any feature data associated with the passed-in node ' or child nodes from the graphic feature and node data caches. Private Sub OnDemandTaskResults_NodeRemoved(ByVal sender As Object, ByVal args As ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNodeRemovedEventArgs) ' Remove any feature data associated with the node or any of its descendants from the web tier cache Me.RemoveDataFromCache(args.Node) ' Add a callback result to dispose the client tier node object Dim disposeClientNodeScript As String = String.Format("$find('{0}').dispose()", args.Node.NodeID) Me.CallbackResults.Add(ESRI.ArcGIS.ADF.Web.UI.WebControls.CallbackResult.CreateJavaScript(disposeClientNodeScript)) End Sub #End Region #Region "ICallbackEventHandler Overrides - RaiseCallbackEvent" ' Retrieves node and graphic feature attributes when a task result MapTip is expanded Public Overrides Sub RaiseCallbackEvent(ByVal eventArgument As String) ' Get the callback arguments Dim callbackArgs As System.Collections.Specialized.NameValueCollection = ESRI.ArcGIS.ADF.Web.UI.WebControls.CallbackUtility.ParseStringIntoNameValueCollection(eventArgument) ' Check whether the argument specifies retrieval of a graphic feature's attributes or a new node page Select Case callbackArgs("EventArg") Case "getAttributes" ' Get the graphics layer and feature nodes associated with the graphic feature Dim graphicsLayerNode As ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNode = Me.Nodes.FindByNodeID(callbackArgs("LayerNodeID")) Dim featureNode As ESRI.ArcGIS.ADF.Web.UI.WebControls.FeatureNode = TryCast(Me.FindNodeByAttribute(graphicsLayerNode, "GraphicID", callbackArgs("GraphicID")), ESRI.ArcGIS.ADF.Web.UI.WebControls.FeatureNode) If featureNode Is Nothing Then Return End If ' If the node data cache contains data for the graphic feature, then update the feature ' and its node from the web tier cache. Otherwise, the control that created the result ' must implement IAttributesOnDemandProvider, so we update the result with information ' retrieved from that control. If Me.NodeDataCache.ContainsKey(featureNode.Attributes("GraphicID")) Then Me.UpdateResultFromCache(featureNode, callbackArgs("MapTipsID")) ElseIf (Not Me.ProcessedNodes.Contains(featureNode.NodeID)) Then Me.UpdateResultFromProvider(featureNode, callbackArgs("MapTipsID")) End If ' === USED ONLY FOR DEMONSTRATION - REMOVE FOR PRODUCTION PURPOSES == ' Suspend the current thread to allow the retrieving attributes indicator to display System.Threading.Thread.Sleep(1000) Case "getPage" ' Get the parent node for which a new page of child nodes needs to be retrieved Dim node As ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNode = Me.Nodes.FindByNodeID(callbackArgs("NodeID")) ' Update the node's page number property node.PageNumber = Integer.Parse(callbackArgs("PageNumber")) ' Get JSON used to initialize the new page of nodes on the client Dim nodesJson As String = Me.GetNodesJson(node) ' Construct JavaScript to initialize the new page on the client using client-side initialization ' methods and the nodes JSON. Wrap the script in a callback result for processing on the client. Dim changeNodePageScript As String = "" & ControlChars.CrLf & " var node = $find('{0}');" & ControlChars.CrLf & " node._newNodes[{1}] = {2};" & ControlChars.CrLf & " node.set_page({1});" changeNodePageScript = String.Format(changeNodePageScript, callbackArgs("NodeID"), node.PageNumber, nodesJson) Me.CallbackResults.Add(ESRI.ArcGIS.ADF.Web.UI.WebControls.CallbackResult.CreateJavaScript(changeNodePageScript)) Case "nodeChecked" ' Get the node that was checked Dim checkedNode As ESRI.ArcGIS.ADF.Web.UI.WebControls.FeatureNode = TryCast(Me.Nodes.FindByNodeID(callbackArgs("NodeID")), ESRI.ArcGIS.ADF.Web.UI.WebControls.FeatureNode) If checkedNode Is Nothing Then Exit Select End If ' Update the web tier Checked property of the node checkedNode.Checked = Boolean.Parse(callbackArgs("Checked")) ' Get the parent node of the checked node and make sure it's a GraphicsLayerNode Dim parentNode As ESRI.ArcGIS.ADF.Web.UI.WebControls.GraphicsLayerNode = TryCast(checkedNode.Parent, ESRI.ArcGIS.ADF.Web.UI.WebControls.GraphicsLayerNode) If parentNode Is Nothing Then Exit Select End If ' Find the row in the graphics layer corresponding to the checked node and update its selected ' value. This is necessary so Web ADF operations relying on this value, such as zooming to ' selected features, still function properly. For Each row As System.Data.DataRow In parentNode.Layer.Rows Dim graphicFeatureID As String = Me.GetGraphicClientID(row(parentNode.Layer.GraphicsIDColumn).ToString(), parentNode) If graphicFeatureID = checkedNode.Attributes("GraphicID") Then row(parentNode.Layer.IsSelectedColumn) = Boolean.Parse(callbackArgs("Checked")) Exit For End If Next row End Select MyBase.RaiseCallbackEvent(eventArgument) End Sub #End Region #Region "Private Methods" #Region "On-Demand Initialization - CreateOnDemandNodes, CreateOnDemandMapTips, CreateMapTipsInitScript" ' Replaces any feature data displayed by the passed-in node or child nodes with a "retrieving ' data" activity indicator and stores that feature data for on-demand retrieval Private Sub CreateOnDemandNodes(ByVal parentNode As ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNode, ByVal nodeMarkup As String, ByVal cacheInWebTier As Boolean) ' Attempt to get a reference to the passed-in node as a FeatureNode Dim featureNode As ESRI.ArcGIS.ADF.Web.UI.WebControls.FeatureNode = TryCast(parentNode, ESRI.ArcGIS.ADF.Web.UI.WebControls.FeatureNode) ' Check whether the node is a FeatureNode and has a child node. The child node will contain ' the feature's attribute data If Not featureNode Is Nothing AndAlso featureNode.Nodes.Count > 0 AndAlso featureNode.Nodes(0).Text.ToLower().Contains("<table>") Then ' Add the node's feature data to the node data cache If cacheInWebTier Then Me.NodeDataCache.Add(featureNode.Attributes("GraphicID"), featureNode.Nodes(0).Text) End If ' Update the node's text with the passed-in markup featureNode.Nodes(0).Text = nodeMarkup End If ' Recursively call this method for any nodes that are children of the passed-in node If parentNode.Nodes.Count > 0 Then For Each childNode As ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNode In parentNode.Nodes Me.CreateOnDemandNodes(childNode, nodeMarkup, cacheInWebTier) Next childNode End If Return End Sub ' Removes the attribute data of features contained in the graphics layer referenced by the passed-in node ' and stores this data in the web tier. Generates script to initialize the graphics layer's MapTips to ' display an activity indicator and retrieve stored data when a MapTip is expanded. Private Sub CreateOnDemandMapTips(ByVal graphicsLayerNode As ESRI.ArcGIS.ADF.Web.UI.WebControls.GraphicsLayerNode, ByVal activityIndicatorMarkup As String, ByVal cacheAttributes As Boolean) ' Get the layer referenced by the node Dim graphicsLayer As ESRI.ArcGIS.ADF.Web.Display.Graphics.GraphicsLayer = graphicsLayerNode.Layer If cacheAttributes Then ' Store a JSON string containing the MapTips attributes for each feature (row) in the ' graphics layer. For Each row As System.Data.DataRow In graphicsLayer.Rows Dim jsonAttributes As String = Me.GetAttributesJson(row, graphicsLayer) ' Add the attributes for the current row to the graphics attribute cache, specifying the ' corresponding client-side GraphicFeature ID as the key Dim graphicID As String = Me.GetGraphicClientID(row(graphicsLayer.GraphicsIDColumn).ToString(), graphicsLayerNode) Me.GraphicsAttributeCache.Add(graphicID, jsonAttributes) Next row ' Get the graphics layer's title template. This is the template used to format the title ' of the layer's MapTips Dim titleTemplate As String = graphicsLayer.GetTitleTemplate(True) ' Remove data from the graphics layer for all fields except IsSelected, graphics ID, geometry, and any column ' that is included in the MapTips title. Dim removeIndex As Integer = 0 Dim columnCount As Integer = graphicsLayer.Columns.Count Dim currentColumn As System.Data.DataColumn Dim i As Integer = 0 Do While i < columnCount currentColumn = graphicsLayer.Columns(removeIndex) If (Not titleTemplate.Contains("{" & currentColumn.ColumnName & "}")) AndAlso (currentColumn.ColumnName <> graphicsLayer.GraphicsIDColumn.ColumnName) AndAlso (currentColumn.ColumnName <> graphicsLayer.IsSelectedColumn.ColumnName) AndAlso (Not currentColumn.DataType.IsAssignableFrom(GetType(ESRI.ArcGIS.ADF.Web.Geometry.Geometry))) Then graphicsLayer.Columns.RemoveAt(removeIndex) Else removeIndex += 1 End If i += 1 Loop End If ' Generate JavaScript to initialize on-demand functionality for the graphics layer's MapTips ' on the client, and add this script to the TaskResults control's callback results Dim initializeMapTipsJavaScript As String = Me.CreateMapTipsInitScript(graphicsLayerNode, activityIndicatorMarkup) Me.CallbackResults.Add(ESRI.ArcGIS.ADF.Web.UI.WebControls.CallbackResult.CreateJavaScript(initializeMapTipsJavaScript)) End Sub ' Generates the JavaScript necessary to initialize attributes-on-demand functionality for the MapTips ' of the graphics layer referenced by the passed-in node. Private Function CreateMapTipsInitScript(ByVal graphicsLayerNode As ESRI.ArcGIS.ADF.Web.UI.WebControls.GraphicsLayerNode, ByVal activityIndicatorMarkup As String) As String ' Get the AJAX client ID of the GraphicFeatureGroup corresponding to the passed-in graphics layer Dim graphicsLayerClientID As String = Me.GetGraphicsLayerClientID(graphicsLayerNode.Layer) ' Add an attribute on the node to allow convenient retrieval of the GraphicFeatureGroup on the client graphicsLayerNode.Attributes.Add("GraphicFeatureGroupID", graphicsLayerClientID) ' Create a dictionary storing the on-demand mapTips initialization properties Dim mapTipsInitProperties As System.Collections.Generic.Dictionary(Of String, Object) = New System.Collections.Generic.Dictionary(Of String, Object)() mapTipsInitProperties.Add("graphicFeatureGroupID", graphicsLayerClientID) mapTipsInitProperties.Add("graphicsLayerNodeID", graphicsLayerNode.NodeID) mapTipsInitProperties.Add("activityIndicatorTemplate", activityIndicatorMarkup) mapTipsInitProperties.Add("callbackFunctionString", Me.CallbackFunctionString) Dim jsSerializer As System.Web.Script.Serialization.JavaScriptSerializer = New System.Web.Script.Serialization.JavaScriptSerializer() ' Construct script to call the client tier on-demand MapTips initialization method. This logic is ' embedded in a timeout so it executes after the results graphics layer has been created. The ' initialization properties, stored in a dictionary above, are serialized to JSON and temporarily ' stored on the client tier task results object so they can be accessed from within the timeout. Dim mapTipsInitJavaScript As String = "var taskResults = $find('{0}');" & ControlChars.CrLf & " if (!taskResults._initializationProps)" & ControlChars.CrLf & " taskResults._initializationProps = new Array();" & ControlChars.CrLf & " taskResults._initializationProps.push({1});" & ControlChars.CrLf & " window.setTimeout(""var mapTips = $find('{2}').get_mapTips();"" +" & ControlChars.CrLf & " ""mapTips.setupOnDemandMapTips($find('{0}')._initializationProps.splice(0,1)[0]);"", 0);" Return String.Format(mapTipsInitJavaScript, Me.ClientID, jsSerializer.Serialize(mapTipsInitProperties), graphicsLayerClientID) End Function #End Region #Region "On-Demand Data Retrieval - UpdateResultFromProvider, UpdateResultFromCache, UpdateNodeData, UpdateGraphicFeatureData" ' Creates and adds the callback results necessary to update the graphic feature and feature node ' referred to by the passed-in node. Retrieves the feature data from the control that created ' the result. Private Sub UpdateResultFromProvider(ByVal featureNode As ESRI.ArcGIS.ADF.Web.UI.WebControls.FeatureNode, ByVal mapTipsClientID As String) ' Get the graphics layer and task result nodes that contain the passed-in feature node Dim graphicsLayerNode As ESRI.ArcGIS.ADF.Web.UI.WebControls.GraphicsLayerNode = TryCast(featureNode.Parent, ESRI.ArcGIS.ADF.Web.UI.WebControls.GraphicsLayerNode) If graphicsLayerNode Is Nothing Then Return End If Dim taskResultNode As ESRI.ArcGIS.ADF.Web.UI.WebControls.TaskResultNode = TryCast(graphicsLayerNode.Parent, ESRI.ArcGIS.ADF.Web.UI.WebControls.TaskResultNode) If taskResultNode Is Nothing Then Return End If ' Get the control that added the result and cast it to the IAttributesOnDemandProvider interface Dim attributesProvider As OnDemandTaskResults_VBNet.IAttributesOnDemandProvider = TryCast(ESRI.ArcGIS.ADF.Web.UI.WebControls.Utility.FindControl(taskResultNode.Attributes("AttributesProviderID"), Me.Page), OnDemandTaskResults_VBNet.IAttributesOnDemandProvider) ' Extract the feature ID from the graphic feature ID Dim graphicFeatureID As String = featureNode.Attributes("GraphicID") Dim featureID As String = graphicFeatureID.Substring(graphicFeatureID.LastIndexOf("_"c) + 1) ' Get the feature's attributes Dim attributesRow As System.Data.DataRow = attributesProvider.GetAttributeData(featureID) ' Update the feature node and graphic feature with the attributes Dim nodeMarkup As String = Me.GetDataRowHtmlTable(attributesRow) Me.UpdateNodeData(featureNode, nodeMarkup) Dim attributesJson As String = Me.GetAttributesJson(attributesRow, graphicsLayerNode.Layer) Me.UpdateGraphicFeatureData(graphicFeatureID, attributesJson, mapTipsClientID) ' Flag the passed-in node as processed so the control won't attempt to retrieve attributes for the same ' node again. Me.ProcessedNodes.Add(featureNode.NodeID) End Sub ' Creates and adds the callback results necessary to update the graphic feature and feature node ' referred to by the passed-in node. Retrieves the feature data from the web tier cache created ' by this instance. Private Sub UpdateResultFromCache(ByVal featureNode As ESRI.ArcGIS.ADF.Web.UI.WebControls.FeatureNode, ByVal mapTipsClientID As String) ' Get the graphic feature ID Dim graphicID As String = featureNode.Attributes("GraphicID") ' Update the server-side representation of the node with the stored data Dim nodeMarkup As String = Me.NodeDataCache(graphicID) Me.UpdateNodeData(featureNode, nodeMarkup) ' Remove the node's data from web tier storage Me.NodeDataCache.Remove(graphicID) ' Get the JSON string containing the feature data from storage Dim attributesJson As String = Me.GraphicsAttributeCache(graphicID) ' Update the graphic feature's data Me.UpdateGraphicFeatureData(graphicID, attributesJson, mapTipsClientID) ' Remove the attribute data for the specified feature from storage Me.GraphicsAttributeCache.Remove(graphicID) ' Flag the passed-in node as processed so the control won't attempt to retrieve attributes for the same ' node again. Me.ProcessedNodes.Add(featureNode.NodeID) End Sub ' Updates the text of the passed-in node with the feature data corresponding to the node Private Sub UpdateNodeData(ByVal node As ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNode, ByVal nodeMarkup As String) ' Update the node's text node.Nodes(0).Text = nodeMarkup ' Get the node's markup and serialize it to JSON Dim jsSerializer As System.Web.Script.Serialization.JavaScriptSerializer = New System.Web.Script.Serialization.JavaScriptSerializer() nodeMarkup = jsSerializer.Serialize(Me.GetNodeHtml(node)) ' Construct JavaScript to update the node's content on the client. Package the script in a callback ' result for client-side processing. Dim nodeUpdateScript As String = "" & ControlChars.CrLf & " var node = $find('{0}');" & ControlChars.CrLf & " if (node)" & ControlChars.CrLf & " node.set_content({1});" nodeUpdateScript = String.Format(nodeUpdateScript, node.NodeID, nodeMarkup) Me.CallbackResults.Add(ESRI.ArcGIS.ADF.Web.UI.WebControls.CallbackResult.CreateJavaScript(nodeUpdateScript)) End Sub ' Updates the attributes of the client tier GraphicFeature specified by the passed-in ID with ' the feature data stored in the web tier Private Sub UpdateGraphicFeatureData(ByVal graphicFeatureID As String, ByVal attributesJson As String, ByVal mapTipsClientID As String) Dim updateAttributesScript As String ' Check whether a client-side MapTips control was specified If Not mapTipsClientID Is Nothing Then ' Create script to update the attributes from the specified client tier MapTips control. This ' will not only update the GraphicFeature, but also immediately update the MapTip to show ' those attributes, if that MapTip is still open when the data is returned to the client. updateAttributesScript = "" & ControlChars.CrLf & " var mapTips = $find('{0}');" & ControlChars.CrLf & " mapTips.updateAttributes('{1}', {2});" updateAttributesScript = String.Format(updateAttributesScript, mapTipsClientID, graphicFeatureID, attributesJson) Else ' Create script to update the attributes of the specified GraphicFeature directly updateAttributesScript = "" & ControlChars.CrLf & " var graphicFeature = $find('{0}');" & ControlChars.CrLf & " graphicFeature.set_attributes({1});" & ControlChars.CrLf & " graphicFeature.set_hasAttributes(true);" updateAttributesScript = String.Format(updateAttributesScript, graphicFeatureID, attributesJson) End If ' Add the script to the TaskResults control's callback results Me.CallbackResults.Add(ESRI.ArcGIS.ADF.Web.UI.WebControls.CallbackResult.CreateJavaScript(updateAttributesScript)) End Sub #End Region #Region "Activity Indicator Creation - CreateActivityIndicatorTable, GetControlHtml" ' Generates a Table WebControl containing an activity indicator with the text specified via the ' ActivityIndicatorText property Private Function CreateActivityIndicatorMarkup() As String ' Get the web resource URL for the activity indicator gif Dim activityIndicatorUrl As String = Me.Page.ClientScript.GetWebResourceUrl(GetType(OnDemandTaskResults_VBNet.OnDemandTaskResults), "callbackActivityIndicator.gif") ' Create an Image control and initialize it to reference the activity indicator Dim activityIndicator As System.Web.UI.WebControls.Image = New System.Web.UI.WebControls.Image() activityIndicator.ImageUrl = activityIndicatorUrl ' Create a cell for the indicator Dim tableCell As System.Web.UI.WebControls.TableCell = New System.Web.UI.WebControls.TableCell() tableCell.Controls.Add(activityIndicator) ' Create a row and add the indicator cell to it Dim tableRow As System.Web.UI.WebControls.TableRow = New System.Web.UI.WebControls.TableRow() tableRow.VerticalAlign = System.Web.UI.WebControls.VerticalAlign.Middle tableRow.Cells.Add(tableCell) ' Create the indicator label Dim activityLabel As System.Web.UI.WebControls.Label = New System.Web.UI.WebControls.Label() activityLabel.Text = Me.ActivityIndicatorText activityLabel.Font.Italic = True activityLabel.ForeColor = System.Drawing.Color.Gray ' Add the indicator label to a new cell tableCell = New System.Web.UI.WebControls.TableCell() tableCell.Controls.Add(activityLabel) tableRow.Cells.Add(tableCell) ' Add the indicator label cell to the indicator row Dim activityIndicatorTable As System.Web.UI.WebControls.Table = New System.Web.UI.WebControls.Table() activityIndicatorTable.Rows.Add(tableRow) ' Return the table's markup Return Me.GetControlHtml(activityIndicatorTable) End Function ' Used to retrieve the markup for the activity indicator that will be shown when task results feature ' nodes or MapTips are first expanded Private Function GetControlHtml(ByVal control As System.Web.UI.WebControls.WebControl) As String ' Use RenderControl to retrieve the markup for the passed-in control Dim stringWriter As System.IO.StringWriter = New System.IO.StringWriter() Dim htmlWriter As System.Web.UI.HtmlTextWriter = New System.Web.UI.HtmlTextWriter(stringWriter) control.RenderControl(htmlWriter) ' Return the markup Return stringWriter.ToString() End Function #End Region #Region "Node Retrieval - FindNodeByAttribute, FindChildGraphicsLayerNode, FindFeatureNodes" ' Searches the passed-in node and its descendants for a node with the specified attribute Private Function FindNodeByAttribute(ByVal parentNode As ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNode, ByVal attribute As String, ByVal value As String) As ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNode ' Retrieve the specified attribute from the passed-in node Dim currentValue As String = parentNode.Attributes(attribute) ' Return the passed-in node if it has an attribute matching the one sought If currentValue = value Then Return parentNode End If ' Recursively call this function to search for the specified attribute on child nodes Dim matchingNode As ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNode = Nothing For Each childNode As ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNode In parentNode.Nodes matchingNode = Me.FindNodeByAttribute(childNode, attribute, value) If Not matchingNode Is Nothing Then Exit For End If Next childNode Return matchingNode End Function ' Retrieves all GraphicsLayerNodes that are descendants of the passed-in node, if available Private Function FindChildGraphicsLayerNodes(ByVal node As ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNode) As System.Collections.Generic.List(Of ESRI.ArcGIS.ADF.Web.UI.WebControls.GraphicsLayerNode) Dim nodes As System.Collections.Generic.List(Of ESRI.ArcGIS.ADF.Web.UI.WebControls.GraphicsLayerNode) = New System.Collections.Generic.List(Of ESRI.ArcGIS.ADF.Web.UI.WebControls.GraphicsLayerNode)() Dim graphicsLayerNode As ESRI.ArcGIS.ADF.Web.UI.WebControls.GraphicsLayerNode = TryCast(node, ESRI.ArcGIS.ADF.Web.UI.WebControls.GraphicsLayerNode) If Not graphicsLayerNode Is Nothing Then nodes.Add(graphicsLayerNode) End If For Each childNode As ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNode In node.Nodes nodes.AddRange(Me.FindChildGraphicsLayerNodes(childNode)) Next childNode Return nodes End Function ' Retrieves all FeatureNodes that are descendants of the passed-in node, if available Private Function FindChildFeatureNodes(ByVal node As ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNode) As System.Collections.Generic.List(Of ESRI.ArcGIS.ADF.Web.UI.WebControls.FeatureNode) Dim nodes As System.Collections.Generic.List(Of ESRI.ArcGIS.ADF.Web.UI.WebControls.FeatureNode) = New System.Collections.Generic.List(Of ESRI.ArcGIS.ADF.Web.UI.WebControls.FeatureNode)() Dim featureNode As ESRI.ArcGIS.ADF.Web.UI.WebControls.FeatureNode = TryCast(node, ESRI.ArcGIS.ADF.Web.UI.WebControls.FeatureNode) If Not featureNode Is Nothing Then nodes.Add(featureNode) End If For Each childNode As ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNode In node.Nodes nodes.AddRange(Me.FindChildFeatureNodes(childNode)) Next childNode Return nodes End Function #End Region #Region "Node Information - GetNodesJson, GetNodeHtml, GetCurrentPageNodeIDs" ' Retrieves the IDs and html for child nodes of the passed-in node on the node's current page, ' formatted as a JSON string Private Function GetNodesJson(ByVal parentNode As ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNode) As String ' Get the index of the first and last nodes on the curernt page Dim startIndex As Integer = (parentNode.PageNumber - 1) * parentNode.PageSize Dim stopIndex As Integer = parentNode.PageNumber * parentNode.PageSize If stopIndex > parentNode.Nodes.Count Then stopIndex = parentNode.Nodes.Count End If ' Create a list to store all the node properties Dim nodeList As System.Collections.Generic.List(Of System.Collections.Generic.Dictionary(Of String, Object)) = New System.Collections.Generic.List(Of System.Collections.Generic.Dictionary(Of String, Object))() ' Create a dictionary to store each node's contents Dim nodeContents As System.Collections.Generic.Dictionary(Of String, Object) ' Loop through the nodes on the current page and add the ID and markup of each to the property list Dim i As Integer = startIndex Do While i < stopIndex nodeContents = New System.Collections.Generic.Dictionary(Of String, Object)() nodeContents.Add("nodeID", parentNode.Nodes(i).NodeID) nodeContents.Add("_content", Me.GetNodeHtml(parentNode.Nodes(i))) nodeList.Add(nodeContents) i += 1 Loop ' Serialize the node properties to JSON and return Dim jsSerializer As System.Web.Script.Serialization.JavaScriptSerializer = New System.Web.Script.Serialization.JavaScriptSerializer() Return jsSerializer.Serialize(nodeList) End Function ' Retrieves the markup for the passed-in node Private Function GetNodeHtml(ByVal node As ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNode) As String Dim stringWriter As System.IO.StringWriter = New System.IO.StringWriter() Dim htmlWriter As System.Web.UI.HtmlTextWriter = New System.Web.UI.HtmlTextWriter(stringWriter) ' Output the node markup to the html text writer and return its string representation node.Render(htmlWriter) Return stringWriter.ToString() End Function ' Retrieves the Node IDs of any feature nodes on the passed-in graphics layer node's current page and returns ' them as a comma-delimited string. Private Function GetCurrentPageNodeIDs(ByVal graphicsLayerNode As ESRI.ArcGIS.ADF.Web.UI.WebControls.GraphicsLayerNode) As System.Collections.Generic.List(Of String) ' Retrieve feature nodes that are descendants of the passed-in node Dim featureNodes As System.Collections.Generic.List(Of ESRI.ArcGIS.ADF.Web.UI.WebControls.FeatureNode) = Me.FindChildFeatureNodes(graphicsLayerNode) ' Get the indexes of the first and last nodes on the current page Dim startIndex As Integer = (graphicsLayerNode.PageNumber - 1) * graphicsLayerNode.PageSize Dim stopIndex As Integer = graphicsLayerNode.PageNumber * graphicsLayerNode.PageSize If stopIndex > graphicsLayerNode.Nodes.Count Then stopIndex = graphicsLayerNode.Nodes.Count End If ' Loop through the nodes on the page and add the ID of each to a string Dim featureNode As ESRI.ArcGIS.ADF.Web.UI.WebControls.FeatureNode = Nothing Dim nodeIDs As System.Collections.Generic.List(Of String) = New System.Collections.Generic.List(Of String)() Dim i As Integer = startIndex Do While i < stopIndex featureNode = TryCast(graphicsLayerNode.Nodes(i), ESRI.ArcGIS.ADF.Web.UI.WebControls.FeatureNode) If Not featureNode Is Nothing Then nodeIDs.Add(featureNode.NodeID) End If i += 1 Loop Return nodeIDs End Function #End Region #Region "Client Graphic ID Retrieval - GetGraphicsLayerClientID, GetGraphicClientID" ' Retrieves the client ID of the GraphicFeatureGroup corresponding to the passed-in GraphicsLayer. ' Assumes this GraphicsLayer is associated with a GraphicsLayerNode - the ID will be different otherwise. Private Function GetGraphicsLayerClientID(ByVal graphicsLayer As ESRI.ArcGIS.ADF.Web.Display.Graphics.GraphicsLayer) As String Dim featureGraphicsLayer As ESRI.ArcGIS.ADF.Web.Display.Graphics.FeatureGraphicsLayer = TryCast(graphicsLayer, ESRI.ArcGIS.ADF.Web.Display.Graphics.FeatureGraphicsLayer) If featureGraphicsLayer Is Nothing Then Return Nothing End If Dim graphicsLayerID As String = String.Format("{0}_{1} {2} Results_{3}", Me.MapInstance.ClientID, Me.ClientID, featureGraphicsLayer.FeatureType.ToString(), graphicsLayer.TableName) Return graphicsLayerID End Function ' Retrieves the AJAX component ID of a GraphicFeature referenced by the passed-in node or its descendants, ' given the unique ID specified in the feature's attribute data. The unique ID is part of the component ' ID, but the two are not the same. Private Function GetGraphicClientID(ByVal attributeUniqueID As String, ByVal parentNode As ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNode) As String ' Attempt to get a reference to the passed-in node as a FeatureNode Dim featureNode As ESRI.ArcGIS.ADF.Web.UI.WebControls.FeatureNode = TryCast(parentNode, ESRI.ArcGIS.ADF.Web.UI.WebControls.FeatureNode) Dim clientGraphicID As String = Nothing If Not featureNode Is Nothing AndAlso (Not String.IsNullOrEmpty(featureNode.Attributes("GraphicID"))) Then ' Get the AJAX componenent ID of the GraphicFeature corresponding to the current FeatureNode Dim graphicID As String = featureNode.Attributes("graphicID") ' Extract the GraphicFeature's unique ID from the component ID Dim nodeUniqueID As String = graphicID.Substring(graphicID.LastIndexOf("_") + 1) ' If the extracted unique ID matches that passed-in, use the passed-in node's GraphicFeature ID ' to initialize the return value If attributeUniqueID = nodeUniqueID Then clientGraphicID = graphicID End If ElseIf parentNode.Nodes.Count > 0 Then ' Try retrieving the GraphicFeature ID from the passed-in node's child nodes For Each childNode As ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNode In parentNode.Nodes clientGraphicID = Me.GetGraphicClientID(attributeUniqueID, childNode) If (Not String.IsNullOrEmpty(clientGraphicID)) Then Exit For End If Next childNode End If Return clientGraphicID End Function #End Region ' Removes feature data contained by the passed-in node or any of its descendants from the node and ' graphic feature attributes data caches Private Sub RemoveDataFromCache(ByVal parentNode As ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNode) ' If the passed-in node's graphic ID has entries in the node and graphic attribute caches, ' remove those entries Dim graphicID As String = parentNode.Attributes("GraphicID") If (Not String.IsNullOrEmpty(graphicID)) AndAlso Me.NodeDataCache.ContainsKey(graphicID) Then Me.NodeDataCache.Remove(graphicID) Me.GraphicsAttributeCache.Remove(graphicID) End If ' Remove node and graphic attribute data corresponding to nodes that are children of the ' passed-in node If parentNode.Nodes.Count > 0 Then For Each childNode As ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNode In parentNode.Nodes Me.RemoveDataFromCache(childNode) Next childNode End If Return End Sub ' Retrieves the control that initiated the asynchronous request Private Function GetCallingControl(ByVal page As System.Web.UI.Page) As System.Web.UI.Control If page Is Nothing Then Return Nothing End If If page.IsCallback Then Dim controlID As String = page.Request.Params("__CALLBACKID") Dim control As System.Web.UI.Control = page.FindControl(controlID) Return control ' For 9.3 we could be using a partial postback instead ElseIf page.IsPostBack AndAlso Not System.Web.UI.ScriptManager.GetCurrent(page) Is Nothing AndAlso System.Web.UI.ScriptManager.GetCurrent(page).IsInAsyncPostBack Then Dim controlID As String = System.Web.UI.ScriptManager.GetCurrent(page).AsyncPostBackSourceElementID Dim control As System.Web.UI.Control = page.FindControl(controlID) Return control Else 'Not an asyncronous request Return Nothing End If End Function ' Converts a DataRow to a JSON string. Uses the passed-in graphics layer to determine which fields ' in the row should be included. Private Function GetAttributesJson(ByVal attributesRow As System.Data.DataRow, ByVal graphicsLayer As ESRI.ArcGIS.ADF.Web.Display.Graphics.GraphicsLayer) As String Dim visibilityString As String Dim visibility As Boolean Dim attributesDictionary As System.Collections.Generic.Dictionary(Of String, Object) = New System.Collections.Generic.Dictionary(Of String, Object)() ' Add the value of each column that is visible, not the is selected column, and not a geometry column ' to the string dictionary. For Each column As System.Data.DataColumn In attributesRow.Table.Columns visibilityString = TryCast(column.ExtendedProperties(ESRI.ArcGIS.ADF.Web.Constants.ADFVisibility), String) If (visibilityString Is Nothing) Then visibility = False Else visibility = Boolean.Parse(visibilityString) End If If (column.ColumnName <> graphicsLayer.IsSelectedColumn.ColumnName) AndAlso visibility AndAlso (Not column.DataType.IsAssignableFrom(GetType(ESRI.ArcGIS.ADF.Web.Geometry.Geometry))) Then attributesDictionary.Add(column.ColumnName, attributesRow(column.ColumnName)) End If Next column ' Convert the string dictionary to JSON Dim jsSerialzer As System.Web.Script.Serialization.JavaScriptSerializer = New System.Web.Script.Serialization.JavaScriptSerializer() Return jsSerialzer.Serialize(attributesDictionary) End Function #End Region End Class End Namespace