.NET Performance Blog

February 2, 2010

ASP.NET GridView – edit records using JQuery Dialog

Filed under: .NET,ASP.NET,JQuery — Eric P @ 6:25 am

There are many ways in which you could modify modular data in Grid View:
1. Inline by making rows editable when u click on Edit
2. By using separate pages for Edit
3. By using popups/dialogs for adding/editing

In this post I will show how to implement the 3rd approach using JQuery UI Dialog for editing/adding data in ASP.NET GridView.

The following features have been implemented:
1. List customers
2. Add customer – Save/Cancel
3. Edit customer – Save/Cancel
4. Delete customer
5. Server side validation
6. Edit dialogs appear next to trigger links

You can check out the demo here:
http://samples.entechsolutions.com/GridWithEditDialog

The code is available here:
http://samples.entechsolutions.com/Downloads/GridWithEditDialog.zip

Implementation

Data source

The grid lists customers which are stored in list which is initialized for each new session. This way each user trying out this sample will not affect other users.
CustomerService is a class wrapped around list of customers that allows to add/edit/list customers:

public class CustomerService
{
	private List<Customer> Customers
	{
		get
		{
			List<Customer> customers;
			if (HttpContext.Current.Session["Customers"] != null)
			{
				customers = (List<Customer>)HttpContext.Current.Session["Customers"];
			}
			else
			{
				//Create customer data store and save in session
				customers = new List<Customer>();

				InitCustomerData(customers);

				HttpContext.Current.Session["Customers"] = customers;
			}

			return customers;
		}
	}


	public Customer GetByID(int customerID)
	{
		return this.Customers.AsQueryable().First(customer => customer.ID == customerID);
	}


        ...
        //Add/Update/Delete/GetAll

Add/Edit dialog

There are two main operations that are handled using JQuery dialog
* Add Customer
* Edit Customer

In both cases the same dialog is used, as well as the same content div and Update Panel

	<div id="divEditCustomerDlgContainer">	
			<div id="divEditCustomer" style="display:none">
					
				<asp:UpdatePanel ID="upnlEditCustomer" runat="server">
					<ContentTemplate>
						<asp:PlaceHolder ID="phrEditCustomer" runat="server">
							<table cellpadding="3" cellspacing="1">
							<tr>
								<td>
									*First Name:
								</td>
								<td>
									<asp:TextBox ID="txtFirstName" Columns="40" MaxLength="50" runat="server" />
								        ...
								</td>
							</tr>
							<tr>
								<td>
									*Last Name:
								</td>
								...
							</tr>
							<tr>
								<td colspan="2" align="right">
									<asp:Button ID="btnSave" onclick="btnSave_Click" Text="Save" runat="server" />
									<asp:Button ID="btnCancel" onclick="btnCancel_Click" onClientClick="closeDialog()" CausesValidation="false" Text="Cancel" runat="server" />
								</td>
							</tr>
							</table>
								
						</asp:PlaceHolder>
					</ContentTemplate>
							
				</asp:UpdatePanel>
			
			</div>
		</div>	<!-- divEditCustomerDlgContainer -->

You may notice that there are two divs around UpdatePanel – divEditCustomerDlgContainer and divEditCustomer. The reason for that is that when JQuery dialog opens – it is added after FORM tag in dom. That disables any ASP.NET submits and server side validation. The issue is described here:
http://www.trentjones.net/index.php/2009/03/jqueryui-dialog-with-aspnet-empty-post-values

To fix the issue I added a handler for JQuery Dialog open event that takes JQuery dialog DOM object and inserts it as a child of “divEditCustomerDlgContaine”. Note that JQuery dialog references divEditCustomer.

	<script type="text/javascript">
		$(document).ready(function() {
			$("#divEditCustomer").dialog({
				autoOpen: false,
				modal: true,
				minHeight: 20,
				height: 'auto',
				width: 'auto',
				resizable: false,
				open: function(event, ui) {
					$(this).parent().appendTo("#divEditCustomerDlgContainer");    //This is where JQuery dialog is added to DlgContainer
				},
			});
		});
     ...

When user clicks on Add/Edit customer links the following steps are followed:
1. JQuery dialog is opened
2. Content of dialog is overlayed with AJAX indicator and white background – BlockUI is used for that purpose
3. AJAX call is made that triggers refresh of upnlEditContent
4. On server side – dialog content is cleared for adding or loaded for editing.
5. Server side uses RegisterStartupScript to trigger a javascript call to UnlockDialog
6. Dialog is unlocked and available to user for editing

With this approach user sees the dialog right away and indicator tells him that data is being loaded. In most cases it should be pretty fast from click to being able to edit.

Positioning Dalog

You may notice that dialog appears to the right of the link that you click on. For that JQuery position() functionality is used:

	function openDialog(title, linkID) {
	
		var pos = $("#" + linkID).position();
		var top = pos.top;
		var left = pos.left + $("#" + linkID).width() + 10;
		
		
		$("#divEditCustomer").dialog("option", "title", title);
		$("#divEditCustomer").dialog("option", "position", [left, top]);
		
		$("#divEditCustomer").dialog('open');
	}

linkID is the ClientID of the link. Using the position() u can display dialog anywhere on the page or in relation to another control.

Running Client Side code using RegisterStartupScript in Async

To run some javascript script when response comes back from asynchronous request – function ScriptManager.RegisterStartupScript can be used.

The issue is that the first parameter in:
RegisterStartupScript Method (Control, Type, String, String, Boolean)

must be a control in update panel that will be rendered on the current request. In the “List Customers” page there are two update panels -“upnlEditCustomers” and “upnlCustomers” (for grid view).
The first panel is always updated on any async request (UpdateMode=”Always” by default). Originally I though to make it conditional which would be more efficient, but there were some issues to resolve and I decided it wasn’t worth the trouble in this case. As for “upnlCustomers” – we definitely don’t want that re-rendered on every async request – like opening/canceling the dialog, so this one is set to update conditionally.

To get RegisterStartupScript to work without worrying that triggering conrol is rendered in current request, I added another UpdatePanel whose sole purpose is to facilitate JavaScript calls from Async requests. This panel has an empty placeholder that’s used as a control in RegisterStartupScript:


	<asp:UpdatePanel ID="upnlJsRunner" UpdateMode="Always" runat="server">
		<ContentTemplate>
			<asp:PlaceHolder ID="phrJsRunner" runat="server"></asp:PlaceHolder>
		</ContentTemplate>
	</asp:UpdatePanel>

On server side there is a function RegisterStartupScript(key, script) that uses update panel above:

	private void RegisterStartupScript(string key, string script)
	{
		ScriptManager.RegisterStartupScript(phrJsRunner, phrJsRunner.GetType(), key, script, true);
	}

        private void TriggerClientGridRefresh()
	{
		string script = "__doPostBack(\"" + btnRefreshGrid.ClientID + "\", \"\");";
		RegisterStartupScript("jsGridRefresh", script);   //Trigger async grid refresh on Save
	}

	private void HideEditCustomer()
	{
		ClearEditCustomerForm();
		RegisterStartupScript("jsCloseDialg", "closeDialog();");    //Close dialog on client side
	}

Clear data in dialog

When user clicks on “Add Customer” – data in dialog is cleared and all validators are reset to Valid. For this I wrote a function that loops through all the controls in dialog and then performs a certain action on the controls of certain type:

	private void ClearEditCustomerForm()
	{
		//Empty out text boxes
		var textBoxes=new List<Control>();
		FindControlsOfType(this.phrEditCustomer, typeof(TextBox), textBoxes);
		
		foreach (TextBox textBox in textBoxes)
			textBox.Text = "";

		//Clear validators
		var validators=new List<Control>();
		FindControlsOfType(this.phrEditCustomer, typeof(BaseValidator), validators);
	
		foreach (BaseValidator validator in validators)
			validator.IsValid = true;
	}


	static public void FindControlsOfType(Control root, Type controlType, List<Control> list)
	{
		if (root.GetType() == controlType || root.GetType().IsSubclassOf(controlType))
		{
			list.Add(root);
		}

		//Skip input controls
		if (!root.HasControls())
			return;

		foreach (Control control in root.Controls)
		{
			FindControlsOfType(control, controlType, list);
		}
	}

Right now it only handles TextBoxes and Validators, but it can be easily expanded to support Text Areas, Drop Downs, etc…

JQuery Includes

This sample doesn’t include the files for JQuery UI and JQuery base library. Instead those are referenced at the google AJAX CDN.
The approach for including JQuery UI from Google AJAX CDN is covered here: http://encosia.com/2009/10/11/do-you-know-about-this-undocumented-google-cdn-feature/

<link href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.7.2/themes/start/jquery-ui.css" type="text/css" rel="Stylesheet" />
	
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.0/jquery.min.js"></script>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.7.2/jquery-ui.min.js"></script>

This makes the application and code download a lot smaller (a lot less files), so it is great for sample projects.

Advertisements

21 Comments »

  1. Thanks but the example doesn’t work

    Comment by yasserzaid — March 13, 2010 @ 8:18 am | Reply

    • Do you have problems building it or are you getting a run-time error?

      I checked both sample and download and it works fine for me.

      Comment by Eric P — March 15, 2010 @ 4:20 am | Reply

  2. Default.aspx.cs can’t find the CustomerService class.

    Comment by bwilemon — March 23, 2010 @ 1:32 pm | Reply

  3. Worked fine if i created an Ajax Website project!

    Comment by bwilemon — March 23, 2010 @ 2:47 pm | Reply

  4. Dear sir no the project doesn’t work for me 😦

    Comment by yasserzaid — March 24, 2010 @ 4:19 am | Reply

