IT Evolution Structure for Progressive Enhancement

IT Evolution « Test

This page demonstrates getting the specific progressions to specific browsers.

Here is the source code for this page to get an overview of its structure:

Page XHTML

view source

Progressively Enhance a Link to a CSS3 Button

Step 1 - Markup

To demonstrate, this is the mark-up, a simple link, XHTML 1.0 Strict, which we will progressively enhance. This is the semantic mark-up:

<a href="http://validator.w3.org/check?uri=referer"
   title="Check this document is WC3 XHTML 1.0 Strict compliant">XHTML 1.0 Strict</a>

This is the actual link shown above as Rendered by your current Browser.

XHTML 1.0 Strict

Browser Identifier

With Internet Explorer versions, the foreground colour of this container will also be changed using a different technique.

Skip down to Steps 2, 3 or 7, to find the header that matches this containers colour to identify your browser.

Until I design a sprite for all browsers and OS platforms, I will use abbreviated names, such as FF for Firefox and W=Windows, M=Mac and L=Linux. IE will always be on Windows

Step 2 - Link for Linear Layout 3 Style-sheet

IE 4 - IE 5 - IE 5(M) - IE5.5 - NS 4 - Non-CSS/CSS Disabled Devices

Link to the linear layout style-sheet.

<link rel="stylesheet" type="text/css" href="c/linear.css" /><!-- For all browsers -->

Take a look at the styles given to all CSS level 1 browsers.

Linear Layout Style-Sheet

linear.css

Keep plain and simple. Black on white, an easily readable font and no positioning. Any earlier or non-CSS devices will also have this linear layout and look very similar.

If you do not wish to support old browsers for CSS level 1, then do not add this link.

Disable CSS and/or JavaScript and refresh the page to see the linear layout. Remember, this is a test page and a tutorial. If this was for customer consumption, I would not show unnecessary tutorial content.

Notice the additional content that is not visible when JavaScript is available. Check it out.

Step 3 - Import Filter for CSS 3 Style-sheet

CSS Level 3 Compliant - IE 9

CSS Level 3 Compliant - Opera 10.6(WML)

The next step is to give all compliant devices, a valid CSS level 3 style-sheet. If a browser needs vendor specific styles to render an effect, then it does not belong to this category.

To eliminate up to IE5.5, we need to use an @import filter:

<style type="text/css">
  @import "null?\"\{";
  @import "c/style.css";
  @import "null?\"\}";
</style>

Update to Eliminate IE5.5 and Below

After doing some testing I found I was getting two 404 errors in my server logs, not locating the two null references in the high pass filter used above.

My resolution was to point to null.css, which just contains a comment and to use a slightly more elegant high pass filter:

<style type="text/css">
@import "c/null.css?\"\{"; 
@import "c/style.css";
</style>

If you want to eliminate up to IE7 instead, use this filter, @import url(c/style.css) All;, which many customers are now happy to go with as IE6 is being dropped from support by the big sites. Why not push the users to upgrade directly to IE8 or IE9. Depending on the Windows version they are using.

Hopefully, the list of browsers in this section will increase as vendors adhere to the standards. Ideally, this is the only style-sheet any site should require. If we all agreed to give browsers either a fully-functional linear layouts or a fully-compliant CSS 3 design, this would be a great way to get everyone to move forward! Take a look at the CSS3 style-sheet:

CSS Level 3 Style-Sheet

style.css

Unfortunately, @import has performance issues; it will wait until the page is fully downloaded before displaying in some browsers. Eventually, (or if you don't care about browsers trying to read what they do not understand and screwing up your presentation), we will let this one go and just use a single <link> for compliant browsers

Snapshot Image of the Rendered Results

IE 9Internet Explorer Version 9Opera 10.6(WML)Opera 10.6 on Windows, Macintosh and Linux

Let's make sure the CSS3 style-sheet validates CSS level 3

Notes

IE5.5, IE 6 and IE7 do not get the font graphic character t because they do not support the CSS pseudo selector :before. More reasons why we should move to IE8.

.blue:before {
    font-family: EfonRegular;
    content: 't';
    color: #0030c0;
    font-size: 28px;
    float: left;
    margin-left: 10px;
    text-shadow: 0 1px 0 #4190AF;
}

IE5.5 is buggy and did not display @font-face characters at all in my tests. Maybe I'm missing something!

@font-face {
		font-family: 'EfonRegular';
		src: url('/c/f/EFON-webfont.eot');
		src: local('☺'), url('/c/f/EFON-webfont.woff') format('woff'), 
      url('/c/f/EFON-webfont.ttf') format('truetype'), url('/c/f/EFON-webfont.svg#webfont') format('svg');
		font-weight: normal;
		font-style: normal;
}

Step 4 - Alternate Style-Sheet

Some browsers offer the ability to switch style-sheets. I will offer an alternative style-sheet to present a more accessible version of my presentation.

<link rel="alternate stylesheet" type="text/css" title="Accessible" href="c/accessible.css" />

accessible.css

accessible.css

Another alternative that will work with my sites is for the user to disable styles and return to my fully-functional linear layout. Which is available in all browsers.

Step 5 - Reference Initialisation Script

Best practise tells us to place our scripts at the bottom of the page and I follow this practise. Unfortunately, I need to lazy-load style-sheets to fix browser specific bugs and bring the towards the CSS3 specification. If these style-sheets are added after the page has rendered, it will cause a FOUC. Hence my need to put this script in the head section and get the job down before the content renders.

<script type="text/javascript" src="j/init.js"></script>

Take a look at the whole script before breaking it down into tasks:

init.js

init.js

Step 6 - Behaviour Capabilities

Most libraries do not support IE5.5 and below. It looks like IE6 is shortly for the chop. We need to eliminate these old browsers.

// Set a global flag that allows compliant browsers to run JavaScript 
// and my global WW namespace
var
  go = false, // Not web 2.0 capable
  WW // My namespace
;
/* Eliminates IE4, NS4 and other old devices */
if ( document.getElementById && document.childNodes && document.createElement) {
	go = true;
  /* Eliminates IE5 and IE5.5 using IE conditional comments */
  /*@cc_on
  go = false;
  if (@_jscript_version > 5.5) { // could change this to 5.6 (IE6) or even 5.7 (IE7)
    go = true;
  }
  @*/
}

The structure of my JavaScript files can now follow this pattern:

function init() {
  if (go) { // the variable 'go' is global remember
    // Code to process
  } else { 
    // bailout or run some code for non-compliant browsers
    return false;
  }
}

init();

I only have three properties on the global namespace, (the window object). That is the init function, go and my namespace, WW.

Step 7 - Preparation for Vendor Specific Presentation

Because CSS 3 is not yet a standard, many vendors offer their own version of CSS 3 rules. Unfortunately, these rules do not pass validation and, if used, are going to bloat your, now in-valid, style-sheet.

It would be great to only use a CSS 3 valid style-sheet to take us forward and in the mean time, give each browser only the style-sheet it needs to progressively enhance towards a CSS 3 effect.

This can be achieved by testing for layout engines instead of sniffing for specific browser versions. Any browser that uses a particular layout engine can share the same style-sheet.

Here I have a solution that uses this technique and also shows you how we might target specific versions of IE within JavaScript, rather than using IE HTML conditional comments.

Step 7a - Empty Style-sheet Links

The first task is to create a couple of empty style blocks so that we can attach external style-sheets as required. One for the detected layout engine and one for the IE version specific fixes.

link.rel = "stylesheet";
link2.rel = "stylesheet";
link.type = "text/css";
link2.type = "text/css";
link.id = "link";
link2.id = "link2";
document.getElementsByTagName("head")[0].appendChild(link);    
document.getElementsByTagName("head")[0].appendChild(link2); 

Notes

Here's how to do the job using jQuery, including additional attributes if required:

$('<link>') 
  .attr('rel', 'stylesheet') 
  .attr('type', 'text/css') 
  .attr('id', 'link') 
  .attr('title', title) 
  .attr('href', filename) 
  .appendTo('head') 
;

Just in case you need to add script blocks, here's how:

/* Scripts - Append to body section */
fileref=document.createElement('script');
fileref.type = "text/javascript";
fileref.id = "script";
fileref.src = filename;
document.getElementsByTagName("body")[0].appendChild(fileref);

/* jQuery */
$('<script>') 
  .attr('type', 'text/javascript') 
  .attr('id', 'script') 
  .attr('src', 'j/animate.js') 
  .appendTo('head') 
;

Step 7b - Detect Layout Engines and Browser

Each detection is added as a property of my namespace

WW = {
  Gecko: ua.indexOf('Gecko') > -1 && ua.indexOf('KHTML') === -1?true:false,
  WebKit: ua.indexOf('AppleWebKit/') > -1?true:false,
  Presto: ua.indexOf('Presto/2') > -1?true:false,
  MobileSafari: /Apple.*Mobile.*Safari/.test(ua)?true:false,
  KHTML: window.navigator.vendor=='KDE'?true:false,
  NS4: document.layers?true:false,
  Trident: false, /* Set some flags for IE */
  fixIE: false,
  IE6: false,
  IE7: false,
  IE8: false,
  IE9: false
}

Step 7c - Load Gecko, Webkit and Presto Style-sheets

if (WW.Gecko) {
  link.setAttribute("href", '/c/gecko.css');
}
if (WW.WebKit) {
  link.setAttribute("href", '/c/webKit.css');
}
if (WW.Presto) {
  link.setAttribute("href", '/c/presto.css');
}

Step 7d - Load Trident Style-sheet and IE Specific Style-sheets

Again, we need to switch into IE conditional comments for these detections.

/*@cc_on
WW.Trident = @_win32?true:false;
if ((@_jscript_version > 5.5) && (@_jscript_version < 5.9)) {
  link.href = '/c/trident.css'; // Sets background colour for all IE browsers
  WW.fixIE = true; // Flag to target IE6 to IE8
}
if (@_jscript_version === 5.5) {
  old = true;
}
if (@_jscript_version === 5.6) {
  link2.href = '/c/ie5.6.css';
  WW.IE6 = true;
}
if (@_jscript_version === 5.7) {
  link2.href = '/c/ie5.7.css';
  WW.IE7 = true;
}
if (@_jscript_version === 5.8) {
  link2.href = '/c/ie5.8.css';
  WW.IE8 = true;
}
if (@_jscript_version === 9) {
  link2.href = '/c/ie9.css';
  WW.IE9 = true;
}
@*/

Step 8 - The Vendor Specific Style-sheets

Gecko - FF 3.6(WML) - Minefield 4(W) - SeaMonkey 2.1(WL)

Here is the style-sheet to bring Gecko browsers on-board

gecko.css

gecko.css

Snapshots of the rendered result.

FF 3.6(WML)FF 3.6.3 on Windows, Macintosh and LinuxMinefield 4(W)Minefield 4.0b7pre on Windows
SeaMonkey 2.1(WL)SeaMonkey 2.1a3 on Windows and Linux

Webkit - Safari 5.0(WM) - Chrome 6.0(WML) - Flock 3(W) - Rekonq 0.4(L) - Epiphany 2.3(L)

Here is the style-sheet to bring Webkit browsers on-board

webkit.css

webkit.css

Snapshots of rendered results

Safari 5.0(WM)Safari 5.0.2 on Windows and MacintoshChrome 6.0(WL)Google Chrome 6.0.472.63 on Windows and Linux
Flock 3(W)Flock 3.0.0.4177 on LinuxChrome 6.0(M)Google Chrome 6.0.472 on Macintosh
Epiphany 2.3(L)Epiphany 2.30.2 on LinuxRekonq 0.4(L)Rekonq 0.4.0 on Linux

Notice the slight indication of the container behind the button in Safari and the very bad appearance around the Chrome button (Which spoils the effect in my opinion).

It looks like Flock gave up on gecko and switched to webkit as used by Chrome, with its problems.

Rekonq does not recognise @font-face and does not merge inset colours.

Epiphany does not recognise @font-face.

Presto - Opera 10.6(WML)

For this example, Opera 10.6(WML) was CSS level 3 compliant and did not need any fixes

Trident for IE6, IE7 and IE8

The Trident style-sheet targets all of the supported IE browsers up to version 8. It applies different background colours to each browser.

Trident.css

trident.css

The way we target each version of IE is as follows:

#bg {
  background: #0000ff; /* IE 8 and below - blue */
  *background: #ff6611; /* IE 7 and below - override - orange */
  _background: #ff0000; /* IE 6 and below - but we are not supporting IE 5.x browsers */
  _bac\kground: #993300; /* IE 6 only */
}

The alternative approach is to give each supported version of IE its own style-sheet. Remember the code in the IE conditional comments. For example, here's the focus for IE6:

/*@cc_on
...
if (@_jscript_version === 5.6) {
  link2.href = '/c/ie5.6.css';
  WW.IE6 = true;
}
...
@*/

These style-sheets will also change the foreground colour of the IE browser indicator.

IE6

IE5.6.css

ie6.css

Here is a snapshot capture of the rendered result.

Internet Explorer Version 6

IE7

IE5.7.css

ie7.css

Here is a snapshot capture of the rendered result.

Internet Explorer Version 7

IE8

IE5.8.css

ie8.css

Here is a snapshot capture of the rendered result.

Internet Explorer Version 8

IE9

Just in case it is ever needed, IE 9 has been targeted specifically to additionally style the color property using this style-sheet:

IE9.css

ie9.css

However, IE9 is fully CSS3 compliant and does not need any fixes to produce the glassy button.

Internet Explorer Version 9

Step 9 - Hiding Content

The last task that the initialisation script has to do before the page starts to render its content, is to hide any content that should not be displayed on initial page load.

It is a mistake to hide content with CSS and then use JavaScript to display it. If JavaScript is not available and content has been hidden with CSS then these users will never see the content.

A few solutions exist for resolving this problem. My preference is use CSS specificity.

