Common_TaskResults_CSharp\OnDemandTaskResults\OnDemandTaskResults.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 OnDemandTaskResults_CSharp { [System.Web.UI.ToolboxData(@"<{0}:OnDemandTaskResults runat=""server"" Width=""200px"" Height=""200px"" BackColor=""#ffffff"" Font-Names=""Verdana"" Font-Size=""8pt"" ForeColor=""#000000"" ShowAttributesOnDemand=""True"" ActivityIndicatorText=""Retrieving Data""> </{0}:OnDemandTaskResults>")] // 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. [AjaxControlToolkit.ClientScriptResource("OnDemandTaskResults_CSharp.OnDemandTaskResults", "OnDemandTaskResults_CSharp.JavaScript.OnDemandTaskResults.js", LoadOrder = 3)] public class OnDemandTaskResults : 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 bool ShowAttributesOnDemand { get { return (this.StateManager.GetProperty("ShowAttributesOnDemand") == null) ? true : (bool)this.StateManager.GetProperty("ShowAttributesOnDemand"); } set { this.StateManager.SetProperty("ShowAttributesOnDemand", value); } } /// <summary> /// Determines the text shown while data is being retrieved for task results /// </summary> public string ActivityIndicatorText { get { return (this.StateManager.GetProperty("ActivityIndicatorText") == null) ? "Retrieving Data" : (string)this.StateManager.GetProperty("ActivityIndicatorText"); } set { this.StateManager.SetProperty("ActivityIndicatorText", value); } } #endregion #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 System.Collections.Generic.Dictionary<string, string> NodeDataCache { get { // Attempt to retrieve the node data from state System.Collections.Generic.Dictionary<string, string> nodeDataCache = this.StateManager.GetProperty("NodeCache") as System.Collections.Generic.Dictionary<string, string>; // If the node data is not yet stored, create a new dictionary for the data and store it if (nodeDataCache == null) { nodeDataCache = new System.Collections.Generic.Dictionary<string, string>(); this.StateManager.SetProperty("NodeCache", nodeDataCache); } return nodeDataCache; } } /// <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 System.Collections.Generic.Dictionary<string, string> GraphicsAttributeCache { get { // Attempt to retrieve the node data from state System.Collections.Generic.Dictionary<string, string> graphicsAttributeCache = this.StateManager.GetProperty("GraphicsAttributeCache") as System.Collections.Generic.Dictionary<string, string>; // If the node data is not yet stored, create a new dictionary for the data and store it if (graphicsAttributeCache == null) { graphicsAttributeCache = new System.Collections.Generic.Dictionary<string, string>(); this.StateManager.SetProperty("GraphicsAttributeCache", graphicsAttributeCache); } return graphicsAttributeCache; } } /// <summary> /// Stores the IDs of nodes for which attribute data has been retrieved /// </summary> private System.Collections.Generic.List<string> ProcessedNodes { get { // Attempt to retrieve the node data from state System.Collections.Generic.List<string> processedNodes = this.StateManager.GetProperty("ProcessedNodes") as System.Collections.Generic.List<string>; // If the node data is not yet stored, create a new dictionary for the data and store it if (processedNodes == null) { processedNodes = new System.Collections.Generic.List<string>(); this.StateManager.SetProperty("ProcessedNodes", processedNodes); } return processedNodes; } } #endregion #region ASP.NET WebControl Life Cycle Event Handlers - CreateChildControls protected override void CreateChildControls() { base.CreateChildControls(); foreach (ESRI.ArcGIS.ADF.Web.UI.WebControls.ContextMenuItem menuItem in this.FeatureContextMenu.Items) { if (menuItem.Text.ToLower() == "remove") { this.FeatureContextMenu.Items.Remove(menuItem); break; } } foreach (ESRI.ArcGIS.ADF.Web.UI.WebControls.ContextMenuItem menuItem in this.GraphicsLayerContextMenu.Items) { if (menuItem.Text.ToLower() == "remove") { this.GraphicsLayerContextMenu.Items.Remove(menuItem); break; } } // Wire event handlers to fire when a node is added, removed, or expanded this.NodeAdded += new ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNodeAddedEventHandler(OnDemandTaskResults_NodeAdded); this.NodeExpanded += new ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNodeExpandedEventHandler(OnDemandTaskResults_NodeExpanded); this.NodeRemoved += new ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNodeRemovedEventHandler(OnDemandTaskResults_NodeRemoved); } #endregion #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. void OnDemandTaskResults_NodeAdded(object sender, ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNodeEventArgs args) { // If attributes are not to be retrieved on-demand, exit the method if (!this.ShowAttributesOnDemand || !(args.Node is ESRI.ArcGIS.ADF.Web.UI.WebControls.TaskResultNode)) return; // Attempt to get GraphicsLayerNodes that are descendants of the added node System.Collections.Generic.List<ESRI.ArcGIS.ADF.Web.UI.WebControls.GraphicsLayerNode> graphicsLayerNodes = this.FindChildGraphicsLayerNodes(args.Node); // Make sure a GraphicsLayerNode was found if (graphicsLayerNodes.Count == 0) return; // Get the control that is adding the result node System.Web.UI.Control callingControl = this.GetCallingControl(this.Page); // Cast the control to IAttributesOnDemandProvider OnDemandTaskResults_CSharp.IAttributesOnDemandProvider attributesOnDemandProvider = callingControl as OnDemandTaskResults_CSharp.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. bool cacheAttributesInWebTier = (attributesOnDemandProvider == null); // If the control that has added results implements IAttributesOnDemandProvider, add the ID of // the control as an attribute on the task result node. if (attributesOnDemandProvider != null) args.Node.Attributes.Add("AttributesProviderID", callingControl.ID); // Stores the properties that will be used to initialize client tier graphics layer nodes System.Collections.Generic.List<System.Collections.Generic.Dictionary<string, object>> graphicsLayerNodeList = new System.Collections.Generic.List<System.Collections.Generic.Dictionary<string, object>>(); foreach (ESRI.ArcGIS.ADF.Web.UI.WebControls.GraphicsLayerNode graphicsLayerNode in graphicsLayerNodes) { // Get the feature nodes contained by the current graphics layer node System.Collections.Generic.List<ESRI.ArcGIS.ADF.Web.UI.WebControls.FeatureNode> featureNodes = this.FindChildFeatureNodes(graphicsLayerNode); // Get the markup for the activity indicator that will be shown when retrieving attributes for // a MapTip or feature node string activityIndicatorMarkup = this.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. this.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) this.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 System.Collections.Generic.Dictionary<string, object> graphicsLayerNodeProperties = new System.Collections.Generic.Dictionary<string, object>(); graphicsLayerNodeProperties.Add("nodeID", graphicsLayerNode.NodeID); graphicsLayerNodeProperties.Add("featureNodes", this.GetCurrentPageNodeIDs(graphicsLayerNode)); // If the node has more than one page of child nodes, add properties storing page information if (graphicsLayerNode.NumberOfPages > 1) { graphicsLayerNodeProperties.Add("pagingTextFormatString", graphicsLayerNode.PagingTextFormatString); graphicsLayerNodeProperties.Add("pageSize", graphicsLayerNode.PageSize); graphicsLayerNodeProperties.Add("pageCount", graphicsLayerNode.NumberOfPages); graphicsLayerNodeProperties.Add("nodeCount", graphicsLayerNode.Nodes.Count); } // Add the properties to the list of graphics layer node properties graphicsLayerNodeList.Add(graphicsLayerNodeProperties); } // 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. System.Collections.Generic.Dictionary<string, object> taskResultNodeProperties = new System.Collections.Generic.Dictionary<string, object>(); taskResultNodeProperties.Add("nodeID", args.Node.NodeID); taskResultNodeProperties.Add("graphicsLayerNodes", graphicsLayerNodeList); System.Web.Script.Serialization.JavaScriptSerializer jsSerializer = 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 string nodeInitScript = @" var taskResults = $find('{0}'); taskResults.initClientNodes({1});"; nodeInitScript = string.Format(nodeInitScript, this.ClientID, jsSerializer.Serialize(taskResultNodeProperties)); this.CallbackResults.Add( ESRI.ArcGIS.ADF.Web.UI.WebControls.CallbackResult.CreateJavaScript(nodeInitScript)); } // Fires when a node is expanded. Updates the node text and associated graphic feature with // stored attribute data. void OnDemandTaskResults_NodeExpanded(object sender, ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNodeEventArgs args) { // Make sure the expanded node is a feature node if (!(args.Node is ESRI.ArcGIS.ADF.Web.UI.WebControls.FeatureNode)) return; // 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 (this.NodeDataCache.ContainsKey(args.Node.Attributes["GraphicID"])) this.UpdateResultFromCache(args.Node as ESRI.ArcGIS.ADF.Web.UI.WebControls.FeatureNode, null); else if (!this.ProcessedNodes.Contains(args.Node.NodeID)) this.UpdateResultFromProvider(args.Node as ESRI.ArcGIS.ADF.Web.UI.WebControls.FeatureNode, null); // === 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); } // 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. void OnDemandTaskResults_NodeRemoved(object sender, ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNodeRemovedEventArgs args) { // Remove any feature data associated with the node or any of its descendants from the web tier cache this.RemoveDataFromCache(args.Node); // Add a callback result to dispose the client tier node object string disposeClientNodeScript = string.Format("$find('{0}').dispose()", args.Node.NodeID); this.CallbackResults.Add( ESRI.ArcGIS.ADF.Web.UI.WebControls.CallbackResult.CreateJavaScript(disposeClientNodeScript)); } #endregion #region ICallbackEventHandler Overrides - RaiseCallbackEvent // Retrieves node and graphic feature attributes when a task result MapTip is expanded public override void RaiseCallbackEvent(string eventArgument) { // Get the callback arguments System.Collections.Specialized.NameValueCollection callbackArgs = 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 switch (callbackArgs["EventArg"]) { case "getAttributes" : // Get the graphics layer and feature nodes associated with the graphic feature ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNode graphicsLayerNode = this.Nodes.FindByNodeID(callbackArgs["LayerNodeID"]); ESRI.ArcGIS.ADF.Web.UI.WebControls.FeatureNode featureNode = this.FindNodeByAttribute(graphicsLayerNode, "GraphicID", callbackArgs["GraphicID"]) as ESRI.ArcGIS.ADF.Web.UI.WebControls.FeatureNode; if (featureNode == null) return; // 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 (this.NodeDataCache.ContainsKey(featureNode.Attributes["GraphicID"])) this.UpdateResultFromCache(featureNode, callbackArgs["MapTipsID"]); else if (!this.ProcessedNodes.Contains(featureNode.NodeID)) this.UpdateResultFromProvider(featureNode, callbackArgs["MapTipsID"]); // === 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); break; case "getPage" : // Get the parent node for which a new page of child nodes needs to be retrieved ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNode node = this.Nodes.FindByNodeID(callbackArgs["NodeID"]); // Update the node's page number property node.PageNumber = int.Parse(callbackArgs["PageNumber"]); // Get JSON used to initialize the new page of nodes on the client string nodesJson = this.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. string changeNodePageScript = @" var node = $find('{0}'); node._newNodes[{1}] = {2}; node.set_page({1});"; changeNodePageScript = string.Format(changeNodePageScript, callbackArgs["NodeID"], node.PageNumber, nodesJson); this.CallbackResults.Add( ESRI.ArcGIS.ADF.Web.UI.WebControls.CallbackResult.CreateJavaScript(changeNodePageScript)); break; case "nodeChecked": // Get the node that was checked ESRI.ArcGIS.ADF.Web.UI.WebControls.FeatureNode checkedNode = this.Nodes.FindByNodeID(callbackArgs["NodeID"]) as ESRI.ArcGIS.ADF.Web.UI.WebControls.FeatureNode; if (checkedNode == null) break; // Update the web tier Checked property of the node checkedNode.Checked = bool.Parse(callbackArgs["Checked"]); // Get the parent node of the checked node and make sure it's a GraphicsLayerNode ESRI.ArcGIS.ADF.Web.UI.WebControls.GraphicsLayerNode parentNode = checkedNode.Parent as ESRI.ArcGIS.ADF.Web.UI.WebControls.GraphicsLayerNode; if (parentNode == null) break; // 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. foreach (System.Data.DataRow row in parentNode.Layer.Rows) { string graphicFeatureID = this.GetGraphicClientID(row[parentNode.Layer.GraphicsIDColumn].ToString(), parentNode); if (graphicFeatureID == checkedNode.Attributes["GraphicID"]) { row[parentNode.Layer.IsSelectedColumn] = bool.Parse(callbackArgs["Checked"]); break; } } break; } base.RaiseCallbackEvent(eventArgument); } #endregion #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 void CreateOnDemandNodes(ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNode parentNode, string nodeMarkup, bool cacheInWebTier) { // Attempt to get a reference to the passed-in node as a FeatureNode ESRI.ArcGIS.ADF.Web.UI.WebControls.FeatureNode featureNode = parentNode as 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 (featureNode != null && featureNode.Nodes.Count > 0 && featureNode.Nodes[0].Text.ToLower().Contains("<table>")) { // Add the node's feature data to the node data cache if (cacheInWebTier) this.NodeDataCache.Add(featureNode.Attributes["GraphicID"], featureNode.Nodes[0].Text); // Update the node's text with the passed-in markup featureNode.Nodes[0].Text = nodeMarkup; } // Recursively call this method for any nodes that are children of the passed-in node if (parentNode.Nodes.Count > 0) { foreach (ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNode childNode in parentNode.Nodes) this.CreateOnDemandNodes(childNode, nodeMarkup, cacheInWebTier); } return; } // 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 void CreateOnDemandMapTips(ESRI.ArcGIS.ADF.Web.UI.WebControls.GraphicsLayerNode graphicsLayerNode, string activityIndicatorMarkup, bool cacheAttributes) { // Get the layer referenced by the node ESRI.ArcGIS.ADF.Web.Display.Graphics.GraphicsLayer graphicsLayer = graphicsLayerNode.Layer; if (cacheAttributes) { // Store a JSON string containing the MapTips attributes for each feature (row) in the // graphics layer. foreach (System.Data.DataRow row in graphicsLayer.Rows) { string jsonAttributes = this.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 string graphicID = this.GetGraphicClientID(row[graphicsLayer.GraphicsIDColumn].ToString(), graphicsLayerNode); this.GraphicsAttributeCache.Add(graphicID, jsonAttributes); } // Get the graphics layer's title template. This is the template used to format the title // of the layer's MapTips string titleTemplate = 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. int removeIndex = 0; int columnCount = graphicsLayer.Columns.Count; System.Data.DataColumn currentColumn; for (int i = 0; i < columnCount; i++) { currentColumn = graphicsLayer.Columns[removeIndex]; if (!titleTemplate.Contains("{" + currentColumn.ColumnName + "}") && (currentColumn.ColumnName != graphicsLayer.GraphicsIDColumn.ColumnName) && (currentColumn.ColumnName != graphicsLayer.IsSelectedColumn.ColumnName) && !currentColumn.DataType.IsAssignableFrom(typeof(ESRI.ArcGIS.ADF.Web.Geometry.Geometry))) graphicsLayer.Columns.RemoveAt(removeIndex); else removeIndex++; } } // 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 string initializeMapTipsJavaScript = this.CreateMapTipsInitScript(graphicsLayerNode, activityIndicatorMarkup); this.CallbackResults.Add( ESRI.ArcGIS.ADF.Web.UI.WebControls.CallbackResult.CreateJavaScript(initializeMapTipsJavaScript)); } // Generates the JavaScript necessary to initialize attributes-on-demand functionality for the MapTips // of the graphics layer referenced by the passed-in node. private string CreateMapTipsInitScript(ESRI.ArcGIS.ADF.Web.UI.WebControls.GraphicsLayerNode graphicsLayerNode, string activityIndicatorMarkup) { // Get the AJAX client ID of the GraphicFeatureGroup corresponding to the passed-in graphics layer string graphicsLayerClientID = this.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 System.Collections.Generic.Dictionary<string, object> mapTipsInitProperties = new System.Collections.Generic.Dictionary<string, object>(); mapTipsInitProperties.Add("graphicFeatureGroupID", graphicsLayerClientID); mapTipsInitProperties.Add("graphicsLayerNodeID", graphicsLayerNode.NodeID); mapTipsInitProperties.Add("activityIndicatorTemplate", activityIndicatorMarkup); mapTipsInitProperties.Add("callbackFunctionString", this.CallbackFunctionString); System.Web.Script.Serialization.JavaScriptSerializer jsSerializer = 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. string mapTipsInitJavaScript = @"var taskResults = $find('{0}'); if (!taskResults._initializationProps) taskResults._initializationProps = new Array(); taskResults._initializationProps.push({1}); window.setTimeout(""var mapTips = $find('{2}').get_mapTips();"" + ""mapTips.setupOnDemandMapTips($find('{0}')._initializationProps.splice(0,1)[0]);"", 0);"; return string.Format(mapTipsInitJavaScript, this.ClientID, jsSerializer.Serialize(mapTipsInitProperties), graphicsLayerClientID); } #endregion #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 void UpdateResultFromProvider(ESRI.ArcGIS.ADF.Web.UI.WebControls.FeatureNode featureNode, string mapTipsClientID) { // Get the graphics layer and task result nodes that contain the passed-in feature node ESRI.ArcGIS.ADF.Web.UI.WebControls.GraphicsLayerNode graphicsLayerNode = featureNode.Parent as ESRI.ArcGIS.ADF.Web.UI.WebControls.GraphicsLayerNode; if (graphicsLayerNode == null) return; ESRI.ArcGIS.ADF.Web.UI.WebControls.TaskResultNode taskResultNode = graphicsLayerNode.Parent as ESRI.ArcGIS.ADF.Web.UI.WebControls.TaskResultNode; if (taskResultNode == null) return; // Get the control that added the result and cast it to the IAttributesOnDemandProvider interface OnDemandTaskResults_CSharp.IAttributesOnDemandProvider attributesProvider = ESRI.ArcGIS.ADF.Web.UI.WebControls.Utility.FindControl(taskResultNode.Attributes["AttributesProviderID"], this.Page) as OnDemandTaskResults_CSharp.IAttributesOnDemandProvider; // Extract the feature ID from the graphic feature ID string graphicFeatureID = featureNode.Attributes["GraphicID"]; string featureID = graphicFeatureID.Substring(graphicFeatureID.LastIndexOf('_') + 1); // Get the feature's attributes System.Data.DataRow attributesRow = attributesProvider.GetAttributeData(featureID); // Update the feature node and graphic feature with the attributes string nodeMarkup = this.GetDataRowHtmlTable(attributesRow); this.UpdateNodeData(featureNode, nodeMarkup); string attributesJson = this.GetAttributesJson(attributesRow, graphicsLayerNode.Layer); this.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. this.ProcessedNodes.Add(featureNode.NodeID); } // 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 void UpdateResultFromCache(ESRI.ArcGIS.ADF.Web.UI.WebControls.FeatureNode featureNode, string mapTipsClientID) { // Get the graphic feature ID string graphicID = featureNode.Attributes["GraphicID"]; // Update the server-side representation of the node with the stored data string nodeMarkup = this.NodeDataCache[graphicID]; this.UpdateNodeData(featureNode, nodeMarkup); // Remove the node's data from web tier storage this.NodeDataCache.Remove(graphicID); // Get the JSON string containing the feature data from storage string attributesJson = this.GraphicsAttributeCache[graphicID]; // Update the graphic feature's data this.UpdateGraphicFeatureData(graphicID, attributesJson, mapTipsClientID); // Remove the attribute data for the specified feature from storage this.GraphicsAttributeCache.Remove(graphicID); // Flag the passed-in node as processed so the control won't attempt to retrieve attributes for the same // node again. this.ProcessedNodes.Add(featureNode.NodeID); } // Updates the text of the passed-in node with the feature data corresponding to the node private void UpdateNodeData(ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNode node, string nodeMarkup) { // Update the node's text node.Nodes[0].Text = nodeMarkup; // Get the node's markup and serialize it to JSON System.Web.Script.Serialization.JavaScriptSerializer jsSerializer = new System.Web.Script.Serialization.JavaScriptSerializer(); nodeMarkup = jsSerializer.Serialize(this.GetNodeHtml(node)); // Construct JavaScript to update the node's content on the client. Package the script in a callback // result for client-side processing. string nodeUpdateScript = @" var node = $find('{0}'); if (node) node.set_content({1});"; nodeUpdateScript = string.Format(nodeUpdateScript, node.NodeID, nodeMarkup); this.CallbackResults.Add( ESRI.ArcGIS.ADF.Web.UI.WebControls.CallbackResult.CreateJavaScript(nodeUpdateScript)); } // Updates the attributes of the client tier GraphicFeature specified by the passed-in ID with // the feature data stored in the web tier private void UpdateGraphicFeatureData(string graphicFeatureID, string attributesJson, string mapTipsClientID) { string updateAttributesScript; // Check whether a client-side MapTips control was specified if (mapTipsClientID != null) { // 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 = @" var mapTips = $find('{0}'); mapTips.updateAttributes('{1}', {2});"; updateAttributesScript = string.Format(updateAttributesScript, mapTipsClientID, graphicFeatureID, attributesJson); } else { // Create script to update the attributes of the specified GraphicFeature directly updateAttributesScript = @" var graphicFeature = $find('{0}'); graphicFeature.set_attributes({1}); graphicFeature.set_hasAttributes(true);"; updateAttributesScript = string.Format(updateAttributesScript, graphicFeatureID, attributesJson); } // Add the script to the TaskResults control's callback results this.CallbackResults.Add( ESRI.ArcGIS.ADF.Web.UI.WebControls.CallbackResult.CreateJavaScript(updateAttributesScript)); } #endregion #region Activity Indicator Creation - CreateActivityIndicatorTable, GetControlHtml // Generates a Table WebControl containing an activity indicator with the text specified via the // ActivityIndicatorText property private string CreateActivityIndicatorMarkup() { // Get the web resource URL for the activity indicator gif string activityIndicatorUrl = this.Page.ClientScript.GetWebResourceUrl( typeof(OnDemandTaskResults_CSharp.OnDemandTaskResults), "OnDemandTaskResults_CSharp.Images.callbackActivityIndicator.gif"); // Create an Image control and initialize it to reference the activity indicator System.Web.UI.WebControls.Image activityIndicator = new System.Web.UI.WebControls.Image(); activityIndicator.ImageUrl = activityIndicatorUrl; // Create a cell for the indicator System.Web.UI.WebControls.TableCell tableCell = new System.Web.UI.WebControls.TableCell(); tableCell.Controls.Add(activityIndicator); // Create a row and add the indicator cell to it System.Web.UI.WebControls.TableRow tableRow = new System.Web.UI.WebControls.TableRow(); tableRow.VerticalAlign = System.Web.UI.WebControls.VerticalAlign.Middle; tableRow.Cells.Add(tableCell); // Create the indicator label System.Web.UI.WebControls.Label activityLabel = new System.Web.UI.WebControls.Label(); activityLabel.Text = this.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 System.Web.UI.WebControls.Table activityIndicatorTable = new System.Web.UI.WebControls.Table(); activityIndicatorTable.Rows.Add(tableRow); // Return the table's markup return this.GetControlHtml(activityIndicatorTable); } // Used to retrieve the markup for the activity indicator that will be shown when task results feature // nodes or MapTips are first expanded private string GetControlHtml(System.Web.UI.WebControls.WebControl control) { // Use RenderControl to retrieve the markup for the passed-in control System.IO.StringWriter stringWriter = new System.IO.StringWriter(); System.Web.UI.HtmlTextWriter htmlWriter = new System.Web.UI.HtmlTextWriter(stringWriter); control.RenderControl(htmlWriter); // Return the markup return stringWriter.ToString(); } #endregion #region Node Retrieval - FindNodeByAttribute, FindChildGraphicsLayerNode, FindFeatureNodes // Searches the passed-in node and its descendants for a node with the specified attribute private ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNode FindNodeByAttribute( ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNode parentNode, string attribute, string value) { // Retrieve the specified attribute from the passed-in node string currentValue = parentNode.Attributes[attribute]; // Return the passed-in node if it has an attribute matching the one sought if (currentValue == value) return parentNode; // Recursively call this function to search for the specified attribute on child nodes ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNode matchingNode = null; foreach (ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNode childNode in parentNode.Nodes) { matchingNode = this.FindNodeByAttribute(childNode, attribute, value); if (matchingNode != null) break; } return matchingNode; } // Retrieves all GraphicsLayerNodes that are descendants of the passed-in node, if available private System.Collections.Generic.List<ESRI.ArcGIS.ADF.Web.UI.WebControls.GraphicsLayerNode> FindChildGraphicsLayerNodes(ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNode node) { System.Collections.Generic.List<ESRI.ArcGIS.ADF.Web.UI.WebControls.GraphicsLayerNode> nodes = new System.Collections.Generic.List<ESRI.ArcGIS.ADF.Web.UI.WebControls.GraphicsLayerNode>(); ESRI.ArcGIS.ADF.Web.UI.WebControls.GraphicsLayerNode graphicsLayerNode = node as ESRI.ArcGIS.ADF.Web.UI.WebControls.GraphicsLayerNode; if (graphicsLayerNode != null) nodes.Add(graphicsLayerNode); foreach (ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNode childNode in node.Nodes) nodes.AddRange(this.FindChildGraphicsLayerNodes(childNode)); return nodes; } // Retrieves all FeatureNodes that are descendants of the passed-in node, if available private System.Collections.Generic.List<ESRI.ArcGIS.ADF.Web.UI.WebControls.FeatureNode> FindChildFeatureNodes(ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNode node) { System.Collections.Generic.List<ESRI.ArcGIS.ADF.Web.UI.WebControls.FeatureNode> nodes = new System.Collections.Generic.List<ESRI.ArcGIS.ADF.Web.UI.WebControls.FeatureNode>(); ESRI.ArcGIS.ADF.Web.UI.WebControls.FeatureNode featureNode = node as ESRI.ArcGIS.ADF.Web.UI.WebControls.FeatureNode; if (featureNode != null) nodes.Add(featureNode); foreach (ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNode childNode in node.Nodes) nodes.AddRange(this.FindChildFeatureNodes(childNode)); return nodes; } #endregion #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 string GetNodesJson(ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNode parentNode) { // Get the index of the first and last nodes on the curernt page int startIndex = (parentNode.PageNumber - 1) * parentNode.PageSize; int stopIndex = parentNode.PageNumber * parentNode.PageSize; if (stopIndex > parentNode.Nodes.Count) stopIndex = parentNode.Nodes.Count; // Create a list to store all the node properties System.Collections.Generic.List<System.Collections.Generic.Dictionary<string, object>> nodeList = new System.Collections.Generic.List<System.Collections.Generic.Dictionary<string, object>>(); // Create a dictionary to store each node's contents System.Collections.Generic.Dictionary<string, object> nodeContents; // Loop through the nodes on the current page and add the ID and markup of each to the property list for (int i = startIndex; i < stopIndex; i++) { nodeContents = new System.Collections.Generic.Dictionary<string, object>(); nodeContents.Add("nodeID", parentNode.Nodes[i].NodeID); nodeContents.Add("_content", this.GetNodeHtml(parentNode.Nodes[i])); nodeList.Add(nodeContents); } // Serialize the node properties to JSON and return System.Web.Script.Serialization.JavaScriptSerializer jsSerializer = new System.Web.Script.Serialization.JavaScriptSerializer(); return jsSerializer.Serialize(nodeList); } // Retrieves the markup for the passed-in node private string GetNodeHtml(ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNode node) { System.IO.StringWriter stringWriter = new System.IO.StringWriter(); System.Web.UI.HtmlTextWriter htmlWriter = 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(); } // 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 System.Collections.Generic.List<string> GetCurrentPageNodeIDs(ESRI.ArcGIS.ADF.Web.UI.WebControls.GraphicsLayerNode graphicsLayerNode) { // Retrieve feature nodes that are descendants of the passed-in node System.Collections.Generic.List<ESRI.ArcGIS.ADF.Web.UI.WebControls.FeatureNode> featureNodes = this.FindChildFeatureNodes(graphicsLayerNode); // Get the indexes of the first and last nodes on the current page int startIndex = (graphicsLayerNode.PageNumber - 1) * graphicsLayerNode.PageSize; int stopIndex = graphicsLayerNode.PageNumber * graphicsLayerNode.PageSize; if (stopIndex > graphicsLayerNode.Nodes.Count) stopIndex = graphicsLayerNode.Nodes.Count; // Loop through the nodes on the page and add the ID of each to a string ESRI.ArcGIS.ADF.Web.UI.WebControls.FeatureNode featureNode = null; System.Collections.Generic.List<string> nodeIDs = new System.Collections.Generic.List<string>(); for (int i = startIndex; i < stopIndex; i++) { featureNode = graphicsLayerNode.Nodes[i] as ESRI.ArcGIS.ADF.Web.UI.WebControls.FeatureNode; if (featureNode != null) nodeIDs.Add(featureNode.NodeID); } return nodeIDs; } #endregion #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 string GetGraphicsLayerClientID(ESRI.ArcGIS.ADF.Web.Display.Graphics.GraphicsLayer graphicsLayer) { ESRI.ArcGIS.ADF.Web.Display.Graphics.FeatureGraphicsLayer featureGraphicsLayer = graphicsLayer as ESRI.ArcGIS.ADF.Web.Display.Graphics.FeatureGraphicsLayer; if (featureGraphicsLayer == null) return null; string graphicsLayerID = string.Format("{0}_{1} {2} Results_{3}", this.MapInstance.ClientID, this.ClientID, featureGraphicsLayer.FeatureType.ToString(), graphicsLayer.TableName); return graphicsLayerID; } // 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 string GetGraphicClientID(string attributeUniqueID, ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNode parentNode) { // Attempt to get a reference to the passed-in node as a FeatureNode ESRI.ArcGIS.ADF.Web.UI.WebControls.FeatureNode featureNode = parentNode as ESRI.ArcGIS.ADF.Web.UI.WebControls.FeatureNode; string clientGraphicID = null; if (featureNode != null && !string.IsNullOrEmpty(featureNode.Attributes["GraphicID"])) { // Get the AJAX componenent ID of the GraphicFeature corresponding to the current FeatureNode string graphicID = featureNode.Attributes["graphicID"]; // Extract the GraphicFeature's unique ID from the component ID string nodeUniqueID = 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) clientGraphicID = graphicID; } else if (parentNode.Nodes.Count > 0) { // Try retrieving the GraphicFeature ID from the passed-in node's child nodes foreach (ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNode childNode in parentNode.Nodes) { clientGraphicID = this.GetGraphicClientID(attributeUniqueID, childNode); if (!string.IsNullOrEmpty(clientGraphicID)) break; } } return clientGraphicID; } #endregion // Removes feature data contained by the passed-in node or any of its descendants from the node and // graphic feature attributes data caches private void RemoveDataFromCache(ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNode parentNode) { // If the passed-in node's graphic ID has entries in the node and graphic attribute caches, // remove those entries string graphicID = parentNode.Attributes["GraphicID"]; if (!string.IsNullOrEmpty(graphicID) && this.NodeDataCache.ContainsKey(graphicID)) { this.NodeDataCache.Remove(graphicID); this.GraphicsAttributeCache.Remove(graphicID); } // Remove node and graphic attribute data corresponding to nodes that are children of the // passed-in node if (parentNode.Nodes.Count > 0) { foreach (ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNode childNode in parentNode.Nodes) this.RemoveDataFromCache(childNode); } return; } // Retrieves the control that initiated the asynchronous request private System.Web.UI.Control GetCallingControl(System.Web.UI.Page page) { if (page == null) return null; if (page.IsCallback) { string controlID = page.Request.Params["__CALLBACKID"]; System.Web.UI.Control control = page.FindControl(controlID); return control; } // For 9.3 we could be using a partial postback instead else if (page.IsPostBack && System.Web.UI.ScriptManager.GetCurrent(page) != null && System.Web.UI.ScriptManager.GetCurrent(page).IsInAsyncPostBack) { string controlID = System.Web.UI.ScriptManager.GetCurrent(page).AsyncPostBackSourceElementID; System.Web.UI.Control control = page.FindControl(controlID); return control; } else return null; //Not an asyncronous request } // Converts a DataRow to a JSON string. Uses the passed-in graphics layer to determine which fields // in the row should be included. private string GetAttributesJson(System.Data.DataRow attributesRow, ESRI.ArcGIS.ADF.Web.Display.Graphics.GraphicsLayer graphicsLayer) { string visibilityString; bool visibility; System.Collections.Generic.Dictionary<string, object> attributesDictionary = new System.Collections.Generic.Dictionary<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. foreach (System.Data.DataColumn column in attributesRow.Table.Columns) { visibilityString = column.ExtendedProperties[ESRI.ArcGIS.ADF.Web.Constants.ADFVisibility] as string; visibility = (visibilityString == null) ? false : bool.Parse(visibilityString); if ((column.ColumnName != graphicsLayer.IsSelectedColumn.ColumnName) && visibility && !column.DataType.IsAssignableFrom(typeof(ESRI.ArcGIS.ADF.Web.Geometry.Geometry))) attributesDictionary.Add(column.ColumnName, attributesRow[column.ColumnName]); } // Convert the string dictionary to JSON System.Web.Script.Serialization.JavaScriptSerializer jsSerialzer = new System.Web.Script.Serialization.JavaScriptSerializer(); return jsSerialzer.Serialize(attributesDictionary); } #endregion } }