The latest design pattern I worked with before this was MVP, with which I have done 100% of my projects since 2007. MVC, I learned was a couple of more steps towards a well thought-out, well designed framework for web development.
Well, my opinions aside, the first stumbling block I encountered was, “How the heck do I make smooth page interaction in a semi-elite way (meaning: I don’t want to find an <asp:UpdatePanel /> equivalent; I want to do this and actually learn something new in the process).
Enter jQuery. It has been bundled with Visual Studio since VS2010 was released. It has helped me in the past conquer some sticky situations with validation, UI tricks, and helping me shorten my JavaScript. That being said, I am by far no expert in it, so to tackle AJAX with jQuery was going to take some reading.
After scanning some introductory texts on the MVC 3 release, I read that this is the first time that action methods can receive Json-encoded data as parameters. This is covered more in bullet number 3.
My goal was to have two dropdowns,
- Activity Categories
- Activities
Here goes:
1) Client-side Event Handlers To Spawn AJAX Calls
Define the client-side event handler for the category selection changed event. (yeah, sometimes I still talk in Webform talk)The code below basically says, when the document is ready (finished loading all controls) set a function up that, when a Control (in this case, an <input/>) having the id “Category”’s selection is changed, call a function named callActivityCategoryAJAX(id), passing the value of the selected option.
$(document).ready(function () { $('#Category').change(function () { callActivityCategoryAJAX($("#Category option:selected").val()); }); });This block can be placed anywhere on the view inside <script> tags. I chose to implement the function that it will call, callActivityCategoryAJAX in a separate .js file, referenced from the view.
By the way, this way of setting event handlers up inside script tags is known as “unobtrusive JavaScript”, coming from the idea that separation of code and markup is essential to good design and reuse, and keeping the markup free of onchange=”somefunction()” will make the markup easier to read. Unobtrusive JavaScript… almost as big on the scene right now as table-less design. Anyway, I agree with it, and being a very big fan of separation of code (logic) and markup, I chose to go with it. Why else would I be learning MVC, anyway, if I didn’t believe logic should work from behind the scenes?
2) The AJAX Call from jQuery
We need to design our function that this ‘event handler’ will call.function callActivityCategoryAJAX(id) { var categoryOptions = ''; $.ajax( { url: 'ActivitiesByCategory', type: "POST", data: ({ categoryId: id }), success: function (activities) { ActivitiesByCategory_CallbackSuccess(activities); HideShowElementsBasedOnCategory($("#Category option:selected").val()); } }) }This takes as a parameter the id. In my case, this is a GUID. The $.ajax part defines the behavior of the call to the controller, which acts as a webservice in other AJAX calls. The “url:” specifies just the name of the method here. It needs not reference a specific Controller here, because in my case this will reside in the same Controller class that contains the View’s main Controller.
The “type:” will be a POST, as this is what we need to use to call the method in the controller. The “data:” element sets the arguments for the call; in this case, our method will take one parameter, a GUID of the selected category.
Finally, “success:” is, as you may have guessed, the callback that executes when our Controller’s method is finished and returns. Make sure that the return value is placed as the argument for this. In this case, the method returns an array of Activity objects. So, I further split the callback, though you could, in theory, write the code right here under “success:” and the world not come to an end.
I will not spell out what this method does, as I want to keep this post short and to the point. Basically, it contains client-side code (JavaScript) to erase the current options from the Activities input and ‘for’ through the resulting array returned from the server-side method and add these to the list of options. Feel free to make more use of jQuery, as I did, and fade the Activities control out while it is being updated, and then back in. Or not.
One final thing I would like to say here, is there are other callbacks that can and should be specified to handle exceptional cases, cleanup, or customization of the request. I’ll list them here:
- beforeSend(XMLHttpRequest, settings) – use this to modify the XMLHttpRequest object before it is sent.
- complete(XMLHttpRequest, textStatus) – use this to do any final cleanup after either the success or error callbacks are executed.
- error(XMLHttpRequest, textStatus, errorThrown) – used when an exception occurs – i.e. 404 errors, exceptions from the server, and/or other HTTP status errors.
- success(data, textStatus, XMLHttpRequest) – the one we are using which handles the case that everything went smoothly.
Documentation of these and other related methods and properties can be found on the official jQuery Documentation Site here.
3) Server-Side Methods in the Controller
The final piece to this puzzle is the server-side method, residing in this View’s Controller. Here it is:namespace MyProject.Controllers { public class Activities: Controller { //.......[code here cut for clarity]........ [HttpPost] public JsonResult ActivitiesByCategory(Guid categoryId) { var returnValue = [something]; //code here that probably pulls from // a cached IEnumerable of Activities matched //with Categories, or even pull from the Model itself return Json(returnValue); } //......[more code here cut].......... } }
Pretty simple method that is behind the scenes. Not much should need explaining, but briefly – [HttpPost] specifies that this method will handle HTTP requests which are of type POST. It will return a type of JsonResult, which is natively handled by our JavaScript on the client-side. All the work that is done is to pull the list of corresponding Activities. Maybe you have cached this somewhere for performance, or maybe it is small enough, or executed infrequently enough, to pull from the Model. Either way, it’s your choice, depending on your needs. The resultant array is cast into JavaScript Object Notation (Json) via use of the Json class’ constructor, and sent back to the client.
NOTE: If one is to use an Object Relational Mapping such as LINQ To SQL or the Entity Framework, one should break circular references by using a ‘.select(x=> new{…}) and end it with a .ToList(). When I did not take time to do this the first time, I was bombarded with errors dealing with circular references. Since that phrase describes well what goes on in the ORM frameworks, it didn’t take too long to narrow down the problem.
Once back at the client-side of things, you can have access to the array and its properties using the standard [] and/or . (dot) operators.
And this sums the process up in three steps. In summary:
- Client-side event handler- done in JavaScript, unobtrusively. It is easily accomplished using jQuery, and should be done in the document’s ready() handler, which executes when the DOM is finished loading.
- AJAX call from client – specifies the method to call, along with other options. The most important here are the options which tell the client-side code how to handle communications from the server, be they the expected results, or a failure message.
- Server-side method – resides in the View’s controller. It needs to accept HTTP request method (sometimes called ‘verbs’) of POST. In the declaration, it is set to return a JsonResult, and the data to be returned is cast into a Json object via the constructor.
Wes
No comments:
Post a Comment