WebMatrix And jQuery Forms

Even though WebMatrix is still in its first Beta, there have been a few requests in forums already for examples illustrating how to create Web Pages forms powered by AJAX. Building on previous articles, I thought I'd put together a little sample that shows how to do this to add a record to a database using jQuery. Here's how it's done.

First of all, if you are new to web development, AJAX is Asynchronous Javascript and XML. It was so named after Google's use of the technology to power Google Mail, and although Google used XML in the early days, the more popular means of transmitting data between client and server (and vice versa) these days is HTML, plain text or Javascript Object Notation (JSON). In short, however, AJAX uses Javascript behind the scenes to send asynchronous requests to the server, and to update parts of a web page according to the response. The key benefit of this approach is that it avoids full page post backs and refreshes. This can offer the user a much smoother experience than they would otherwise be treated to. Probably the most popular Javascript library is jQuery. It's been designed to provide simple to use wrappers around complex Javascript functionality, which makes it relatively easy for beginners to create fantastic effects with little Javascript knowledge. In fact, jQuery is so impressive that Microsoft ditched their own client-side javascript library in favour of supporting jQuery.

If you are not new to web development, apologies for not advising you to skip the previous paragraph...

At the moment, jQuery is part of the template for a new MVC or Web Forms application if you are using Visual Web Developer or Visual Studio. A Scripts folder is autogenerated and populated with a variety of javascript files, including a version of the jQuery core library and validation library. The Beta version of WebMatrix doesn't include this convenience, even though jQuery is required to take advantage of the AJAX sorting and paging offered by the WebGrid helper. Whether this will change in a future release is not known (by me, at least...) at the moment. Consequently, if you are playing along while reading this article, you need to obtain the jQuery files yourself (although they are available in the download provided at the end of this article).

The first file you will need is the latest jQuery library, and then you need to navigate to the UI download part of the site. Without giving too much away, choose all of the UI Core parts, and then choose Draggable and Resizable from the Interactions section, followed by Button, Dialog and DatePicker from Widgets. Finally, select Highlight from the Effects section, which will ensure that Core is automatically also selected for you. I left the theme as the default UI Lightness, although you can choose any you prefer. Once you click download, you get a little package which includes a customised js file and css for your chosen theme and components.

Now to the sample. As I mentioned before, this example simply shows how to insert a record into a database. The database itself is the Books database I built for previous articles. To keep things simple, I shall just focus on the core parts of the "application" to illustrate how things are done, without any real validation or error handling. We can always look to enhance the pages at some other time. To start with, I will give a quick overview of how this sample works.

The main part of the sample is a single page called Default.cshtml, which contains a data entry form for adding books, and a grid to display the current data. A button will invoke the form in a modal dialog, and when the form has been filled and submitted, the grid will refresh to show the new book entry at the top of the page of data. The grid itself is generated from a second file called BookGrid.cshtml, which is included in Default.cshtml using the RenderPage() method introduced here. The method for adding an entry into the database is also housed in a separate file for convenience.

Here's the code for the data entry form in Default.cshtml:

    <div id="dialog-form-add" title="Add New Book">
    <form id="add-book-form" action="@Href("~/Methods/AddBook")">
         <div class="row">
                <span class="label"><label for="title">Title:</label></span>
                <input type="text" name="title" id="title" size="50" />
            </div>
            <div class="row">
                <span class="label"><label for="isbn">ISBN:</label></span>
                <input type="text" name="isbn" id="isbn" size="20" />
            </div>
            <div class="row">
                <span class="label"><label for="description">Description:</label></span>
                <textarea cols="50" rows="8" name="description" id="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 == 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="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="datePublished" name="datePublished" />
            </div>    
        </form>
    </div>

This form is almost identical to the basic data entry form I produced for a previous article on adding and editing data in WebMatrix. There are three differences - the action attribute for the form points to a different URL /Methods/AddBook, and the form is wrapped in a div. Finally, the form has been given an ID so that it can be referenced easily using jQuery. At the top of the page, some code is added which gets data out of the database to populate the dropdown lists for Authors and Categories:

