WebMatrix and jQuery Forms Part 2 - Editing Data

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: 21940

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

Gayan 7/3/2015 6:20 AM
In response to 7 C# 6.0 Features That Every ASP.NET Developer Should Know About
Great Article thanks...

Semil 7/1/2015 7:03 AM
In response to iTextSharp - Drawing shapes and Graphics
I have created a rectangle using above methode. Now I want to add a text in the center of this How I...

Satyabrata Mohapatra 6/30/2015 6:12 PM
In response to Reading Excel Files Without Saving To Disk In ASP.NET
Ahh.....this is awesome. Happy to see after a long time you wrote a article on web form :D...

Marty 6/30/2015 7:16 AM
In response to Posting Data With jQuery AJAX In ASP.NET Razor Web Pages
Mike, you're the Man! Another great article. So incredibly helpful. I'm definitely going to buy your...

Rohan 6/30/2015 5:32 AM
In response to ASP.NET MVC 5 with EF 6 - Working With Files
Very good and helpful tutorial. Thanks. Just wanted to know what would be the max file size limit we...

Fernando 6/30/2015 1:59 AM
In response to Programmatically accessing data from DataSource controls
What if I want to pass parameters natively using the DataSourceSelectArguments object, instead of be...

pankaj 6/29/2015 3:13 PM
In response to How to retain carriage returns or line breaks in an ASP.NET web page
very nice i'm use this in my code thank you.... ...

Mike 6/29/2015 2:22 AM
In response to MVC 5 with EF 6 in Visual Basic - Sorting, Filtering and Paging
This is the first example that I have found that works....

Marty 6/28/2015 4:57 AM
In response to Posting Data With jQuery AJAX In ASP.NET Razor Web Pages
Mike, what if I don't want to render back the text to the browser, but I want to send it some other...

Mike 6/27/2015 4:00 PM
In response to Migrating Classic ASP To ASP.NET Razor Web Pages Part One- Razor Syntax And Visual Basic
have you used any of the code converters to convert classic asp to c#? If so, which one do you have...