Confirm GridView Deletes with the ModalPopupExtender

I was looking for good examples of how the ModalPopupExtender control could be used as a confirmation dialog.  I was especially curious in seeing implementations where the popup is used to confirm deletes performed on rows of a GridView.  I couldn't find any good samples so I figured I would take a shot at it. 

Live Demo

If you would like to view a running version of the sample, just follow the Live Demo link.  If don't really care about the step by step break down of this implementation you can jump to the bottom of the page to view the full code sample.  As always, feedback is encouraged.   

 

Step 1: Add the GridView to your page, placing it inside an UpdatePanel. 

<asp:UpdatePanel ID="updatePanel" runat="server" UpdateMode="Conditional">
    <ContentTemplate>            
        <asp:Label ID="lblTitle" runat="server" Text="ToDo List" BackColor="lightblue" Width="95%" />
        <asp:GridView 
            ID="gvToDoList" runat="server" AutoGenerateColumns="false" Width="95%">
            <AlternatingRowStyle BackColor="aliceBlue" />
            <HeaderStyle HorizontalAlign="Left" />
            <Columns>
                <asp:BoundField DataField="ID" HeaderText="ID" />
                <asp:BoundField DataField="Item" HeaderText="Description" />
                <asp:BoundField DataField="IsCompleted" HeaderText="Complete?" />                     
            </Columns>                    
        </asp:GridView>
    </ContentTemplate>
</asp:UpdatePanel>  

