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

MVC Form Helper

by Seth 23. July 2008 20:50

Moving away from ASP 3.0 Request.Form(" ...

As mentioned in my previous post, I wanted to find a way to auto-populate and entity object from the Request.Form collection. I also wanted (as an aside) to see if the grid still worked on MVC Preview 4 (it does). Here is the code:

public static T Request<T>(this Controller controller) where T : class, new()
{
    T o = new T();
    var request = controller.Request;
    foreach (var property in typeof(T).GetProperties())
    {
        // Check if the object has the appropriate property
        var q = (request.Form.AllKeys
            .Where<string>(s => s.ToLower() == property.Name.ToLower()))
            .ToList<string>();

        // if more than one... ignore
        if(q.Count == 1)
        {
             string datum = request.Form[q[0]];
             if (property.PropertyType == typeof(string))
                 property.SetValue(o, datum, null);
             else
                 property.SetValue(o,
                    Convert.ChangeType(datum, property.PropertyType),
                    null);
         }
    }

    return o;
}

Notice that this is not an extension on the HtmlHelper since this will only be used inside the controller (hence a controller extension method). Here is how it is used:

Student s = this.Request<Student>();

Now for the explanation! The constraints on T (line 1, the where part) say that T must be a reference type and have a parameter-less constructor. I guess it doesn't have to have a parameter-less constructor but I thought it would be a pain to find out which request key matched up with which parameter in the constructor. I also thought that Entity classes should be POCO objects so they would naturally have an empty constructor. Some screenshots:

Before:

image

After:

image

The Grid

I am continuing to work on the grid (this was a part of it). I just haven't gotten around to implementing the next thing all the way. The goal again was to have more control over how the grid should be rendered. I am borrowing some of the ideas from the MVCContrib grid but simplifying it a whole lot (for simple users like myself)

MVC Preview 4

I uninstalled preview 3 and then installed preview 4 and shortly after found that VS could not open my preview 3 stuff. Instead of rigging it, I just re-created the MVC part. The Controls assembly (the one with the grid) worked perfectly after correcting the references to the MVC dll's.

Tags: ,

ASP.NET | 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

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