@{
    if(IsPost){
        var db = Database.Open("Books");
        var sql = "INSERT INTO Books (Title, ISBN, Description, AuthorId, CategoryId, DatePublished) " + 
            "VALUES (@0, @1, @2, @3, @4, @5)";
        var title = Request["title"];
        var isbn = Request["isbn"];
        var description = Request["description"];
        var authorId = Request["authorId"];
        var categoryId = Request["categoryId"];
        var datePublished = Request["datePublished"];
        db.Execute(sql, title, isbn, description, authorId, categoryId, datePublished);
    }
}

Next, some code is required to retrieve data for the grid, and to generate the html. This is in a file called BookGrid.cshtml, and is placed in a folder called Partials:

@{
    var db = Database.Open("Books");
    var books = db.Query("SELECT BookId, Title, ISBN, Description, " +
        "FirstName + ' ' + LastName AS AuthorName, Category " +
        "FROM Books INNER JOIN Authors ON Books.AuthorId = Authors.AuthorId " +
        "INNER JOIN Categories ON Books.CategoryId = Categories.CategoryId ORDER BY BookId DESC");
    var grid = new WebGrid(books, ajaxUpdateContainerId: "grid");                    
}
@grid.GetHtml(tableStyle: "ui-widget ui-widget-content",
                    headerStyle: "ui-widget-header",
                    columns: grid.Columns(
                    grid.Column("Title"),
                    grid.Column("AuthorName",
                                    header: "Author"),
                    grid.Column("Category"),
                    grid.Column("ISBN")
                )
    )

You should notice a couple of things. First, the grid is Ajaxified in that the ajaxUpdateContainerId property is specified in the constructor. More about this and the WebGrid helper itself can be found here. Second, the table and header styles have been taken from the jQuery theme css. You'll see how that looks a bit later. But essentially, all this file does is to render an html <table> with data in it. This file needs to be referenced from within the Default.cshtml file so that the rendered table can be included together with a button for adding new books:

<div id="grid" class="ui-widget">
    @RenderPage("~/Partials/BookGrid.cshtml")
</div>
    
<p><button id="add-book">Add New Book</button></p>

If you run the page now, it will look a mess. We need to reference some CSS files and the jQuery files, and then use jQuery to work some magic. First, in the head area of Default.cshtml, this gets added:

<title>Manage Books</title>
<link type="text/css" href="@Href("~/Styles/jquery-ui-1.8.4.custom.css")" rel="stylesheet" />
<link type="text/css" href="@Href("~/Styles/StyleSheet.css")" rel="stylesheet" />
<script type="text/javascript" src="@Href("~/Scripts/jquery-1.4.2.min.js")"></script>
<script type="text/javascript" src="@Href("~/Scripts/jquery-ui-1.8.4.custom.min.js")"></script>

Now to the main bulk of the jQuery. All the code appears here, followed by an explanation for it

$(function () {
    $('#dialog-form-add').dialog({
        autoOpen: false,
        modal: true,
        height: 425,
        width: 475,
        buttons: {
            'Add Book': function () {
                $.ajax({
                    type: "POST",
                    url: $("#add-book-form").attr('action'),
                    data: $("#add-book-form").serialize(),
                    dataType: "text/plain",
                    success: function (response) {
                        $('#dialog-form-add').dialog('close');
                        $("#grid").load('/Default/ #grid', function () {
                            $('tbody > tr:first')
                        .effect("highlight", {}, 2000);
                        });
                    },
                    error: function (response) {
                        alert(response);
                        $('#dialog-form').dialog('close');
                    }
                });
            },
            Cancel: function () {
                $('#dialog-form-add').dialog('close');
            }
        }
    });
    $('#datePublished').datepicker({ dateFormat: 'yy-mm-dd' });


    $('#add-book')
    .button().click(function () {
        $('#dialog-form-add').dialog('open');
    });
});

The first part of the code creates a modal dialog from the contents of the dialog-form div. This has the effect of hiding it from view when the page is requested. Some options are set in terms of the height and width of the resulting modal dialog, along with an argument that tells the form not to open automatically. Then the buttons on the modal form are added. One is called Add Book, and on clicking that, jQuery is used to serialise the contents of the form and post it asynchronously to the Methods/AddBook URL. This URL actually resolves to a file called AddBook.cshtml in a folder called Methods. We'll look at what that does later. If this AJAX call is successful, the modal form is closed, and the div with the id of "grid" is loaded with just the right piece of html obtained as a result of asynchronously requesting the Default.cshtml file. The jQuery Highlight effect is applied to the newly added book. The Cancel button is simply wired up to ensure the modal form closes.

There are just two remaining jobs for jQuery. One is to apply the jQuery Datepicker to the Date Published input so that you can control the format that data is entered into this box. And lastly, the Add Book button is given a bit of a jQuery makeover to look nice, before having its click event wired up to open the modal form. Really all quite simple.

This is how the page looks on first request:

 

On clicking the Add New Book button, the modal form appears:

And finally, when the Add Book button is clicked, the Methods/AddBook.cshtml file is requested with the form values passed in the Request.Form collection:

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

This code checks to make sure that it is being requested as a POST, and then searches the Request.Form collection for the values it expects. Having obtained them, they are passed as parameter values into the SQL which adds the new book to the database.

That's it. jQuery form, simply done. However, as I stated at the beginning of the article, much more should be done in a proper application. For example, the values passed in to AddBook.cshtml chould be validated to ensure that they are of the correct type and within expected ranges. This should be done on the server, and as a convenience to users, some validation should also be provided within the jQuery form itself. In a future article, I shall concentrate on this aspect of web development in more detail. In the meantime, feel free to experiment with the code provided in the download here.

Date Posted: Tuesday, August 10, 2010 5:23 PM
Last Updated: Friday, October 31, 2014 12:58 PM
Posted by: Mikesdotnetting
Total Views to date: 48708

12 Comments

Tuesday, August 10, 2010 6:11 PM - infocyde

Cool post, thank you!

Wednesday, August 11, 2010 5:08 PM - Vinz

Great post Mike!

Friday, August 13, 2010 3:11 PM - EinarT

Great tutorial, but I got an error in jquery-1.4.2-min.js when clicking "Add book" in the Ajax form:

Line: 144
Error: Invalid property value.

a.elem.style[a.prop]=(a.prop==="width"||a.prop==="height"?Math.max(0,a.now):a.now)+a.unit

a.now is NaN

I use IE-8.

Friday, August 13, 2010 6:05 PM - Mike

@EinarT

That's something to do with the animate() calls in the jQuery. I must confess that I kind of threw that that bit together and didn't test it in IE. I'd have to investigate a bit more, but in the meantime, you can comment out the animate() calls. I suspect there are loads of better ways to achieve the effect I was after anyway.

Saturday, August 14, 2010 9:57 AM - Mike

@EinerT

I've updated the download to remove the animate calls and replace them with the following:

.effect("highlight", {}, 2000);


I needed to redo the custom jQuery UI download to include Effect "Highlight" and the revised jQuery file is also part of the download.

Should have used Effects in the first place.... ;o)

Tuesday, August 17, 2010 3:20 PM - EinarT

I downloaded the new file and opened and tested it OK with WebMatrix.

There is one show-stopper I think is only related to Ajax and not WebMatrix. I tried to change in BookGrid.cshtml the cansort to true. Then when I added a book and tried to sort, the grid disappeared. Is it possible to have the cansort option when addig records?

I tried to make the program more general adding a parameter:

var myTab = "book";

and using it like this:

@RenderPage("~/Partials/Add"+myTab+"Form.cshtml")

