Tuesday, September 4, 2012

ASP.NET 4.0 : Using a Modal Popup Box in a Gridview as an Inputbox delete confirm

So here’s our scenario,  We have a GridView with a row command that will delete a record from the database.  However, since we NEVER, ever, ever actually delete data from a database, we will give the user an opportunity to tell us WHY they wanted to delete the record.  Then we will mark the record inactive and add the user’s comments. 
Oh sure easy right?  Well, most developers will just put an ItemTemplate on the Grid and then simply add the ModalPopupExtender targeting an asp button.  Why do this?  Well because it is an easy shortcut.  Well until you want to know what record to look up.
So, with some slight of hand, we’re going to do it and keep the Grid object’s structure in place so that we can utilize the functionality already given to us (for things like datakeynames).
Nay, you say? Well lets just dive in and see…

First, we’ll need to drag out our old trusty dusty friend.  That’s right, I’m talking about the Northwind database.  You are now on an imaginary adventure of owning the Northwind Company that sells things like Tofu (although any database with data in it will work with a few modifications to the code below, you’ll be totally messing up my vibe for the Northwind background imagery thing though).  As the owner, you (of course) are interested in all orders and want to know why in the world someone would want to cancel their order for your delicious Tofu.  This is going to take the entire collective help of your developer team.  Nay, just kidding, with a few lines of code, we’ll get it done….

So once you have secured the database, which can be downloaded here.  We need to make a few modifications to it.  So we can mark things inactive instead of deleting them.  We need to add a field called ‘active’ as a bit value type with a default value of 1.  Why? Well commonly a value of 0 (or zero) is typically referred to as false (or off) and a value of 1 (one) is typically referred to as true (or on).  So we can assume that unless we change the value to a value of 0, then it is an active order:
Right click the table dbo.Orders | Left click Design.  You should see something similar:
image

Now under ShipCountry add Active with a bit DataType and to not allow NULL (Uncheckmark the Allow Nulls checkbox).  Finally, go down into the Column Properties and for Default Value or Binding give it a value of 1.
image

Additionally, we’ll need a column that will hold the comment on WHY we decided to delete the order.  Add a Comments column and give it a Data Type of varchar(250) or varchar(max) if your users are long winded.
image

 

Save it by closing the table.  If you get that STUPID error saying it can not save the table, take a look in your Tools | Options | Designers | Tables and Database Designers.  Uncheckmark that Prevent saving changes that require table re-creation checkbox.
image

 

Stupid fricken Microsoft.
Now because we can not write a data driven website without knowing what data we are getting we are going to move into the SP’s.  Rather than rewrite the given SP’s, I’m going to write new ones because you might need the original down the road in some hippy class thing you decided to donate money to or something… So, let’s decide what we need.  We need an SP that will delete (mark the order inactive) and also add our comments.  We also need an SP that will show us all our active orders.
So our select stored procedure will look like:

Use [Northwind]
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Author: Chad Carter
-- Create date: 9/4/2012
-- Description: Retrieves all active orders
-- =============================================
CREATE PROCEDURE dbo.usp_orders_all_active
AS
BEGIN
SET NOCOUNT ON
;
SELECT
o.OrderID
, o.OrderDate
, o.ShippedDate
, c.CompanyName
, c.ContactName
From
dbo.Orders o
Left Outer Join dbo.Customers c
On o.CustomerID = c.CustomerID
Where
o.Active = 1
END
GO


Pretty simple select statement.  Now let’s move on to our Update (Delete) statement.



Use [Northwind]
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Author: Chad Carter
-- Create date: 9/4/2012
-- Description: InActivates an order with comments
-- =============================================
CREATE PROCEDURE dbo.usp_orders_delete_with_comments
-- Add the parameters for the stored procedure here
@OrderID Int
, @Comment varchar(250)
AS
BEGIN
SET NOCOUNT ON
;
Update
dbo.Orders
Set
Active = 0
, Comments = @Comment
Where
OrderID = @OrderID
END
GO

Again, we are not working Chinese Algebra here. 

At this point, we are done with the database and can put away SSMS.  Let’s fire up the ole VS2010 and start working on our site!

So I assume you know how to create a new website.  We’ll be using Visual Basic.Net (why?  Because it is my blog and I get to choose).


image



Now, we have a somewhat templated website.  This is both a blessing and a curse.  We need to delete some of the crap that Microsoft puts in here for us.  To be exact, on the Default.aspx delete out the following lines:



