Progressive Enhancement

February 19th, 2010

Progressive Enhancement (PE) is an approach for building Web Applications that starts from the perspective that a user browser experience will support a minimum functionality, this is called base line, but has hooks to allow functional enhancements when a browser can support them. PE benefits users by supporting older browsers, but also supporting users with modern browsers and technologies by providing them an improved experience.

The progressive enhancement and its counterpart, Graceful Degradation, are approaches that can help rich Web Applications support more browsers and have a wider reach.

Disclaimer: We are writing this documentation as part of the new Web Client Guidance that is being done in patterns & practices. We are close to finishing this project, so I would like to get more feedback or validation from everyone that gets the chance to read this before we release. Have in mind that the content of this topic can change both in content and in form (it can change because of YOUR feedback).

Progressive Enhancement vs. Graceful Degradation

Graceful degradation is the practice of building an application for modern browsers while ensuring it remains functional in older browsers and other user agents (for example, accessibility tooling and mobile devices).

Progressive enhancement is usually preferable to graceful degradation: it starts with the simple basics and improves usability and aesthetics on top of that. When developing using Progressive enhancement you should define a development base, for example when using MVC the base could be a user that does not have JavaScript enabled. Therefore, the expectation is that you should have the basic functionality available without JavaScript; but with JavaScript, the experience is richer, including functionality, such as client-side form validation, predictive fetching, and preview.

It is strongly recommended to use progressive enhancement, when designing something from scratch. Graceful degradation can be tedious, difficult and requires more work to implement.

However, if you are maintaining an existing site, the easiest choice is to provide graceful degradation, unless you want to rewrite the whole site.

It is also possible to combine both approaches in the application. Even some features may end up being the same, independent of the approach used.

Note: For more information about both concepts, see Progressive Enhancements and Graceful Degradation: Making a Choice.

Core Principles

  • Progressive Enhancement consists of the following core principles:
  • The basic content should be accessible to all browsers
  • The basic functionality should be accessible to all browsers
  • Semantic markup contains all content
  • Enhanced layout is provided by externally linked CSS
  • Enhanced behavior is provided by unobtrusive, externally linked JavaScript
  • End user browser preferences are respected

There are several scenarios to consider for using these patterns:

  1. Browsers that have JavaScript turned off
  2. Different browsers that:
    1. implement JavaScript and DOM features differently
    2. implement CSS differently
  3. The need for SEO (Search Engine Optimization)

Browsers that Have JavaScript Turned Off

Browsers may have JavaScript disabled due to company policies, screen readers, or other accessibility issues. This is usually the biggest challenge of all, as it requires the most development effort. If it is not done correctly, it could prevent the user from interacting with the site at all.

To address this scenario, you should start developing the site using HTML for the basic content. You should use semantic markup and CSS to enhance the layout. Then you should start adding functionality for browsers that do support JavaScript and other client technologies.

The following are some advantages and disadvantages of creating applications without JavaScript.

Advantages:

  • Can support browsers without JavaScript
  • It is SEO friendly (as search engine spiders do not use JavaScript)
  • Easier to create an accessible site as a side effect without too much effort
  • Possibility to open links in new tabs and bookmark it (for example if you use the middle mouse, it will open the link in href instead of executing the JavaScript defined for the click event).

Disadvantages:

  • There is usually a need to create a server-side version of a view, and a separate client version of the same view or portions of it.

The developer has to be aware of the scripted and non-scripted features of the application, instead of just assuming that JavaScript will always be present.

Different Browsers that Implement JavaScript and DOM Features Differently

This challenge is typically known as cross-browser incompatibility. Compared to the previous scenario, when not implemented successfully, might prevent the user from interacting with the site in some cases, which causes frustration for the user, or even breaking JavaScript functionality completely, and cause a situation similar to the previous scenario.

This challenge has been a major pain point for web developers in past years, which led to using mostly server-side controls made by expert web developers that emit islands of JavaScript automatically, in order to prevent the application developer to have to learn all the browser differences, which in turn led to application developers from getting away from learning any JavaScript at all, even for simple tasks.

In recent years several JavaScript libraries have emerged that provide a cross browser experience for all of the most common tasks. By writing your code on top of these base libraries, in most cases you can avoid dealing with branching logic for the different browsers, which leads to a better appealing to the JavaScript language as a renewed development tool.

Different Browsers that Implement CSS Differently