because I have many lookup tables that should have been handled the same way in a CRUD program. But then I see that all the partials regarding one table should have been gathered in a single file. Maybe it will be possible to render a part of a file, for example one <div> paragraph in a file with paragraphs for form, grid and sql.

Tuesday, August 17, 2010 5:25 PM - Mike

@Einar

Paging doesn't work as-is either. Change the line in the success callback from:

$("#grid").load('/Partials/BookGrid/', function(){

to:

$("#grid").load('/Default.cshtml #grid', function(){

I'll update the code in the article and the download as soon as I can. Thanks for spotting that. I hadn't noticed before that the designers of WebMatrix had chosen to use the jQuery load command with the data parameter before: http://api.jquery.com/load/.

Tuesday, August 17, 2010 9:01 PM - EinarT

Since the JQuery .load can load a partial page, I will suggest to have all about Books in a single page. That would be the grid, edit form and the AddBook.cshtml. Then I would create similar files for editing Authors and Categories. Then the default.cshtml becomes a general page for browse and CRUD functions where the selected table is a parameter. But I am still not sure about how to implement the AddBook functionality if all about Books is to be in a single file. CAn you give me a hint about that?

Wednesday, August 18, 2010 9:49 AM - Mike

@Einar

I think I would still have the AddBook stuff in a separate file. As I get time, I'm looking to extend the example to show editing with jQuery dialogs.

Sunday, January 23, 2011 9:57 AM - Jure Cotic

I've edited the example to suit me and would like more functions. Did you have the time to extend the example with the edit function?

Jure

Sunday, January 23, 2011 10:14 AM - Mike

@Jure

It's on my TODO list, but I'm currently writing a book on WebMatrix which is taking a lot of my time at the moment. I can't promise very many new articles in the meantime.

Wednesday, January 22, 2014 2:24 PM - emre arikan

Mike,
the code you kindly provide contains the statement;
var db = Database.OpenFile("Books.sdf");
doesnt kick in anymore
special thanks
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.

Recent Comments

Gautam 11/20/2014 8:01 AM
In response to I'm Writing A Book On WebMatrix
Hello Mike, I read your book, loved it! However, I have a few request/suggestions: 1) an example...

Bret Dev 11/19/2014 8:39 PM
In response to The Difference Between @Helpers and @Functions In WebMatrix
Excellent post! One concern - where can you place global @Functions code within an MVC project to Is...

Rob Farquharson 11/19/2014 4:28 PM
In response to iTextSharp - Links and Bookmarks
How can I place text at an absolute position on the page? Also, how can I rotate text?...

Andy 11/17/2014 8:08 PM
In response to MVC 5 with EF 6 in Visual Basic - Sorting, Filtering and Paging
Hello I'm testing your sorting instructions above. This is great and I was able to get it to work...

Gautam 11/17/2014 5:51 PM
In response to WebMatrix - Database Helpers for IN Clauses
Hi Mike, I am very new to programming: In the above example if I want to use a delete button the...

donramon 11/17/2014 3:22 PM
In response to Entity Framework 6 Recipe - Alphabetical Paging In ASP.NET MVC
Congratulations on your new website look and the excellent articles. Thank you!...

Gautam 11/17/2014 11:26 AM
In response to Looking At The WebMatrix WebGrid
Hi Mike, I add the jquery script at the end of my html file.. when ajax attribute is added to the be...

Chet Ripley 11/15/2014 6:57 PM
In response to Adding A New Field
It appears the command is case sensitive. I had the same issue as Cameron. When I changed the to it...

Alvin 11/14/2014 12:49 PM
In response to Razor Web Pages E-Commerce - Adding A Shopping Cart To The Bakery Template Site
Great article Mike! When do you plan to extend the bakery shopping cart beyond this point?...

Gautam 11/14/2014 10:16 AM
In response to Web Pages - Efficient Paging Without The WebGrid
to get the count can we use only the below sql, why to join category and author table var sql =...