ASP.Net Refresh Data on Parent Page from Lightbox without Postback

Something that has always bothered me about using “light boxes” or even popup windows and data is added or updated in that light box the parent window that is displaying the data doesn’t refresh the data without refreshing the page. Using AJAX to improve the user experience  makes matters worse because the data should automatically asp.net refresh when changed. This tutorial will show how to update a GridView on a parent page when a light box updates the data. I will be using Fancybox but the same functionality can be added to any kind of light box.

 The Data Page

For simplicity I’ll be using trusty old AdventureWorks. To cut down on the fields my GridView will display, I’m going to create class called LightContact. Here’s the class:

public class LightContact
{ public int id { get; set; }
 public string Title { get; set; }
 public string FirstName { get; set; }
 public string LastName { get; set; }
 public string EmailAddress { get; set; }
 public string Phone { get; set; }
}

I totally stole this idea from a recent Silverlight video I was watching. I would give the author credit but I completely forgot which one it was. Sorry! Basically the point of this is to simply cut down on the amount of data that will display in the asp.net refresh GridView.

I created an Entity Data Model that defines the Contact table from AdventureWorks. The data method or model you choose really doesn’t matter. I really like the Entity Framework so that’s what I’m sticking with.

Next I created the page to display the data. For this particular tutorial I'm using an UpdatePanel. This is not the only way to do this so feel free to experiment. Anyway, here's my web form with a ScriptManager, UpdatePanel and GridView. (this all goes inside the
by the way)
<asp:ScriptManager ID="ScriptManager1" runat="server">
</asp:ScriptManager>
<asp:UpdatePanel ID="UpdatePanel1" runat="server">
 <ContentTemplate>
 <asp:GridView ID="grdEmployees" runat="server">
 </asp:GridView>
 </ContentTemplate>
</asp:UpdatePanel>

Nothing fancy going on yet. Here’s the code-behind to bind the data. Notice I have a bindContacts method to bind the data rather than putting it in the Page_Load. If you’re not doing it this way, you should.

protected void Page_Load(object sender, EventArgs e)
 {
 if (!Page.IsPostBack)
 {
 bindContacts();
 }
 }

private void bindContacts()
 {
 using (AdventureWorks_DataEntities context = new AdventureWorks_DataEntities())
 {
 var x = (from c in context.Contacts
 select new LightContact() {
 ID = c.ContactID,
 Title = c.Title,
 FirstName = c.FirstName,
 LastName = c.LastName,
 EmailAddress = c.EmailAddress,
 Phone = c.Phone
 }).Take(30).ToList();
 grdEmployees.DataSource = x;
 grdEmployees.DataBind();
 }
 }

If you’re not familiar with what’s going on here, I’m simply selecting 30 contacts and creating a collection of the LightContact class that was created above. Then it binds that collection to grdContacts.

Add/Update Contact Page

Now let’s say I want to add a new contact. I don’t want to re-direct to a new page, I’d like a “light box” to overlay the current page to handle the new entry. There are a couple of ways to go about this. My preferred method is to add a separate add/update page that will open inside an iframe within the fancybox. The other option would be to add the add/update form to the same form inside of a hidden panel. If you are going to do this and are using Fancybox, you’ll need to make a small tweak. I’ve described the process in this post. But again, I prefer to add a new page to handle this. I created a page named ContactAddUpdate.aspx. I’ll not include the code here but it basically pulls in a contact’s information if the id is passed in the querystring otherwise it assumes a new contact will be created.

Implement the Fancybox

Using Fancybox is quite simple. You add a reference to the fancybox.js file (it’s name something like jquery.fancybox-1.3.4.pack.js, make sure you use the packed version for production). I would also recommend adding the easing and mousewheel packs as well. You also need to reference the fancybox.css file as well.

I’m going to add a pageLoad JavaScript function to hook up any element that has a CSS class of “fancybox” to open the asp.net lightbox (for a complete guide on how to use Fancybox you can view their usage guide here.). The pageLoad function is provided by the ScriptManager to basically emulate the Page_Load() that happens on the server side. The reason I’m not putting this in the $(document).ready(); event is that it only runs on the first page load, not on the AsyncPostBacks the UpdatePanel calls.

If you’re using a MasterPage it would be a good idea to include the jquery script on your MasterPage, that way any page that uses it will automatically have the script ready to use. If you’re not using a MasterPage then add the following to your page. I like to add it at the bottom of the page just before the ending </body> tag. If you prefer to put it in the <head> then that is fine too. It’s up to you.

function pageLoad() {
 $('.fancybox').fancybox({
 showCloseButton: true,
 overlayOpacity: 0.75,
 overlayColor: '#3d5366',
 width: 720,
 height: 420
 });
}

Update the Web Form

