jQuery and MVC Part 2

By James at November 15, 2010 17:46
Filed Under: JavaScript, MVC, jQuery

In our last episode we went over the basics of JavaScript and the jQuery library. In this installment I will show you the basics of MVC and how it renders HTML differently than Web Forms pages, and finish up showing some basic Ajax with jQuery.

When ASP.NET first arrived it was a good thing. Web Forms allowed developers to build websites in a way that was very similar to how Windows Forms applications where built, with drag and drop of controls and a “code behind” architecture. Web Forms served their purpose for many, many applications in the enterprise, and many commercial applications, both large and small were successfully built using this technology.

When “Web 2.0” arrived, complete with Ajaxy interactions, Microsoft put forth their own brand of controls to handle this, and for the most part this worked, and continues to work well. However the Web Forms framework makes it difficult to build clean, lean web sites, as the technology depends on many different things in the rendered HTML to make it usable when posting back to the server. Two of the main items are ViewState and Control Rendering.

ViewState is sent to the browser as a way to capture what is on the page, and what has been changed when the request is sent back to the browser. For ViewState to work, controls on the page need to be rendered with specifically named ID’s. This makes it difficult to use JavaScript to find elements on the page by their ID. For example adding a button to a page that is using an ASP.NET Master Page will render the following HTML:

Source code:

   1: <asp:Panel ID="Panel1" runat="server">
   2:     <asp:Button ID="btnClick" runat="server" Text="Button" />
   3: </asp:Panel>

Rendered HTML:

   1: <div id="MainContent_Panel1">    
   2:    <input type="submit" name="ctl00$MainContent$btnClick" 
   3:       value="Button" id="MainContent_btnClick" />    
   4: </div>

ViewState:

   1: <div class="aspNetHidden">
   2: <input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" 
   3:    value="/wEPDwUJNjE3ODYxMzIwZGQjyMGaAJ/BFAzxJv1b+/lEXPaj4I/hCsdqNKZUozZiyw==" />
   4: </div>

If you notice you will see both the name and the id of the button has been changed to show that it is a child of the asp:Panel which is a child of the ASP.NET MasterPage. The ViewState in this example is fairly lean, but in large pages, it can become very large, and this is data the browser needs to download.

While this is all fine and dandy, trying to access these elements can be difficult, especially when trying to use jQuery’s selector mechanisims. You may think, that if you keep the name the same when traversing the DOM, will keep it good. But if for some reason the ID of the parent element changes in your code, then the names will change. There are some tricks you can use, but they are not the basis of this tutorial.

Now, let’s look at the same page when rendered with MVC:

Source code:

   1: <asp:Panel ID="Panel1" runat="server">
   2:     <input type="submit" value="button" />
   3: </asp:Panel>

Rendered HTML:

   1: <div id="MainContent_Panel1">    
   2:    <input type="submit" value="button" />    
   3: </div>

Now keep in mind that ASP.NET MVC does not typically use ASP.NET controls and in this example I am using a standard HTML input tag for the submit button.

The ASP.NET MVC Framework – What is it?

Now before we get into a big discussion about what is better and what is not, and all the underpinnings of “who moved my cheese”, MVC is yet another way to build web sites using the .NET Framework. It is a web application framework which implements the model-view-controller pattern of development. It is based on ASP.NET and allows the development of a web application to be built using three roles; Models – the data coming into and out the application, Views – the displayed pages, and Controllers – which handle the traffic coming in for HttpRequest and HttpResponse.

Microsoft released a CTP version of MVC in December, 2007, MVC version 1 in March 2009 and MVC version 2 in March 2010. They are currently working on version 3.

The default View engine for MVC is the Web Forms view engine and this view engine uses regular .aspx pages to design the layout of the user interaction pieces of the web site or web application. Instead of PostBacks any interactions are routed to the Controllers using a Routing mechanism.

A typical ASP.NET MVC application will have the following directory structure as seen in this figure:

3036.MVC-Folder-Structure_741D04C1

As you can see there are folders for Controllers, Models, and Views. Look closely and you will see that the Controllers names are associated with the folders in the Views folder. AccountController maps to Views\Account while HomeController maps to Views\Home. Views that are shared throughout the application are saved in the Views\Shared folder.

Let’s take a look.

4621.Student-Course-Admin_3568075B

The first part of the application allows the user to look up a student by ID. As we can see from this screen shot the student with an ID of 1 is Jeanine Abbott. There are many steps involved in doing this using HTML, CSS and jQuery, so let’s dive in.

