What are the advantages and drawbacks of using Ajax to update web pages? How to decide if Ajax calls should return html or JSON? In this post I will give some answers to the above questions and I will give some tricks to enhance also the Html created dynamically as a consequence of Ajax calls with JQuery Plug-ins .
Most of people that uses Ajax when asked why they are using it, answer that Ajax calls improve performance and user experience….Well, for sure they improve user experience, but I don’t know if possible performance improvements might be relevant…Finding the right answer to all these questions is the first step toward an optimized use of Ajax based techniques.
What are the times that compose the the total response time of a server request? A network latency time is needed to establish the connection with the server, a trasmission time is needed to send all bytes (that depends on the available bandwidth), a server response time, and a browser re-drawing time. Now, if the server is well designed, and if we are not sending tons of html , the bottlenecks are the latency time and the browser re-drawing time. With all nowadays continuous technological improvements bandwidth will impact always less on performance. Also the browser re-draw time will impact always less on the performance. So the request response time will be always more and more tied to the network latency. Accordingly, redrawing the whole page or just a part of it would require almost the same communication time. Moreover re-drawing just a not negligible area of the page (say the 25% of the page) requires almost the same time as re-drawing the whole page since a whole page re-draw is more efficient than a partial re-draw.
As a conclusion in most of the cases Ajax techniques don’t imply any appreciable improvement in the total response time! So why using Ajax?
- If we need to refresh just a small part of the page, as in the case of an auto-complete that write suggestions under the textbox we are writing in, there is a not negligible improvement of the response time.
- During the Ajax update the state of the remainder of the page is maintained. What does this mean? From the user experience point of view this means something like: the browser will not loose the scrolling we have done, and the textbox the user is writing in will not loose the focus…otherwise it would be impossible to have auto-complete and similar widgets working properly. One might object that we might restore the whole state also after a standard page redraw…Yes it is true…but before the page has been completely redrawn the user would see the page returning to the top of the document, the Textbox disappearing, and so on….and similar unacceptable stuffs.
Maintaining the whole state is important also for more macroscopic state information. Let think ,for instance, to a grid with a detail view that retrieves row details from the server when a row is selected, and show them in a separate area of the page. A complete page refresh might cause the loss of the whole grid state, that is the data page shown, sorting and filtering, the possible scrolling of the grid body, etc. Now, in theory, it is possible to rebuild the whole state also after a whole page refresh but this might confuse the user that would see the grid disappearing, being re-drawn in a different position and then being scrolled till reaching the previous position. However, this isn’t the only drawback, if grid data are taken from a shared database (as it is usual..), also the same page with the same sorting and filtering might show different data disappointing completely the user. Moreover, any attempt to take into account all these state information on the server side might undermine the modularity of our application turning our Controllers into “spaghetti code”.
So when is it convenient to use Ajax techniques?
Simple, either when we need to update just a small part of the page or when we need to keep the state of a part of the page. Also the reason for implementing our application as a Single Page Application, that is, an application that never leaves the same physical page, is always the same: keeping state information in the physical page. In the next post dedicated to Single Page Applications we will see also other reasons for keeping state information in the page but the fact remains that …the reason why Single Page Applications exist is… keeping state information in the page.
Now, when our pages become more an more complex keeping state information inside Html nodes may lead to “spaghetti code”, so in a way that is completely analogous to the Mvc pattern on the server side it is more and more convenient to store information inside a client side ViewModel, and then using that ViewModel to render adequate Html. On the server side we use Razor Views to turn models into Html, while on the client side we use client side templates to create dynamically Html from a JavaScript model…..Well, this is the main reason to use Ajax calls where client and server exchange JSON!
JSON based Ajax techniques will be discussed in greater detail in the next post of this series. Here it is worth to point out just when they should be preferred to standard Ajax techniques where the server returns immediately the needed html. Since JSON techniques conforms with the idea of using a client side ViewModel, that in turn ensures a better modularity, one might draw the conclusion that they should always be preferred to standard Html-returning Ajax techniques!….NO…false, JSON based techniques should be preferred only when you may use client side templates! Below the typical reasons that, in some circumnstances, prevent the use of client templates:
- Pages created with client templates are not visible to search engines.
- Some slow mobile device might not be able to render client templates with an acceptable performance.
You might object that also in case Ajax calls return Html, that Html is not visible to search engines. TRUE….but IRRELEVANT because, when we use Ajax calls returning Html, the initial page is not rendered with Ajax, because the same Action Method that serves an Ajax request may be called also when the initial page is rendered,…but without using Ajax and usingt @Html.Action(…), and @Html.RenderAction(…), instead. This way, the initial page is completely visible to search engines. Accordingly, if we make a clever use of Html-returning Ajax Mvc controllers we may produce web Applications that are completely visible to search engines. Here “clever use” means, for instance, that when we change the page of a grid we don’t do it with Ajax but with a link based pager (possibly...with a smart encoding of the page number in the URL). In other terms, we should use Ajax only for that operations that are not performed by search engines. So, for instance, we may show a detail area in a grid page, but then the same detail page must be available also as a separate page either through a "pretty url", through a link.
Let see in detail, how we may avoid initial Ajax calls when we render the whole page with the simple example of the grid with a detail view.
Suppose we have a PlannerController with a ToDoList action method that fills a ViewModel with a paged list of ToDoItems, and a DisplayDetailToDo action method that fills a ViewModel with the details of a single ToDoItem. Suppose that we display the ViewModel filled by the ToDoList action method in a ToDoList View containing a Grid and a detail area, and suppose that initially the detail are should contain the first item of the grid. Then the detail area of the ToDoList View should be something like:
- <div id="detailArea" data-update-url="@Url.Action("DisplayDetailToDo", "Planner")">
- @Html.Action("DisplayDetailToDo", "Planner", new {ItemId=Model.Items[0].ItemId})
- </div>
Then, whenever the user select the ToDoItem with Id –> selectedId in the grid we perform the Ajax call:
- var ajaxRoot=$('#detailArea');
- ajaxRoot.load(ajaxRoot.attr("data-update-url")+"?ItemId="+selectedId );
That we may place in a click handler (my previous post shows how to add modularly click handlers) that catches all events bubbled by the rows of the grid. The grid side code depends on the chosen grid, but we may take selectedId from an Html5 attribute of the button, link or other Html node used to select the grid row.
In both cases we use the same action method that should be something like:
- public ActionResult DisplayDetailToDo(int ItemId)
- {
- var model = repository.GetToDo(ItemId);
- ...
- ...
- ...
- return PartialView(model);
- }
Where I omitted all errors handling code.
Our problem now is how to enhance also the Html returned by the Ajax call with jQuery widgets. We may use the basically the same technique I have shown in my previous post, based on the widgetsHelpers.initialize method, since when we insert new Html with the jQuery .html method all JavaScript contained in the Html string is executed. However, the widgetsHelpers.initialize method contains the .ready jQuery method…that doesn’t work with dynamically added content. This problem is easily solved with a temporary substitution of the .ready jQuery method with a custom method during the processing of the Ajax response:
- var delayedExecution = [];
- var newReady = function (x) {
- delayedExecution.push(x);
- };
- var oldReady = jQuery.fn.ready;
- jQuery.fn.ready = newReady;
- try {
- //response processing here
- }
- finally {
- jQuery.fn.ready = oldReady;
- }
- for (var i = 0; i < delayedExecution.length; i++)
- delayedExecution[i]();
Where in most of the cases the response processing is just the call to the jQuery .html method. Thus, we may define a widgetsHelpers.dynamicHtml(jTarget, html) that does the job of attaching an Html string to a jTarget node while ensuring that all JavaScript enhancements contained in the Html string are properly applied:
- widgetsHelpers.dynamicHtml=function(jTarget, html){
- var delayedExecution = [];
- var newReady = function (x) {
- delayedExecution.push(x);
- };
- var oldReady = jQuery.fn.ready;
- jQuery.fn.ready = newReady;
- try {
- jTarget.html(html);
- }
- finally {
- jQuery.fn.ready = oldReady;
- }
- for (var i = 0; i < delayedExecution.length; i++)
- delayedExecution[i]();
- };
However, we have another problem, too….Avoiding that the jQuery plug-ins that we apply to the newly added Html are re-applied also to the remainder of the Html page. In fact, if, for instance, we enhance all input fields contained in our dynamic Html that have the “datetime” CSS class with a Bootstrap datepicker, the datepicker plug-in would be re-applied also to the input fields of the remainder of the page with the same attribute. Often jQuery plug-ins are robust and re-applying them to the same nodes doesn’t produce any effect. However, you can’t rely on this robustness, and, in any case, a similar solution would be very inefficient. The only way out is using different names….however, as we have seen in my previous post the code for generating datepickers is all contained into an unique Date.cshtml partial view that is called both by our initial page and by any other Ajax request.
Actually this is not the only “name convention” problem of Ajax provided content that we must solve! Normally, in Asp.net Mvc all input fields have names that MUST be strictly tied to the position where their content will be inserted in the ViewModel of the action method that the receive the data posted by the client. So for instance, a date that must be inserted, in the DateOfBirth property of Person instance inserted in the PersonalInfos property of the ViewModel MUST be rendered in an input field with name PersonalInfos.DateOfBirth, and id PersonalInfos_DateOfBirth, otherwise the default model binder wouldn’t be able to fill properly the ViewModel. The dot in the name is turned into an underscore in the id because the id can’t contain dots. Now, if the ViewModel used to render the page is the same as the model used to receive the post all above conventions are automatically enforced by the Asp.net Mvc Html helpers (TextBoxFor, etc.).
However, in general the ViewModel used by the Ajax controller differs from the one used for the initial page, since the Ajax call furnish just a part of the page data. So, for instance, in our previous example, if the Ajax call returns just the data obtained by rendering a Person object the name of our Date field would be DateOfBirth instead of PersonalInfos.DateOfBirth. Now if the Person data are submitted separately with another Ajax call to an Action method that uses a Person ViewModel all works ok, but if the Person data must be submitted together with the main page data we must add someway the PersonalInfos prefix.
Adding the PersonalInfos prefix to all input fields rendered by the partial view used by the controller that respond to the Ajax request is quite easy. It is enough to add the following code at the beginning of the View:
Html.ViewData.TemplateInfo.HtmlFieldPrefix = "PersonalInfos";
The prefix should be added just to the top level Partial View, since each time we call EditorFor and DisplayFor the Asp.net Mvc engine takes care of defining the right prefix for the child Partial View. However, in general the server doesn’t know this prefix, since the prefix depends on the role that the Ajax content will play in the overall page ViewModel. Suppose, for instance that the Html returned by the Ajax call must be used to add a new row to a grid, that contains Person data. The prefix to add should be something like AllPersons[i], where i is the 0 based index of the new row in the grid. So if the grid already contains 10, rows i=10, if the grid already contains 15 rows i= 15, and so on. In other terms only the client may know our prefix! So we must add the prefix as a further parameter of the Ajax call.
Unluckily the previous prefix, in general, cannot be used also to solve the problem we have with the datetime CSS class, because in the second case the CSS class must be unique withineach Ajax call not within a specific position in the ViewModel. Accordingly, for the CSS classes used to enhance the Ajax Html we might use a different prefix based on a count of all Ajax calls made to the server from the current Html page:
- (function ($) {
- ...
- ...
- ...
- var ajaxCount=0;
- widgetsHelpers.newClassPrefix= function(){
- return "classprefix"+(ajaxCount++);
- };
- })(jQuery)
The two prefixes must be added to the parameters of the Ajax call together with the original request parameters, say, personId, to get the final request URL:
ajaxRoot.attr("data-update-url")+"?personId="+personId+"&htmlPrefix="+htmlPrefix+"&classPrefix="+classPrefix
On the server side, any Ajax enabled controller must take care of receiving the two prefixes:
public ActionResult PersonData(int personId, string htmlPrefix, string classPrefix)
{
if (!string.IsNullOrWhiteSpace(htmlPrefix)) ViewData[htmlPrefix]=htmlPrefix;
if (!string.IsNullOrWhiteSpace(classPrefix)) System.Web.HttpContext.Current.Items["classPrefix"] = classPrefix;
var model = repository.GetPersonById(personId);
...
...
...
return PartialView(model);
}
The classPrefix has been added to the HttpContext dictionary, since it must be used by all Partial Views called in the current request, while the htmlPrefix has been added to the ViewData since it must be used just by the top level Partial View.
Now in the top level Partial View:
- @{
- Html.ViewData.TemplateInfo.HtmlFieldPrefix = ViewData["htmlPrefix"] as string ?? "";
- string classPrefix = System.Web.HttpContext.Current.Items.Contains("classPrefix") ?
- System.Web.HttpContext.Current.Items["classPrefix"] as string + "-" :
- "";
- }
In the Data.cshtml Partial View, and in general in all Partial Views that might be involved in an Ajax call:
- @{
- string classPrefix = System.Web.HttpContext.Current.Items.Contains("classPrefix") ?
- System.Web.HttpContext.Current.Items["classPrefix"] as string + "-" :
- "";
- }
and then in each CSS class enhanced input field:
@Html.TextBoxFor(m => m.DateOfBirth, new {@class=classPrefix+"datetime"})
That’s all for now!
In the next post Json based Ajax calls and Single Page Applications.
Stay tuned!
Francesco
Tags: JQuery UI, jQuery, Ajax, JavaScript, JQuery Mobile, Bootstrap