WebMatrix and jQuery Forms Part 2 - Editing Data

4.93 (14 votes)

This article continues on from one I wrote a while ago, showing how to use jQuery to create a data entry form in conjunction with the WebGrid. The original article prompted a number of requests to show how to extend the example to provide editing functions, and now I have found some time to answer those requests.

The initial article uses a sample database I created which contains details of books and their authors. The example code in that article is quite straightforward. Once you have worked out how to use the jQuery Dialog, you use that to display a data entry form, and when the new book is added, you give the first row of the grid a highlight effect. The task of adding Edit features poses are few new challenges. For one thing, the dialog form must know which book's details it should display so that the user can alter them. Secondly, the items location in the grid (row and page) must be known so that the correct row can be highlighted.

The edit form works in the same way as the Add form, in that it is defined in the Default.cshtml file and the jQuery dialog command is what gives it life:

    <div id="dialog-form-edit" title="Edit Book">
    <form id="edit-book-form" action="@Href("~/Methods/UpdateBook")">
         <div class="row">
                <span class="label"><label for="title">Title:</label></span>
                <input type="text" name="title" id="edit-title" size="50" />
            </div>
            <div class="row">
                <span class="label"><label for="isbn">ISBN:</label></span>
                <input type="text" name="isbn" id="edit-isbn" size="20" />
            </div>
            <div class="row">
                <span class="label"><label for="description">Description:</label></span>
                <textarea cols="50" rows="8" name="description" id="edit-description"></textarea>
            </div>
            <div class="row">
                <span class="label"><label for="authorId">Author:</label></span>
                <select name="authorId" id="edit-authorId">
                    <option value="">-- Select Author --</option>
                @{
                    foreach(var author in authors){
                        if(author.AuthorId == Request["authorId"].AsInt()){
                            <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="edit-categoryId">
                    <option value="">-- Select Category --</option>
                @{
                    foreach(var category in categories){
                        if(category.CategoryId == Request["categoryId"].AsInt()){
                            <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="edit-datePublished" name="datePublished" />
                <input type="hidden" id="edit-bookId" name="bookId" />
            </div>    
        </form>
    </div>    

The form is pretty much identical to the Add Book form, except that the action has changed, and the id values for the form elements have been prefixed with "edit-". With a bit more effort, you could probably find a way to reuse the same form instead of creating a separate form for each operation. In the meantime, you need something to instantiate this form in a dialog, and it needs to know which book it should populate itself with so that you can amend the details. That is the job of the following piece of jQuery:

$('.edit-book').live('click', function(){
    $.getJSON('/Methods/GetBook/' + $(this).attr('id'), function(data){
        var book = data;
        $('#edit-title').val(book.Title);
        $('#edit-isbn').val(book.ISBN);
        $('#edit-description').val(book.Description);
        $('#edit-authorId').val(book.AuthorId);
        $('#edit-categoryId').val(book.CategoryId);
        var date = new Date(parseInt(book.DatePublished.substr(6)));
        var year = date.getFullYear();
        var month = date.getMonth() + 1;
        var day = date.getDate();
        $('#edit-datePublished').val(year + '-' + month + '-' + day);
        $('#edit-datePublished').datepicker({ dateFormat: 'yy-mm-dd' });
        $('#edit-bookId').val(book.BookId);
    });
    $('#dialog-form-edit').dialog('open');
});

This block of code looks for items with a class of edit-book, and attaches a click event handler to them. Those items are buttons in the first column of a revised WebGrid:

@grid.GetHtml(tableStyle: "ui-widget ui-widget-content",
              headerStyle : "ui-widget-header",
              columns : grid.Columns(
                grid.Column("", format: @<button class="edit-book" id="@item.BookId">Edit</button>),
                grid.Column("Title"),
                grid.Column("AuthorName",header : "Author"),
                grid.Column("Category"),
                grid.Column("ISBN")
                )    
              )

The buttons have the Id of the current book applied as their id attribute value. The preceding block of jquery uses live to bind the event handler. This ensures that all existing items - and all items that are created in the future - with the specified class have the event handler bound to them. This is important when you have AJAX paging enabled. When a button is clicked, the Id of the current book is passed to an AJAX call to GetBook.cshtml in the Methods folder. The content of that file is as follows:

@{
    Response.Cache.SetCacheability(HttpCacheability.NoCache);
    if(UrlData[0].IsInt()){
        var db = Database.Open("Books");
        var sql = "SELECT * FROM Books WHERE BookId = @0";
        var book = db.QuerySingle(sql,UrlData[0]);
        Json.Write(book, Response.Output);
    }
}

This code simply queries the database for details of the book which is to be edited, and whose Id was passed in the URL. The top line of code prevents the browser (especially IE) from caching the result, which is output using the JSON helper. Once the JSON has been received by the calling block of jQuery, it is used to populate the edit form, and finally the dialog command is used to invoke the form in a dialog box. The code that controls the edit form once it has been created is as follows:

$('#dialog-form-edit').dialog({
    autoOpen: false,
    modal: true,
    height: 425,
    width: 475,
    buttons: {
        'Edit Book' : function(){
            $.ajax({
                type: "POST",
                url: $("#edit-book-form").attr('action'),
                data: $("#edit-book-form").serialize(),
                dataType: "text/plain",
                success: function(response) {
                    $('#dialog-form-edit').dialog('close');
                    $("#grid").load('/Default/?page=' + page + ' #grid', function(){
                    var id = $('#edit-bookId').val();
                        $('#' + id).parent('td').parent('tr')
                        .effect("highlight", {}, 2000);
                    });
                },
                error: function(response) {
                    alert(response);
                    $('#dialog-form-edit').dialog('close');
                }
            });
        },
        Cancel: function() {
            $('#dialog-form-edit').dialog('close');
        }
    }// end buttons
});

This is much the same as the add form in the previous article. The data in the form is sent to Methods/UpdateBook, which takes care of ensuring changes are committed to the database:

@{
    if(IsPost){
        var db = Database.Open("Books");
        var sql = @"UPDATE Books SET Title = @0, ISBN = @1, Description = @2, AuthorId = @3, 
                    CategoryId = @4, DatePublished = @5 WHERE BookId = @6";
        var title = Request["title"];
        var isbn = Request["isbn"];
        var description = Request["description"];
        var authorId = Request["authorId"];
        var categoryId = Request["categoryId"];
        var datePublished = Request["datePublished"];
        var bookId = Request["bookid"];
        db.Execute(sql, title, isbn, description, authorId, categoryId, datePublished, bookId);
    }
}

If that operation was successful, the code makes an AJAX request for Default.cshtml (the page that the code is actually in) and passes a querystring value for the WebGrid page it wants to display. This value was obtained from the paging link via jQuery if one was used, otherwise it is assumed that the grid is still on page 1:

var page = 1;
$('tfoot a').live('click', function () {
    page = $(this).text();
});

Once the revised grid, complete with updated book details appears, the row containing the Id of the book that was updated is located, and the highlight effect is applied to the whole row.

The code is available as a sample site at GitHub

 

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

6 Comments

- Craig

This is a great article! Exactly what I needed. Could you please also describe how to add validation, with both the unobtrusive client validation library, as well as the Razor Validation helpers? I have not been able to get the @Html.ValidationMessage Razor helpers to work on app of mine that uses the techniques described here.

- Alan

Great article, I followed most everything but the following line
$('#' + id).parent('td').parent('tr'). What does this do? I do not understand $('#' + id) say I have an id of 142 this will give a selector of value #142. I do not have this in the program.

- Mike

@Alan,

It gets the id of the current item. If you followed the code properly, the only way you will get an id of 142 is if you have an item with that value as its id.

- Jorge

Hi, I tried to run this example using the latest version of jQuery (1.11.2) and have not been solved when changing page in the grid.

I changed the following code:

$('tfoot a').live('click', function () {
page = $(this).text();
});

By the following:

$('tfoot a').on('click', function () {
page = $(this).text();
});

I just changed the .live by .on

The result is that selecting a page, enter only the first time and then reenters no more. The page number is not retained.

- Jorge

Hi Mike, tell him I've solved the problem. The problem is generated from an extra parameter preventing retention of historical parameters.
By using the known function for parameters from href performed the following changes:

//still need to retain sort and sortdir

var sort = 'null';
var sortdir = 'null';
var page = 1;
$('#grid').on('click', 'tfoot a', function () {
page = $(this).text();
$('#grid').load('/Component/?sort=' + sort + '&sortdir=' + sortdir + '&page=' + page + ' #grid');
return false;
});

$('#grid').on('click', 'thead a', function () {
var url = ($(this).attr('href'));
sort = getURLParameter(url, 'sort');
sortdir = getURLParameter(url, 'sortdir');
$('#grid').load('/Component/?sort=' + sort + '&sortdir=' + sortdir + '&page=' + page + ' #grid');
return false;
});

function getURLParameter(url, name) {
return (RegExp(name + '=' + '(.+?)(&|$)').exec(url) || [, null])[1];
}

Do not know if this is the best implementation, but it works great for me.
Thank you very much for your website, I found it very helpful to solve my doubts.
Regards

Jorge Fraga

- Jim

Hello MikeThank you for all your hard work on this and other sites.
I have used this project,enhanced a little, for my own little numbers because it is so very good. I would like to make use of the AJAX Accordion to show master/detail records. I find,however, that when I add the relevant script references, it blows up the basic functionality. I was hoping to incorporate the methodology you show in - jqueryAccordian-Master.
Can you please suggest an alternative approach or am I missing something?
Kind regards
Jim

Recent Comments

Ghazanfar 30/01/2016 06:43
In response to Getting Started with ASP.NET MVC 5 using Visual Basic
Nice working. Please keep it up to convert csharp code into vb.net. Its very helpful for vb...

sara 29/01/2016 09:39
In response to Simple Login and Redirect for ASP.NET and Access
Hi there, I am trying to validate and check for inputs entered. When I don't enter any inputs and...

Martin Thatcher 28/01/2016 17:28
In response to MVC 5 with EF 6 in Visual Basic - Advanced Entity Framework Scenarios
A small typo I think. In the code section that begins Function Index(ByVal SelectedDepartment As As...

Suresh_thefame 28/01/2016 08:03
In response to Sessions and Shopping Carts
Helpful....

Andrey Kurdyumov 28/01/2016 05:47
In response to ASP.NET 5: Uploading files with ASP.NET MVC 6
@Lee IFormFile has OpenReadStream(): Stream method...

Alisa 27/01/2016 18:37
In response to A Better Way To Export Gridviews To Excel
Thanks for this example! I am also asking about the date fields having to be re-formatted when the a...

Dan Buckley 27/01/2016 00:47
In response to What ASP.NET Can And Cannot Do
New to all things programming, this was very helpful and clear. Please write more....

satyabrata 26/01/2016 16:16
In response to Request.Form Is Empty When Posting To ASPX Page
Thank you....

david sanchez 26/01/2016 09:51
In response to ASP.NET MVC 5 with EF 6 - Working With Files
Thanks! This line " <img src="~/images/@Model.FilePaths.First(f => f.FileType == alt="" />" an...

Bryon 25/01/2016 15:06
In response to Windows Authentication With ASP.NET Web Pages
Was hoping this would help solve the issues I'm having. Then I saw the dates and new it was too old....