<p>
To learn more about ASP.NET visit <a href=http://www.asp.net
title="ASP.NET Website">www.asp.net</a>.
</p>
<
p>
You can also find
<a href="http://go.microsoft.com/fwlink/?LinkID=152368&amp;clcid=0x409"
title="MSDN ASP.NET Docs">documentation on ASP.NET at MSDN</a>.
</p>


We want that crap out of there so that we can make room for our Gridview. So just whack the crap out of it.

Now we need to go in the web.config and let the framework know which database we want to talk to:




<configuration>
<
connectionStrings>
<
add name="wia" connectionString="Server=SQLServer;
Database=Northwind;Trusted_Connection=True;"
providerName="System.Data.SqlClient"/>
</
connectionStrings>



Obviously, you will need to configure your own connectionString per your environment.  There are many examples online about how to do this, so I’m not going to waste anyone’s time going into it here when we could be programming.

Since we need to get the data before we can delete it, we always start at the Data Access Layer (or DAL).  Just for keeping a clean shop, lets go ahead and add what we need:


Right click your solution name in the Solution Explorer | Add ASP.Net Folder | App_Code.


Then Right click App_Code | New Folder | Name this one BLL.  Perform this step again and name is DAL.  Your structure should look similar to:


image



Right click the folder DAL | Add New Item | Class.  Name the class Orders.

Ok, we’ll need to get rid of that stupid Imports declaration and change it to what we need:




Imports System
Imports System.Data
Imports System.Data.SqlClient
Imports System.Web.Configuration.WebConfigurationManager

Public Class Orders

End Class


Now we need to Namespace our class so that we don’t get it confused between the BLL (Business Logic Layer) and the DAL (Data Access Layer).  This being the DAL, let’s Namespace it DAL:



Imports System
Imports System.Data
Imports System.Data.SqlClient
Imports System.Web.Configuration.WebConfigurationManager

Namespace DAL
Public Class Orders

End Class
End Namespace




Now when referring to this class, it will be DAL.Orders.  So finally, we need to write the code that will get our data.  As this is not an ADO.Net article, this article assumes you know what the following method is doing:




Imports System
Imports System.Data
Imports System.Data.SqlClient
Imports System.Web.Configuration.WebConfigurationManager

Namespace DAL
Public Class Orders
Public Function getActiveOrders() As DataSet
Dim ds As DataSet = New DataSet
Dim conn As SqlConnection = & _
New SqlConnection(ConnectionStrings("wia").ToString())
Dim cmd As SqlCommand = & _
New SqlCommand("dbo.usp_orders_all_active", conn)
cmd.CommandType = CommandType.StoredProcedure
Dim da As SqlDataAdapter = New SqlDataAdapter
da.SelectCommand = cmd
conn.Open()
da.Fill(ds)
conn.Close()
Return ds
End Function
End Class
End Namespace


This is our entire DAL.Orders class (thus far).  Now we need to move to the BLL.  Right click the BLL Folder in your Solution Explorer | Add New Item | Class.  Name the class Orders.

Get rid of that stupid templated Import declaration and replace it with:




Imports System.Data

Public Class Orders

End Class


Let’s namespace our class so that we can refer to it without it getting confused:



Imports System.Data

Namespace BLL
Public Class Orders

End Class
End Namespace


Now we need a function that calls our DAL.Orders.getActiveOrders method that returns a dataset and return that dataset to the UI.  We can accomplish this with 2 lines of code:



Imports System.Data

Namespace BLL
Public Class Orders
Public Function getActiveOrders() As DataSet
Dim ordersDAL As DAL.Orders = New DAL.Orders
Return ordersDAL.getActiveOrders()
End Function
End Class
End Namespace




Well now, we are ready to populate a Grid!  Except, we need to build one.  So jump on over to your UI in default.aspx.  Let’s add a simple Grid here:




<asp:Content ID="BodyContent" runat="server" 
ContentPlaceHolderID="MainContent">
<
h2>
Welcome to ASP.NET!
</h2>
<
asp:GridView ID="gdvOrders" runat="server" AutoGenerateColumns="false"
DataKeyNames="OrderID" enableviewstate="true"
EmptyDataText="There are no orders available to display."
ShowHeader="True" >
<
Columns>
<
asp:BoundField HeaderText="Order Date"
DataField="OrderDate" DataFormatString="{0:MM/dd/yyyy}" />
<
asp:BoundField HeaderText="Ship Date"
DataField="ShippedDate" DataFormatString="{0:MM/dd/yyyy}" />
<
asp:BoundField HeaderText="Company Name"
DataField="CompanyName" />
<
asp:BoundField HeaderText="Contact Name"
DataField="ContactName" />
</
Columns>
</
asp:GridView>
</
asp:Content>