Nothing out of the ordinary here, just a basic GridView contained in an UpdatePanel (don't forget to set UpdateMode to Conditional)

 

Step 2: Add a TemplateField with a Delete button.  Wire up an event handler for the button click.
<asp:TemplateField ControlStyle-Width="50px" HeaderStyle-Width="60px" ItemStyle-HorizontalAlign="Center">
    <ItemTemplate>
        <asp:Button ID="btnDelete" runat="server" OnClick="BtnDelete_Click" Text="Delete" />
    </ItemTemplate>
</asp:TemplateField>      

You can either explicitly use the OnClick event of the delete button or add the CommandName attribute and implement the RowDeleting event.  I chose the former for this sample.

* I also thought about adding the ModalPopupExtender embedded in each of the ItemTemplates and having the OK button just be the delete command for the row.  I didn't go down this path because doing so would have included all of the popup markup for each row in the grid.  Usual page size's for our gridview is anywhere from 25 to 50 rows.  So this didn't seem like a good approach.

 

Step 3: Add the HTML markup for the modal popup.

<div id="div" runat="server" align="center" class="confirm" style="display:none">
    <img align="absmiddle" src="Img/warning.jpg" />Are you sure you want to delete this item?
    <asp:Button ID="btnOk" runat="server" Text="Yes" Width="50px" />
    <asp:Button ID="btnNo" runat="server" Text="No" Width="50px" />
</div> 

Nothing fancy here, just a div with a couple of buttons and a warning image:

   

* I have explicitly set the style to none on my container elements when using the ModalPopupExtender to avoid the initial flicker that sometimes occurs.  Has anyone else seen this?

 

Step 4: Add the ModalPopupExtender to the page. 
<ajaxToolKit:ModalPopupExtender 
    runat="server" BehaviorID="mdlPopup" 
    TargetControlID="div" PopupControlID="div" 
    OkControlID="btnOk" CancelControlID="btnNo" BackgroundCssClass="modalBackground" 
/>

Once the ModalPopupExtender has been added to the page, you can go ahead and configure its attributes.  The PopupControlID is the id of the div we created in Step 3 that contains the markup we want displayed by the ModalPopup.  The OkControlID refers to the ID of the OK button and the CancelControlID refers to the Cancel button (clicking either will dismiss the popup). 

In most cases the TargetControlID would point to the button that causes the ModalPopup to be displayed.  But for this example, we will always be hiding and showing the popup explicitly from javascript, we can just point this property to our div (this is a required property so we have to set it to something.  Some people recommend putting a hidden button or some other element on the page to fake it out, but here I am just setting it to our div).

* I have noticed that the ModalPopup will not be automatically dismissed if it is explicitly displayed using the show method of the animation.  Does anyone know if this is a bug or the desired behavior?

 

Step 5: Add an OnClientClick for the delete button
<asp:TemplateField ControlStyle-Width="50px" HeaderStyle-Width="60px" ItemStyle-HorizontalAlign="Center">
    <ItemTemplate>
        <asp:Button 
            ID="btnDelete" runat="server" OnClientClick="showConfirm(this); return false;" 
            OnClick="BtnDelete_Click" Text="Delete" />
    </ItemTemplate>
</asp:TemplateField>    

Next we go back to the markup for our delete button and add the OnClientClick attribute.  For the value, we invoke the showConfirm javascript function (created in the next step) passing it a reference to the delete button element.  The OnClientClick handler also returns false so the page won't postback after the client script is done running.  If this isn't done the delete will happen before the user has a chance to confirm or disallow it!

 

Step 6: Add the supporting javascript
<script type="text/javascript">
    //  keeps track of the delete button for the row
    //  that is going to be removed
    var _source;
    // keep track of the popup div
    var _popup;
    
    function showConfirm(source){
        this._source = source;
        this._popup = $find('mdlPopup');
        
        //  find the confirm ModalPopup and show it    
        this._popup.show();
    }
    
    function okClick(){
        //  find the confirm ModalPopup and hide it    
        this._popup.hide();
        //  use the cached button as the postback source
        __doPostBack(this._source.name, '');
    }
    
    function cancelClick(){
        //  find the confirm ModalPopup and hide it 
        this._popup.hide();
        //  clear the event source
        this._source = null;
        this._popup = null;
    }
</script>
<ajaxToolKit:ModalPopupExtender BehaviorID="mdlPopup" runat="server" 
    TargetControlID="div" PopupControlID="div" 
    OkControlID="btnOk" OnOkScript="okClick();" 
    CancelControlID="btnNo" OnCancelScript="cancelClick();" BackgroundCssClass="modalBackground" />

Now we can implement the final piece.  In the previous step, we wired the OnClientClick event of the delete button to the showConfirm javascript function.  Now we can implement this function.  First we store a reference to the delete button as well as display the modal popup by calling show() on the modal popups animation component (note: in ASP.NET AJAX, DOM elements are retrieved using $get('id') and components are retrieved using $find('id')).  The reference to the delete button is stored so we can use it later to initiate the postback (if the user did indeed confirm the delete).  

Finally, the ModalPopupExtender contains two properties that allow you to hook into the OK and Cancel buttons and run a little bit of code on the client when the dialog is dismissed.  We will hook into both of these events to hide the dialog as well as start the postback causing the item to be deleted if the delete was confirmed.

 

Step 7: Put it all together. 

Here is the complete listing for this page.  Again, you can view this page in action here.

Enjoy!

 

<%@ Page Language="C#" %>
<%@ Register Assembly="AjaxControlToolkit" Namespace="AjaxControlToolkit" TagPrefix="ajaxToolkit" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head id="head" runat="server">
    <title>Delete Confirm Example</title>
    <script runat="server">
        /// <summary>
        /// 
        /// </summary>
        public class ToDo
        {
            private int _id;
            private string _item;
            private bool _isCompleted;

            public ToDo(int id, string item, bool isCompleted)
            {
                this._id = id;
                this._item = item;
                this._isCompleted = isCompleted;
            }

            public int ID
            {
                get { return this._id; }
            }
            
            public string Item
            {
                get { return this._item; }
            }

            public bool IsCompleted
            {
                get { return this._isCompleted; }
            }
        }

        /// <summary>
        /// 
        /// </summary>
        private System.Collections.Generic.List<ToDo> ToDoList
        {
            get 
            {
                System.Collections.Generic.List<ToDo> item = this.Session["ToDoList"] as System.Collections.Generic.List<ToDo>;
                
                if (item == null)
                {
                    item = new System.Collections.Generic.List<ToDo>();
                    item.Add(new ToDo(1, "Go to the store", false));
                    item.Add(new ToDo(2, "Go to work", true));
                    item.Add(new ToDo(3, "Feed the dog", false));
                    item.Add(new ToDo(4, "Take a nap", true));
                    item.Add(new ToDo(5, "Eat some lunch", false));

                    this.Session["ToDoList"] = item;
                }

                return item; 
            }
        }
        
        /// <summary>
        /// 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected void Page_Load(object sender, EventArgs e)
        {
            if (!this.IsPostBack)
            {
                this.gvToDoList.DataSource = this.ToDoList;
                this.gvToDoList.DataBind();
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected void BtnDelete_Click(object sender, EventArgs e)
        {
            //  get the gridviewrow from the sender so we can get the datakey we need
            Button btnDelete = sender as Button;
            GridViewRow row = (GridViewRow)btnDelete.NamingContainer;
            //  find the item and remove it
            ToDo itemToRemove = this.ToDoList[row.RowIndex];
            this.ToDoList.Remove(itemToRemove);
            //  rebind the datasource
            this.gvToDoList.DataSource = this.ToDoList;
            this.gvToDoList.DataBind();
        }
    
    </script>
    <script type="text/javascript">
        //  keeps track of the delete button for the row
        //  that is going to be removed
        var _source;
        // keep track of the popup div
        var _popup;
        
        function showConfirm(source){
            this._source = source;
            this._popup = $find('mdlPopup');
            
            //  find the confirm ModalPopup and show it    
            this._popup.show();
        }
        
        function okClick(){
            //  find the confirm ModalPopup and hide it    
            this._popup.hide();
            //  use the cached button as the postback source
            __doPostBack(this._source.name, '');
        }
        
        function cancelClick(){
            //  find the confirm ModalPopup and hide it 
            this._popup.hide();
            //  clear the event source
            this._source = null;
            this._popup = null;
        }
    </script>
    <style>
        .modalBackground {
            background-color:Gray;
            filter:alpha(opacity=70);
            opacity:0.7;
        }
        .confirm{
           background-color:White;
           padding:10px;
           width:370px;
        }
    </style>
</head>
<body>
    <form id="form" runat="server" style="font-family:Trebuchet MS;">
        <asp:ScriptManager ID="scriptManager" runat="server" />
        <div>
            <p style="background-color:AliceBlue; width:95%">
                Example of using a ModalPopupExtender as a delete confirm button<br />
                for the indivdual rows of a GridView.  To test out the functionality,<br />
                click the Delete button of any of the rows and watch what happens.<br />
            </p>
            <br />
            <asp:UpdatePanel ID="updatePanel" runat="server" UpdateMode="Conditional">
                <ContentTemplate>            
                    <asp:Label ID="lblTitle" runat="server" Text="ToDo List" BackColor="lightblue" Width="95%" />
                    <asp:GridView 
                        ID="gvToDoList" runat="server" AutoGenerateColumns="false" Width="95%">
                        <AlternatingRowStyle BackColor="aliceBlue" />
                        <HeaderStyle HorizontalAlign="Left" />
                        <Columns>
                            <asp:BoundField DataField="ID" HeaderText="ID" />
                            <asp:BoundField DataField="Item" HeaderText="Description" />
                            <asp:BoundField DataField="IsCompleted" HeaderText="Complete?" />
                            <asp:TemplateField ControlStyle-Width="50px" HeaderStyle-Width="60px" ItemStyle-HorizontalAlign="Center">
                                <ItemTemplate>
                                    <asp:Button 
                                        ID="btnDelete" runat="server" OnClientClick="showConfirm(this); return false;" 
                                        OnClick="BtnDelete_Click" Text="Delete" />
                                </ItemTemplate>
                            </asp:TemplateField>                            
                        </Columns>                    
                    </asp:GridView>
                </ContentTemplate>
            </asp:UpdatePanel>    
            <ajaxToolKit:ModalPopupExtender BehaviorID="mdlPopup" runat="server" 
                TargetControlID="div" PopupControlID="div" 
                OkControlID="btnOk" OnOkScript="okClick();" 
                CancelControlID="btnNo" OnCancelScript="cancelClick();" BackgroundCssClass="modalBackground" />
            <div id="div" runat="server" align="center" class="confirm" style="display:none">
                <img align="absmiddle" src="Img/warning.jpg" />Are you sure you want to delete this item?
                <asp:Button ID="btnOk" runat="server" Text="Yes" Width="50px" />
                <asp:Button ID="btnNo" runat="server" Text="No" Width="50px" />
            </div> 
        </div>
    </form>
</body>
</html>
 

TrackBack

TrackBack URL for this entry:
http://mattberseth.com/blog-mt/mt-tb.fcgi/43

Listed below are links to weblogs that reference Confirm GridView Deletes with the ModalPopupExtender:

» Confirm GridView Deletes with the ModalPopupExtender from DotNetKicks.com
You've been kicked (a good thing) - Trackback from DotNetKicks.com [Read More]

» Pertinent comments on several modal popup posts on here. from ASP.NET AJAX Toolkit Forum Posts
Here is the most recent: http://forums.asp.net/t/1249093.aspx This is an excerpt of a response to this [Read More]

Comments


Posted by: Ryan on July 23, 2007 12:00 AM

Matt - this is a very clever trick. In case youve embedded any validator controls in the GridView, just change your okClick function to the below code and take advantage of client-side validation!

The experience from the standpoint of the user will remain the same due to the UpdatePanel; however, youll save the round trip to the server.

function okClick(){
if (typeof(Page_ClientValidate) == function){
Page_ClientValidate();
if (Page_IsValid){
__doPostBack(this._source.name, );
}
return Page_IsValid;
} else {
__doPostBack(this._source.name, );
return true;
}
}

Posted by: daniel on August 28, 2007 12:00 AM

Hi Matt,
trying to use your sample, the thing is that the BtnDelete_Click erver side event is invoked simultaneously with the client event when the delete button is clicked so the delete operation is executed while the user havent confirmed yet on the popup dialog

Posted by: Kevin Humfreville on September 6, 2007 12:00 AM

I just wanted to say thanks for this great tutorial and workaround. It took quite a bit of searching to find, but its exactly what Ive been pulling my hair out over for the past week or two. Thanks a bunch and keep the good stuff coming! :)

Posted by: Jero on September 12, 2007 12:00 AM

Exelent!!

Thanks for this tutorial Matt.

Posted by: Joaquim on September 13, 2007 12:00 AM

Hello.
First thanks.
I have proven east example with IE 7.0.5730.11 and it does not work correctly, always delete the registry.
With FireFox 2.0.0.6 yes it works correctly.
Configurating IE.?
Greetings.

Pardon I do not speak English.

With FireFox 2.0.0.6 yes it works correctly.


Hay to form something of IE.?


Greetings.

Posted by: Michael on September 15, 2007 12:00 AM

Thanks, this works great.

Just one warning to people. It does not work with a LinkButton. I changed it to a LinkButton and didnt test it til a later time. I couldnt figure out why it wasnt working anymore.

Posted by: Brian on September 20, 2007 12:00 AM

Unreal article. This has been driving me crazy for a day. You would think the ModalPopupExtender could be used more easily in a damn GridView row, but of course, no.

Thanks man!

Posted by: Erick on September 28, 2007 12:00 AM

Hello Matt

I use this script to delete a item from my GridView:

function ConfirmDelete(sName) {
var sMsg = Are you sure you want to delete " + sName + ?";
return (confirm(sMsg));}

Codebehind :

Private Sub dtgrequisitions_ItemDataBound(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.DataGridItemEventArgs) Handles dtgrequisitions.ItemDataBound

Dim oType As ListItemType = CType(e.Item.ItemType, ListItemType)
If oType = ListItemType.Item _
Or oType = ListItemType.AlternatingItem Then

Dim sName As String = e.Item.Cells(1).Text

Dim oCtrl As LinkButton _
= CType(e.Item.FindControl("cmdDel"), LinkButton)

oCtrl.Attributes.Add("onclick", _
"return ConfirmDelete(" + sName + ");")

End Sub

Private Sub dtgrequisitions_ItemCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridCommandEventArgs) Handles dtgrequisitions.ItemCommand

.

ElseIf (e.CommandName = "Delete") Then
.

End if


How can i swith to your way ? I tried but not worked

Posted by: Jim on October 25, 2007 12:00 AM

Matt -- nice post, works great.

I added a CommandArgument to the delete button (btnDelete) which was the primary key of my datarow. e.g. CommandArgument= .

This made my postback delete function look like:
{
Button btn = (Button) object;
SomeDeleteFunction( btn.CommandArgument );
}

Posted by: micenote on November 1, 2007 12:00 AM

is very cool
????

Hi,
I used the above code in a ContentPlaceHolder control, all the JavaScript is written in the head section of the MasterPage.

The JavaScript throws a ArgumentNULLException "Value cannot be NULL Parameter Element.

Any Ideas

Posted by: Gersy on November 11, 2007 12:00 AM

Hi there, thank you , this is good cod3e but it works well with pages with no master page,
i tried to use it with master pages and it goes ok in Firfox but in IE6 it display the alert in the left top of the page and nothing appeared from it exept small pixels !!! so any update plz reply to my mail if you want,
mahmoud.ramzy@hotmail.com
thanks in advance.

Posted by: melih on January 17, 2008 12:00 AM

You said:
I also thought about adding the ModalPopupExtender embedded in each of the ItemTemplates and having the OK button just be the delete command for the row. I didnt go down this path because doing so would have included all of the popup markup for each row in the grid. Usual page sizes for our gridview is anywhere from 25 to 50 rows. So this didnt seem like a good approach.
This holds for gridview but by using ListView I could not see any drawback to use this way (offcourse confirm panel should be placed outside of ListView).

While the link button doesnt work, the image button does!

Posted by: Andy on March 15, 2008 12:00 AM

To get the LinkButton to work, you need to postback the proper control name. Easiest way is like this in okClick():
__doPostBack(this._source.id.replace(/_/g,$), );

This uses the generated ClientID (which delimits using underscores by default) and replaces the delimiter with $ (which is the expected name).

Someone else can probably explain this better, but it works.

Posted by: JeDa on March 19, 2008 12:00 AM

I just want to say Thanks!

Posted by: Gersy on March 23, 2008 12:00 AM

Hello Matt , this is great post
but I got that error when I press on yes button

/********/

Sys.WebForms.PageRequestManagerServerErrorException : An unknown error occured while processing the request on the server. the status code returned from the server was: 500

/********/

can you help me ??

Posted by: Rodrigo on April 2, 2008 12:00 AM

Hi Matt,

Excellent post! Thanks for such great contribution to the ASP.NET community :)