CSS differences between browsers, though it is generally good to account for, it is usually not a big problem if bypassed. The reason is that having CSS that do not work in all browsers might cause some browsers to render the page with some inconsistencies in sizes, placement, overlapping of sections, but this will generally not prevent the user from interacting with the site entirely.

The following are some tips for dealing with these CSS inconsistencies:

  • Use a CSS reset, which improve a lot of the cross-browser inconsistencies in size, placement, and overlapping issues.
  • Develop the site with standards in mind (consider targeting XHTML 1.0 Transitional here), which almost always works in IE8, Firefox, Safari, Chrome, and usually Opera.
  • If the standards targeted HTML/CSS has IE6/7 issues, fix those issues by including CSS “hacks” in separate stylesheets referenced through conditional comments.

It is up to the business to decide the ROI for creating a site that looks identical across all the browsers. Because not supporting all versions of CSS implementations will not prevent users from interacting with your site, you could typically decide to support the CSS features in the browsers with the largest market shares, while ignoring older browsers.

How to Achieve Progressive Enhancement

To implement PE you should begin with the basic version, and then add enhancements for those browsers that can handle them.

First, you should start developing your application in plain HTML. Plain HTML is understood by all browsers. Furthermore, search spiders will be able to access and index your site content.

This also means that all anchors and forms must have a working target URL for navigating or posting data without the need for JavaScript.

Then, add styles using CSS in an external file to improve the look and feel of the site. Almost all browsers support CSS, and those which do not support it, will simply ignore the styling.

Finally, add JavaScript support, using unobtrusive JavaScript. Unobtrusive scripts are silently ignored by browsers that do not support JavaScript, but it is applied by those that do it.

Unobtrusive JavaScript separates content from behavior. This means you should avoid having inline JavaScript as in the following example.

HTML
<form id=”profile” action=”http://mySite.com/SaveProfile”>
  <input type=”text” name=”age” />
  <input type=”submit” value=”Save” onclick=”SaveProfileWithAjax();” />
</form>

This is because the purpose of markup is to describe a document’s structure, not its programmatic behavior.

The unobtrusive solution is to register the necessary event handlers programmatically, rather than inline. This is commonly achieved by assigning a particular CSS selector to all elements which are affected by the script, to reduce the amount of script code. The JavaScript code should reside in a separate file. In the following code the id attribute is used for identifying a form:

HTML
<form id=”profile” action=”http://mySite.com/SaveProfile”>
  <input type=”text” name=”age” />
  <input type=”submit” value=”Save” />
</form>

It is recommended that you use libraries that provide an abstraction of the DOM. The jQuery and ASP.NET Ajax libraries do a good job at this.

The following jQuery script binds the submit event of the form with id=”profile”, to the SaveProfileWithAjax function:

Note: jQuery simplifies this, by providing a CSS-like selector, instead of just getting the elements by ID.