Here you can see that I have a fairly basic GridView defined.  Of importance is the DataKeyNames property in the declaration.  Additionally, you can see that I formatted the dates in the BoundFields.  This is for readability purposes.  Finally, in order for this to work for us, we need to have VIEWSTATE enabled.  Now if we run this, we’ll receive a whole lot of ……… Nothing.  We need to go into our code-behind and create the databind for the grid.  So open the Default.aspx.vb out of your solution explorer.  This is done by clicking the + infront of the Default.aspx page.  First thing we need to do is create our griddatabind method like so:



Protected Sub griddatabind()
Dim ordersBLL As BLL.Orders = New BLL.Orders
gdvOrders.DataSource = ordersBLL.getActiveOrders()
gdvOrders.DataBind()
End Sub


This still doesn’t give us any data.  We need to tie it to an event.  So go to the Page Events Load, like so:



Protected Sub Page_Load(sender As Object, e As System.EventArgs) & _
Handles Me.Load
If Not (Page.IsPostBack) Then
griddatabind()
End If
End Sub


So your entire code-behind should look like:



Partial Class _Default
Inherits System.Web.UI.Page

Protected Sub griddatabind()
Dim ordersBLL As BLL.Orders = New BLL.Orders
gdvOrders.DataSource = ordersBLL.getActiveOrders()
gdvOrders.DataBind()
End Sub

Protected Sub
Page_Load(sender As Object, e As System.EventArgs) & _
Handles Me.Load
If Not (Page.IsPostBack) Then
griddatabind()
End If
End Sub
End Class


The reason we check to make sure it is not a postback is because the grid would databind during partial page postbacks, or ajax postbacks and it can disrupt what we are trying to accomplish.  Now when we run the code, we should get values:

image



 



Now we need to add a button to delete the record:



<asp:GridView ID="gdvOrders" runat="server" AutoGenerateColumns="false"
DataKeyNames="OrderID" enableviewstate="true"
EmptyDataText="There are no orders available to display."
ShowHeader="True" >
<
Columns>
<
asp:BoundField HeaderText="Order Date"
DataField="OrderDate" DataFormatString="{0:MM/dd/yyyy}" />
<
asp:BoundField HeaderText="Ship Date"
DataField="ShippedDate" DataFormatString="{0:MM/dd/yyyy}" />
<
asp:BoundField HeaderText="Company Name"
DataField="CompanyName" />
<
asp:BoundField HeaderText="Contact Name"
DataField="ContactName" />
<
asp:TemplateField>
<
ItemTemplate>
<
asp:LinkButton ID="lnkbtnDeleteRFP"
CommandName="cmdDeleteRFP" runat="server" Text="Delete" />
</
ItemTemplate>
</
asp:TemplateField>
</
Columns>
</
asp:GridView>


Which gives us:

image



Now what we need is some magic to allow us to confirm the delete.  We need to create our Modal Window.  Let’s create a Panel that will serve as the modal window:



<asp:Content ID="BodyContent" runat="server" 
ContentPlaceHolderID="MainContent">
<
h2>
Welcome to ASP.NET!
</h2>
<
asp:GridView ID="gdvOrders" runat="server" AutoGenerateColumns="false"
DataKeyNames="OrderID" enableviewstate="true"
EmptyDataText="There are no orders available to display."
ShowHeader="True" >
<
Columns>
<
asp:BoundField HeaderText="Order Date"
DataField="OrderDate" DataFormatString="{0:MM/dd/yyyy}" />
<
asp:BoundField HeaderText="Ship Date"
DataField="ShippedDate" DataFormatString="{0:MM/dd/yyyy}" />
<
asp:BoundField HeaderText="Company Name"
DataField="CompanyName" />
<
asp:BoundField HeaderText="Contact Name"
DataField="ContactName" />
<
asp:TemplateField>
<
ItemTemplate>
<
asp:LinkButton ID="lnkbtnDeleteRFP"
CommandName="cmdDeleteRFP" runat="server" Text="Delete" />
</
ItemTemplate>
</
asp:TemplateField>
</
Columns>
</
asp:GridView>
<
asp:panel id="pnlModalPopUp" CssClass="modalPopup" runat="server">
<
div class="ModalPopupContainer">
<
div class="modalTitle" id="PopupHeader">
<
asp:Label ID="lblDeleteRFPHeader" runat="server" /></div>
<
div class="modalLabel">
<
asp:Label ID="lblDeleteRFPText"
runat="server" Text="Please enter a reason for the delete:"/>
</
div>
<
div class="modalControl">
<
asp:TextBox ID="txtDeleteReason"
runat="server" TextMode="MultiLine" Width="500px"></asp:TextBox>
</
div>
<
br />
<
div class="modalControl">
<
asp:Button id="btnModalOkay"
runat="server" cssclass="formbutton" Text="Delete"
OnClick="btnModalOkay_Click" />
<
asp:Button id="btnModalCancel"
runat="server" cssclass="formbutton" Text="Cancel"
OnClick="btnModalCancel_Click" />
</
div>
<
asp:HiddenField ID="hdnModalCD" runat="server" />
<
asp:HiddenField ID="hdnModalRFPName" runat="server" />
</
div>
</
asp:panel>
</
asp:Content>