Index.aspx has the following code:

   1: <div class="row">
   2:     Find student by ID
   3:     <input type="text" id="studentId" value=""  class="studentId" />
   4:     <input type="button" id="btnAjax" value="Get Student" />
   5:     <img src="../../Content/Images/ajax-loader.gif" class="ajaxLoader" 
   6:          alt="ajaxloader" />
   7: </div>
   8: <div class="row">
   9:    <div class="errorMessage"></div>
  10:    <div class="studentData">
  11:     <table>
  12:         <thead>
  13:         <tr>
  14:             <th>First Name</th>
  15:             <th>Last Name</th>
  16:             <th>Email</th>
  17:             <th>Submit Date</th>
  18:             <th>Approved</th>
  19:             <th>Approved Date</th>
  20:         </tr>
  21:     </thead>
  22:     <tbody>
  23:         <tr>
  24:             <td class="studentFirstName"></td>
  25:             <td class="studentLastName"></td>
  26:             <td class="studentEmail"></td>
  27:             <td class="studentSubmitDate"></td>
  28:             <td class="studentApproved"></td>
  29:             <td class="studentApprovedDate"></td>
  30:         </tr>
  31:     </tbody>
  32:     </table>
  33:   </div>
  34: </div>

The next piece is in the HomeController where we have a method called GetStudent(int id)

   1: private readonly CourseEntities _db = ModelHelper.CourseEntities;
   2: public JsonResult GetStudent(int id)
   3: {
   4:     var student = (from s in _db.Students
   5:                    where s.Id.Equals(id)
   6:                    select s).FirstOrDefault();
   7:  
   8:     if (student == null) 
   9:         return Json("error:Student not found.", JsonRequestBehavior.AllowGet);
  10:  
  11:     var singleStudent = new SingleStudent
  12:         {
  13:             Id = student.Id,
  14:             FirstName = student.FirstName,
  15:             LastName = student.LastName,
  16:             Email = student.Email,
  17:             SubmitDate = student.SubmitDate.ToShortDateString(),
  18:             Approved = student.Approved.ToString(),
  19:             ApprovedDate = string.Empty
  20:         };
  21:  
  22:     if(student.ApprovedDate.HasValue)
  23:         singleStudent.ApprovedDate = student.ApprovedDate.Value.ToShortDateString();
  24:     
  25:     return Json(singleStudent, JsonRequestBehavior.AllowGet);
  26: }

The third piece of the puzzle is in the CSS. Looking at the ASPX code there are two div elements with classes of “studentData ” and “errorMessage”. In the CSS for this project these two classes have been defined as:

   1: div.studentData{display:none}
   2: div.errorMessage{display:none;color:Red;font-weight:bold;}

This makes these elements, and any elements contained within them to not be displayed when the page is first rendered.

The last piece to come into play is the JavaScript and jQuery. It is important to know that when adding your script references to your page, to make sure they are in the correct order. When you are going to use jQuery in your application and additional JavaScript files, the reference to jQuery needs to be first.

   1: <head id="Head1" runat="server">
   2:     <link href="../../Content/Site.css" rel="stylesheet" type="text/css" />
   3:     <script src="../../Scripts/jquery-1.4.1.js" type="text/javascript"></script>
   4:     <script src="../../Scripts/AppScripts.js" type="text/javascript"></script>
   5: </head>

I have written an additional JavaScript file, “AppScripts.js” and included it in the Scripts folder of the application. And by adding a reference to “jquery-1.4.1-vsdoc.js” to your additional JavaScript files, Visual Studio will provide jQuery Intellisense to make it easier reference the jQuery functions and attributes.

   1: /// <reference path = "/Scripts/jquery-1.4.1-vsdoc.js"/>
   2:  
   3: $(document).ready(function () {
   4:     $("#btnAjax").click(function () { getStudent(); });    
   5: });

Going back to the first article in this series, you can see that when the document has been fully loaded into the DOM, with $(document).ready(), the element with the ID of “btnAjax” will have a click event assigned to it. The JavaScript function getStudent() is below.

   1: function getStudent(id) {
   2:     $("img.ajaxLoader").fadeIn(500);
   3:     $("div.errorMessage").fadeOut(500);
   4:     $("div.studentData").fadeOut(500);
   5:     $.ajax({
   6:         url: "/Home/GetStudent/" + $("#studentId").val(),        
   7:         type: "GET",
   8:         success: function (result) {
   9:             if (typeof (result) == "string" && result.indexOf("error:") > -1) {
  10:                 showError(result);
  11:             } else {
  12:                 showStudentData(result);
  13:             }
  14:         }
  15:     });
  16: }

