AsyncUI MVC with Progressive Enhancement

AsyncUI MVC with Progressive Enhancement

The top concerns for any site are having basic functionality, having it perform as fast as possible, and then working for as many customer browsers as possible. Asynchronous User Interfaces are the latest technology to achieve the ultimate in performance.

Web developers are still stuck in the request/response mindset. I call it the ‘click and wait’ approach – where every UI interaction results in a delay before another interaction can be performed. […] developers still insist on using the request/response model. Even the introduction of Ajax hasn’t improved the scene much, replacing blank loading states with spinners. […]

I’ve been working on this problem, specifically with a MVC JavaScript framework called Spine, and implementing what I’ve dubbed asynchronous user interfaces, or AUIs. The key to this is that interfaces should be completely non-blocking. Interactions should be resolved instantly; there should be no loading messages or spinners. Requests to the server should be decoupled from the interface.

I am using different technology to solving the problem than Alex, but the end result is the same. My approach focuses on using Microsoft Asp.Net MVC, jQuery, and a custom javascript library I created. This technique let’s me focus on building HTML with attributes and then the script automatically enhances those views to interact with my MVC controller action methods.

Final Output

Here is a screenshot of the final profile page for which I will show how to implement the email section. There are 9 different areas in which you can edit something on the page, including your sign in credentials, real name, birthdate, a markdown editor for a bio, an email address, a phone number, an address, a phone number, and a school/job section. In loading this entire page the initial load time is only 1/2 a second, plus another 4/5 second for images from another domain. I’ll cut that down after I add bundling and more minification of the resources.

As the following screenshot shows the Undo Delete only takes 58 milliseconds post time. But before the post began the view had already toggled to the readonly version. I don’t know of any tools to track how long javascript ran before the post began, but to me using the button it felt instantaneous. All the buttons feel just as fast using the code shown in this article.

MVC Adaptive Controller

To support progressive enhancement you will need to support users that do not have AJAX support or that have limited support. It is easy to make a controller action return either JSON data for consumption by javascript and to support returning view HTML. Here are some simple controller actions that returns a profile email address for a user as either an HTML view or as JSON.

The key in adapting your result to what is requested is to check if the request is for JSON. You can support this by adding a simple Controller class extension method somewhere in your project or in a shared library.

The create action method is slightly different in that it needs to potentially return a success flag or possibly error messages. In the case of create and delete these action methods will rarely, if ever, be called in the case when the browser supports AJAX. So these action methods support both JSON and the normal get HTML options, but they are primarily for the latter.

All of these methods are using a class called UserProfileManager to get data. In case you are wondering how that business layer works, you can read my previous articles on provider model. It really isn’t pertinent to this article so I won’t mention it in this article again.

MVC Post Action Methods

When doing a post for create, edit, or delete you may be responding to an AJAX request from the AsyncUI javascript or it may be a full browser request from a client that does not support AJAX properly. In the AsycnUI case you want to return a message, if applicable, and then let the calling page determine if the user stays on the page or needs to go elsewhere. In the form post method, the client was likely just on a full edit page since they don’t support AJAX and AsyncUI, so on success you want to redirect them back to the page from before the edit. Or in the failure case (with no AsyncUI) you want to keep them on the edit page and show the error message. To support these different result options, I create helper methods in the controller to determine the correct ActionResult.

I can now leverage this in the save, delete, and undo delete operations. The XCancel action methods will not typically be invoked for most users. But these methods are here for the case when javascript is disabled and the button needs to cause a navigation somewhere. You can accomplish this by setting urls on cancel links instead of having cancel behave like a button. But I included the XCancel action methods because I wanted to demonstrate the FormPostActionSelector attribute also.

FormPostActionSelector

Some of the code above used a custom action name selector called FormPostActionSelector. This lets you just put a button on a form in your view and then let the controller action method intercept which button it handles. If you only have one button (like Save), then this is not necassary. But if you have two or more buttons on a form then it let’s you have a post action for each of the form’s buttons. I have this in a shared library and it is available within NuGet package candor.web.mvc.

Asp.Net MVC View Models