JavaScript using jQuery
$(document).ready(function(){ //Wait for the page to load.
    $(‘form#profile’).bind(‘submit’, SaveProfileWithAjax);
});

function SaveProfileWithAjax(event){
    event.preventDefault(); // this will prevent the browser for submitting the form in the default way, as we will handle the post programmatically using AJAX
    // Post the data using an AJAX call
}

Note: The event.preventDefault JavaScript method cancels the event if it is cancelable, meaning any default action normally taken by the implementation as a result of the event will not occur.

The following code shows the implementation in ASP.NET Ajax Library.

JavaScript using ASP.NET Ajax Library
Sys.Application.add_init(function() {
    $addHandler($get(‘#profile’), ‘submit’, SaveProfileWithAjax);
});

function SaveProfileWithAjax(event){
    event.preventDefault();
    // Post the data using an AJAX call
}

Note: To attach events, modern browsers use the addEventListener function specified in the DOM Level 2 (Events) specification, while Internet Explorer will use its proprietary attachEvent function. For this reason, if you want to achieve cross-browser compatibility in a simple manner, you should always attach events using a library like jQuery or ASP.NET Ajax Library, which automatically deals with these compatibility issues.

Tips for Achieving Progressive Enhancement in ASP.NET MVC

Consider the following tips when implementing the progressive enhancement pattern in ASP.NET MVC.

First, build an HTML feature that works without JavaScript. For example, the song rating functionally of the Reference Implementation, which was created using radio buttons.

The following rules are applied to every web application (not just ASP.NET MVC ones) to achieve PE.

  1. Use semantic markup to render the basic content.
  2. Use CSS to enhance the layout.
  3. Anchors should always have the href attribute set to return a working view from a controller.
  4. Forms should always have the action attribute set to post the data to a working action in a controller, based on the input elements in the form. Consider rendering hidden inputs for preset values that the users don’t need to update or see.

Once the basic functionality works without JavaScript, consider the following:

  1. Enhance the experience using Unobtrusive JavaScript. This typically includes:
    • Adding client-side validation to acquire immediate feedback without requiring a full post.
    • Converting full page POST/GET requests into AJAX calls that update portions of the page.
    • Adding animations / eye candy features.
  2. When converting a full page request into an AJAX call, hijack and prevent the default action of the anchor link or form submit, and replace it with the AJAX call. You typically do this by calling the preventDefault method of the arguments object received when handling the click/submit event using JavaScript.
  3. Avoid having different URLs for accomplishing the same business result for these cases. You can add branching code in your controller that checks the Request’s headers to see if it is an AJAX call as opposed to a typical GET or POST call, and return a different result in this case.
    • Typical approaches include returning partial HTML markup to be inserted without processing into the DOM, or data represented in JSON that requires some processing in the browser to display it.
    • ASP.NET MVC provides a standard way of checking if the request was initiated using XmlHttpRequest by calling the Request.IsAjaxRequest() extension method
      Note: If you use ASP.NET WebForms instead of ASP.NET MVC, you might need to create different endpoints, such as Web Services, ASP.NET Page Methods, or even expose MVC controllers for these actions, as there is no easy way of reusing the same endpoint, because a URL maps to a physical ASPX file.
  4. In the cases where you return JSON, you might need to use JavaScript to directly manipulate the DOM, or when possible, have a template view that renders data that was retrieved in JSON format. Having templates can help you better separate UI logic from the model in the JavaScript code. The ASP.NET Ajax Library has a very good templating engine that allows you to bind to a ViewModel in a somewhat similar way as WPF & Silverlight. For more information, see Isolating the Domain Model from the Presentation Layer (this is in the Web Client Guidance documentation).
  5. In some cases, you may have DOM elements that only make sense in a non-JavaScript version of the page. This is common, and you might want to remove these elements by using JavaScript if it is available in the browser.
    For example, you may have a link that redirects to a different page, but when you enhance it with JavaScript, you might hide those links entirely, and replace it with a much richer experience that does not require a redirect for example.
    Note: You can also use the noscript HTML element to render content when JavaScript is not enabled or not supported by your browser.
  6. Cascading drop-downs is another canonical example: If you have a State drop down, that once selected will set available cities in another drop-down, the non-JavaScript version will have a visible submit button, and so after setting the State, the user can POST back to the server and get the same page with the available cities already populated. When JavaScript is enabled, after the page is loaded, you might want to hide this button, and on selection change of the State dropdown, request the available cities with an AJAX call that returns JSON, and update the dependant Cities dropdown. There is no need for the user to explicitly click the button to get the cities.

Finally, move on to the next feature. Remember, to always build a non-JavaScript version of a feature, and enhance it afterwards. There might be secondary features in the application where you can decide to implement in browsers that only have JavaScript enabled, but you should make this decision consciously, identifying the risks for not supporting that scenario.

Sources

More Web Client Guidance

You can find this and many other topics and key decisions for creating web client applications (both in ASP.NET Web Forms and ASP.NET MVC) in our latest Web Client Guidance drops. We are close to finishing this guidance, so your prompt feedback is invaluable to us. Make sure you check it out and comment in this topic or in the codeplex forums (there is a special tag to mark conversations for this new guidance)

kick it on DotNetKicks.com Shout it

  • Damian Schenkelman

    In the “Core Principles” section, “Progressive Enhancement consists of the following core principles:” should be an introductory text and not a bullet.
    In the “Browsers with JS off” section, there seem to be too many advantages and just one disadvantage. It makes it seem simple to implement (or just something that provides a high ROI). Is it like that?
    In the “Different Browsers that Implement JavaScript and DOM Features Differently” section, I would add a subtitle like “Challenge” or something similar starting at “This challenge has been a major pain point for web developers in…”. Otherwise it seems that you are explaining the pattern, but you are explain some of the difficulties implementing it. To avoid repeating the word challenge to many times you can use “scenario” at the beginning of the section.

    That’s as far as I got.