Here you can see that I have created a Panel after the GridView.  This will serve as our Modal Window.  However, we need to make it look pretty.  Right click your Styles Folder and add a New Stylesheet.  Name it ModalPopup.css and put the following code into it.:




.modalBackground{background-color: Black;filter: alpha(opacity=90);
opacity: 0.8;}
.modalPopup{background-color: #FFFFFF;border-width: 3px;border-style: solid;
border-color: black;padding-top: 10px;padding-left: 10px;
padding-right: 10px;width: 600px;height: 140px;}
div.modalTitle{background-color:#001F55; font-weight:700;
font-size:12px; color:#FFFFFF; border-left:solid 1px #FFFFFF;
padding:6px 16px 7px 9px; text-align:left;}
div.modalLabel{ padding: 1px 10px 1px 0px; vertical-align:top;
color:#000;font-weight:700; text-align:left;font-size:11px;}
div.modalControl{padding: 1px 15px 1px 5px; vertical-align:top;
color: #000;font-size:11px;}


Now simply drag the file from your folder to the HeaderContent of your UI (Default.aspx):



<asp:Content ID="HeaderContent" runat="server" 
ContentPlaceHolderID="HeadContent">
<
link href="Styles/ModalPopup.css" rel="stylesheet" type="text/css" />
</
asp:Content>




Now drag a ScriptManager under your content tag on your UI (Default.aspx) page, you must drag this control from your toolbox as it will put the proper references in place:




<asp:Content ID="BodyContent" runat="server" 
ContentPlaceHolderID="MainContent">
<
asp:ToolkitScriptManager ID="ToolkitScriptManager1" runat="server">
</
asp:ToolkitScriptManager>
<
h2>
Welcome to ASP.NET!
</h2>


And now we have made it to the interesting part.  You can’t make the target of a Modal Extender a Link Button.  But we don’t want to use an ASP Button because we loose the functionality of the Grid?  What to do?  Lets combine the two.  We can control the firing of the Modal Window in the code-behind of the Grid.  We just need the Extender to physically exist on our page.  So lets create a Button, that will remain hidden and serve no purpose other than to be the target of the Extender and the Extender itself:



<Columns>
<
asp:BoundField HeaderText="Order Date" DataField="OrderDate"
DataFormatString="{0:MM/dd/yyyy}" />
<
asp:BoundField HeaderText="Ship Date" DataField="ShippedDate"
DataFormatString="{0:MM/dd/yyyy}" />
<
asp:BoundField HeaderText="Company Name" DataField="CompanyName" />
<
asp:BoundField HeaderText="Contact Name" DataField="ContactName" />
<
asp:TemplateField>
<
ItemTemplate>
<
asp:Button ID="btnDelete" runat="server" style="Display:none;" />
<
AjaxToolkit:ModalPopupExtender ID="mpeDeleteConfirm" runat="server"
targetcontrolid="btnDelete" popupcontrolid="pnlModalPopUp"
CacheDynamicResults="false"
popupdraghandlecontrolid="PopupHeader" drag="true"
BackgroundCssClass="modalBackground">
</
AjaxToolkit:ModalPopupExtender>
<
asp:LinkButton ID="lnkbtnDeleteRFP" CommandName="cmdDeleteRFP"
runat="server" Text="Delete" />
</
ItemTemplate>
</
asp:TemplateField>
</
Columns>


As you can see here, we are creating a button and then through CSS Styling we are setting it to display none (hiding the button).  We then setup the Modal Extender to use that as its trigger.  You may wonder, how then do we trigger the Modal Window?  Well, the row_command of the gridview event… of course:



Protected Sub gdvOrders_RowCommand(sender As Object, & _
e As System.Web.UI.WebControls.GridViewCommandEventArgs) & _
Handles gdvOrders.RowCommand
If e.CommandName = "cmdDeleteRFP" Then
Dim
rowIndex As Integer = & _
Convert.ToInt32(DirectCast(DirectCast(e.CommandSource, & _
LinkButton).NamingContainer, GridViewRow).RowIndex)
ClearModalFields()
Dim modalPopupExtender1 As ModalPopupExtender = & _
DirectCast(gdvOrders.Rows(rowIndex).FindControl("mpeDeleteConfirm"), & _
ModalPopupExtender)
modalPopupExtender1.Show()

'Perform any specific processing.
lblDeleteRFPHeader.Text = & _
Server.HtmlEncode(String.Format("Delete Order From: {0}", & _
gdvOrders.Rows(rowIndex).Cells(2).Text))
hdnModalCD.Value = String.Format("{0}", & _
gdvOrders.DataKeys(rowIndex).Values(0).ToString())
hdnModalRFPName.Value = & _
Server.HtmlEncode(gdvOrders.Rows(rowIndex).Cells(2).Text)
End If
End Sub


Ok, this one takes a little explanation.  Because we are using a LinkButton, we need to get the row index a little bit differently.  Then we clear the fields for our Modal (Explained Later).  After that we must instantiate the modalpopupextender (particularly the one on the same grid row as our link button.  Finally we show the extender.  Following that are some nice additions that we use.  We have a delete header that will appear at the top of our modal window.  We need to keep the ID in a hidden field to ensure that we update the correct record.  Finally, I put the name in a hidden field (Just for S&Gs).  Our clearModalFields method is simple enough:



Protected Sub ClearModalFields()
lblDeleteRFPHeader.Text = String.Empty
txtDeleteReason.Text = String.Empty
hdnModalCD.Value = String.Empty
hdnModalRFPName.Value = String.Empty
End Sub


Now if you run the application, you will get an error about our buttons that we have (OK and Cancel).  Lets take care of those, but first, like any new procedure we need to start at the Data.  We’ve already written the SP, so now we need to go into our DAL (DAL.Orders).  Our method will look like:



Public Function deleteOrderWithComment(ByVal ID As Integer, & _
ByVal comment As String) As Integer
Dim
conn As SqlConnection = & _
New SqlConnection(ConnectionStrings("wia").ToString())
Dim cmd As New SqlCommand("dbo.usp_orders_delete_with_comments", conn)
cmd.CommandType = CommandType.StoredProcedure
cmd.Parameters.Add(New SqlParameter("@OrderID", ID))
cmd.Parameters.Add(New SqlParameter("@Comment", comment))
Try
conn.Open()
cmd.ExecuteNonQuery()

Catch ex As SqlException

Finally

conn.Close()
End Try
Return Nothing
End Function


We need to walk it backwards through our BLL (BLL.Orders) by adding the following method there:



Public Function deleteOrderWithComment(ByVal ID As Integer, & _
ByVal comment As String) As Integer
Dim
ordersDAL As DAL.Orders = New DAL.Orders
Return ordersDAL.deleteOrderWithComment(ID, comment)
End Function


Now we are ready to add our button events to our UI (Default.aspx):



Protected Sub btnModalCancel_Click(sender As Object, & _
e As System.EventArgs) Handles btnModalCancel.Click
ClearModalFields()
End Sub

Protected Sub
btnModalOkay_Click(sender As Object, & _
e As System.EventArgs) Handles btnModalOkay.Click
If txtDeleteReason.Text = String.Empty Then
lblDeleteRFPText.Text = & _
"YOU MUST profide a reason to delete the order from :" & _
hdnModalRFPName.Value.ToString()
txtDeleteReason.Focus()
Else
Dim
ordersBLL As BLL.Orders = New BLL.Orders
ordersBLL.deleteOrderWithComment(CInt(hdnModalCD.Value), & _
txtDeleteReason.Text)
griddatabind()
ClearModalFields()
End If
End Sub


So if we click cancel, all we want to do is clear our fields.  If we click OK then we check to make sure they have entered a reason.  If not we tell them and then put the cursor in the the appropriate field.  Otherwise we update the database.

image



 



Here we can see our Modal Window at work….  And if we look in the database:

image



We can see it is InActive (By having a 0 in the active field) and has a comment.  More importantly, it is no longer showing up in our Grid (thus to the user, it has been deleted)!



You can make ASP.Net do anything a conventional Windows Forms application can do…



Håþþ¥ .ñꆆïñg…

No comments:

Post a Comment