Archive for the ‘AJAX’ Category

It’s been a while since I’ve used AJAX, but I came across the error “Script control may not be registered before PreRender” when trying to add a timer control to an User Control embedded in a WebPart. I’m pretty certain I have encountered this error before, but as it took me a little while to resolve I thought I’d better blog about it.

What was I trying to do? Well, I was trying to create a Web Part which created a sort of slideshow of web parts! The idea was to use various web parts, which displayed different charts, by loading them dynamically using an Update Panel, a Timer and an AsyncPostBackTrigger. 

At first, things didn’t go to well and I received the error screen shown below

Script control may not be registered before PreRender 

It turns out that the solution is slightly easier that I expected.  Initially, I added a script manager to the user control but, because the timer control automatically posts back the script manager is not loaded when its is looking for it.

So, to resolve the issue, I added the ScriptManager to the SharePoint master page added a ScriptManagerProxy to the user control and it worked a treat.

User Control HTML
  1.   <asp:ScriptManagerProxy ID="ScriptManagerProxy1"runat="server">
  2. </asp:ScriptManagerProxy>
  3. <asp:Timer ID="Timer1" runat="server" OnTick="Timer1_Tick" Interval="20000">
  4. </asp:Timer>
  5. <asp:UpdatePanel ID="DynamicUpdatePanel" runat="server">
  6.    <Triggers>
  7.         <asp:AsyncPostBackTrigger ControlID="Timer1" EventName="Tick" />
  8.     </Triggers>
  9.     <ContentTemplate>
  10.         <asp:PlaceHolder ID="DynamicPlaceHolder" runat="server"></asp:PlaceHolder>
  11.     </ContentTemplate>
  12. </asp:UpdatePanel>

Advertisements

Problem: After adding an AJAX Calendar Extender to a Text Box you need to set the date displayed in the Text Box to the Selected Date value of the Calendar Extender.

Solution: Firstly you need to ensure that the Calendar Extender, as seen below in Figure 1, has a BehaviorID value set.

Figure 1
  1. <label for="dateLastCheckedTextBox" class="textBoxLabel">Date Last Checked</label>
  2. <asp:TextBox ID="dateLastCheckedTextBox" runat="server" CssClass="textBox"  />
  3. <asp:CalendarExtender id="dateLastCheckedCalendarExtender" BehaviorID="dateLastCheckedCalendarExtender" runat="server" TargetControlID="dateLastCheckedTextBox" Format="dd/mm/yyyy"  EnabledOnClient="true" OnClientShown="checkDate" />

Then, assign the OnClientShown event of the CalendarExtender to the checkDate function shown below, setting the date using the set_selectedDate accessor.

Figure 2
  1. <script>
  2.     function checkDate(sender, args) {
  3.         var currentDate = $("input[id$='dateLastCheckedTextBox']:visible").val();
  4.         var calendarBehavior = $find("dateLastCheckedCalendarExtender");
  5.         calendarBehavior.set_selectedDate(getDateFromUkDateString(currentDate));
  6.     }
  7.     function getDateFromUkDateString(dateStr) {
  8.         dateStr = dateStr.split("/");
  9.         if (dateStr.length == 1)
  10.             return null;
  11.         else
  12.             return new Date(dateStr[2], dateStr[1] – 1, dateStr[0]);
  13.     }
  14.    
  15. </script>

In this particular example I have also converted the date to UK format.

AJAX Accordion OutOfMemoryException

Posted: 29th March 2010 in AJAX, JQuery, WCF

Recently I came across a problem with the ASP.NET AJAX accordion control which caused my machine to have a System.OutOfMemoryException. This was caused by an attempt to bind a large result set returned from a WCF service  to the accordion control. The error never directly specified out of memory, but instead display the following webpage error: “Could not get the value property. Not enough storage is available to complete this operation”

image

This is actually a SOAP error, but its effectively the same thing. There is simply too much data for the machine to cope.

After some investigation I found that this problem was down to the fact that the accordion control not only loads the header detail for the bound list, but all the content detail too. In my case, the content control contained a lot of dropdown lists.  Firstly, I thought about implementing paging, but after consultation with the client I soon discovered that this was not an option.

