Pages

Monday, June 20, 2011

Implementing a ModalPopupExtender

It should be easy, but as I've encountered some problems along the way, I thought I'd best blog about it.


AjaxToolkit has an extender to make it easier for us to implement a modal dialog (or a Popup) for our ASP.NET Web Forms, the ModalPopupExtender. It could be used for confirmations, when a user choses to delete something, for example. I'll demonstrate a simple Popup with two options, and two buttons.


From now on I'll assume you have AjaxToolkit controls added to your toolbox. If you don't, you can go ahead and find out how to do it here.


Poping it up


After you've added AjaxToolkit to your toolbox, add a new WebSite to your solution, and open the default page code.

First of all, let us define our Popup's style:

.ModalPopupBackground
{
    background-color: Gray;
    filter: alpha(opacity=70);
    opacity: 0.7;
}

.PopupBody
{
    border: 1px solid silver;
    padding: 5px;
    background-color: #f5f5c5;
    text-align: left;
    font-size: 15px;
    color: Gray;
}        

This will add a Gray background when the Popup is active, and our PopupBody class will be defined to be our Panel's Css Class, which will be shown inside our Popup.

We also have to register our AjaxToolkit DLL on the page, and add a ScriptManager, like this:

<%@ Register Assembly="AjaxControlToolkit" Namespace="AjaxControlToolkit" TagPrefix="ajaxToolkit" %>

<ajaxToolkit:ToolkitScriptManager runat="server" ID="manager1" />



Now we can add our controls to the page. Add a button, and a label to interact with the Popup, and our ModalPopupExtender. Just remember that these controls must be inside a tag form with runat="server".


<asp:UpdatePanel runat="server">
        <ContentTemplate>
            <center>
                <asp:Button Text="Click Me" ID="btnClickMe" runat="server" />
                <br />
                <asp:Label ID="lblResult" Visible="false" Text="" runat="server" />
                <ajaxToolkit:ModalPopupExtender ID="modalPopup" runat="server" TargetControlID="btnClickMe"
                    PopupControlID="pnlPopup" OkControlID="btnHidden" CancelControlID="btnHidden"
                    BackgroundCssClass="ModalPopupBackground" DropShadow="true" />
            </center>
        </ContentTemplate>
    </asp:UpdatePanel>

I don't think I need to explain the ModalPopupExtender's properties. They're very straight-forward, and you can also find their explanation at the ASP.NET Ajax page.


One thing to note here is that we've set the OkControlID and CancelControlID to a dummy, hidden button. We do that because if we set them to the right controls, no PostBack is fired, so no code-behind is executed. It would be OK for the Cancel button though, but as I was developing this example, when I had the CancelControlID property set to the actual cancel button, after I clicked it once I had to reload the page for the Popup to be shown again. So instead, I put some JavaScript code on the Cancel's button OnClientClick event to hide the Popup. This was the simplest workaround I could think of.


And now this is our panel, which will be displayed as the Popup:

<asp:Panel runat="server" ID="pnlPopup" CssClass="PopupBody">
        <asp:RadioButtonList ID="radioList" runat="server" >
            <asp:ListItem Text="Option 1" Value="1" />
            <asp:ListItem Text="Option 2" Value="2" />
        </asp:RadioButtonList>
        
        <br />
        <center>
            <asp:Button Text="dummy" ID="btnHidden" runat="server" Style="display: none;" />
            <asp:Button Text="Ok" ID="btnOk" runat="server" style="padding-right: 10px;" 
                onclick="btnOk_Click" />
            <asp:Button Text="Cancel" ID="btnCancel" runat="server" OnClientClick="$find('modalPopup').hide();" />            
        </center>
    </asp:Panel>

As I said, the cancel button has some JavaScript to hide the Popup when it's clicked.

You can also notice that our dummy button doesn't have its visible property set to false, instead we've hidden it through style marks. We did that because if we set it to not be visible through the Visible property, it won't even be rendered, so the Popup won't find it, and won't be shown. But hidding it through style marks does the trick, and makes it work just fine. So if your Popup is not being shown, check if your dummy button has Visible = false and change it to style marks, as display: none.


To complete the trick, we add some code to handle our button OK click event:

protected void btnOk_Click(object sender, EventArgs e)
{
    lblResult.Text = string.Format("You have selected option {0}", radioList.SelectedValue);
    lblResult.Visible = true;
}


So when the Popup is shown and the Ok button is clicked, a PostBack is generated and the Click event handler code is executed and the Popup is hid. And when the Cancel button is clicked, it does nothing but hide the Popup. If you would add some code-behind to it, it would also be executed though.


And that's it, you should now have a modal Popup with two Radio Buttons as options, an OK and a Cancel button, like the images below. Once again, hope this helps...



Monday, June 13, 2011

GridView with ObjectDataSource and Pagination

The title is self explanatory: I'm going to develop a GridView bound to an ObjectDataSource with pagination directly at the DataSource, not just on the Grid.