Target Mark-up to Hide

First, a selector is needed so that we can target the content to hide. Let's say we want to collapse and hide the content of an accordion. The mark-up might be:

<dl class="accordion">
	<dt><a href="#">Title goes here</a></dt>
	<dd>
		<p>Content</p>

	</dd>
	...
</dl>

Hide Content the Wrong Way

The idea is to collapse all the definition descriptions that have a parent container with a class name of accordion.

.accordion dd { 
  display: none; 
}

The content is hidden but we now rely on JavaScript to display it. No JavaScript, then no content.

Hide Content the Correct Way

Using the same mark-up as above, we modify the style-sheet with a more specific class name.

.js .accordion dd { 
  display: none; 
}

Without JavaScript, the class name js does not exist so the rule is not applied and the content is not hidden.

All our init.js has to do is add the class name to a parent of our target container. Why not use the <html> element!

document.documentElement refers to the root element in the HTML page.

document.documentElement.className = 'js';

Now the CSS rule is applied and our content is hidden.

Promoting Browser Upgrades

I am using the technique described above to only show some browser upgrade promotion content if we are in linear layout and thus, using an old browser.

Here is the mark-up for my upgrade campaign.

<div class="hidden" style="background: #FF8676; padding-left: 1em; border: 2px solid #8C0000;">
<h2>Important Notice</h2>
<p>This site is fully-functional for you but please read why you are missing out on <a href="http://www.bfinternet.co.uk/news-info/articles/why-upgrade-your-browser" title="You can have a much better experience in a safer environment">a much better and safer experience</a></p>
<h3>JavaScript Errors</h3>
<p>If you should experience JavaScript errors, it will be because your internet browser is so old, it does not understand basic JavaScript instructions.</p>
<p>I only found one browser that had this problem during my tests, Internet Explorer version 4. If you cannot upgrade this browser, then I recommend you disable scripting: <code>View - Internet Options - Security - Custom - Settings - Scripting - Active scripting - Disable</code></p>
</div>

Try disabling JavaScript and this is what you will see at the top of the page.

Important Notice

This site is fully-functional for you but please read why you are missing out on a much better and safer experience

JavaScript Errors

If you should experience JavaScript errors, it will be because your internet browser is so old, it does not understand basic JavaScript instructions.

I only found one browser that had this problem during my tests, Internet Explorer version 4. If you cannot upgrade this browser, then I recommend you disable scripting: View - Internet Options - Security - Custom - Settings - Scripting - Active scripting - Disable

Here is the CSS in linear.css that will hide the content.