So, to allow me to load the full list I had to change how the master and detail worked. Here’s how I went about it.

Firstly, I created a user control for the detail to be displayed and instead of loading the control in the html of the accordion content template I added a place holder as shown in Figure 1.

Figure 1
  1. <ContentTemplate>
  2. <asp:PlaceHolde

Now, I have to load this control dynamically. However, I only want to load it for the first item of the accordion. To do this I used the OnItemDataBound event of the accordion as can be seen below in Figure 2.

Figure 2
  1. protected void CircuitAccordion_ItemDataBound(object sender, AccordionItemEventArgs e)
  2. {
  3. if (e.ItemType != AccordionItemType.Content || ((Accordion) sender).Panes.Count > 1)
  4. {
  5. return;
  6. }
  7. var circuitDto = e.Item as CircuitDto;
  8. if (circuitDto == null)
  9. {
  10. return;
  11. }
  12. var circuitDetail = (CircuitDetail)LoadControl(“UserControls/CircuitDetail.ascx”);
  13. circuitDetail.Circuit = circuitDto;
  14. var circuitUserControlPlaceHolder =
  15. e.AccordionItem.FindControl(“circuitUserControlPlaceHolder”) as PlaceHolder;
  16. if (circuitUserControlPlaceHolder == null)
  17. {
  18. return;
  19. }
  20. circuitUserControlPlaceHolder.Controls.Add(circuitDetail);
  21. }

Great, now the first item loads, but not the rest.  To get the other items to load I needed to use a little jquery and a AJAX enabled WCF service (to get the data for each item).

To override the functionality of the accordion control I first need to create a client side event handler for  the on selected index changed event. This can be seen in figure 3 and attach the handler on the page load figure 4