Posted by: Anonymous on May 5, 2008 12:00 AM

Perfect article, thanks...

Posted by: andreas on May 13, 2008 12:00 AM

Perfect!!! thanks.

Ive been looking for this solution for hours; tried many solutions, but this is the one that works! Thanks Matt :)

Posted by: Luigi on June 26, 2008 12:00 AM

Thanks Andy for the the LinkButton code....works like a charm.

To use with a MasterPage, you need to get the id of the ModalPopupExtender from the actual source code generated for the page. For example, Im using this on a nested MasterPage, so the id of my ModalPopupExtender is ctl00_ctl00_ContentPlaceHolder1_ContentPlaceHolder1_ModalPopupExtender1. Make that change in the script for this._popup = $find(...). Get the id by viewing the page source.

Thank you, thank you, thank you. This code rocks.

Posted by: ali on July 11, 2008 12:00 AM

Great article and good work.............

This code is simply superb......

Posted by: Cherukuri on July 11, 2008 12:00 AM

Great Work

Great!!
But when I am using it with command Argument in Imagebutton of Gridview,its working but also delete the row parallel without getting confirmation . I am also using master page. Please help me in this situation to tanushree_sahay@rediffmail.com

Posted by: Yazeed Adams on August 19, 2008 08:46 AM

Brilliant Code and very well explained