  5. Hi Eric
    Great example to work with and no problems running it. However when I started applying with my project found some issues that I wanna discuss.
    1) The document.ready function syntax is not right observed in IE8, the charm dosen’t work 🙂 In your code document.ready the comma down there is causing problem removing this makes it all ok
    open: function(event, ui) {
    $(this).parent().appendTo(“#divEditCustomerDlgContainer”);
    },

    2) The other thing I cant fix is the blue title bar of dialog, it is not extended to the whole div’s width. Please hava look in IE8 may be you can fix it

    But in the end thanks for sharing a beautiful example with all of us, regards from
    Emad (http://www.syedemad.info)

    Comment by syedemad — April 29, 2010 @ 9:26 am | Reply

    • Hi Syedemd,
      Your comment saved my life. Thanks a lot(That extra comma)

      Hi eric,

      Thank you for nice article.

      Comment by karsach — November 1, 2011 @ 5:02 pm | Reply

  6. Hi Eric found the Title bar problem with IE its due to the ‘auto’ in width property of document.ready function, giving some fixed value working fine

    Comment by syedemad — April 29, 2010 @ 9:51 am | Reply

  7. hello ,
    i have tried to run the example you have uploaded,but it is giving me the error ‘object does not support this’
    i m using IE7

    Comment by saira123 — May 3, 2010 @ 3:48 pm | Reply

  8. Excellent article and a tremendous help. Thanks!

    Comment by Charles J Scally III — September 5, 2011 @ 1:41 pm | Reply

  9. I am having hard time putting the dropdownlist control with add and edit template.
    It always looses it state at the time it validates. Tried bunch of different mechanisms but didn’t seem to be working .

    any help will be helpful!

    Thanks

    Comment by Tarun Yadav (@taruny) — October 4, 2011 @ 12:19 pm | Reply

  10. I downloaded the code sample and manage to get it working however for reasons unknown to me, the “Add Customer” link button is not been displayed, any ideas why this is happening

    Comment by ziekmain — November 2, 2011 @ 10:15 am | Reply

  11. There is a bug regarding the ID part.
    From the demo try this,
    Delete the first record, then add new record. Now delete the first record.
    Both records will be gone. The reason for this is because the ID for both record is ‘2’. When you look at the delete function in the service class, you use removeall function and you passed in the ID, so it will remove all the record with ID = ‘2’, which in this case both records have the same ID = 2.

    My suggestions is that, you should try to use ‘Guid’ datatype instead of Integer, so that the record in the gridview will always be unique.

    Comment by suwandichandra — November 7, 2011 @ 12:49 am | Reply

  12. HI, I need some help with the positioning of the dialog box. I set it to center but on initial open of the dialog box the window isn’t centered but anytime after until I refresh it’ll be centered.

    Comment by Mathieu Blondin — March 21, 2012 @ 11:21 am | Reply

  13. Great jQuery gridView example!!! I’ve tried adding a jQuery date picker $(“#txtDateOfBirth”).datepicker(); and Ajax calendar extender to the panel but I can’t get either to work. I even adjusted the z-index with no success. Do you have any suggestion on how to incorporate a date picker in your modal? Thanks,

    Comment by stan1826 — June 10, 2012 @ 12:07 pm | Reply

  14. Hey this was some nice coding — thank you! Never would have thought of it myself. Some things I noticed — the validators must run on the server (as you’ve shown). If not they go off when they’re hidden. I enable and disable them just for good measure! You can use the jquery button to make the form look better, using AJAX pageload of jquery document.ready. Anyway, I just wanted to say “thanks!”. And thanks for the BlockUI tip. All works like a charm!

    Comment by George Beier — July 20, 2012 @ 11:48 pm | Reply

  15. Oh and there’s a glitch in the blockui with the “Wait” cursor not going away until you move the cursor. There’s a work around — http://gojooz.blogspot.com/2010/01/fwd-jquery-blockui-bug-with-internet.html. Just add a few lines to the blockui script.

    Comment by George Beier — July 25, 2012 @ 3:23 pm | Reply

  16. Anyone tried to load this in a usercontrol and display it over a page which has a master page? I have been researching on this and have been reading about ScriptManagerProxy but not sure where will Java Script reside and how to invoke ScriptManagerProxy …Any ideas???

    Comment by Rahman Ray — August 23, 2012 @ 12:27 pm | Reply

  17. This really helped me.. Thanks!

    Comment by Robert Dyson — October 11, 2012 @ 4:36 am | Reply

  18. Eric thanks for the post. It works fine for me in my project after a couple of tweaks. The comma is required (ref other posts.) It did not work first go for me because in my project I was using a master page with update panels. This causes the ClientID of controls to be different to the ServerID. I fixed the reference to the controls to be the clientID and it worked great. Also had a problem with JQuery versions installed and loaded and referenced. Once I sorted that out it worked. Thanks again. The MSIE developer tools, Firebug and Chrome Dev tools also helped to debug at various stages.

    Comment by Cameron Townshend — October 17, 2012 @ 5:31 pm | Reply

  19. excellent article, it helped me very much 🙂

    Comment by Kamal Al-Seisy (@KamalAlseisy) — February 10, 2013 @ 9:49 am | Reply


RSS feed for comments on this post. TrackBack URI

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Blog at WordPress.com.

%d bloggers like this: