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:

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: Tuesday, November 13, 2012 10:37 AM
Last Updated: Friday, December 14, 2012 8:01 PM
Posted by: Mikesdotnetting
Total Views to date: 32777

23 Comments

Tuesday, November 13, 2012 3:23 PM - 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...

Tuesday, November 13, 2012 4:04 PM - Mike

Apologies, the download link should work now.

Tuesday, November 13, 2012 4:25 PM - Nasir Raza

Thanks, its working now.

Wednesday, November 14, 2012 10:42 AM - Adrien Sleeg

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

Wednesday, November 14, 2012 1:20 PM - Mike

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

Thursday, November 15, 2012 9:07 PM - 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

Friday, November 16, 2012 6:08 AM - 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 () {
///

Friday, November 30, 2012 7:15 AM - 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?

Thursday, December 13, 2012 6:02 AM - Om Prakash Bishnoi

Thanks, nice and very interesting article !!

Monday, December 31, 2012 10:45 PM - Marco

How would u handle validation?

Tuesday, January 1, 2013 8:15 PM - Mike

@Marco,

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

Thursday, January 3, 2013 4:29 PM - upen

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

Thursday, January 3, 2013 4:32 PM - Mike

@upen,

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

Wednesday, February 20, 2013 11:14 AM - 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?

Friday, March 1, 2013 8:26 PM - Patrick

Just what I needed! Thank you very much!!

Wednesday, March 6, 2013 5:37 AM - kkDubey

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

Wednesday, March 6, 2013 5:59 AM - 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.

Tuesday, April 9, 2013 9:55 PM - 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.

Thursday, September 19, 2013 8:26 PM - 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?

Monday, September 23, 2013 7:51 PM - 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.

Wednesday, September 25, 2013 7:06 PM - bluers0

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

Tuesday, November 26, 2013 11:24 AM - 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?

Saturday, December 7, 2013 9:12 PM - 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.
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 some may not be published. The kind of things that will ensure your comment is deleted without ever seeing the light of day are as follows:

  • Not relevant to the article
  • Gratuitous links to your own site or product
  • Anything abusive or libellous
  • Spam
  • Anything in a language I don't understand including gibberish.

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