User Controls, Update Panels and JQuery scripts all working together happily.

While working on implementing new functionality on my online favorites manager (www.linqto.me) which I encourage everyone to check out, I came across the following problem:

  • Given a UserControl, I would like to have an UpdatePanel that would refresh some of the HTML that was generated by user control on the pages it is used on. Furthermore, I would like to control to dynamically inject some JavaScript into the page so that once the HTML is rendered, I could make use of JQuery to further change the DOM and animate things each time a partial postback occurs.

In order to explain how you could go about doing such a thing, I have created a simpler example (although you can look into the keywords feature described here for www.linqto.me to see a more complex usage). The sample I will walk you through looks like the following screen-shot: it consists of two bar like indicators that will show how much of a total value is used up – this can be business data, metrics, pressure, anything else you want.

The bar indicators are actually composed of two divs each (one div – with a blue or pink color - inside another div with a gray background). When you start the example, the two indicators (the divs) which are both gray will only appear, since the width values of the interior divs are 0:

<style>
  .grayDiv{
     background-color: lightgray;
     border: solid 1px;
     border-color: gray;
     width: 500px;
   }

 

  .blueDiv{
     background-color: lightblue;
     width: 0px;
   }

</style>

 ...

 Indicator 1:
<br />
<div id="bar1" class="grayDiv">
   <div id="indicator1" class="blueDiv">&nbsp;</div>
</div>
<br />

Notice that the style element for the .blueDiv class attributes a width of 0 to the inner div, making it invisible.

When the 'Update Indicators' button is pressed, a portion of the markup is updated, and I then use a script, which will be triggered following the async postback from the update panel to make changes to the width of the inner divs and set a new value in the CSS style.

The entire control is contained in ASCX and ASCX.cs files that are named WebUserControl. If you look at the markup of the control, you will see that it makes use of an UpdatePanel control that contains a LinkButton control (the 'Update Indicators' button) and two hidden fields:

<asp:UpdatePanel ID="updUpdateIndicators" runat="server" UpdateMode="Conditional">
   <ContentTemplate>
      <asp:HiddenField ID="valIndicator1" runat="server" ClientIDMode="Static" />
      <asp:HiddenField ID="valIndicator2" runat="server" ClientIDMode="Static" />
      <asp:LinkButton ID="lnkUpdateValues" runat="server" Text="Update Indicators" OnClick="lnkUpdateValues_Click" />
   </ContentTemplate>
</asp:UpdatePanel>

When the button is clicked, the update panel initiates a partial postback to the server, where the code for the lnkUpdateValues_Click() event handler will run. Here is the code for the event handler in question:

//event handler for the link button click event
protected void lnkUpdateValues_Click(object sender, EventArgs e)
{
   //create a couple of random numbers that vary between 0 and 500 and assign them to the hidden fields
   Random randGenerator = new Random();
   int val1 = randGenerator.Next(0, 500);
   int val2 = randGenerator.Next(0, 500);

   //assign these new values to the hidden fiels and send the entire thing back to the update panel
   valIndicator1.Value = val1.ToString();
   valIndicator2.Value = val2.ToString();
}

All the code does is that it calculates two random integer values that are between 0 and 500 and then sets the resulting numbers as the values of the two hidden fields valIndicator1 and valIndicator2.

However, please note that the divs that are responsible for displaying the data in a more graphical format are not contained inside the update panel, and the markup resulting from the partial post-back will not change these elements.

Here is where the magic comes in. We need a script that is fired whenever the update panel is updated and will then make further changes to the DOM (Document Object Model). The script needs to be dynamically injected the first time the control is rendered on the page it will live on. To achieve this, we use the Page_Load event handler for the control with the following code:

protected void Page_Load(object sender, EventArgs e)

{
    //attempt to inject javascript into the loading page
    ScriptManager.RegisterClientScriptInclude(
       this, GetType(), "valuesJqScript", ResolveUrl("~/scripts/ValuesUpdater.js"));
 
    ScriptManager.RegisterStartupScript(this.Page, GetType(), ClientID, "WireUpValues();", true);
}

This code is the interesting part: the first line, where we call the ScriptManager.RegisterClientScriptInclude method, will indicate to the page hosting the control that it should also load a JavaScript file that is located in the /scripts/ folder and that is called ValuesUpdater.js. This will append a script block to the page instructing the browser to also load the script alongside the rest of the resources that are used by this page.

The second line will call the ScriptManager.RegisterStartupScript overload method. This method will specify what the name of the JavaScript function to be called is: in our case the file called ValuesUpdater.js defines a function called WireUpValues, which should be called. Here is the code of the script function:

function WireUpValues() {
   //each time the update panel reload the HTML markup, get a hold of the hidden controls
   var val1Control = $("#valIndicator1").val();
   var val2Control = $("#valIndicator2").val();

   //select the two divs that are supposed to show the progress bars
   var div1 = $("#indicator1");
   var div2 = $("#indicator2");

   //set the width css width values of the two divs to match the values passed in
   div1.css("width", val1Control);
   div2.css("width", val2Control);
}

What the code does is that it uses JQuery to select the two hidden fields from the HTML that is rendered by the User Control, and stores their values into two variables called va1Control and val2Control. It then selects the divs we need to change the width for, using JQuery selectors, and will proceed to call the css() function on these JQuery wrapped objects to reset the width property to the values indicated by the hidden input controls.

The call to one of the two overloads of RegisterStartupScript achieves one of the following actions:

  • The script is called once when the control is loaded, if this overload is used
  • The script is called when the control is loaded and each and every time there is a partial postback on the control, if this overload is called – which is the overload used in the sample project.

To wrap up, here is the workings of the project from A-Z now that we have looked at the code:

  1. The control gets loaded on the page and the Page_Load event handler is called.
  2. This instructs the page to include a JavaScript file which should be loaded with the page and will wire up the function to be called once the page has loaded and when a partial postback from the control has completed (the HTML markup from the postback is incorporated into the page).
  3. The function fires on the page load and on subsequent partial postbacks and executes a simple logic of transferring the values of two integers (expressed as hidden fields) into CSS classes that make the divs be wider or smaller.

You can download the sample from the link below. Happy coding with ASP.net Webforms and JQuery.

Paul Cociuba
www.linqto.me/about/pcociuba

UserControlSample.zip