Let’s step through this. Line 1 hides the ajaxLoader image, lines 2 and 3 hide the errorMessage and studentData div tags. Line 4 starts the jQuery ajax function by defining the url, the form action type, and what to do on a successful response. Because I am using a “GET” in the ajax code, I don’t have to include a <form> on the page. Looking back to the GetStudent(int id) method in the Controller, it is expecting a parameter of the type int to be passed in as well. This is done by getting the value of the text field which has an ID of “studentId”;

How the routing is setup with the application, the MVC framework will automatically cast the “1” to an int before it is passed into the method.

The GetStudent () method will take the ID passed in then select the student with a matching ID. If no student is found, GetStudent() will return a string, “error:Student not found.” If a student is found, then the method will return a Json object containing the data of the student.

Using Firebug for Firefox is a good way to see the data which is coming back from the Controller.

When an error is returned, in this case, the student was not found

3036.GetStudent-with-Error_33D6C57A

6557.GetStudent-with-Error-web-page_44028D73

When a student object is returned

5086.GetStudent-with-student_1B83AB5F

5873.GetStudent-with-student-web-page_604FCF93

The success attribute of the $.ajax function will check the type of the result coming back. If it is a string, the showError() function is called. If the type of the result coming back is an object, we know it is a JSON object and the showStudentData() function is called which will display the student’s data.

   1: function showError(result) {
   2:     var msg = result.split(":");
   3:     $("div.errorMessage").html(msg[1]).fadeIn(500);
   4:     $("img.ajaxLoader").fadeOut(500);
   5: }
   6:  
   7: function showStudentData(student) {
   8:     $("td.studentFirstName").html(student.FirstName);
   9:     $("td.studentLastName").html(student.LastName);
  10:     $("td.studentEmail").html(student.Email);
  11:     $("td.studentSubmitDate").html(student.SubmitDate);
  12:     $("td.studentApproved").html(student.Approved.toLowerCase());
  13:     $("td.studentApprovedDate").html(student.ApprovedDate);
  14:     $("div.studentData").fadeIn(500);
  15:     $("img.ajaxLoader").fadeOut(500);
  16: }

Another example

In this project, I want to quickly approve a student for the current course. I could build an Edit Student form and approve them with that, but I don’t want to handle all of that overhead. All I want to do is click the Approved checkbox and go onto the next student.

I can do this by adding a click event to the checkboxes that have been rendered on the page with the following line of jQuery code.

   1: $("input[type=checkbox][name=Approved]").live("click", function () {
   2:     toggleStudentApproval($(this)); 
   3: });

Notice how I am using the “live” event to bind the click event to the checkboxes. What is cool about doing it this way is, the event will be bound to all selected elements now and in the future. So if the page is heavy with elements and long in loading, the $(document).ready() function may fire before all the elements have been rendered. You may also create new DOM elements with jQuery which won’t be available during $(document).ready(), so setting events with the .live() event is a good practice to start.

The JavaScript code:

   1: function toggleStudentApproval(elem) {
   2:     $(elem).siblings("img.ajaxLoader").fadeIn(500);
   3:     var approved = $(elem).attr("checked");
   4:     var studentId = $(elem).prev("input[type=hidden]").val();
   5:     $.ajax({
   6:         url: "/Home/SetStudentApproval/" + approved + "/" + studentId,
   7:         type: "GET",
   8:         success: function (result) {
   9:             showStudentApproval(result);            
  10:         }
  11:     });
  12: }

When the checkbox is clicked the code first shows the animated GIF, “ajaxLoader” that is a sibling of the checkbox. It then sets the variable “approved” to the “checked” attribute of the checkbox, then gets the value of the hidden field “studentId”. Line 6 shows the url which will be sent to the Controller, and line 9 shows the function to be called when the response is received. The request is then sent to the SetStudentApproval method in the Home Controller.