The difference of paginating directly on the data source and paginating at the Grid level, is that in the first case you'll only have the records that are shown in the grid loaded to memory. This means that for each page selected, the data source will get the next N results to be displayed. When you do that directly on the grid, every row will be loaded on the first fetch the data source will perform, and the grid will handle paginating and mantaining those records in memory for that.


First of all, let's create our table:


CREATE TABLE Customers
(
    CustomerId INT IDENTITY (1,1) PRIMARY KEY,
    FirstName VARCHAR(60) NOT NULL,
    LastName VARCHAR(60) NOT NULL,
    Age INT NOT NULL,
    Phone VARCHAR(8) NULL
)

Insert some data in it, about 15 Rows or so...


If you mark that the ObjectDataSource allows Paging (and you will), you have to implement a select method which takes two special integer parameters (startRowIndex and maximumRows) and a count method that returns an integer of how many records we have in our table. It's obvious enough that we need the count method to calculate how many pages we'll have, and the two special parameters to limit our result...

You could also change startRowIndex and maximumRows to whatever you'd like to with the properties StartRowIndexParameterName and MaximumRowsParameterName. However, if you change their names your select method must be changed too. You'll learn exactly why we need those parameters with the code below.


Now implement our Data Class, notice that I've created a constant called ConnStr just for demonstration purposes, you should get your Connection String from wherever you think is better.

[System.ComponentModel.DataObject(true)]
public class CustomerData
{

    [System.ComponentModel.DataObjectMethod(System.ComponentModel.DataObjectMethodType.Select, true)]
    public static DataTable GetCustomers(int maximumRows, int startRowIndex)
    {
        string strCommand = "SELECT * FROM ";
        strCommand += "(SELECT *, ROW_NUMBER() OVER (ORDER BY FirstName) as RowNum FROM Customers) as Sub ";
        strCommand += "WHERE RowNum BETWEEN @StartRow AND @MaximumRows";

        using (SqlDataAdapter sqlAdapter = new SqlDataAdapter(strCommand, new SqlConnection(CustomerData.ConnStr)))
        {
            using (DataTable dtRet = new DataTable())
            {
                try
                {
                    startRowIndex++;

                    sqlAdapter.SelectCommand.Parameters.Add("@StartRow", SqlDbType.Int).Value = startRowIndex;
                    sqlAdapter.SelectCommand.Parameters.Add("@MaximumRows", SqlDbType.Int).Value = (maximumRows + startRowIndex);

                    sqlAdapter.Fill(dtRet);

                    return dtRet;
                }
                catch (Exception)
                {
                    throw;
                }                   
            }                
        }
    }


    [System.ComponentModel.DataObjectMethod(System.ComponentModel.DataObjectMethodType.Select, false)]
    public static int GetCustomersCount()
    {
        using (SqlCommand sqlCommand = new SqlCommand("SELECT COUNT(*) FROM [Customers]", new SqlConnection(CustomerData.ConnStr)))
        {
            try
            {
                sqlCommand.Connection.Open();
                return (int)sqlCommand.ExecuteScalar();                    
            }
            catch (Exception)
            {                    
                throw;
            }
        }
    }
}


Take a better look at our SELECT command:

SELECT
    *
FROM
(
    SELECT
        *,
        ROW_NUMBER() OVER (ORDER BY FirstName) AS RowNum 
    FROM 
        Customers
) AS Sub
WHERE
    RowNum BETWEEN @StartRow AND @MaximumRows

Since our DataSource passes us "0" on StartRowIndex for our first page, we have to add 1 to it, because our column RowNum starts at 1. We then limit the records to start on StartRowIndex + 1 and end on MaximumRows + our StartRowIndex (already increased by one).

We can see that when we add our parameters to the SqlCommand object:

startRowIndex++; // Increment our start row number by one

sqlAdapter.SelectCommand.Parameters.Add("@StartRow", SqlDbType.Int).Value = startRowIndex;

sqlAdapter.SelectCommand.Parameters.Add("@MaximumRows", SqlDbType.Int).Value = (maximumRows + startRowIndex); // MaximumRow plus StartRowIndex is the number of our last row for the actual page

Ok, so after that we start designing our page's objects.

Heres our ObjectDataSource, with no big secrets:

<asp:ObjectDataSource 
    ID="dsCustomers" 
    runat="server" 
    TypeName="CustomerData" 
    SelectMethod="GetCustomers" 
    SelectCountMethod="GetCustomersCount" 
    EnablePaging="true"
/>

And this is our GridView, marked to auto generate our columns, just for demonstration purposes:

<asp:GridView
    ID="gridCustomers" 
    AllowPaging="True" 
    runat="server" 
    PageSize="5" 
    DataSourceID="dsCustomers"
    AutoGenerateColumns="true"
/>  

If you've done everything right, you should be good to go with an ugly ass GridView, showing a maximum of 5 rows on each page, ordered by our Customers' first name, like the images below:



Go on and customize the columns, add Insert / Edit / Delete functionality, and the grid's skin / theme.