Figure 3
  1. // handles the event which fires on the accordion selected index changing
  2. function onAccordionSelectedIndexChanged(sender, eventArgs) {
  3. var contentTemplate = $(‘#’ + sender._id.replace(‘_AccordionExtender’, ‘_Pane_’ + sender.get_SelectedIndex() + ‘_content’));
  4. contentTemplate.append($(“fieldset[id*=’DetailsControl’]:visible”));
  5. var headerTemplate = sender._id.replace(‘_AccordionExtender’, ‘_Pane_’ + sender.get_SelectedIndex() + ‘_header’);
  6. var id = $(‘#’ + headerTemplate + ‘_idHiddenTextBox’).val();
  7. var tabContainer = $get(‘<%=locationTabContainer.ClientID%>’).control;
  8. loadDetailControlData(tabContainer.get_activeTab().get_headerText(), id);
  9. }

In figure 3 I perform the following steps:

  • Line 3 – I get reference to the content template of the header which has been clicked.
  • Line 4 – I append the visible details user control to the associated content template of the selected index of the header template clicked.
  • Line 5 – Get reference to the header template to allow access to the hidden textbox control.
  • Line 6 – Retreive the value of the primary key, which is in a hidden textbox control named idHiddenTextbox in the header template pane.
  • Line 7- Get reference to the selected tab
  • Line 8 –  Pass the id and the selected tab control to the loadDetailControlData function in figure 5

To add the event handler to the control I use the javascript pageLoad() function as can be seen in figure 4.

Figure 4
  1. function pageLoad() {
  2. $find(‘<%= devicesAccordion.ClientID %>’ + ‘_AccordionExtender’).add_selectedIndexChanged(onAccordionSelectedIndexChanged);
  3. $find(‘<%= cabinetsAccordion.ClientID %>’ + ‘_AccordionExtender’).add_selectedIndexChanged(onAccordionSelectedIndexChanged);
  4. $find(‘<%= endBCircuitsAccordion.ClientID %>’ + ‘_AccordionExtender’).add_selectedIndexChanged(onAccordionSelectedIndexChanged);
  5. $find(‘<%= endACircuitsAccordion.ClientID %>’ + ‘_AccordionExtender’).add_selectedIndexChanged(onAccordionSelectedIndexChanged);
  6. $find(‘<%= nonNetworkDevicesAccordion.ClientID %>’ + ‘_AccordionExtender’).add_selectedIndexChanged(onAccordionSelectedIndexChanged);
  7. }

In this particular situation, I have a tab control which, in turn, contains the accordion controls. So, I created a JavaScript function to load the detail control data dependent on the selected tab, seen in figure 5 which call the WCF service shown in figure 6 and ultimately set the field values from the service response seen in figure 7.

figure 5
  1. function loadDetailControlData(tabHeaderText, id) {
  2. switch (tabHeaderText) {
  3. case ‘Cabinets’:
  4. getCabinet(id);
  5. break;
  6. case ‘Devices’:
  7. getDevice(id);
  8. break;
  9. case ‘End A Circuits’:
  10. case ‘End B Circuits’:
  11. getCircuit(id);
  12. break;
  13. case ‘Non-Network Devices’:
  14. getNonNetworkDevice(id);
  15. break;
  16. }
  17. }

Figure 6 shows the call to the WCF service using jquery. This function is called from the function above in Figure 5.

Figure 6
  1. function getCircuit(circuitId) {
  2. $.ajax({
  3. url: http://localhost:10785/LocationServiceAjax.svc/GetCircuit&#8221;,
  4. type: “POST”,
  5. contentType: “application/json”,
  6. data: ‘{“circuitId”:’ + circuitId + ‘}’,
  7. dataType: “json”,
  8. success: function(data) {
  9. setCircuitDetail(data.d);
  10. }
  11. });
  12. }

Seen below is the call-back function to set the control values of the details form which are returned from the WCF Ajax response seen in figure 6

Figure 7
  1. // Sets the circuit detail form callback from the ajax wcf call.
  2. function setCircuitDetail(circuit) {
  3. if (circuit == null) { alert(“Something is not configure correctly.”); return; }
  4. $(“input[id$=’circuitReferenceTextBox’]:visible”).val(circuit.CircuitReference);
  5. $(“select[id$=’circuitUsageDropDownList’]:visible”).val(circuit.Usage.Description);
  6. $(“select[id$=’circuitTypeDropDownList’]:visible”).val(circuit.Type.Name);
  7. $(“select[id$=’circuitOwnerDropDownList’]:visible”).val(circuit.Owner.Name);
  8. $(“select[id$=’circuitStatusDropDownList’]:visible”).val(circuit.Status.Status);
  9. $(“select[id$=’endACircuitLocationDropDownList’]:visible”).val(circuit.EndALocationCode);
  10. $(“select[id$=’endBCircuitLocationDropDownList’]:visible”).val(circuit.EndBLocationCode);
  11. }

Hopefully this post helps someone as it took me a while to work it out… 🙂

When you want to update a value from a control in the control template section of a data bound collection of an accordion control it is necessary to find the control in the collection to update it.

Shown below is a basic example to explain what I mean.

basic accordion control
  1. <asp:ToolkitScriptManager ID="ToolkitScriptManager1" runat="server">
  2.         </asp:ToolkitScriptManager>
  3.         <asp:Accordion ID="Accordion1" runat="server" OnItemCommand="Accordion1_ItemCommand" >
  4.         <HeaderTemplate>
  5.             <asp:Label runat="server" ID="Label1" Text='<% #Eval("Text") %>'></asp:Label>
  6.         </HeaderTemplate>
  7.         <ContentTemplate>
  8.              <asp:Label ID="label1" runat="server" Text='<% #Eval("Id") %>'  ></asp:Label>
  9.              <asp:TextBox ID="textBox1" runat="server" Text='<% #Eval("Text") %>'  ></asp:TextBox>
  10.              <asp:Button ID="Button1" runat="server" Text="submit" CommandName="Update" CommandArgument='<% #Eval("Id") %>' />
  11.         </ContentTemplate>
  12.         
  13.         </asp:Accordion>

As seen below, the server side code to bind this control is generated by a loop in the LoadData() method

Binding to the accordion
  1. Accordion1.DataSource = this.LoadData();
  2.             Accordion1.DataBind();

And the LoadData() implementation is a simple iteration to create a List of a  simple class Class1 consisting of two properties Id and Title

Creating the list to bind to
  1. protected IList<Class1> LoadData()
  2.       {
  3.           for (var i = 0; i < 9; i++ )
  4.           {
  5.               list.Add(new Class1 { Id = i, Text = i.ToString() });
  6.           }
  7.  
  8.           return list;
  9.       }

Okay, simple enough. Now, we want to update the the value of the displayed value of textBox1 in the ContentTemplate of the “Basic accordion control” example.

To do this, we need to use the ItemCommand event of the accordion control and cast it to a AccordionCommandEventArgs as seen in the following example.

 

Getting the value
  1. protected void Accordion1_ItemCommand(object sender, EventArgs e)
  2.         {
  3.             var t = ((AccordionCommandEventArgs) e).Container.DataItem;
  4.             var id = ((Label) ((AccordionCommandEventArgs) e).Container.FindControl("label1")).Text;
  5.             var text = ((TextBox) ((AccordionCommandEventArgs) e).Container.FindControl("textBox1")).Text;
  6.         }

The variable “text” will now hold the value you are looking for.

Happy coding 🙂

Given the task of populating and binding controls within an AJAX accordion control I thought things would be easy.  However, binding to a selected index of a dropdown control was not as easy as I first suspected.

Below is one of the accordion section included in my tab container.

Accordion with dropdown
  1. <asp:Accordion ID="devicesAccordion" runat="server" OnItemDataBound="DeviceAccordion_ItemDataBound">
  2.                                 <HeaderTemplate>
  3.                                      <tr>
  4.                                         <td><asp:Label ID="Label2" Text='<%# Eval("Name") %>' runat="server"  /></td>
  5.                                      </tr>
  6.                                 </HeaderTemplate>
  7.                                 <ContentTemplate>
  8.                                     <label for="nameTextBox">Name</label>
  9.                                     <asp:TextBox ID="nameTextBox" Text='<%# Eval("Name") %>' runat="server" />
  10.                                     <label for="deviceStatusDropDownList">Status</label>
  11.                                     <asp:DropDownList ID="deviceStatusDropDownList" runat="server" />
  12.                                     <asp:Button runat="server" ID="updateDevicesButton" CommandName="UpdateDevice" OnCommand="UpdateDevicesButton_Command" CommandArgument='<%# Eval("Id") %>' Text="Update" />
  13.                                 </ContentTemplate>
  14.                             </asp:Accordion>

Next, I had to bind the value from the DB to the status of the device and to do this I use the item data bound event of the accordion control. Firstly, I have to AccordionItemEvent being passed in is within the content part of the  control, i.e. the one being edited.

Once I’ve confirmed this, I need to convert the item in the control to the Data Transfer Object being retrieved from the WCF Service.  Then, if the cast to the DTO is not null. I find the control and set the value of the dropdown.

Binding an accordion item
  1. /// <summary>
  2.         /// Event handler when a new item is bound in the contentTemplate of the devices accordion.
  3.         /// </summary>
  4.         /// <param name="sender">The sender.</param>
  5.         /// <param name="e">The <see cref="AjaxControlToolkit.AccordionItemEventArgs"/> instance containing the event data.</param>
  6.         protected void DeviceAccordion_ItemDataBound(object sender, AccordionItemEventArgs e)
  7.         {
  8.             if (e.ItemType != AccordionItemType.Content)
  9.             {
  10.                 return;
  11.             }
  12.  
  13.             var deviceDto = e.Item as DeviceDto;
  14.             if (deviceDto == null)
  15.             {
  16.                 return;
  17.             }
  18.  
  19.             var dropDownList = e.AccordionItem.FindControl("deviceStatusDropDownList") as DropDownList;
  20.             if (dropDownList == null)
  21.             {
  22.                 return;
  23.             }
  24.  
  25.             this.PopulateDropDownListAndSetValue(dropDownList, LookupDataType.DeviceStatuses, deviceDto.Status.Status);
  26.         }

To help me with this process, I created a basic interface to be implemented by all DTO’s that can be used in a dropdown. 

Interface for Dropdowns
  1. /// <summary>
  2.     /// Interface to be implemeted to use a class as part of a drop down list.
  3.     /// </summary>
  4.     public interface ILookupData
  5.     {
  6.         /// <summary>
  7.         /// Gets the key for the lookup data.
  8.         /// </summary>
  9.         /// <value>The key of the lookup.</value>
  10.         string Key { get; }
  11.  
  12.         /// <summary>
  13.         /// Gets the value for the lookup data.
  14.         /// </summary>
  15.         /// <value>The value of the lookup.</value>
  16.         string Value { get; }
  17.     }

I also created a method to populate and set the value of the dropdown’s  selected index setting the Text and Key properties defined in the above interface.

Populate and set the dropdown
  1. /// <summary>
  2.         /// Populates the drop down list and set value.
  3.         /// </summary>
  4.         /// <param name="dropDownList">The drop down list.</param>
  5.         /// <param name="lookupDataType">Type of the lookup data.</param>
  6.         /// <param name="selectedValue">The selected value.</param>
  7.         protected void PopulateDropDownListAndSetValue(ListControl dropDownList, LookupDataType lookupDataType, string selectedValue)
  8.         {
  9.             dropDownList.DataSource = this.serviceConnector.GetLookupData(lookupDataType);
  10.             dropDownList.DataTextField = "Value";
  11.             dropDownList.DataValueField = "Key";
  12.             dropDownList.DataBind();
  13.             if (string.IsNullOrEmpty(selectedValue))
  14.             {
  15.                 return;
  16.             }
  17.  
  18.             dropDownList.SelectedValue = selectedValue;
  19.         }

In the code sample above it is shown that the data source for the the dropdown comes from a WCF Service which, in turn, returns a list of interfaces ILookupData. The working and casting for this service call is shown in the code sample below.

Casting items in a list
  1. /// <summary>
  2.         /// Gets the lookup data.
  3.         /// </summary>
  4.         /// <param name="lookupDataType">Type of the lookup data.</param>
  5.         /// <returns></returns>
  6.         public IList<LookupData> GetLookupData(LookupDataType lookupDataType)
  7.         {
  8.             IList<ILookupData> lookDataList = new List<ILookupData>();
  9.             switch (lookupDataType)
  10.             {
  11.                 case LookupDataType.Countries:
  12.                     lookDataList = this.GetCountries().ToList().ConvertAll(item => (ILookupData)item);
  13.                     break;
  14.                 case LookupDataType.DeviceStatuses:
  15.                     lookDataList = this.GetDevicesStatuses().ToList().ConvertAll(item => (ILookupData)item);
  16.                     break;
  17.                 case LookupDataType.Towns:
  18.                     lookDataList = this.GetAllTowns().ToList().ConvertAll(item => (ILookupData)item);
  19.                     break;
  20.             }
  21.  
  22.             return lookDataList.ToList().ConvertAll(item => (LookupData)item);
  23.         }

Hopefully, this post helps someone out there…

WCF 10 connection limitation

Posted: 15th February 2010 in AJAX, WCF

Recently, I had a problem with an AJAX enabled application which was calling a WCF service. As can be seen below, in my calls to the Service, I used the “using statement” to allow the connection to the service to be closed after use. 

Code Snippet
  1. /// <summary>
  2.         /// Gets the towns.
  3.         /// </summary>
  4.         /// <returns>The list from the cache if it exists; or from the database</returns>
  5.         public List<TownDto> GetTowns()
  6.         {
  7.             if (!this.cacheManager.Contains("towns"))
  8.             {
  9.                 using (var svc = new NetworkServiceClient())
  10.                 {
  11.                     var towns = svc.GetAllTowns();
  12.                     this.cacheManager.Add(
  13.                         "towns",
  14.                         towns,
  15.                         CacheItemPriority.Normal,
  16.                         null,
  17.                         new SlidingTime(TimeSpan.FromMinutes(Constants.CacheTimeout)));
  18.                 }
  19.             }
  20.  
  21.             return (List<TownDto>)this.cacheManager.GetData("towns");
  22.         }

However, as i played with the application it became apparent that the more calls its made the slower the application became.   Eventually, after wading threw all the application tiers I found the culprit was: by default WCF only allows 10 connections, which meant my connections weren’t being closed by the using statement.

The code has now been refactored to close the service client explicitly. It now works!