The Controller method, SetStudentApproval

   1: public JsonResult SetStudentApproval(bool approved, int id)
   2: {
   3:     var student = (from s in _db.Students
   4:                     where s.Id.Equals(id)
   5:                     select s).FirstOrDefault();
   6:  
   7:     if (student == null)
   8:         return Json("error:Student not found.", JsonRequestBehavior.AllowGet);
   9:  
  10:     student.Approved = approved;
  11:     student.ApprovedDate = DateTime.Now;
  12:     if (!approved)
  13:         student.ApprovedDate = null;
  14:     _db.SaveChanges();
  15:  
  16:     var singleStudent = new SingleStudent
  17:     {
  18:         Id = student.Id,
  19:         FirstName = student.FirstName,
  20:         LastName = student.LastName,
  21:         Email = student.Email,
  22:         SubmitDate = student.SubmitDate.ToShortDateString(),
  23:         Approved = student.Approved.ToString()
  24:     };
  25:  
  26:     if (student.ApprovedDate.HasValue)
  27:         singleStudent.ApprovedDate = student.ApprovedDate.Value.ToShortDateString();
  28:     
  29:     return Json(singleStudent, JsonRequestBehavior.AllowGet);
  30: }

This method is the similar to the GetStudent method earlier in the article. For ease in reading, I copied it and added passing in the “approved” status, then passing back the student object to the JavaScript function.

The additional JavaScript functions

   1: function showStudentApproval(result) {
   2:     var id = result.Id;
   3:     var hid = getHiddenField(id);
   4:     var td = hid.parent("td").prev("td");
   5:     td.html("");
   6:     if (result.ApprovedDate != null)
   7:         td.html(result.ApprovedDate);
   8:     hid.siblings("img.ajaxLoader").fadeOut(500);
   9: }
  10:  
  11: function getHiddenField(id) {
  12:     var hid;
  13:     $("input[type=hidden]").each(function () {
  14:         if ($(this).val() == id) {
  15:             hid = $(this);
  16:             return false;
  17:         }
  18:     });
  19:     return hid;
  20: }

The function showStudentApproval gets the Id of the student, then calls a helper function, getHiddenField to find the matching hidden field. This is so the table cell displaying the Approved Date can be found. The helper function gets an array of all hidden fields in the DOM, and uses the jQuery function .each() to loop through each one checking the value to see if it matches what was passed in. When it finds the correct element, the variable “hid” is set to the hidden field, then the looping is stopped. The hidden field is then sent back to the showStudentApproval function.

For ease, and since only the checkbox is visible, the hidden field, the checkbox and the animated GIF are all in the same table cell. Line 4 finds the table cell which is to the left of the cell holding these elements by traversing the DOM, looking first for the parent(), then parents previous sibling.

8688_nested-table-cell_0500E70B

Line 5 shows that once you have a jQuery object in memory, you don’t have to use the $() syntax to refer to it again. This is a best practice, as using the $() to find elements which you already have in memory will lead to a degradation in performance.

Line 5 clears out the html of the table cell we found by traversing the DOM in line 4. Lines 6 and 7 set the html of the table cell to the student’s ApprovedDate.

Step 1

2287.student-approval-1_63350E79

Step 2

4405.student-approval-2_61F0759A

Step 3

3718.student-approval-3_4BBA5A48

In this article we talked about some of the differences between a Web Forms application and a MVC application. Showed how to include jQuery files, and did some cool things with Ajax and jQuery to streamline an application’s functionality. In the next article, I will show you how to extend jQuery’s functionality with plugins.

Happy Programming,

James

Add comment




  Country flag
biuquote
  • Comment
  • Preview
Loading


About the author

James James is a five time and current Microsoft MVP in Client App Development, a Telerik Insider, a past Director on the INETA North America Board, a husband and dad, and has been developing software since the early days of Laser Discs and HyperCard stacks. As the Founder and President of the Inland Empire .NET User's Group, he has fondly watched it grow from a twice-a-month, early Saturday morning group of five in 2003, to a robust and rambunctious gathering of all types and sizes of .NET developers.

James loves to dig deep into the latest cutting edge technologies - sometimes with spectacular disasters - and spread the word about the latest and greatest bits, getting people excited about developing web sites and applications on the .NET platform, and using the best tools for the job. He tries to blog as often as he can, but usually gets distracted by EF, LINQ, MVC, ASP, SQL, XML, and most other types of acronyms. To keep calm James plays a mean Djembe and tries to practice his violin. You can follow him on twitter at @latringo.

And as usual, the comments, suggestions, writings and rants are my own, and really shouldn't reflect the opinions of my employer. That is, unless it really does.

James Twitter Feed

Recent Comments

Comment RSS

Month List