JQuery MVC Form Helper

by Seth 31. July 2008 01:53

Simple Form Helper

As I continue to use JQuery and MVC I am completely impressed with how much you can do with very little. I know the new preview 4 came out with an AjaxForm helper. I could not resist, so I made my own and added it to the whole controls project I've been working on. (I have not forgot about the grid, but I've gotten a little bored of it for the time being although I do want to finish it...). So here is the idea:

  1. Have complete control over HOW the form is rendered
  2. Be VERY light (I attached the whole thing to the onSubmit attribute on the form)
  3. Make it fast (it took me like a half an hour or so)

Using it

I found this nifty way of using an action delegate that "reads between the lines." The motivation came from the MVCContrib grids ability to completely define inline ASP.NET looking code and pass it into the helper. Here is how you use it (for some reason I find it useful to see how it works first, and then explain how it works):

<% Html.JQueryForm("studentForm",   
      c => c.EditStudent(),   
      (ViewData.Model as IEnumerable).First(),    
      s =>    
      { %>   
          <%= Html.TextBox("Id", s.Id.ToString()) %>   
          <%= Html.TextBox("FirstName", s.FirstName) %>   
          <%= Html.TextBox("LastName", s.LastName) %>   
          <%= Html.SubmitButton("Submit", "Submit") %>  
    <% }  
   );   
%>

Parameter explanation:

  1. The name to give the form
  2. The action that should be invoked on the controller
  3. The POCO object that holds the data
  4. What to render (the Action delegate that says take a student S and make Html textboxes)

The types passed in are the POCO data object and the controller that will get the action.

Building it

I really just copied a lot of what I have already done with the grid (see previous posts) and made it more lightweight. First the helper:

public static void JQueryForm<T, TController>(this HtmlHelper helper, 
	string name, Expression<Action<TController>> editAction, 
	T data, 
	Action<T> block) 
	where T : class
	where TController : Controller
{
	Form f = new Form(name, helper.BuildUrlFromExpression<TController>(editAction), 
		helper.ViewContext.HttpContext);
	f.RenderOpen();
	block.Invoke(data);
	f.RenderClose();
}

Notice how simple it really is! Using the helper, I build a url from the controller expression, and then pass everything else into a Form object that only does 2 things: render the opening form tag, and render the closing form tag. The only catch is rendering the correct JavaScript code in the onSubmit attribute of the form tag. The rest was simple. Notice on line 4 above the delegate that takes the markup. This markup is rendered on line 11. For the "hard" part, I just copied the JQuery Ajax call from the Grid Control I have been working on. Here is the gist of it:

var formData = $(this).serializeArray();
$.ajax({
     type : 'POST',
     contentType : 'application/x-www-form-urlencoded',
     url : 'edit ACTION here',
     data : formData,
     dataType : 'json',
     success : function(msg){   
         alert('Complete! ('+msg+')');
     }
});
return false;

The return false is there to prevent the form from posting back.

Screenshot

Here are some screenshots of the whole thing:

The form rendered:

image

The postback:

image

Notice that I used the Request helper I built in a previous post. In order to do this, I am required to name the textboxes with the same name as the property of the object. This is how the helper resolves that appropriate attribute.

Code

Here is the code. You will find fragments of me starting to do the grid code as well.

Tags: , ,

Ajax | JQuery | MVC

Ajax HTML Grid Control for ASP.NET MVC (Part 3)

by Seth 10. July 2008 20:24

Moving to an HTML Helper

As I was looking around at the various HTML helpers out there, I realized that I should probably conform and stick to what is being done. Having said that, I refactored the Grid class to be a new HTML helper. Here is how it is used (similar to an MvcForm<T> actually):

<% using (Html.GridControl<StudentController, StudentEntity, int>(
   s => s.EditStudent(), 
   s => s.DeleteStudent(), 
   ViewData.Model, 
   s => s.StudentId, 
   "Student",
   null)) {  %>
Special text here!
<% } %>