Above my Gridview I’m going to add a Hyperlink and set its CssClass to “fancybox”. Also, because I want to open a different page into the lightbox I am also going to add “iframe” to the class, here’s the HTML:

<asp:HyperLink ID="hlAdd" NavigateUrl="ContactAddUpdate.aspx?i=-1" CssClass="fancybox iframe" runat="server">+ Add Contact</asp:HyperLink>

Now when I view the page in a browser and click the Add Contact link, a fancybox will appear with my Add/Update contact page loaded in an iFrame.

Now to refresh the parent

It doesn’t make much sense to open a fancybox and do some action, especially when dealing with data on the opening page, and just close the fancybox. The parent page should be automatically updated when the light box closes. The easiest way to accomplish this would be to add a window.location.reload(true); when on the close event of the fancybox. So the JavasScript would look like:

$(document).ready(function () {
 $('.fancybox').fancybox({
 showCloseButton: true,
 overlayOpacity: 0.75,
 overlayColor: '#3d5366',
 width: 720,
 height: 420,
 onClosed: onFancyClose
 });
});
function onFancyClose() {
 window.location.reload(true);
}

Now when the fancybox closes the parent window will reload and the change in the data will be displayed.

But what’s the point of using a lightbox and having to refresh the entire page when it closes, that kind of defeats the purpose of why we’re opening a light box in the first place. Light boxes are supposed to enhance the user experience and eliminate all the unnecessary post-backs to the server. I want to just reload the GridView without having to reload the entire page and cause that annoying flicker on the screen.

Enter the UpdatePanel

So how do we only refresh the GridView? First, we add a Button or LinkButton inside the UpdatePanel like so:

<asp:LinkButton ID="lbRefresh" runat="server" OnClick="lbRefresh_Click" />

Then in the code-behind I’ll add an onclick event. All this method will do is call the bindContacts method we created earlier. We also need to call the Update method for the UpdatePanel since its UpdateMode=”Conditional”.

protected void lbRefresh_Click(object sender, EventArgs e)
 {
 bindContacts();
 UpdatePanel1.Update();
 }

Since the LinkButton is inside the UpdatePanel an AsyncPostBack will occur, so only what’s in the UpdatePanel will change. The page will not refresh.

You’re probably thinking, “This doesn’t solve anything.” You would be correct. We still have to somehow trigger the LinkButton when the fancybox closes.

Before I do that I want to hide the LinkButton. If I set Visible=”False” it will not work because the control will not render to the HTML and thus would not be available to our JavaScript. You could set style=”display: none;” on the control itself. My preference, however, is to place a <div> around it and hide the div. In my CSS I always add a class called hidden where I simply set the display: none;. In addition to the hidden class I’m going to set a CssClass to the LinkButton called refresh and also set it to the div as well. This is simply an easier way to call the control with jQuery.

.hidden { display: none; }

Here’s the new HTML:

<div class="hidden refresh"><asp:LinkButton ID="lbRefresh" CssClass="refresh" runat="server" OnClick="lbRefresh_Click" Text="refresh" /></div>

Now the LinkButton is hidden from the user. Let’s hook up the JavaScript to simulate our button being clicked.

In the onFancyClose function Remove the window.location.reload(true) with the following:

function onFancyClose() {
 var lb = $('div.refresh a.refresh');
 if (lb.size() > 0) {
 var exp = /javascript:__doPostBack\(\'([\w\d\$]+)\'\,\'(.*)\'\)/;
 if (exp.test(lb.attr('href')) == true) {
 var ar = exp.exec(lb.attr('href'));
 var ctl = ar[1];
 var param = ar[2];
 __doPostBack(ctl, param);
 }
}

If you’re not familiar with Regular Expressions this may seem a little difficult to grasp. Basically what happens when a LinkButton renders as HTML to the page it is an <a> tag with the href set to run a built in asp.net JavaScript function called __doPostBack. If you view the source of the page from the browser you will see that when __doPostBack is called it sends two arguments. The first argument is the reason we need the regular expressions.

If you’ll notice the id of the LinkButton actually ends up being MainContent_lbRefresh because my page is in a MasterPage and so everything on this page has the prefix of the ContentPlaceHolder ClientID which happens to be MainContent. The problem is that the argument sent to the __doPostBack method is not the ID of the control or even the ControlID but it’s a little different.

So basically I wrote the regular expression to extract both parameter values from the linkbutton. Then I manually call __doPostBack with those two extracted parameters. It may not look pretty…but it works!

Now when you view the page in the browser, add or update a contact and close the fancybox and only the data should refresh.

What about using a different light box alternative?

The concept would be exactly the same for any other light box alternative or even just opening a popup or modal display. First look to see if the method you are using as an fancybox onClose event and pass the same onFancyClose function. If you’re using a popup you can add an event to the window.onbeforeunload function and have it run parent.onFancyClose();.