Something to consider when creating a view model class that is used as both a means to bind a view and to return as JSON is that you may need more values in your view file than you want serialized as part of your JSON data. You can accomplish property hiding in your JSON data with the ScriptIgnoreAttribute. For instance, I have a model class that I use for an email address that has a property with business layer validation messages on it. I use it on the server side view to render a validation block. But in the client side view template I don’t use that mechanism for returning error messages since the server side property holding those messages is bloated when serialized to JSON. Another thing you’ll notice about the controller method above is the Load method. This is just my personal preference that the view model knows how to load itself from the core business objects that it represents. You could just as easily put that Load logic in your controller. I prefer it in a Load method so that if I ever reuse my view model elsewhere in the controller or in other controllers I only have to maintain that mapping logic in one place.

This particular view can only be used server side to render the initial list, as you can see with the @model declaration on the first line. There is no client side template for the list as a whole. That isn’t to say you couldn’t do that, but I choose not to for my project. In the next lines notice how a bunch of data attributes are only output if the current viewing user is the owner of the profile. These attributes support the editing, so there is no point in putting them on the page for a visitor of the profile. There is also a section below that defines the client side templates, and it is only output for the owner of the profile.

For the owner of the profile we are also outputting a javascript variable populated with the initial JSON data used to populate the list. However, in the section above you also see the initial view templates are output. The view template is rendered out on the server side initially so that something appears before any javascript runs. This is important for progressive enhancement, which then supports both users without javascript enabled and for search engines. So why output the JSON data? This is to make a second pass at these elements to make them editable via AsyncUI. But you don’t have to output the JSON as a variable. You can just load the JSON after the page loads via AJAX. The data attributes in this view support both options.

Data Attributes

data-json-url. This option let’s you define the url where the JSON will be loaded for the list.

data-json-var. This lets you define the name of the (global scope) variable holding the JSON for this list. This option does pollute your global scope, so the data-json-url option may be preferred for the idealistic approach. However, the variable option is faster given that another request to the server via AJAX is not needed to prep the list items for edit.

data-template. This is the initial template name to use for the initial rendering of each JSON object in the list defined in one of the previous options. You should typically set this to the name of your view template. But if you want all your items shown as editable on page load, you can put that template name here instead.

‘EmailView’ Razor view

As you can see on the list I render out the initial view of each item on the server side. So I need a Razor view for just viewing the data. This code is standard server side MVC Razor. The only thing special about it is that there is a class on the top level element called ‘inline-editable’ that the javascript library looks for.

This view has two buttons on it. The delete button posts a delete to the server immediately and then toggles to the delete undo view. The edit button just toggles to the edit view and does not send anything to the server. Edit, Delete, View, and Undo delete are NOT built in behaviors of the script. You are just setting up those behaviors by the attributes you put on the buttons. The script follows the same steps for the above as it does for the edit view buttons below.

‘EmailEditTemplate’ view

If the user wants to make an edit, we switch over to the edit template on the client side. This is without making a request back to the server. So this view switches practically immediately, or at least as fast as the javascript engine can make the rendering change. No latency or other server processing delay is involved. The only ‘server side’ processed portion of rendering this view is at the time that the view is sent to the browser on the initial page request, which is just to build the form tag posting to the correct controller action, and building textarea and input tags. The data put in those inputs is done when the template is rendered.

Just like the previous view there is a class on the top level element called ‘inline-editable’ that the javascript library looks for. Additionally since this view has buttons on it, it also has an attribute on each button called “data-template” which defines which view to toggle to when the button is pressed. But if the action posted to fails, the view is switched back to the edit view with any error messages from the server or updated values from the server automatically. A few of the other inputs also have special classes and data attributes on them so that they are progressively enhanced into jQuery controls after rendering of the template. I might discuss those in another article. I won’t now since they as are not required for making AsyncUI work.

‘EmailEdit’ View

There is another view for email edit when the server needs to render it. If you look back at the ‘EmailView’ razor view you’ll see that the buttons have an href attribute. These buttons are hyperlinks that are enhanced with the jQuery button widget to look like a button. If script is disabled, when the user clicks on the button/link they will navigate to the edit view. If script is enabled, the script changes the href to “#” and attached a click handler that does the steps mentioned above instead (AsyncUI).

When the edit view is shown as rendered by the server then we render it slightly differently and using Razor syntax instead of template syntax.