The GridControl has now been attached as an extension method to the standard HTML Helper. Doing that required a bit of re-arranging on the parameters that needed to be passed in to the helper. The first are the types being used:

  1. The first type is the Controller being used for updates
  2. The second is the type of item in the collection
  3. The last is the data type of the key of each item

For the parameters:

  1. The first specifies the controller action when an item is edited
  2. The second specifies the controller action when a item is deleted
  3. The third is the actual Collection object
  4. The fourth tells the GridControl how to generate the key for each item
  5. The fifth is the name to use for the control
  6. The last specifies any additional html attributes to use (not implemented currently)

I really liked using the action expressions since it allows a better granularity in handling the actual Ajax requests to the controller. I used the this code to generate the appropriate URLS (strings):

_editAction = helper.BuildUrlFromExpression(editAction);
_deleteAction = helper.BuildUrlFromExpression(deleteAction);

An important thing to note is that the grid is actually printed out when the helper is disposed. This means that anything that is placed inside of the using brackets will be shown before the grid is displayed.

grid

Getting Data to the Controller

Now for the actual work! The first thing I wanted to ensure was that I sent the data back to the controller via a POST and not a GET method. The $.getJSON function in JQuery performs a GET (see post) so I needed to change that. Here is what the grid produced for the save and delete actions:

else if(action == 'save')
{
    var arr = $('#Student_Form').serializeArray();
    $.ajax({
       type: 'POST',
       contentType: 'application/x-www-form-urlencoded',
       url: '/Student/EditStudent',
       data: arr,
       dataType: 'json',
       success: function(msg){
         alert( 'Data Saved: ' + msg );
       }
     });
}
else if(action == 'delete')
{
    var dat = 'DataId=' + id;
    $.ajax({
       type: 'POST',
       contentType: 'application/x-www-form-urlencoded',
       url: '/Student/DeleteStudent',
       data: dat,
       dataType: 'json',
       success: function(msg){
         alert( 'Delete: ' + msg );
       }
     });
}

Notice that this is a continuation of the JavaScript from the previous post. The fundamental JQuery call for AJAX is $.ajax(properties) where the properties are the specifications on how the AJAX call should be made. The first and most important thing was to use POST rather than GET. A GET http call is supposed to be idempotent while a POST call can actually change the state of the server. Both the delete and save actions should change the state of the server. Also, from a security standpoint, it is always easier to craft some form of URL to cause an action to execute on a controller when using GET. This can open things up to malicious attacks. Imagine anyone simply typing http://yoursite/Student/DeleteStudent?DataId=1232 into their browser and the controller deleting that record. That would be bad. With a POST this can be controlled better.

My favorite part of the JQuery code is the .serializeArray() part. This takes each of the valid form members and automagically places the data in a JSON array structure to be passed to the controller.

Getting the Data in the Controller

Here is a nice screenshot of what happens when the controller save action is fired:

postback

The AJAX postback, in conjunction with the .serializeArray() call placed all of the items of the StudentEntity in the Request.Form collection. Now for the ugly code (reminiscent of ASP 3.0)

public JsonResult EditStudent()
{
	StudentEntity student = new StudentEntity();
	student.StudentId = int.Parse(Request.Form["StudentId"]);
	student.Address = Request.Form["Address"];
	
	int age;
	if (int.TryParse(Request.Form["Age"], out age)) student.Age = age;
	student.FirstName = Request.Form["FirstName"];
	student.LastName = Request.Form["LastName"];
	student.Phone = Request.Form["Phone"];
	student.State = Request.Form["State"];
	student.Zip = Request.Form["Zip"];
	
	StudentService.SaveStudent(student);
	
	return new JsonResult
	{
		 Data = "Edited student " + student.StudentId.ToString() + "!"
	};
}

The result sent back is a simple message. More could be done here, but I have not thought about it too much. I also dislike the whole Request.Form["val"] stuff. I think this could be automated using the grid somehow so we can get rid of the tedious code. Actually I think adding a generic static method to the Grid class should do the trick. I will probably add it next time.