Work beautifully.

Posted by: Yasmine on August 31, 2008 10:45 AM

Great post it helped me alot, I was trying to use the modalpopupextender to do the same thing but I failed.

Thanks

Posted by: jolly on September 2, 2008 04:16 AM

But when PopExtender is avtice then Background page can be accessed using Tab Key.
Please suggest the soln for this.

Posted by: sulfur_scratch on September 24, 2008 12:35 PM

Niffty article, but like so many others out there, it does not present an implementation that is truely analogous to the classic javascript confirm function (i.e. 'blocks thread' until user makes selection, ACTUALLY returns a bool value)....sigh...

Post a comment

(If you haven't left a comment here before, you may need to be approved by the site owner before your comment will appear. Until then, it won't appear on the entry. Thanks for waiting.)

Consulting Services

Yep - I also offer consulting services. And heck, I'll do just about anything. If you enjoy my blog just drop me an email describing the work you need done.

Recent Comments

  • sulfur_scratch wrote: Niffty article, but like so many others out there, it does not present an implementation that is tru...
  • jolly wrote: But when PopExtender is avtice then Background page can be accessed using Tab Key. Please suggest th...
  • Yasmine wrote: Great post it helped me alot, I was trying to use the modalpopupextender to do the same thing but I ...
  • Yazeed Adams wrote: Brilliant Code and very well explained Work beautifully....
  • Tanushree wrote: Great!! But when I am using it with command Argument in Imagebutton of Gridview,its working but als...
  • ali wrote: Great article and good work............. This code is simply superb...... ...
  • Cherukuri wrote: Great Work ...
  • Luigi wrote: Thanks Andy for the the LinkButton code....works like a charm. To use with a MasterPage, you need ...