Common Task results
Common_TaskResults_CSharp\TaskResultsWebSite\App_Code\TabularResultsTask.cs
// Copyright 2011 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.
// 

namespace ESRI.ADF.Samples.CustomTasks
{
    public class TabularResultsTask : ESRI.ArcGIS.ADF.Tasks.QueryAttributesTask
    {
        #region Instance Variable Declarations

        // Stores a reference to the buddied TaskResults Control
        private ESRI.ArcGIS.ADF.Web.UI.WebControls.TaskResults m_taskResults = null;

        // Tracks whether the current page request has routed through ExecuteTask
        private bool _taskExecuted = false;

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

        #endregion

        #region ASP.NET WebControl Life Cycle Event Handlers

        // Creates the custom task results context menu and wires task results control event handlers
        protected override void CreateChildControls()
        {
            base.CreateChildControls();

            this.CreateTaskResultsPanelContextMenu();

            // Add a handler to the buddied TaskResults Control's NodeAdded event
            this.TaskResultsInstance.NodeAdded += 
                new ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNodeAddedEventHandler(TaskResultsInstance_NodeAdded);
            this.TaskResultsInstance.NodeRemoved += 
                new ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNodeRemovedEventHandler(TaskResultsInstance_NodeRemoved);
        }    

        // Registers the client script needed by TaskResultsPanel.  We do this here since no 
        // TaskResultsPanel is being created during application start-up.
        protected override void Render(System.Web.UI.HtmlTextWriter writer)
        {
            base.Render(writer);

            ESRI.ADF.Samples.CustomTasks.TaskResultsPanel.RegisterScripts(this);
        }

        #endregion

        #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.
        void TaskResultsInstance_NodeAdded(object sender, ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNodeEventArgs args)
        {
            if (this._taskExecuted)
            {
                // Get the GraphicsLayerNode that is an ancestor or descendant of the current node
                ESRI.ArcGIS.ADF.Web.UI.WebControls.GraphicsLayerNode graphicsLayerNode =
                    this.GetRelatedGraphicsLayerNode(args.Node);

                // Make sure a GraphicsLayerNode was found 
                if (graphicsLayerNode != null)
                {
                    // Create the TaskResultsPanel that will be used to display results
                    string taskResultsPanelID = string.Format("{0}_TaskResultsPanel", graphicsLayerNode.NodeID);
                    string taskResultsPanelTitle = string.Format("{0} - {1} {2}", this.Title,
                        this.PredefinedQuery.FormEntries[0].LabelText, this.PredefinedQuery.FormEntries[0].UserInput);
                    ESRI.ADF.Samples.CustomTasks.TaskResultsPanel taskResultsPanel =
                        this.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.                    
                    string checkDockJavaScript = string.Format("$find('{0}')._checkDock();", taskResultsPanel.ClientID);
                    ESRI.ArcGIS.ADF.Web.UI.WebControls.CallbackResult callbackResultToRemove = null;
                    foreach (ESRI.ArcGIS.ADF.Web.UI.WebControls.CallbackResult callbackResult in taskResultsPanel.CallbackResults)
                    {
                        if (callbackResult.Parameters[0] as string == checkDockJavaScript)
                        {
                            callbackResultToRemove = callbackResult;
                            break;
                        }
                    }
                    if (callbackResultToRemove != null)
                        taskResultsPanel.CallbackResults.Remove(callbackResultToRemove);

                    // Get the name of the resource containing the results GraphicsLayer 
                    string resourceName = 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.
                    ESRI.ArcGIS.ADF.Web.Display.Graphics.FeatureGraphicsLayer featureGraphicsLayer = graphicsLayerNode.Layer as
                        ESRI.ArcGIS.ADF.Web.Display.Graphics.FeatureGraphicsLayer;
                    string graphicsLayerClientID = string.Format("{0}_{1} {2} Results_{3}", this.TaskResultsInstance.MapInstance.ClientID,
                        this.TaskResultsInstance.ClientID, featureGraphicsLayer.FeatureType.ToString(), graphicsLayerNode.Layer.TableName);
                    // Call SetLayer to associate the TaskResultsPanel with the GraphicsLayer
                    taskResultsPanel.SetLayer(featureGraphicsLayer, resourceName, this.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
                    this.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
                    this.CallbackResults.CopyFrom(taskResultsPanel.CallbackResults);

                    // Reset the flag indicating whether a new TaskResultsPanel needs to be created
                    this._taskExecuted = false;
                }
            }
        }

        public override string GetCallbackResult()
        {
            return base.GetCallbackResult();
        }

        // Checks whether the removed node is a GraphicsLayerNode and removes any associated TaskResultsPanel if so
        void TaskResultsInstance_NodeRemoved(object sender, ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNodeRemovedEventArgs args)
        {
            // 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
            ESRI.ArcGIS.ADF.Web.UI.WebControls.GraphicsLayerNode graphicsLayerNode =
                this.FindChildGraphicsLayerNode(args.Node);

            // Check whether a GraphicsLayerNode was found
            if (graphicsLayerNode != null)
            {
                // Construct JavaScript to find the TaskResultsPanel corresponding to the GraphicsLayerNode and 
                // destroy it.
                string taskResultsPanelClientID = string.Format("{0}_{1}_TaskResultsPanel", this.ClientID,
                    graphicsLayerNode.NodeID);
                string disposeResultsPanelJavaScript = @"
                var taskResultsPanel = $find('{0}');
                if (taskResultsPanel)
                {{
                    taskResultsPanel.hide(false);
                    taskResultsPanel.dispose();
                }}
                var element = $get('{0}');
                if (element)
                    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
                ESRI.ArcGIS.ADF.Web.UI.WebControls.CallbackResult disposeResultsPanelCallbackResult =
                    ESRI.ArcGIS.ADF.Web.UI.WebControls.CallbackResult.CreateJavaScript(disposeResultsPanelJavaScript);
                this.TaskResultsInstance.CallbackResults.Add(disposeResultsPanelCallbackResult);
            }
        }

        // Fires when the custom GraphicLayer context menu is closed.
        private void GraphicsLayerContextMenu_Dismissed(object sender, ESRI.ArcGIS.ADF.Web.UI.WebControls.ContextMenuDismissedEventArgs args)
        {
            this.TaskResultsInstance.ContextMenuDismissed(_graphicsLayerContextMenu, args);
        }

        // Fires when an item on the custom GraphicsLayer context menu is clicked
        private void GraphicsLayerContextMenu_ItemClicked(object sender, ESRI.ArcGIS.ADF.Web.UI.WebControls.ContextMenuItemEventArgs args)
        {
            // Get the node on which the context menu was displayed
            ESRI.ArcGIS.ADF.Web.UI.WebControls.GraphicsLayerNode graphicsLayerNode =
                TaskResultsInstance.Nodes.FindByNodeID(args.Context) as ESRI.ArcGIS.ADF.Web.UI.WebControls.GraphicsLayerNode;

            // Check the text of the item clicked
            switch (args.Item.Text)
            {
                case "Zoom To Selected Features":
                    if (graphicsLayerNode == null || this.MapInstance == null)
                        return;
                    bool hasFeaturesSelected = false;

                    // Get the GraphicsLayer associated with the node
                    ESRI.ArcGIS.ADF.Web.Display.Graphics.GraphicsLayer graphicsLayer = graphicsLayerNode.Layer;

                    // Declare an envelope to store the combined extent of all features in the layer
                    ESRI.ArcGIS.ADF.Web.Geometry.Envelope adfEnvelope = 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
                    for (int i = 0; i < graphicsLayer.Rows.Count; i++)
                    {
                        if ((bool)graphicsLayer.Rows[i][graphicsLayer.IsSelectedColumn])
                        {
                            hasFeaturesSelected = true;

                            ESRI.ArcGIS.ADF.Web.Geometry.Geometry rowGeometry =
                                graphicsLayer.GeometryFromRow(graphicsLayer.Rows[i]);
                            adfEnvelope.Union(rowGeometry);
                        }
                    }

                    if (!hasFeaturesSelected) return;

                    // If combined envelope width or height is zero, zoom in the amount specified by the 
                    // ZoomToPointFactor property
                    if (adfEnvelope.Width == 0 || adfEnvelope.Height == 0)
                    {
                        ESRI.ArcGIS.ADF.Web.Geometry.Point adfPoint = new ESRI.ArcGIS.ADF.Web.Geometry.Point(adfEnvelope.XMax, adfEnvelope.YMax);
                        ESRI.ArcGIS.ADF.Web.Geometry.Envelope fullExtentEnvelope = this.MapInstance.GetFullExtent();

                        double widthMargin = (fullExtentEnvelope.Width / TaskResultsInstance.ZoomToPointFactor) / 2;
                        double heightMargin = (fullExtentEnvelope.Height / TaskResultsInstance.ZoomToPointFactor) / 2;

                        ESRI.ArcGIS.ADF.Web.Geometry.Envelope zoomToEnvelope = 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;

                        this.MapInstance.Extent = zoomToEnvelope;
                    }
                    else
                    {
                        // Apply the combined feature extent to the map
                        this.MapInstance.Extent = adfEnvelope;
                    }

                    // Copy the map's callback results to the context menu so the extent change is processed on the client
                    _graphicsLayerContextMenu.CallbackResults.CopyFrom(this.MapInstance.CallbackResults);
                    break;
                case "Remove":
                    if (this.MapInstance == null || graphicsLayerNode == null)
                        return;

                    // Check whether there is a GraphicsLayer associated with the node
                    if (graphicsLayerNode.Layer != null)
                    {
                        // Remove the GraphicsLayer associated with the node from the map and refresh the layer's
                        // parent resource
                        string graphicsResourceName = graphicsLayerNode.RemoveFromMap(this.TaskResultsInstance);
                        this.MapInstance.RefreshResource(graphicsResourceName);

                        // Copy the map's callback results to the context menu so the map is updated on the client
                        _graphicsLayerContextMenu.CallbackResults.CopyFrom(this.MapInstance.CallbackResults);
                    }

                    // Remove the node and refresh the buddied TaskResults control
                    graphicsLayerNode.Remove();
                    this.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);
                    break;
                case "View Attribute Table":
                    // Construct JavaScript to call the client-side Web ADF function to display the TaskResultsPanel
                    string taskResultsPanelClientID = string.Format("{0}_{1}_TaskResultsPanel", this.ClientID, args.Context);
                    string showTaskResultsPanelJavaScript = string.Format("showFloatingPanel('{0}', false);", taskResultsPanelClientID);
                    ESRI.ArcGIS.ADF.Web.UI.WebControls.CallbackResult showTaskResultsPanelCallbackResult =
                        ESRI.ArcGIS.ADF.Web.UI.WebControls.CallbackResult.CreateJavaScript(showTaskResultsPanelJavaScript);
                    _graphicsLayerContextMenu.CallbackResults.Add(showTaskResultsPanelCallbackResult);
                    break;
            }
        }

        #endregion

        #region Task Overrides - ExecuteTask

        public override void ExecuteTask()
        {
            // Create a default set of results
            base.ExecuteTask();

            // Set a flag indicating that the task has executed as part of the current request
            this._taskExecuted = true;
        }

        #endregion

        #region Instance Properties

        // Convenient access to the first TaskResults control in the Task's TaskResultsContainers collection
        private ESRI.ArcGIS.ADF.Web.UI.WebControls.TaskResults TaskResultsInstance
        {
            get
            {
                // Retrieve the TaskResults control if it has not already been
                if ((m_taskResults == null) && (TaskResultsContainers[0] != null))
                    m_taskResults = ESRI.ArcGIS.ADF.Web.UI.WebControls.Utility.FindControl(TaskResultsContainers[0].Name, Page) as
                        ESRI.ArcGIS.ADF.Web.UI.WebControls.TaskResults;
                return m_taskResults;
            }
        }

        // Returns the buddied Map
        private ESRI.ArcGIS.ADF.Web.UI.WebControls.Map MapInstance
        {
            get
            {
                return this.TaskResultsInstance.MapInstance;
            }
        }

        #endregion

        #region Instance Methods

        // Creates a TaskResultsPanel with the passed-in ID and title
        private ESRI.ADF.Samples.CustomTasks.TaskResultsPanel CreateTaskResultsPanel(string ID, string title)
        {
            // Initialize the TaskResultsPanel
            ESRI.ADF.Samples.CustomTasks.TaskResultsPanel taskResultsPanel = 
                new ESRI.ADF.Samples.CustomTasks.TaskResultsPanel();
            taskResultsPanel.ID = ID;
            taskResultsPanel.Visible = false;
            taskResultsPanel.CopyAppearance(this);
            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
            this.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(this, this.CallbackFunctionString);
            return taskResultsPanel;
        }