Things to do

So far things seem to be going well. Here are some things I would like to add:

  1. Better entity population from the Request.Form collection
  2. An "Add New" feature to the grid
  3. On delete of a row, remove the TR element from the table (the database thinks it is gone, but the HTML says otherwise)
  4. CSS Grid customization

If you can think of any other things drop me a line.

Code

The Code

Tags: , , ,

Ajax | ASP.NET | JQuery | MVC

Ajax HTML Grid Control for ASP.NET MVC (Part 2)

by Seth 8. July 2008 00:11

Preamble

Over the long weekend I thought a lot about where I wanted to go with the grid "control" for ASP.NET MVC. One of the things that weighed heavily on my mind was the ability to have the control fully customizable. As I thought about this, I decided that first thing's first: I need to get the functionality working and then worry about the prettiness factor. So for those of you concerned about the customizability - it's coming. For now, I really want to focus on the ability of the grid to get work done.

Code Bloat

As I began to dive again into the previous code I realized that having everything in one extension method was going to be... well ugly. The first order of business was to abstract everything in to a separate DLL. In order to do so, I created a new Grid class that handles all of the grid drawing.

JavaScript

I am partial to using JQuery. So here is the general idea of what the JavaScript code needs to do:

  1. Detect that the user would like to edit a particular row
  2. Detect any rows previously being edited (to move the edit focus to the new row being edited)
  3. Display text boxes for the user to have the ability to edit the values
  4. Post back an edit or a delete

In order to do each of the items above, I needed to change what the grid was outputting a little. First I needed to wrap the table in a <form> tag in order to retrieve any valid values being edited. Also, I needed to add the following links: Edit, Delete, Cancel, Save. The first two (Edit, Delete) should be shown when a row was not in edit mode. The second two (Cancel, Save) should only be shown when a row is in edit mode. I also wanted to make sure that only one row was being edited at a time. Here is a fragment of the HTML the control produces:

<form name="Student_Form" id="Student_Form">
<table id="Student" style="border: solid 1px black;width:100%;">
<tr id="Student_6">
    <td class="edit">
    <span class="editor">
       <a id="6_Student_edit" href="#">Edit</a>  <a id="6_Student_delete" href="#">Delete</a>
    </span>
    <span class="editing">
       <a id="6_Student_cancel" href="#">Cancel</a>  <a id="6_Student_save" href="#">Save</a>
    </span>6</td>
    <td class="FirstName">Frances</td>
    <td class="LastName">Adams</td>
    <td class="Age">&nbsp;</td>
    <td class="Address">&nbsp;</td>
    <td class="City">&nbsp;</td>
    <td class="State">&nbsp;</td>
    <td class="Zip">&nbsp;</td>
    <td class="Phone">&nbsp;</td>
</tr>
...

Once I decided on the DOM elements the form would produce, it was time to put some JavaScript to each row. The first bit of code is designed to attach events to each of the links in the grid as well as hide the "editing" spans since each row by default starts in non-edit mode:

$('document').ready(
    function(){
        $('#Student span.editing').hide();
        $('#Student a').click(
            function(event){
                event.preventDefault();
                handleEditClick(this.id);
            }
        );
    }
);

This code ensures that the editing spans are hidden and each click event on the anchor tags are redirected to the handleEditClick function. Notice that I pass in the id of the actual anchor tag. These I defined as id_Grid_action (see above). Whenever a link is pushed I get those three pieces of information to proceed with processing. Now for the handleClick function (it is long):