Notice this edit view does not have the data attributes. If this view is shown we already know script is disabled on the client so the data attributes are not needed. The buttons on this edit view post directly to the MVC controller EmailEditSave action method synchronously. So we fallback to the ‘click and wait’ model when script is disabled. Look back at the UserProfileEmailResult and this is where the RedirectToAction occurs on a succesful edit, and on a failed edit the EmailEdit view is returned again with the values that were just posted.

‘EmailViewTemplate’ view

After a user completes editing an item and they cancel or save, we need to toggle back over to the edit view. This toggle should be immediate using a template and with the data on the form (in the save case). We can’t use the ‘EmailView’ view because that is a server side rendering. This template initially is rendered on the server as part of the ‘EmailList’ view. This is why all the @Html.Raw Razor syntax blocks are in the file. But that is just to get the template to the client. Once on the client those do not exist are are not part of the rendering of the template client side. Html.Raw is needed because otherwise the file would not be valid Razor syntax.

This template also handles the case when the item is deleted by rendering that as sub-template as needed. Structurally this is the same as the ‘EmailEditTemplate’ view. This looks the same as the ‘EmailView’ view, except that this is in template syntax (inside the Razor syntax), instead of binding to a .net view model class. Another difference from ‘EmailView’ view is that we don’t set the href attribute on the anchor tag buttons. We know if the template is able to render then script is enabled and the subsequent enhancement to change the anchor tag to a button will also work.

As shown in the following screenshot, MVC renders the view code above as valid template syntax.

‘EmailDeleteUndoTemplate’ View

The undo delete template only applies to AsyncUI, at least in my example. If you delete an email without script enabled you get prompted if you are sure, and then you can’t undo delete. But if you then load the page with script enabled you’ll see the items that can be restored with the undo template (deleted section of view template). This template is relatively short and simple. It has one button to restore the item, and that button works the same as the buttons on the other client side template views. Just like the others it also defines the post url via the ‘data-url’ attribute and the template to switch to on click with the ‘data-template’ attribute.

‘EmailDelete’ View

The delete view is only used when script is disabled. It prompts the user if they are sure that they want to delete the item. If so it posts to the EmailDeleteConfirm action. This is the same action method as the client side view template delete button. Look back at the EmailDeleteConfirm controller action method to see how it handles both at the same time.

Custom Command Views

You can define other views to be handled by this AsycnUI script by just defining the necessary controller action methods and then setting the “data-url” and “data-template” attributes on the view buttons. It should be able to handle any custom scenario that you can think of.

The Script

When one of the ‘inline-editable-button’ classed HTML elements is pressed it basically does the following operations in order.

Gets the JSON object that represents this item (which was loaded at page load from data-json-var or data-json-url and stored)

Updates the JSON object from the form values (by element name), if applicable. It supports drilling down to sub-properties and into arrays.

Looks for a template name in the ‘data-template’ attribute and replaces the current ‘inline-editable’ element with an instance of that template bound to the updated JSON object. This is why you see the change back to the other view immediately.

If “data-url” is not explicitely set to an empty string (as in cancel button) it posts to the url defined by the form and includes the button name as a form variable as well (for the FormPostActionSelector to pick up). You can override the post url per button with a “data-url” attribute on the button (not shown above). The button can also define a “url-method” attribute if it wants to use something other than the ‘post’ verb (not shown above). “url-method” is more commonly used for buttons in the non-form (read-only) views.

On post success if the item was passed back it replaces the template again with the new model. This is in place for the times when some properties are calculated on the server from other inputs. The server side also has the option of returning the name of the template to instantiate if the one defined on the button needs to be overridden due to some special case.

On a post business rule failure the view can toggle back to the edit view and show the error message in your placeholder for errors. Note the span with class ‘msg’ in the template above.

The templates in this article currently use the now deprecated jQuery Templates library. You could just as easily use any other templating solution with a couple line modification to the jquery-auto-async script. I am still using this template solution for now since the jQuery team is working on a replacement. It really has little bearing on the functionality of this sample code, so I didn’t take the time to update it to another template library yet.Update August 15,2013: The jQuery team seems to have given up on a templating engine. I changed the dependency to use jsRender instead.

Styles

In my project I am using the themeroller style of my choice, plus the following structural styles.