        // Instantiates and initializes the context menu to show on task results if results are being displayed in a 
        // TaskResultsPanel.
        private void 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(this.Font);
            _graphicsLayerContextMenu.UseDefaultWebResources = this.UseDefaultWebResources;

            // Wire item clicked and menu dismissed event handlers
            _graphicsLayerContextMenu.ItemClicked +=
                new ESRI.ArcGIS.ADF.Web.UI.WebControls.ContextMenuItemClickedEventHandler(
                this.GraphicsLayerContextMenu_ItemClicked);
            _graphicsLayerContextMenu.Dismissed +=
                new ESRI.ArcGIS.ADF.Web.UI.WebControls.ContextMenuDismissedEventHandler(
                this.GraphicsLayerContextMenu_Dismissed);

            // Add a menu item to zoom to selected features
            ESRI.ArcGIS.ADF.Web.UI.WebControls.ContextMenuItem 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
            this.Controls.Add(_graphicsLayerContextMenu);
        }

        // Retrieves a GraphicsLayerNode that is an ancestor or descendant of the passed-in node, if available
        private ESRI.ArcGIS.ADF.Web.UI.WebControls.GraphicsLayerNode GetRelatedGraphicsLayerNode(
            ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNode node)
        {
            // Check whether the passed-in node is a GraphicsLayerNode
            ESRI.ArcGIS.ADF.Web.UI.WebControls.GraphicsLayerNode graphicsLayerNode = node
                as ESRI.ArcGIS.ADF.Web.UI.WebControls.GraphicsLayerNode;

            // Check whether the passed-in node has an ancestor GraphicsLayerNode
            if (graphicsLayerNode == null)
                graphicsLayerNode = this.FindParentGraphicsLayerNode(node);

            // Check whether the passed-in node has a descendant GraphicsLayerNode
            if (graphicsLayerNode == null)
                graphicsLayerNode = this.FindChildGraphicsLayerNode(node);

            return graphicsLayerNode;
        }

        // Retrieves a GraphicsLayerNode that is a descendant of the passed-in node, if available
        private ESRI.ArcGIS.ADF.Web.UI.WebControls.GraphicsLayerNode FindChildGraphicsLayerNode(
            ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNode node)
        {
            ESRI.ArcGIS.ADF.Web.UI.WebControls.GraphicsLayerNode graphicsLayerNode = node
                as ESRI.ArcGIS.ADF.Web.UI.WebControls.GraphicsLayerNode;
            if (graphicsLayerNode == null && node.Nodes.Count > 0)
            {
                foreach (ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNode childNode in node.Nodes)
                {
                    graphicsLayerNode = this.FindChildGraphicsLayerNode(childNode);
                    if (graphicsLayerNode != null)
                        break;
                }
            }

            return graphicsLayerNode;
        }

        // Retrieves a GraphicsLayerNode that is an ancestor of the passed-in node, if available
        private ESRI.ArcGIS.ADF.Web.UI.WebControls.GraphicsLayerNode FindParentGraphicsLayerNode(
            ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNode node)
        {
            ESRI.ArcGIS.ADF.Web.UI.WebControls.GraphicsLayerNode graphicsLayerNode = node
                as ESRI.ArcGIS.ADF.Web.UI.WebControls.GraphicsLayerNode;
            if (graphicsLayerNode == null && node.Parent is ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNode)
                graphicsLayerNode = this.FindParentGraphicsLayerNode(node.Parent as
                    ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNode);

            return graphicsLayerNode;
        }

        #endregion
    }
}