Inline Editing With The WebGrid

Unlike the Web Forms GridView control, the Web Pages WebGrid doesn't offer anything by way of inline editing capability. This article looks at one approach to solving that requirement through the addition of a sprinkling of jQuery.

I have already looked at one approach to editing with the WebGrid, which involved invoking a jQuery dialog that contained a form. The approach covered in this article differs in that each row of data is presented as read-only, but converted to an editable format within the WebGrid so that the user doesn't have to leave the WebGrid to make their alterations:

Inline Editing With The WebGrid

The key to this approach is to populate the grid with both readonly and editable versions of the data, then to use the jQuery toggle command to swap between the two presentations on a line-by-line basis. So I need data prepared for dropdown lists when the page first loads as well as data to be displayed in read-only mode:

@{
    var db = Database.Open("Books"); 
    
    var books = db.Query(@"SELECT b.BookId, b.Title, b.ISBN, b.AuthorId, b.CategoryId, 
                            a.FirstName + ' ' + a.LastName AS AuthorName, c.Category  
                            FROM Books b INNER JOIN Authors a 
                            ON b.AuthorId = a.AuthorId  
                            INNER JOIN Categories c 
                            ON b.CategoryId = c.CategoryId 
                            ORDER BY b.BookId DESC");
                                                   
    var categories = db.Query("SELECT CategoryId, Category FROM Categories")
                        .Select(category => new SelectListItem {
                            Value = category.CategoryId.ToString(), 
                            Text = category.Category
                         }); 
                         
    var authors = db.Query("SELECT AuthorId, FirstName + ' ' + LastName AS AuthorName FROM Authors")
                        .Select(author => new SelectListItem {
                            Value = author.AuthorId.ToString(), 
                            Text = author.AuthorName
                         });   
                         
    var grid = new WebGrid(books);                                                         
}

The code gets three sets of data - the books details that I want to display, the collection of categories that the books can belong to and the authors that the books have been written by. The last two sets of data are converted to Enumerable<SlectListItem>. This is so that they can be plugged directly into Html.DropDownList helpers. Finally, the books data is passed into a WebGrid. Here is the code for the grid:

@grid.GetHtml(
    tableStyle : "table",
    alternatingRowStyle : "alternate",
    selectedRowStyle: "selected",
    headerStyle : "header", 
    columns : grid.Columns(
        grid.Column("", 
                    style: "col1", 
                    format: @<text>
                                <button class="edit-book display-mode" id="@item.BookId">Edit</button>
                                <button class="save-book edit-mode" id="@item.BookId">Save</button>
                            </text>),
        grid.Column("Title",
                    style: "col2",
                    format: @<text>
                                <span id="title" class="display-mode">@item.Title</span>
                                @Html.TextBox("Title", item.Title, new {@class="edit-mode", size = 45})
                            </text>),
        grid.Column("AuthorName",
                    header : "Author",
                    style: "col3",
                    format: @<text>
                                <span id="authorname" class="display-mode">@item.AuthorName</span>
                                @Html.DropDownList("AuthorId", null, authors, item.AuthorId, new {@class="edit-mode"})
                            </text>),
        grid.Column("Category",
                    style: "col4",
                    format: @<text>
                                <span id="category" class="display-mode">@item.Category</span>
                                @Html.DropDownList("CategoryId", null, categories, item.CategoryId, new {@class="edit-mode"})
                            </text>),
        grid.Column("ISBN",
                    style: "col5",
                    format: @<text>
                                <span id="isbn" class="display-mode">@item.ISBN</span>
                                @Html.TextBox("ISBN", item.ISBN, new {@class="edit-mode", size = 20})
                            </text>)
     )    
)

The main thing to take note of is that there are two items in each of the cells. In most of them, there is a span that holds the item for display, and a form element for editing the item. As I mentioned earlier, I am using the Html form helpers mainly because they require a lot less code than HTML itself. All items designed for display are given the CSS class "display-mode". The form fields have the CSS class of "edit-mode" applied to them. Both of the buttons in the first column are given two CSS classes.

The next part of the jigsaw is the jQuery code that manages switching between the two modes:

<script>
    $(function () {
        $('.edit-mode').hide();
        $('.edit-book').on('click', function () {
            var tr = $(this).parents('tr:first');
            tr.find('.edit-mode, .display-mode').toggle();
        });
        $('.save-book').on('click', function () {
            var tr = $(this).parents('tr:first');
            var bookId = $(this).prop('id');
            var title = tr.find('#Title').val();
            var authorId = tr.find('#AuthorId').val();
            var categoryId = tr.find('#CategoryId').val();
            var isbn = tr.find('#ISBN').val();
            $.post(
                '/EditBook',
                { BookId: bookId, Title: title, AuthorId: authorId, CategoryId: categoryId, ISBN: isbn },
                function (book) {
                    tr.find('#title').text(book.Title);
                    tr.find('#authorname').text(book.AuthorName);
                    tr.find('#category').text(book.Category);
                    tr.find('#isbn').text(book.ISBN);
                }, "json");
            tr.find('.edit-mode, .display-mode').toggle();
        });
    })
</script>

The first thing this script does is to hide all items with a CSS class of edit-mode resulting in all of the form fields and the Save button being hidden. An event handler is hooked up to the click event of the Edit button. This gets a reference to the table row that the button is in, and then toggles the visibility of all items within it that have a CSS class of edit-mode and display-mode. Since all edit-mode items were hidden at the outset, they now become visible, and all display items are hidden.

The next section of jQuery code applies an event handler to the click event of the Save button. Like the previous event handler, it obtains a reference the the current row, and then obtains values from the form fields and dropdowns as well as the ID of the current item from the Save button itself (which has been applied to its id attribute). It then packages these values and posts them to a page called EditBook.cshtml. Notice the absence of <form> tags in the code; jQuery takes care of constructing the POST request. Then it takes the data that the EditBook page returns and uses that to update the spans containing the displayed data. Finally, the edit-mode and display-mode items in the current row have their visibility toggled once again.

Here is the code for the EditBook.cshtml file:

@{
    var bookId = Request["BookId"];
    var title = Request["Title"]; 
    var authorId = Request["AuthorId"];
    var categoryId = Request["CategoryId"];
    var isbn = Request["ISBN"];
    var db = Database.Open("Books");
    var sql = "UPDATE Books SET Title = @0, AuthorId = @1, CategoryId = @2, ISBN = @3 WHERE BookId = @4";
    db.Execute(sql, title, authorId, categoryId, isbn, bookId);
    sql = @"SELECT b.Title, b.ISBN,  a.FirstName + ' ' + a.LastName AS AuthorName, c.Category  
            FROM Books b INNER JOIN Authors a ON b.AuthorId = a.AuthorId  
            INNER JOIN Categories c ON b.CategoryId = c.CategoryId 
            WHERE BookId = @0";
    var result = db.QuerySingle(sql, bookId);
    Json.Write(result, Response.Output);
}

It takes the values from the Request collection, and uses them to update the database table. Then it retrieves the data back from the database suitably shaped for display. Finally, the Json helper is used to send that data back to the calling code for consumption.

The code for this article is available at GitHub.

 

Date Posted:
Last Updated:
Posted by:
Total Views to date: 42239

27 Comments

- Nasir Raza

Great break through, I was looking this kind of code in web pages. Keep it up.
However, code for the article is not available. I think you have yet to publish it...

- Mike

Apologies, the download link should work now.

- Nasir Raza

Thanks, its working now.

- Adrien Sleeg

Very interesting article !!
But the download link doesn't work for me...

- Mike

I've revised the link to point to this site. Previously, the download was being hosted on Sky Drive.

- yousaid

Great article !! Now how do you add a "Cancel" Functionality to the Edit mode. This is needed for those scenarios where the user changes their mind in the middle of the process.
Guess this is rather something very simple.
cheers,
yousaid

- Mike

You just need to add another button with the class "cancel", and then alter the edit-book click event to include items with a class of cancel:

$('.edit-book, cancel').on('click', function () {
///

- Emre

Hi this is really good work.Thanks for this.But i have a problem with sorting when i use this as u say.When i click headers to sort all hidden edit fields are opening.Do you have an idea for this why can i handle this problem?

- Om Prakash Bishnoi

Thanks, nice and very interesting article !!

- Marco

How would u handle validation?

- Mike

@Marco,

That's not an easy question to answer in a comment, and would very much depend on what is required.

- upen

this is really good, and how long are you in MVC ?

- Mike

@upen,

I don't understand what you are asking. Sorry.

- Sithelo

Thanks a lot for an eye opener tutorial. In my application, I combined the inline and search functionality. Now when I click the edit button, it show the Save button for a spilt second.
In short the edit functionality s somehow hindered. How can I work around this?

- Patrick

Just what I needed! Thank you very much!!

- kkDubey

It's good...but where is stylesheet (edit-mode,display-mode,etc..)?

- Mike

@kkDubey

The CSS classes you mentioned were not used for setting styles in the sample. They were simply used as selectors for jQuery. You can apply whatever style you like to them, though.

- Isaac Koss

Unfortunately this does not work in MVC 4. Do you have any ideas on how to do this in MVC 4? Thanks very much for your posts.

- Dragon

Nicely done, you edit and save, you dealt with cancel (indirectly) but what about delete? How do you delete a line item and then refresh (update) the grid?

- Dave

Great article. One small point though, you mention CRUD in several articles and in your book, but I haven't seen any of your code show how to implement the D(elete) portion. This article for example would be more complete if you showed how to delete an entry from the webgrid and then have the system refresh the display.

Just a thought, but great work and what is presented is very informative, so thanks.

- bluers0

How do you handle validations (e.g. you enter the same ISBN for 2 different books)?

- Alex

That's just what I was looking for thanks. How would I add fields that are a 1 or 0 in the database as a checkbox on the webgrid?

- Mike

@Alex,

You can pass an input type="checkbox" to the format parameter with the following attribute:

checked="@(item.value == 1)"

where 'value' is the database field that you want to bind to.

- Joop Stringer

How to add a row for a new record ?

- Mike

@Joop

See this article: Adding a New Row In The Razor WebGrid

- faysal

Nice one can you please tell us how we can do ad and delete functionality in this. for e.g if i click on add row added and click on delete to delete row from grid.

Thanks in advance.

- Mike

@faysal,

You can find out how to add new rows in this article: Adding a New Row In The Razor WebGrid
Add your comment

If you have any comments to make about this article, please use this form to do so. Make sure that your comment relates specifically to the article above. More general comments can be posted through the form on the Contact page.

Please note, all comments are moderated, and I end up deleting quite a lot. The kind of things that will ensure your comment is deleted without ever seeing the light of day are as follows:

  • Requests to fix your code (post a question to forums.asp.net instead, please)
  • Gratuitous links to your own site or product
  • Anything abusive or libellous
  • Spam

I do not pass email addresses on to spammers, so a valid one will assist me in responding to you personally if required.

Recent Comments

kaleem 4/16/2015 9:14 AM
In response to ASP.NET MVC, Entity Framework, One-to-Many and Many-to-Many INSERTS
hi this is very helpful ASP.NET MVC, Entity Framework, One-to-Many and Many-to-Many INSERTS if to...

cranston mason 4/16/2015 8:49 AM
In response to Managing Checkboxes And Radios In ASP.NET Razor Web Pages
How do you include a checkbox at the View module and send its values to controller to be processed?...

vamsi 4/15/2015 5:40 AM
In response to Sessions in ASP.NET 5
good article...

James Chaney 4/14/2015 8:44 PM
In response to 7 C# 6.0 Features That Every ASP.NET Developer Should Know About
Question on #5 - I don't see where this gets you out of a NullReferenceException if returns null is...

Grey 4/14/2015 6:09 PM
In response to Getting the identity of the most recently added record
Nice work....

Anh Huynh 4/14/2015 3:36 PM
In response to Create PDFs in ASP.NET - getting started with iTextSharp
I was successfully create and display PDF file from my localhost but when I move the aspx to the I...

Hisham Abdullah Bin Ateya 4/14/2015 7:00 AM
In response to Sessions in ASP.NET 5
Thanks Mike for sharing this article. George it will be nice if we let the web developers implement...

CTR 4/14/2015 6:39 AM
In response to Integrating Web API with ASP.NET Razor Web Pages
Thanks Mike, new to web pages. learning a lot from your posts, Making Ajax calls to Web API extend...

Bayu Angkasa 4/13/2015 11:27 AM
In response to The Difference Between @Helpers and @Functions In WebMatrix
Four years after you wrote it, I still find out how lucky I am to read your posts ... Thank you Mike...

abedon 4/10/2015 9:17 PM
In response to ASP.NET 5 Middleware, Or Where Has My HttpModule Gone?
I feel the title of this article is not that felicitous. ASP.NET 5 is built of top of the concept...