.js .hidden {
  position: absolute;
  left: -10000px;
  top: auto;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

Note that we should not use display: none; or visibility: hidden; to hide this content. Instead, we offset, left: -10000px;, the content outside of the viewport, which means, unlike the other two methods, the content is still accessible by screen-readers.

In init.js, I have to do a little more work before adding the .js class name. I must only do it for supported browsers. For old browsers, like IE5.5, that have JavaScript but are not supported, I still want to promote these guys to upgrade their browsers in the linear layout they receive. Here's the pattern that gets the job done:

var old = false;    
// ... more code here
if (@_jscript_version === 5.5) {
  old = true;
}
// ... 
if (!old) {
// Only hide content if JavaScript is available
document.documentElement.className = 'js';  
}  
// ...  

Step 10 - Behaviour

Finally, at the bottom of the page (for optimisation), add external (site or section wide) and embedded (page specific) JavaScript.

Aim to reduce these references to one external file, minify and gzip for optimisation

<!--Link to CDN for production sites-->
<script type="text/javascript" src="/j/jquery.tools.min.js"></script>
<script type="text/javascript" src="/j/jquery.chili.min.js"></script>
<script type="text/javascript" src="/j/jquery.chili.recipes.js"></script>
<script type="text/javascript" src="/j/utils.js"></script>
<script type="text/javascript" src="/j/dev.js"></script>
<script type="text/javascript" src="/j/teach.js"></script>
<script type="text/javascript" src="j/page.js"></script>

<script type="text/javascript">//<![CDATA[
if (go) {
  
  // Embedded page specific behaviour here ...

}
//]]></script>

</body>
</html>

Here is my current page specific external JavaScript file.

page.js

page.js

Targeting IE Behaviour

If we need to target IE versions for JavaScript, we can make use of the flags we set in the WW namespace. For example, let's add a utility to my namespace:

WW.loadJS = function (url) {
  var script = document.createElement( 'script' );
  script.type = 'text/javascript';
  script.src = url;
  document.body.appendChild( script );
}

Now we can utilise this method to lazy-load a script for IE versions only:

if (WW.Trident) { // All IE versions
  WW.loadJS('j/ie.js'); // For your IEs only
}
/* alternatively we can target specific IE versions */
if (WW.IE7) { // IE 7 only
  WW.loadJS('j/ie7.js');
}

Then, within ie.js we can still target individual versions of IE

if (WW.IE6) {
 // Do something just for IE6
}
if (WW.fixIE) {
 // Do something just for IE6, IE7 and IE8
}

General Points

Centering Content

For a bit of fun, I added a class-name of deadCentre to a container and applied these rules for the class, giving Gecko and Webkit their radius and box-shadow properties. IE9 and Opera get the CSS3 compliant code. The rest just get a square box. This is just progressive enhancement.

.deadCentre {
  width: 280px;
  height: 90px;
  /* margin -(height/2), 0, 0, -(width/2); */
  margin: -45px 0 0 -140px;
  position: fixed; /* Normally position: absolute; cos' the box get's in the way */
  top: 50%;
  left: 50%;
  
  text-align: center;
  background: #f9b736;
  color: #a14b2b;
  border: 3px solid #a14b2b;
  border-radius: 16px;
  box-shadow: 5px 10px 26px rgba(234, 121, 15, 0.5);
}

If the box is in your way, place it back into its static position and adjust the negative margins. Clicking the code block below will evaluate the statements (or click the red cross, if you have not already done so. Refresh the page if needed). Try it:

$('.deadCentre')
  .css({'position': 'static', 'margin': '1em'});
No

Progressive Enhancement

by Braveheart

Fixed Position

Did you notice we are using fixed positioning for our dead-centered container above? Here we see our structure becoming useful because IE6 does not support position: fixed;. A solution would be to include IE hacks to target IE6 and make it work when fixing positions.

* html  { overflow-y: hidden; }
* html body {
  overflow-y: auto;
  height: 100%;
  padding: 0 1em 0 14em;
  font-size: 100%;
}
* html .deadCentre { position: absolute; }

These hacks would cause your style-sheet to be invalid and can add considerable weight to your style-sheets.

We already have a method that can target versions of IE and we don't even need the hacks. Take another look at the style-sheet that is only loaded by IE6 to see the hack-less fix:

IE5.6.css

ie6.css

Where Now?

Ok, I think I have covered everything I need as a basic framework to deal with structure, presentation and behaviour in a cross-browser, standards compliant way.

There is far too much material in this page and my home page (They are becoming slow to run due to size and internal processes, which are purely for tutorial reasons).

I now need to build my XML/XSLT framework, which will allow me to break up the content of these pages into many more, manageable pages and have all the duplicate content generated for me automatically. I will talk more about my XML/XSLT framework at a later date.

It's also about time I started looking at styling my site based on the theme, "The Evolution of Progressive Enhancement".