function handleEditClick(itm) {
    var o = itm.split('_');
    var id = o[0];
    var grid = o[1];
    var action = o[2];
    var name = '#' + grid + '_' + id + ' td';

    if(action == 'edit')
    {
        // un-edit any others that might be in editmode
        $('#Student span.editor:hidden a:first').each(
            function() {
                var cl = this.id.split('_');
                $('#' + cl[0] + '_' + cl[1] + '_cancel').click();
            }
        );

        $('.editor', name).hide();
        $('.editing', name).show();

        $(name).each(
            function() {
                if($(this).hasClass('edit')) return;
                var data = $(this).text() == ' ' ? '' : $(this).text();
                $(this).html('<input type="text" name="' + 
					$(this).attr('class') + 
					'" value="' + 
					data + 
					'" size="10" />');
            }
        );
    }
    else if(action == 'cancel')
    {
        $('.editor', name).show();
        $('.editing', name).hide();
        $(name).each(
            function() {
                if($(this).hasClass('edit')) return;
                var data = $('input', this).val();
                $(this).html(data == '' ? 'nbsp;' : data);
            }
        );
    }
    else if(action == 'save')
    {
        //alert('Save ' + itm);
        var arr = $('#Student_Form').serializeArray();
        $.each(arr, 
            function(i, field) {
                alert(field.name + ': ' + field.value);
            }
        );
    }
    else if(action == 'delete')
        alert('Delete ' + itm);

As advertised, I first break up the id_gird_action pair into variables that will be useful. Also notice that ALL actions come into this function. First lets focus on the edit action. Now this is why I love JQuery:

$('#Student span.editor:hidden a:first').each(...

This particular line of code selects the first anchor tag under a span with class editor that is hidden from the Student grid. Why would I want to do that? From the id of the anchor tag I can reconstruct the id of the cancel anchor tag and then "click" it in order to cancel the update. To do it any other way would be difficult (at least I think so). The elegance of JQuery allows for those kinds of things. Once we cancel any other edit, we proceed to go through each TD in the row in question (with the exception of the edit anchors) and push the data into text boxes. Also, we set the appropriate edit spans to visible and hidden in order to have the correct actions displayed. Next, the Cancel action. The job of the cancel action is to take the data out of the text boxes and stuff them back into the TD tag. I realize that I should probably add a "Do you want to save this?" confirm box, but I will leave that for later. Notice again the elegance of JQuery:

var data = $('input', this).val();

The $(INPUT, TD) functions as a selection within a previous selection. In other words, within the current TD, find me an INPUT HTML element and retrieve the value. Once we retrieve the value, we can put it back into the TD tag without the INPUT element.

The Grid Class

The first thing I did was write the JavaScript code with the output from the Grid class. In other words, I ran the flat table (without the JavaScript) and cut and pasted the table to a standard HTML file. Once I had the new HTML file I worked on the JavaScript until it worked as I expected. Now the only problem left was creating  a RenderJavaScript() that emitted the Grid specific JavaScript we needed. Doing that seemed a bit tedious so I downloaded a nifty little Add In that did it for me.

Outcome

Some screens:

image

Usage

Now that I've abstracted the Grid out to a completely separate project, I changed the helper class to this:

public static string ToAjaxGrid<T, TKey>(this IEnumerable<T> list, 
                                 Func<T, TKey> key, 
                                 string name)
{
   Grid<T, TKey> grid = new Grid(list, key, name);
   return grid.Render();
}

This change allowed me to leave the code in the View the same:

<%= ViewData.Model.ToAjaxGrid(s => s.StudentId, "Student") %>

Next Time

I think the client side functionality is (mostly) done. For the next installment I will try to actually submit the requested actions to the MVC controller (save, delete).

Code

Download

Tags: , , ,

Ajax | ASP.NET | JQuery | MVC

About the author

356044 My name is Seth Juarez. I currently reside in Salt Lake City and develop web applications for my church.

I received my Bachelors Degree in Computer Science at UNLV with a Minor in Mathematics. I recently completed my Masters Degree at the University of Utah and am continuing on to a PhD in the field of Computer Science. I currently am interested in Artificial Intelligence specifically in the realm of Machine Learning. I currently am working on a .NET library meant to simplify the usage of the common machine learning algorithms.

I've been married now for 8 years to a fabulously beautiful girl and have two wonderful daughters and a son.

RecentComments

Comment RSS