First step is to cause a partial postback (aka sync postback) from a button (doesn't have to be inside an UpdatePanel) which will update web ADF controls (or updatePanels) and/or execute javascript response from server. The js response can also be used to update other client-side controls. Code that goes in the aspx page to create the hidden web controls. Here's the declarative code for the hidden button and a textbox for accepting arguments:
Server-side Code to register the web controls which will cause Async Postback:
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load Dim sMgr1 As ScriptManager = ScriptManager.GetCurrent(Page) ' Register the controls that will issue postbacks asynchronously. Note that controls ' registered in this way must implement either INamingContainer, IPostBackDataHandler, or ' IPostBackEventHandler. ' These controls do not need to be contained in an UpdatePanel, and if they are, then ' there is no need to register them this way, they are already async enabled. If (sMgr1 IsNot Nothing) Then sMgr1.RegisterAsyncPostBackControl(div1_btn1) sMgr1.RegisterAsyncPostBackControl(hiddenButton1) End If End SubBecause partial postbacks go through the entire apsx page lifecycle, regular asp.net web control properties are updated during the 'process postback data' phase for a webcontrol (more: webcontrol execution lifecyle). In this case the TextBox webcontrol, 'evtArgsTxtBox.Text' property is updated during partial postback and the value set by the client-side js is available on the server. Alternatively if this approach doesn't work, you can also retrieve post values from the QueryString, Page.Request.Params['evtArgsTxtBox'] Inside the server-side method, we can update esri web adf controls very easily, we can also update any other control inside an asp.net ajax UpdatePanel. Once the web adf controls are updated, their changed client-side state is collected as Callbacks. The callbacks are passed to the client-side js function so that the esri web adf js framework can update the web adf controls. In this case we are serializing the web adf control callbacks as json and passing them to the client as asp.net ajax DataItems, which will then be evaluated by the client-side function ESRI.ADF.System.processCallbackResult('string'). For asp.net controls inside an UpdatePanel, we do not need to pass any js to the client, those controls are rendered and the asp.net ajax framework handles the update automatically. I use RegisterDataItem() instead of the ClientScriptBlock() for the ScriptManager control because using the latter causes script bloating inside the head element of the page. Observe that with firebug. Server-side code to handle the async/partial postback from asp:button, 'hiddenButton1' click event:
Protected Sub hiddenButton1_Load(ByVal sender As Object, ByVal e As System.EventArgs) 'always executed logger.Debug("HiddenButton1.Load event ...") End Sub ''' * The purpose of this function is to execute server-side code/methods ''' which are invoked from clientside js. ''' * It also receives arguments passed from the client. ''' * This method can only work with partial page postback and can only ''' update server web controls of type: ''' 1) web adf controls since they impelemt IPostbackEventHandler and ICallbackEventHandler ''' 2) controls inside UpdatePanels ''' ''' It can also pass javascript back to the client for execution, and so other ''' controls can be updated via js code. ''' ''' The hidden asp.net textbox contol, 'evtArgsTxtBox' contains the argument for this method. ''' That textbox.value is populated by client-side js. ''' The format for the value is: ''' @executionKeyword:@arg1 ''' ''' Note that the value for @arg1 can be in any format, ''' e.g. ''' when calling DisplayLayers(), @arg1 format is "layer1;layer2;layer3" ''' when calling FindAddress(), ''' @arg1 format is "123 Main St" ''' or for finding intersections "Main St & Pole St" Protected Sub hiddenButton1_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles hiddenButton1.Click Me.layerNamesEvtArg = Nothing Dim eventArgument As String = evtArgsTxtBox.Text logger.Debug("Handling Control Event, hiddenButton1.Click, EvtArg received: " & eventArgument) evtArgsTxtBox.Text = "" Dim scriptMgr As ScriptManager = ScriptManager.GetCurrent(Page) If (scriptMgr IsNot Nothing And Not scriptMgr.IsInAsyncPostBack) Then 'DataItems & ScriptBlocks can only be registered during Partial Postbacks. Return End If Dim jsCodeResetTxtBox As String = "$get('" + evtArgsTxtBox.ClientID + "').value = '';" Dim evtArgsCallback As CallbackResult = CallbackResult.CreateJavaScript(jsCodeResetTxtBox) If (String.IsNullOrEmpty(eventArgument)) Then logger.Warn("eventArgument is null or empty") Dim jsCodeToExec As String = "alert('eventArg was not set:" + eventArgument + "')" 'Doesn't work Dim cbResp As String = CallbackResult.CreateJavaScript(jsCodeToExec).ToString() 'This works cbResp = "[" & createJsonCallbackResult(jsCodeToExec, "javascript") _ & "," & createJsonCallbackResult(jsCodeResetTxtBox, "javascript") & "]" ScriptManager1.RegisterDataItem(Page, cbResp) Return End If eventArgument = eventArgument.Trim() If eventArgument.Contains("ClearHighlight") Then ClearHighlight() Map1.CallbackResults.Add(evtArgsCallback) Map1.CallbackResults.Add(CallbackResult.CreateJavaScript("clearClientsideMapGraphics();")) scriptMgr.RegisterDataItem(Page, Map1.CallbackResults.ToString) ElseIf eventArgument.Contains("SuggestPossibleMatches") Then Dim jsCode As String = "///:::///:::javascript::: alert(""some message"");" Dim jsCodeCallback As String = String.Format("ESRI.ADF.System.processCallbackResult('{0}');", _ jsCode.Replace("\", "\\")) ScriptManager.RegisterClientScriptBlock(Page, sender.GetType, _ "SuggestPossibleMatches_Click", jsCodeCallback, True) ElseIf eventArgument.Contains("FindAddress") Then Dim jsCode As String = FindAddressPartialPostback(eventArgument) Map1.CallbackResults.Add(evtArgsCallback) Map1.CallbackResults.Add(CallbackResult.CreateJavaScript("log('executed FindAddress() on server');")) '-----------Client ScriptBlock approach-----------: 'Dim mapCallbackResult As String = String.Format("ESRI.ADF.System.processCallbackResult('{0}');", _ ' Map1.CallbackResults.ToString().Replace("\", "\\")) 'Dim sumCallbackResult As String = mapCallbackResult & ";" & jsCode ' ScriptManager.RegisterClientScriptBlock(Page, sender.GetType, "hiddenButton1_Click", sumCallbackResult, True) '-----------DataItem approach------------: If (Not eventArgument.Contains("&")) Then 'only if it is an address search, update FindAddress Panel Map1.CallbackResults.Add(CallbackResult.CreateJavaScript(jsCode)) End If scriptMgr.RegisterDataItem(Page, Map1.CallbackResults.ToString) ElseIf eventArgument.Contains("FindStreet") Then Dim jsCode As String = FindByStreetName(eventArgument) Map1.CallbackResults.Add(evtArgsCallback) Map1.CallbackResults.Add(CallbackResult.CreateJavaScript("log('executed FindByStreetName() on server');")) scriptMgr.RegisterDataItem(Page, Map1.CallbackResults.ToString) ElseIf eventArgument.Contains("FindFolioNumber") Then FindParcelByFolioNumber(eventArgument) Map1.CallbackResults.Add(evtArgsCallback) scriptMgr.RegisterDataItem(Page, Map1.CallbackResults.ToString) ElseIf eventArgument.Contains("DisplayLayers") Then Dim jsCode As String = DisplayLayers(eventArgument) Map1.CallbackResults.Add(evtArgsCallback) scriptMgr.RegisterDataItem(Page, Map1.CallbackResults.ToString) ElseIf eventArgument.Contains("SaveMapToFile") Then Dim jsCode As String = SaveMapToFile(eventArgument) If (jsCode IsNot Nothing) Then Map1.CallbackResults.Add(evtArgsCallback) Map1.CallbackResults.Add(CallbackResult.CreateJavaScript("log('executed SaveMapToFile() on server');")) Map1.CallbackResults.Add(CallbackResult.CreateJavaScript(jsCode)) scriptMgr.RegisterDataItem(Page, Map1.CallbackResults.ToString) End If End If End SubClient-side code to handle the DataItems passed from asp.net ajax framework. Note that this js code must be executed after the asp.net ajax js libraries & esri web adf have been loaded on client and all page elements have been loaded. Therefore, this can be declared at the bottom of the aspx page.
Sys.Application.add_init(initAppEventHandler); function initAppEventHandler(sender) { var prm = Sys.WebForms.PageRequestManager.getInstance(); if (!prm.get_isInAsyncPostBack()) { //to handle dataItems that are sent back during a PartialPostBack prm.add_pageLoading(partialPostBackDataItemHandler); } } function partialPostBackDataItemHandler(sender, args) { if (_dataItems['__Page'] != null) { ESRI.ADF.System.processCallbackResult(_dataItems['__Page']); } if (_dataItems['_SOME_CONTROL_NAME'] != null) { //do something, like replace contents of a server-side web control //$get('_SOME_CONTROL_NAME').innerHTML = dataItems['_SOME_CONTROL_NAME']; } //... and so on. handle other dataItems passed from server. }There's one more thing we need to add to be able to use these functions defined in our MyPage.aspx from client-side javascript code. Due to a bug in firefox and sometimes security limitations on asp.net ajax library, javascript code may not be able to simulate an user-event, e.g. click. Which means. $get('hiddenButton1').click() may not work in firefox or other browsers.
To fix this we need to use the below code:
/** Why create this method? someButton.click() - doesn't work as desired on firefox, we wanted it to fire a partial page postback using asp.net ajax ScriptManager, but it caused the whole page to reload. It works fine in IE7 though. Mozilla/Firefox incorrectly generates the default action for mouse and printable character keyboard events, when they are dispatched on form elements. Ref: http://forums.asp.net/p/1343661/2728613.aspx http://www.howtocreate.co.uk/tutorials/javascript/domevents (browse down to 'Manually firing events' google search: manaully fire event, fireforx fire an event */ function fireClickEvent(elemName) { var el = $get(elemName); if (el) { if (el.fireEvent) { //IE el.click(); /* if (isIE()) el.click(); else el.fireEvent("onclick"); */ } else { //firefox var clickEvt = window.document.createEvent("MouseEvent"); //initEvent(evtName, bubbles?, cancellable?) clickEvt.initEvent("click", true, true); el.dispatchEvent(clickEvt); } } else { alert("Unable to send Partial Postback to server" +"\nUnable to find DOM element with ID '"+elemName+"'"); } }Now, finally the fun part, using what we just built. Let's say we wanted to invoke the FindAddress() method on our MyPage.aspx page and pass in the arguments. On the server, the FindAddress() method would display the results in the TaskResults control, zoom in to the result on the map and highlight the result. The TaskResults, and Map web adf controls can be updated on the server and their changes will be reflected on the server as long as we pass their CallbackResult collections to the client and invoke the js method, processCallbackResult(...). Client-side javascript code to invoke MyPage.aspx.cs.FindAddress() server-side method:
//write code to get the value for street, city etc. var message = "FindAddress:"+address; $get('evtArgsTxtBox').value = message; fireClickEvent('hiddenButton1);Server-side FindAddress() method:
'''@param eventArgument = "FindAddress: 123 Main St" or ''' "FindAddress: Main St & Pole St" Private Function FindAddress(ByVal eventArgument As String) As String Dim responseSearch As String = "" Dim responseMap As String = "" Dim sResults As String = "" Dim argArray() As String = eventArgument.Split(":") Dim searchtype As String = argArray(0) Dim searchvalue As String = argArray(1) Dim searchArray() As String = Split(searchvalue, ";") Dim datasetLabel As String = "" datasetLabel = "Address: " & searchvalue Dim gisresource As ESRI.ArcGIS.ADF.Web.DataSources.IGISResource = _ GeocodeResourceManager1.GetResource(0) If Not GeocodeResourceManager1.Initialized Then GeocodeResourceManager1.Initialize() End If If Not gisresource.Initialized Then gisresource.Initialize() End If Dim igf As ESRI.ArcGIS.ADF.Web.DataSources.IGeocodeFunctionality = gisresource.CreateFunctionality(GetType(ESRI.ArcGIS.ADF.Web.DataSources.IGeocodeFunctionality), Nothing) Dim addressfields As System.Collections.Generic.List(Of ESRI.ArcGIS.ADF.Web.Geocode.Field) = igf.GetAddressFields() Dim avc As New System.Collections.Generic.List(Of ESRI.ArcGIS.ADF.Web.Geocode.AddressValue) Dim av1 As New ESRI.ArcGIS.ADF.Web.Geocode.AddressValue("STREET", searchArray(0)) ' how to find intersection? Example: ' Dim av3 As New ESRI.ArcGIS.ADF.Web.Geocode.AddressValue("STREET", "pepperfish bay & harbor view way") avc.Add(av1) If searchArray.Length > 1 Then Dim av2 As New ESRI.ArcGIS.ADF.Web.Geocode.AddressValue("ZONE", searchArray(1)) avc.Add(av2) End If Dim dt As System.Data.DataTable Dim recordcount As Integer igf.MinCandidateScore = 30 igf.MinMatchScore = 60 igf.ShowAllCandidates = True dt = igf.FindAddressCandidates(avc, False, True) Dim showTaskResultsFP As CallbackResult = _ CallbackResult.CreateJavaScript( _ " var __trpElem = $find('TaskResultsPanel');" & _ " if (__trpElem && __trpElem.show) __trpElem.show();") If (dt IsNot Nothing And dt.Rows IsNot Nothing And dt.Rows.Count > 0) Then dt.TableName = datasetLabel recordcount = dt.Rows.Count 'TODO optionally remove duplicates if a perfect match sResults = CreateLocationsTable(dt) Dim taskResultCBR As CallbackResultCollection = _ ADFUtils.GetInstance().AddToTaskResults(Map1, dt, "TaskResults1", Nothing, Nothing, Nothing, datasetLabel) Map1.CallbackResults.AddRange(taskResultCBR) Map1.CallbackResults.Add(showTaskResultsFP) If dt.Rows.Count = 1 Then Map1.Extent = GetFirstFeatureExtent(dt) Else 'Map1.RefreshResource("Graphics") End If Else dt = New DataTable(datasetLabel) Dim dc1 As DataColumn = New DataColumn("Search") dt.Columns.Add(dc1) Dim dr1 As DataRow = dt.NewRow() dr1(dc1) = "returned no results" dt.Rows.Add(dr1) Dim str As SimpleTaskResult = New SimpleTaskResult("Find Intersection (0) : None", "No results found") TaskResults1.DisplayResults(Nothing, Nothing, Nothing, str) Dim taskResultCBR As CallbackResultCollection = TaskResults1.CallbackResults Map1.CallbackResults.AddRange(taskResultCBR) Map1.CallbackResults.Add(showTaskResultsFP) sResults = "" & datasetLabel & " No matching locations found" End If responseSearch = "showResultsAddress(""" & sResults & """);" Return responseSearch End FunctionUsing this pattern, we can easily add new methods, call those methods from javascript and modify existing methods. References and more info:
No comments:
Post a Comment