Archive for the ‘JQuery’ Category

My latest user requirement with my on-going accordion control application: the user wanted the DIV containing the navigation, tree view, user control must always be in view.

So, here’s how I went about it, along with some of the pitfalls I found along the way.

Scrolling a Div
  1. var scrollId = 0;
  2.            var SCROLL_DELAY = 500;  // Acceptable delay for scrolling to stop
  3.            $(window).scroll(function() {
  4.                setTimeout('moveNavigationDiv()', SCROLL_DELAY);
  5.            });
  7.            // Moves the navigation div relataive to the page on scroll of the main window
  8.            function moveNavigationDiv() {
  9.                var now = new Date();
  10.                if (scrollId == 0 || (parseInt(parseInt(scrollId) + parseInt(SCROLL_DELAY)) < now.getTime())) {
  11.                    $("div[id$='navigation']").css({ 'position': 'relative' })
  12.                    $("div[id$='navigation']").animate({ "top": $("html").scrollTop() }, "1000", "linear");
  13.                    scrollId = now.getTime();
  14.                }
  15.            }

Firstly, I though this would be fairly straight forward. However, what I never realised was that the windows.onscroll event fires constantly as you are scrolling, not once as I thought. I suppose, when I think about it, this makes sense.

Because of this I had to try and pre-empt when the scrolling has stopped. For this I added a delay of 0.5 seconds.  If you don’t add this delay the control does move to the correct position, but it does it in a jerky motion, which doesn’t look very good.  Adding the delay allows the function to know what the final position is before it moves, making it a smooth movement.

To ensure the animating part of the  moveNavigationDiv()  only gets called once I’ve added a variable, scrollId,  which is updated in the if statement after a comparison with the SCROLL_DELAY and the current time.

I could have just used the CSS top: property to update the position of the div, but I had a little extra time and decided to use the JQuery animate instead to make it a bit easier on the eye.

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.     }
  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”


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… 🙂