The WebGrid Helper - Making Selections

The WebGrid helper, introduced via ASP.NET Web Pages, provides a means for displaying tabular data easily. This article examines how to enable selection within the WebGrid at row level.

The scenario is a common one - you want to display summary data within a table on a web page, and you want to provide your user with a means by which they can view more detailed information on a particular item in the table, or make changes, such as editing or deleting an item. With the Web Forms GridView, this is easily accomplished by ticking boxes that automatically enable selection, editing and deleting. It's also pretty easy to do with the WebGrid. First, let's take a look at a typical WebGrid which displays some data from a database containing information on Books:

Here's the code for the page:

@{
    Layout = "/Shared/_Layout.cshtml";
    var db = Database.Open("Books");
    var sql = "Select BookId, Title, ISBN, Description, FirstName, LastName, Category From Books " + 
               "Inner Join Authors on Books.AuthorId = Authors.AuthorId " + 
               "Inner Join Categories on Books.CategoryId = Categories.CategoryId";
    var data =  db.Query(sql);
    var grid = new WebGrid(data);
}
<div id="grid">
@grid.GetHtml(
    tableStyle : "table",
    alternatingRowStyle : "alternate",
    headerStyle : "header",
    columns: grid.Columns(

        grid.Column(
            columnName : "Author",
            format: @<text>@item.FirstName @item.LastName</text>
            ),
        grid.Column(
            columnName : "Title",
            format: @<text>@item.Title</text>
            ),
        grid.Column(
            columnName : "Category"
            )
    )
)
</div>

If you are not familiar with how to set the basic properties of a WebGrid through Razor code, you should review my previous article on the WebGrid helper. If you want to allow your users to edit items, you ideally want to provide a column which contains a selection link like this:

The WebGridRow object has a GetSelectLink method which provides the means to do this. All you need to do is add another column to the grid and call the method. Here's the revised grid code with an additional column and a selectedRowStyle value set:

@grid.GetHtml(
    tableStyle : "table",
    alternatingRowStyle : "alternate",
    selectedRowStyle: "selected",
    headerStyle : "header",    
    columns: grid.Columns(
        grid.Column(
            header:"", 
            format:@<text>@item.GetSelectLink("Edit")</text>
            ),
        grid.Column(
            columnName : "Author",
            format: @<text>@item.FirstName @item.LastName</text>
            ),
        grid.Column(
            columnName : "Title",
            format: @<text>@item.Title</text>
            ),
        grid.Column(
            columnName : "Category"
            )
    )
)

Notice that the header property in the new column has been set to an empty string, and a string has been passed in to the GetSelectLink method ("Edit"). This string is the text that will appear on each row. You do not have to pass in a string. It is optional, but the default value is "Select" if you don't provide one yourself.

Great! But what happens if you click a link? At the moment, nothing happens except that the page refreshes, and the selected row takes on the colour specified in the css file. However, you might notice that the URL has changed a little in the browser eg:

The selected row is indicated in the querystring. The querystring key value "row" is set by default, but you can change this by applying your own value to the selectionFieldName property in your WebGrid constructor:

var grid = new WebGrid(data, selectionFieldName: "custom");
This would change the URL to read something like http://localhost:39612/Default.cshtml?custom=4. The presence of a selectionFieldName in the querystring (indicating that someone clicked the selection link) is tested using the WebGrid.HasSelection property:
@if(grid.HasSelection){
    //do something
}

In this example, I chose to use RenderPage to bring in the contents of another file, which contains a form for editing a book entry. In order to populate that form with the correct book details, I needed to pass the selected book to the partial page. This is done using the PageData dictionary mechanism:

@if(grid.HasSelection){
    var book = grid.SelectedRow;
    @RenderPage("~/Partials/EditBook.cshtml", new { Book = book } )
}

The code in EditBook.cshtml uses PageData to fetch the correct book, plus some other stuff from the database to populate the form ready for editing:

@{
    
    var bookid = Page.Book.BookId;
    var db = Database.Open("Books");    
    var authors = db.Query("SELECT AuthorId, FirstName + ' ' + LastName AS AuthorName FROM Authors");
    var categories = db.Query("SELECT CategoryId, Category FROM Categories");
    var sql = "SELECT BookId, Title, ISBN, Description, CategoryId, AuthorId, DatePublished FROM Books WHERE BookId = @0";
    var book = db.QuerySingle(sql, bookid);

}


    <form id="edit-book-form" action="@Href("~/Methods/EditBook")">
        <fieldset>
            <legend>Edit Book</legend>
         <div class="row">
                <span class="label"><label for="title">Title:</label></span>
                <input type="text" name="title" id="title" size="50" value="@book.Title" />
            </div>
            <div class="row">
                <span class="label"><label for="isbn">ISBN:</label></span>
                <input type="text" name="isbn" id="isbn" size="20" value="@book.ISBN" />
            </div>
            <div class="row">
                <span class="label"><label for="description">Description:</label></span>
                <textarea cols="50" rows="8" name="description" id="description">@book.Description</textarea>
            </div>
            <div class="row">
                <span class="label"><label for="authorId">Author:</label></span>
                <select name="authorId" id="authorId">
                    <option value="">-- Select Author --</option>
                @{
                    foreach(var author in authors){
                        if(author.AuthorId == book.AuthorId){
                            <option value="@author.AuthorId" selected="selected">@author.AuthorName</option>
                        } else {
                            <option value="@author.AuthorId">@author.AuthorName</option>
                        }
                    }
                }
                </select>
            </div>
            <div class="row">
                <span class="label"><label for="categoryId">Category:</label></span>
                <select name="categoryId" id="categoryId">
                    <option value="">-- Select Category --</option>
                @{
                    foreach(var category in categories){
                        if(category.CategoryId == book.CategoryId){
                            <option value="@category.CategoryId" selected="selected">@category.Category</option>
                        } else {
                            <option value="@category.CategoryId">@category.Category</option>
                        }        
                    }
                }
                </select>
            </div>
            <div class="row">
                <span class="label"><label for="datePublished">Date Published:</label></span>
                <input type="text" id="datePublished" name="datePublished" value="@book.DatePublished.ToString("d")" />
            </div>    
            <div class="row">
                <span class="label">&nbsp;</span>
                <input type="submit" id="submit" name="submit" value="Submit" />
            </div> 
            </fieldset>
        </form>

The resulting page looks like this:

There are alternatives to making rows selectable. For instance, you can use jQuery as illustrated in another of my previous articles. You would need to use a jQuery approach is you enable AJAX paging or sorting on the grid, as no querystring is generated when you click a link in the grid cells. Or you could simply format the text to include a hyperlink to another page:

grid.Column(
    header:"", 
    format:@<a href="/Edit/@item.BookId">Edit</a>)

If you wanted to provide more than one selection column, say an Edit and a Delete option, you would need to use this approach or the jQuery one, as currently, you cannot set the querystring selectFieldName property on a per column basis, so there will be no easy way to distinguish whether someone clicked the Edit link or the Delete link.

The full code is available in a download here.