Common Task results
Common_TaskResults_VBNet\TaskResultsWebSite\App_Code\TabularResultsTask.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 ESRI.ADF.Samples.CustomTasks
  Public Class TabularResultsTask
    Inherits ESRI.ArcGIS.ADF.Tasks.QueryAttributesTask
    #Region "Instance Variable Declarations"

    ' Stores a reference to the buddied TaskResults Control
    Private m_taskResults As ESRI.ArcGIS.ADF.Web.UI.WebControls.TaskResults = Nothing

    ' Tracks whether the current page request has routed through ExecuteTask
    Private _taskExecuted As Boolean = False

    ' Stores a reference to the custom context menu that provides an option to view the
    ' results table
    Private _graphicsLayerContextMenu As ESRI.ArcGIS.ADF.Web.UI.WebControls.ContextMenu

    #End Region

    #Region "ASP.NET WebControl Life Cycle Event Handlers"

    ' Creates the custom task results context menu and wires task results control event handlers
    Protected Overrides Sub CreateChildControls()
      MyBase.CreateChildControls()

      Me.CreateTaskResultsPanelContextMenu()

      ' Add a handler to the buddied TaskResults Control's NodeAdded event
      AddHandler TaskResultsInstance.NodeAdded, AddressOf TaskResultsInstance_NodeAdded
      AddHandler TaskResultsInstance.NodeRemoved, AddressOf TaskResultsInstance_NodeRemoved
    End Sub

    ' Registers the client script needed by TaskResultsPanel.  We do this here since no 
    ' TaskResultsPanel is being created during application start-up.
    Protected Overrides Sub Render(ByVal writer As System.Web.UI.HtmlTextWriter)
      MyBase.Render(writer)

      ESRI.ADF.Samples.CustomTasks.TaskResultsPanel.RegisterScripts(Me)
    End Sub

    #End Region

    #Region "Web ADF Control Event Handlers"

    ' Retrieves the results graphics layer and uses it to initialize a TaskResultsPanel.  Done here for extended tasks
    ' because the results graphics layer available in ExecuteTask is replaced during subsequent task result node creation.
    Private Sub TaskResultsInstance_NodeAdded(ByVal sender As Object, ByVal args As ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNodeEventArgs)
      If Me._taskExecuted Then
        ' Get the GraphicsLayerNode that is an ancestor or descendant of the current node
        Dim graphicsLayerNode As ESRI.ArcGIS.ADF.Web.UI.WebControls.GraphicsLayerNode = Me.GetRelatedGraphicsLayerNode(args.Node)

        ' Make sure a GraphicsLayerNode was found 
        If Not graphicsLayerNode Is Nothing Then
          ' Create the TaskResultsPanel that will be used to display results
          Dim taskResultsPanelID As String = String.Format("{0}_TaskResultsPanel", graphicsLayerNode.NodeID)
          Dim taskResultsPanelTitle As String = String.Format("{0} - {1} {2}", Me.Title, Me.PredefinedQuery.FormEntries(0).LabelText, Me.PredefinedQuery.FormEntries(0).UserInput)
          Dim taskResultsPanel As ESRI.ADF.Samples.CustomTasks.TaskResultsPanel = Me.CreateTaskResultsPanel(taskResultsPanelID, taskResultsPanelTitle)

          ' When a Web ADF FloatingPanel is rendered during an asynchronous request, the ADF automatically creates
          ' a callback result that includes a call to the private client-side function _checkDock.  In cases where the
          ' FloatingPanel does not have a docking container, this interferes with the FloatingPanel's initialization.
          ' So we remove that callback result here.                    
          Dim checkDockJavaScript As String = String.Format("$find('{0}')._checkDock();", taskResultsPanel.ClientID)
          Dim callbackResultToRemove As ESRI.ArcGIS.ADF.Web.UI.WebControls.CallbackResult = Nothing
          For Each callbackResult As ESRI.ArcGIS.ADF.Web.UI.WebControls.CallbackResult In taskResultsPanel.CallbackResults
            If TryCast(callbackResult.Parameters(0), String) = checkDockJavaScript Then
              callbackResultToRemove = callbackResult
              Exit For
            End If
          Next callbackResult
          If Not callbackResultToRemove Is Nothing Then
            taskResultsPanel.CallbackResults.Remove(callbackResultToRemove)
          End If

          ' Get the name of the resource containing the results GraphicsLayer 
          Dim resourceName As String = graphicsLayerNode.Layer.DataSet.DataSetName

          ' Construct the client-side GraphicFeatureGroup ID of the results graphics layer.  Note that this is only necessary
          ' for extended out-of-the-box tasks.  Otherwise, Map::GetGraphicsLayerClientID or MapTips::GraphicsLayerClientID can
          ' be used.
          Dim featureGraphicsLayer As ESRI.ArcGIS.ADF.Web.Display.Graphics.FeatureGraphicsLayer = TryCast(graphicsLayerNode.Layer, ESRI.ArcGIS.ADF.Web.Display.Graphics.FeatureGraphicsLayer)
          Dim graphicsLayerClientID As String = String.Format("{0}_{1} {2} Results_{3}", Me.TaskResultsInstance.MapInstance.ClientID, Me.TaskResultsInstance.ClientID, featureGraphicsLayer.FeatureType.ToString(), graphicsLayerNode.Layer.TableName)
          ' Call SetLayer to associate the TaskResultsPanel with the GraphicsLayer
          taskResultsPanel.SetLayer(featureGraphicsLayer, resourceName, Me.TaskResultsInstance.Map, graphicsLayerClientID)

          ' Call ShowFloatingPanel to display the results panel
          taskResultsPanel.ShowFloatingPanel()

          ' Associate the context menu with the "view table" option to the graphics layer node
          Me.TaskResultsInstance.SetupContextMenu(_graphicsLayerContextMenu, graphicsLayerNode)

          ' Copy the results panel's callback results to the task's results collection so changes made to the
          ' panel requiring client-side handling are processed
          Me.CallbackResults.CopyFrom(taskResultsPanel.CallbackResults)

          ' Reset the flag indicating whether a new TaskResultsPanel needs to be created
          Me._taskExecuted = False
        End If
      End If
    End Sub

    Public Overrides Function GetCallbackResult() As String
      Return MyBase.GetCallbackResult()
    End Function

    ' Checks whether the removed node is a GraphicsLayerNode and removes any associated TaskResultsPanel if so
    Private Sub TaskResultsInstance_NodeRemoved(ByVal sender As Object, ByVal args As ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNodeRemovedEventArgs)
      ' Call method to retrieve a GraphicsLayerNode that is the child of the current node.  Note this method
      ' will also check whether the current node is a GraphicsLayerNode
      Dim graphicsLayerNode As ESRI.ArcGIS.ADF.Web.UI.WebControls.GraphicsLayerNode = Me.FindChildGraphicsLayerNode(args.Node)

      ' Check whether a GraphicsLayerNode was found
      If Not graphicsLayerNode Is Nothing Then
        ' Construct JavaScript to find the TaskResultsPanel corresponding to the GraphicsLayerNode and 
        ' destroy it.
        Dim taskResultsPanelClientID As String = String.Format("{0}_{1}_TaskResultsPanel", Me.ClientID, graphicsLayerNode.NodeID)
        Dim disposeResultsPanelJavaScript As String = "" & ControlChars.CrLf & "                var taskResultsPanel = $find('{0}');" & ControlChars.CrLf & "                if (taskResultsPanel)" & ControlChars.CrLf & "                {{" & ControlChars.CrLf & "                    taskResultsPanel.hide(false);" & ControlChars.CrLf & "                    taskResultsPanel.dispose();" & ControlChars.CrLf & "                }}" & ControlChars.CrLf & "                var element = $get('{0}');" & ControlChars.CrLf & "                if (element)" & ControlChars.CrLf & "                    element.parentNode.removeChild(element);"
        disposeResultsPanelJavaScript = String.Format(disposeResultsPanelJavaScript, taskResultsPanelClientID)

        ' Encapsulate the JavaScript in a callback result and add it to the task's collection of CallbackResults
        Dim disposeResultsPanelCallbackResult As ESRI.ArcGIS.ADF.Web.UI.WebControls.CallbackResult = ESRI.ArcGIS.ADF.Web.UI.WebControls.CallbackResult.CreateJavaScript(disposeResultsPanelJavaScript)
        Me.TaskResultsInstance.CallbackResults.Add(disposeResultsPanelCallbackResult)
      End If
    End Sub

    ' Fires when the custom GraphicLayer context menu is closed.
    Private Sub GraphicsLayerContextMenu_Dismissed(ByVal sender As Object, ByVal args As ESRI.ArcGIS.ADF.Web.UI.WebControls.ContextMenuDismissedEventArgs)
      Me.TaskResultsInstance.ContextMenuDismissed(_graphicsLayerContextMenu, args)
    End Sub

    ' Fires when an item on the custom GraphicsLayer context menu is clicked
    Private Sub GraphicsLayerContextMenu_ItemClicked(ByVal sender As Object, ByVal args As ESRI.ArcGIS.ADF.Web.UI.WebControls.ContextMenuItemEventArgs)
      ' Get the node on which the context menu was displayed
      Dim graphicsLayerNode As ESRI.ArcGIS.ADF.Web.UI.WebControls.GraphicsLayerNode = TryCast(TaskResultsInstance.Nodes.FindByNodeID(args.Context), ESRI.ArcGIS.ADF.Web.UI.WebControls.GraphicsLayerNode)

      ' Check the text of the item clicked
      Select Case args.Item.Text
        Case "Zoom To Selected Features"
          If graphicsLayerNode Is Nothing OrElse Me.MapInstance Is Nothing Then
            Return
          End If
          Dim hasFeaturesSelected As Boolean = False

          ' Get the GraphicsLayer associated with the node
          Dim graphicsLayer As ESRI.ArcGIS.ADF.Web.Display.Graphics.GraphicsLayer = graphicsLayerNode.Layer

          ' Declare an envelope to store the combined extent of all features in the layer
          Dim adfEnvelope As ESRI.ArcGIS.ADF.Web.Geometry.Envelope = New ESRI.ArcGIS.ADF.Web.Geometry.Envelope()

          ' Loop through the rows (i.e. features) of the graphics layer, adding the envelope of each to the
          ' combined extent envelope
          Dim i As Integer = 0
          Do While i < graphicsLayer.Rows.Count
            If CBool(graphicsLayer.Rows(i)(graphicsLayer.IsSelectedColumn)) Then
              hasFeaturesSelected = True

              Dim rowGeometry As ESRI.ArcGIS.ADF.Web.Geometry.Geometry = graphicsLayer.GeometryFromRow(graphicsLayer.Rows(i))
              adfEnvelope.Union(rowGeometry)
            End If
            i += 1
          Loop

          If (Not hasFeaturesSelected) Then
          Return
          End If

          ' If combined envelope width or height is zero, zoom in the amount specified by the 
          ' ZoomToPointFactor property
          If adfEnvelope.Width = 0 OrElse adfEnvelope.Height = 0 Then
            Dim adfPoint As ESRI.ArcGIS.ADF.Web.Geometry.Point = New ESRI.ArcGIS.ADF.Web.Geometry.Point(adfEnvelope.XMax, adfEnvelope.YMax)
            Dim fullExtentEnvelope As ESRI.ArcGIS.ADF.Web.Geometry.Envelope = Me.MapInstance.GetFullExtent()

            Dim widthMargin As Double = (fullExtentEnvelope.Width / TaskResultsInstance.ZoomToPointFactor) / 2
            Dim heightMargin As Double = (fullExtentEnvelope.Height / TaskResultsInstance.ZoomToPointFactor) / 2

            Dim zoomToEnvelope As ESRI.ArcGIS.ADF.Web.Geometry.Envelope = New ESRI.ArcGIS.ADF.Web.Geometry.Envelope()

            zoomToEnvelope.XMax = adfPoint.X + widthMargin
            zoomToEnvelope.XMin = adfPoint.X - widthMargin
            zoomToEnvelope.YMax = adfPoint.Y + heightMargin
            zoomToEnvelope.YMin = adfPoint.Y - heightMargin

            Me.MapInstance.Extent = zoomToEnvelope
          Else
            ' Apply the combined feature extent to the map
            Me.MapInstance.Extent = adfEnvelope
          End If

          ' Copy the map's callback results to the context menu so the extent change is processed on the client
          _graphicsLayerContextMenu.CallbackResults.CopyFrom(Me.MapInstance.CallbackResults)
        Case "Remove"
          If Me.MapInstance Is Nothing OrElse graphicsLayerNode Is Nothing Then
            Return
          End If

          ' Check whether there is a GraphicsLayer associated with the node
          If Not graphicsLayerNode.Layer Is Nothing Then
            ' Remove the GraphicsLayer associated with the node from the map and refresh the layer's
            ' parent resource
            Dim graphicsResourceName As String = graphicsLayerNode.RemoveFromMap(Me.TaskResultsInstance)
            Me.MapInstance.RefreshResource(graphicsResourceName)

            ' Copy the map's callback results to the context menu so the map is updated on the client
            _graphicsLayerContextMenu.CallbackResults.CopyFrom(Me.MapInstance.CallbackResults)
          End If

          ' Remove the node and refresh the buddied TaskResults control
          graphicsLayerNode.Remove()
          Me.TaskResultsInstance.Refresh()

          ' Copy the buddied TaskResults control's callback results to the context menu so the node removal
          ' is processed on the client
          _graphicsLayerContextMenu.CallbackResults.CopyFrom(TaskResultsInstance.CallbackResults)
        Case "View Attribute Table"
          ' Construct JavaScript to call the client-side Web ADF function to display the TaskResultsPanel
          Dim taskResultsPanelClientID As String = String.Format("{0}_{1}_TaskResultsPanel", Me.ClientID, args.Context)
          Dim showTaskResultsPanelJavaScript As String = String.Format("showFloatingPanel('{0}', false);", taskResultsPanelClientID)
          Dim showTaskResultsPanelCallbackResult As ESRI.ArcGIS.ADF.Web.UI.WebControls.CallbackResult = ESRI.ArcGIS.ADF.Web.UI.WebControls.CallbackResult.CreateJavaScript(showTaskResultsPanelJavaScript)
          _graphicsLayerContextMenu.CallbackResults.Add(showTaskResultsPanelCallbackResult)
      End Select
    End Sub

    #End Region

    #Region "Task Overrides - ExecuteTask"

    Public Overrides Sub ExecuteTask()
      ' Create a default set of results
      MyBase.ExecuteTask()

      ' Set a flag indicating that the task has executed as part of the current request
      Me._taskExecuted = True
    End Sub

    #End Region

    #Region "Instance Properties"

    ' Convenient access to the first TaskResults control in the Task's TaskResultsContainers collection
    Private ReadOnly Property TaskResultsInstance() As ESRI.ArcGIS.ADF.Web.UI.WebControls.TaskResults
      Get
        ' Retrieve the TaskResults control if it has not already been
        If (m_taskResults Is Nothing) AndAlso (Not TaskResultsContainers(0) Is Nothing) Then
          m_taskResults = TryCast(ESRI.ArcGIS.ADF.Web.UI.WebControls.Utility.FindControl(TaskResultsContainers(0).Name, Page), ESRI.ArcGIS.ADF.Web.UI.WebControls.TaskResults)
        End If
        Return m_taskResults
      End Get
    End Property

    ' Returns the buddied Map
    Private ReadOnly Property MapInstance() As ESRI.ArcGIS.ADF.Web.UI.WebControls.Map
      Get
        Return Me.TaskResultsInstance.MapInstance
      End Get
    End Property

    #End Region

    #Region "Instance Methods"

    ' Creates a TaskResultsPanel with the passed-in ID and title
    Private Function CreateTaskResultsPanel(ByVal ID As String, ByVal title As String) As ESRI.ADF.Samples.CustomTasks.TaskResultsPanel
      ' Initialize the TaskResultsPanel
      Dim taskResultsPanel As ESRI.ADF.Samples.CustomTasks.TaskResultsPanel = New ESRI.ADF.Samples.CustomTasks.TaskResultsPanel()
      taskResultsPanel.ID = ID
      taskResultsPanel.Visible = False
      taskResultsPanel.CopyAppearance(Me)
      taskResultsPanel.Style(System.Web.UI.HtmlTextWriterStyle.Position) = "absolute"
      taskResultsPanel.Style(System.Web.UI.HtmlTextWriterStyle.Left) = "200px"
      taskResultsPanel.Style(System.Web.UI.HtmlTextWriterStyle.Top) = "200px"
      taskResultsPanel.ExpandCollapseButton = True
      taskResultsPanel.WidthResizable = True
      taskResultsPanel.HeightResizable = True
      taskResultsPanel.Title = title
      taskResultsPanel.Docked = False
      taskResultsPanel.InitialMaxHeight = New System.Web.UI.WebControls.Unit(300, System.Web.UI.WebControls.UnitType.Pixel)
      taskResultsPanel.InitialMaxWidth = New System.Web.UI.WebControls.Unit(500, System.Web.UI.WebControls.UnitType.Pixel)

      ' Add the panel to the task's controls collection
      Me.Controls.Add(taskResultsPanel)

      ' Since we are adding the taskResultsPanel dynamically at run time, script must be created and
      ' returned to the client that initializes the panel client-side.  InitializeOnClient creates
      ' this script and adds it to the panel as a callback result.
      taskResultsPanel.InitializeOnClient(Me, Me.CallbackFunctionString)
      Return taskResultsPanel
    End Function

    ' Instantiates and initializes the context menu to show on task results if results are being displayed in a 
    ' TaskResultsPanel.
    Private Sub CreateTaskResultsPanelContextMenu()
      ' Instantiate and initialize the appearance of the context menu
      _graphicsLayerContextMenu = New ESRI.ArcGIS.ADF.Web.UI.WebControls.ContextMenu()
      _graphicsLayerContextMenu.ID = "graphicsLayerContextMenu"
      _graphicsLayerContextMenu.BorderColor = System.Drawing.Color.Silver
      _graphicsLayerContextMenu.BorderStyle = System.Web.UI.WebControls.BorderStyle.Solid
      _graphicsLayerContextMenu.BorderWidth = New System.Web.UI.WebControls.Unit(1, System.Web.UI.WebControls.UnitType.Pixel)
      _graphicsLayerContextMenu.HoverColor = System.Drawing.Color.Gainsboro
      _graphicsLayerContextMenu.BackColor = System.Drawing.Color.White
      _graphicsLayerContextMenu.ForeColor = ForeColor
      _graphicsLayerContextMenu.Font.CopyFrom(Me.Font)
      _graphicsLayerContextMenu.UseDefaultWebResources = Me.UseDefaultWebResources

      ' Wire item clicked and menu dismissed event handlers
      AddHandler _graphicsLayerContextMenu.ItemClicked, AddressOf GraphicsLayerContextMenu_ItemClicked
      AddHandler _graphicsLayerContextMenu.Dismissed, AddressOf GraphicsLayerContextMenu_Dismissed

      ' Add a menu item to zoom to selected features
      Dim contextMenuItem As ESRI.ArcGIS.ADF.Web.UI.WebControls.ContextMenuItem = New ESRI.ArcGIS.ADF.Web.UI.WebControls.ContextMenuItem()
      contextMenuItem.ImageUrl = "images/contextMenuZoomTo.gif"
      contextMenuItem.Text = "Zoom To Selected Features"
      _graphicsLayerContextMenu.Items.Add(contextMenuItem)

      ' Add a menu item to remove the GraphicsLayer corresponding to the node on which the context menu was shown
      contextMenuItem = New ESRI.ArcGIS.ADF.Web.UI.WebControls.ContextMenuItem()
      contextMenuItem.ImageUrl = "images/contextMenuRemove.gif"
      contextMenuItem.Text = "Remove"
      _graphicsLayerContextMenu.Items.Add(contextMenuItem)

      ' Add a menu item to show the corresponding TaskResultsPanel
      contextMenuItem = New ESRI.ArcGIS.ADF.Web.UI.WebControls.ContextMenuItem()
      contextMenuItem.ImageUrl = "images/contextMenuViewTable.gif"
      contextMenuItem.Text = "View Attribute Table"
      _graphicsLayerContextMenu.Items.Add(contextMenuItem)

      ' Add the context menu to the task's controls collection
      Me.Controls.Add(_graphicsLayerContextMenu)
    End Sub

    ' Retrieves a GraphicsLayerNode that is an ancestor or descendant of the passed-in node, if available
    Private Function GetRelatedGraphicsLayerNode(ByVal node As ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNode) As ESRI.ArcGIS.ADF.Web.UI.WebControls.GraphicsLayerNode
      ' Check whether the passed-in node is a GraphicsLayerNode
      Dim graphicsLayerNode As ESRI.ArcGIS.ADF.Web.UI.WebControls.GraphicsLayerNode = TryCast(node, ESRI.ArcGIS.ADF.Web.UI.WebControls.GraphicsLayerNode)

      ' Check whether the passed-in node has an ancestor GraphicsLayerNode
      If graphicsLayerNode Is Nothing Then
        graphicsLayerNode = Me.FindParentGraphicsLayerNode(node)
      End If

      ' Check whether the passed-in node has a descendant GraphicsLayerNode
      If graphicsLayerNode Is Nothing Then
        graphicsLayerNode = Me.FindChildGraphicsLayerNode(node)
      End If

      Return graphicsLayerNode
    End Function

    ' Retrieves a GraphicsLayerNode that is a descendant of the passed-in node, if available
    Private Function FindChildGraphicsLayerNode(ByVal node As ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNode) As 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 graphicsLayerNode Is Nothing AndAlso node.Nodes.Count > 0 Then
        For Each childNode As ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNode In node.Nodes
          graphicsLayerNode = Me.FindChildGraphicsLayerNode(childNode)
          If Not graphicsLayerNode Is Nothing Then
            Exit For
          End If
        Next childNode
      End If

      Return graphicsLayerNode
    End Function

    ' Retrieves a GraphicsLayerNode that is an ancestor of the passed-in node, if available
    Private Function FindParentGraphicsLayerNode(ByVal node As ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNode) As 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 graphicsLayerNode Is Nothing AndAlso TypeOf node.Parent Is ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNode Then
        graphicsLayerNode = Me.FindParentGraphicsLayerNode(TryCast(node.Parent, ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNode))
      End If

      Return graphicsLayerNode
    End Function

    #End Region
  End Class
End Namespace