Progressive Enhancement With ColdFusion 8 Ajax

I've been heavy into some research lately, trying to become more familiar with some advanced concepts. Part of such research has been to learn more about Progressive Enhancement. I wouldn't exactly call myself an expert on the topic, but essentially it's a shift in mindsight from what I've gotten used to with Ajax development over the last year.

My typical Ajax mindset had been: "Just build it, cuz after all it's just so darn cool". No thought was given as to the experience users without JavaScript enabled would have, because after all much of the Ajax revolution is built upon the assumption that if you don't have JavaScript you're just gonna have a really sucky experience.

The Progressive Enhancement (which I'll abbreviate as 'PE' for laziness sake) camp, as I understand it, takes a bit of a different stance. With PE the content is geared towards the lowest common denominator. The worst case scenario if you will. No JS, no CSS support, no Flash, no Java. Layout is then enhanced by externally linked CSS. Content is then enhanced with unobtrusive, external scripts. And so on. You get the picture.

It's a noble concept for sure. Make 'everyone' happy. I'm not saying I'm 100% jumping to the "make it perfect for every possible user that comes across your site" camp, but I wanted to take a stab at creating something that fits the PE mold with ColdFusion 8 Ajax. My first crack at such a demo can be seen here (source code is attached to this entry). Go on, give it a shot then come back and read the rest of this entry. While you're there make sure to disable JS and check the behavior of the page.

So as I said this is my first attempt. Not being an expert, I may have broken a "rule" or two, but generally speaking the page behaves in the exact same manner (paginates through a recordset with Next/Prev links) regardless of whether JavaScript is enabled or not. The layout also should be acceptable whether or not CSS is included.

The basic idea of my code was to build the pagination with typical HTTP requests (passing start/end through the URL as needed). I encapsulated this code in a custom tag for reusability (so that when I called it from Ajax/JS I could reuse the template with little modifications). I then wrapped the call to the custom tag with a cfdiv, which would dynamically fetch the first 5 records if JS is enabled. Here's a quick look at that:

<cfdiv bind="url:art.cfm?start=#url.start#&end=#url.end#" id="artDiv">
   <cfmodule template="art.cfm" start="#url.start#" end="#url.end#" />
</cfdiv>

After that it was a matter of making art.cfm a little ambiguous as to whether or not it was being called directly via Ajax or as a custom tag. It's not 100% pretty, but here is art.cfm in it's entirety:

<!--- is this template being called as a custom tag? --->
<cfif isDefined("thistag.executionmode")>
   <!--- if we're in end mode, exit the tag (so it doesn't run twice) --->
   <cfif thistag.executionmode eq "end">
      <cfexit method="exittag" />
   </cfif>
</cfif>
<!---
   if attributes.start isn't defined
   then assume the template is being
   called from javascript
--->

<cfif not isDefined("attributes.start")>
   <!--- so no attributes, but js is going to be passing url vars --->
   <cfparam name="url.start" default="1" />
   <cfparam name="url.end" default="5" />
   <cfset attributes.start = url.start />
   <!--- bind the click of the link to a CF.navigate function call --->
   <cfajaxproxy bind="javascript:next({nextLink.id@click},'#url.start + 5#', '#url.end + 5#')" />
</cfif>
<!---
   ditto for end...
--->

<cfif not isDefined("attributes.end")>
   <cfset attributes.end = url.end />
   <cfif attributes.start gt 1>
      <cfajaxproxy bind="javascript:prev({prevLink.id@click},'#url.start - 5#', '#url.end - 5#')" />
   </cfif>
</cfif>

<!--- get the query data --->
<cfset art = application.art.getArt() />
<cfset total = art.recordCount />

<cfif attributes.start gt total>
<cfset attributes.start = total>
</cfif>
<cfif attributes.end gt total>
<cfset attributes.end = total>
</cfif>



<table>
   <tr>
      <th>&nbsp;</th>
      <th>Name</th>
      <th>Description</th>
   </tr>
   <cfoutput>
      <cfloop query="art" startrow="#attributes.start#" endrow="#attributes.end#">
         <tr>
            <td>#currentRow#</td>
            <td>#artname#</td>
            <td>#description#</td>
         </tr>
      </cfloop>
   <tr>
      <td colspan="3" style="text-align:right;">
         <cfif attributes.start gt 1>
            <a href="#cgi.SCRIPT_NAME#?start=#attributes.start - 5#&end=#attributes.end - 5#" id="prevLink"><--Previous</a>
         </cfif>
         <cfif url.end + 5 lte total>
            <a href="#cgi.SCRIPT_NAME#?start=#attributes.start + 5#&end=#attributes.end + 5#" id="nextLink">Next--></a>
         </cfif>
      </td>
   </tr>
   </cfoutput>
</table>

Notice I do a little cheesy check to see whether the tag is being called as a custom tag or not, and if not (assumption is Ajax) then I register a cfajaxproxy bind on the Next/Prev links to enable the dynamic content loading. Here's the JS functions that are called with the ajaxproxy binds:

next = function(t,s,e){
   var nL = document.getElementById('nextLink');
   if(nL.href != '#'){
      nL.href = '#';
   }
   ColdFusion.navigate('art.cfm?start='+s+'&end='+e, 'artDiv');
}
prev = function(t,s,e){
   var pL = document.getElementById('prevLink');
   if(pL.href != '#'){
      pL.href = '#';
   }
   ColdFusion.navigate('art.cfm?start='+s+'&end='+e, 'artDiv');
}

And that's pretty much all there is to it. As I said, I may have overcomplicated things a bit with this demo, but I think it's still easy enough to understand the concept. I'm not going to go as far as to say that *all* CF 8 Ajax can apply Progressive Enhancement, but it's good to know that there are some options.



Comments
This really is the best way to develop, some of the JavaScript frameworks out there like jQuery and Prototype make this so easy with 'observers', so you can have normal links in your href's but 'listen' for the click if the user has javascript...then perform an action on that event (a user clicking a link etc) like ajax.

Keeps your markup clean, easier to maintain and accessible.

Nice :)
# Posted By Michael Sharman | 3/18/08 6:21 AM
So right, not all of the CF 8 components can give you this functionality. The Ext grid would not be accessible in the CF8 implementation, nor would the paging grid in a straight Ext implementation. One would have to use the Ext table-to-grid, with custom paging written in (similar to your example). This is why I don't suggest using the CF 8 components in a user-facing environment, but rather in a backend control panel type of environment, and then only if you know your user base. Front end usage should definitely be PE, and JQuery + Ext can give you good tools for some good unobtrusive scripting.
# Posted By Steve 'Cutter' Blades | 3/18/08 10:22 AM
Honestly? I think that the concept is cool, and it's a great exercise in development, but almost completely worthless in the real world. The number of people who have Javascript disabled is almost nill because why WOULD they turn it off? There's no benefit to doing so, and I'd argue that there's a lot to lose by turning it off.

Sure you can now say "my sites work the same with and without JS" but how what's your ROI? How much time are you spending for that fraction of a percent of users?
# Posted By Andy Matthews | 3/18/08 10:28 AM
@cutter:

I agree with you - creating an 'application' heavy with ajax is much more suited to backend usage (admin areas or full blown intranet rich applications). I used to have the luxury of working in the intranet 'vacuum' where all users had JS and used IE (yuck).

@andy:

Well....as I said, I don't think i'm 100% in the 'fully accessible' camp - but - we have to be cognizant of things like mobile browsers, etc when we write our code. And, if you look at my code, you'll notice that it's really not that much extra work. I would have needed to write the code for the Ajax interface anyways. So in this case, it's just a matter of writing the fallback interface *first* and then 'enhancing' it with the Ajax version (thus the reason it's called progressive enhancement).

It's just a change in mindset from writing the Ajax interface first and then putting in the old bandaid for users that don't have JS enabled.

I think at the very least we owe it to our users to at least degrade gracefully to let them know what they're missing. Ray's given the following example on his blog:

<cfdiv bind="url:art.cfm?start=#url.start#&end=#url.end#" id="artDiv">
Enable JavaScript to see this awesome content!!
</cfdiv>

That way the user has a clue - instead of dead whitespace...
# Posted By todd sharp | 3/18/08 11:17 AM
@michael & cutter:

Based on your experience, would you have done the Ajax override differently, or is this method typical:

next = function(t,s,e){
var nL = document.getElementById('nextLink');
if(nL.href != '#'){
nL.href = '#';
}
ColdFusion.navigate('art.cfm?start='+s+'&end='+e, 'artDiv');
}

I'm checking for an href, and if one exists I'm overriding it with '#'...
# Posted By todd sharp | 3/18/08 11:19 AM
Same basic concept, overriding the links default action. I typically will apply a specific class to the links, then bind a new click event to those links through JQuery, which will do an action, using the href attribute value, but not do the standard link action (to a new page).
# Posted By Steve 'Cutter' Blades | 3/18/08 11:53 AM
Yep, cfajaxproxy binds to the <a> element through the id/class and registers a click listener...but it must not use the href because I was getting both actions firing on me (which is why I had to set the href like so).
# Posted By todd sharp | 3/18/08 12:20 PM
@Todd - I use jQuery or Prototype for this kind of thing. So I might have

<a href="http://www.cfsilence.com/" id="cfsilence">visit cfsilence</a>

Then in my linked .js file have something like:

$('cfsilence').observer("click", handlerFunction);

function handlerFunction(event)
{
//whatever I want here
}

I hope that came out, I have a few recent entries on my blog about this stuff for Prototype as it's what I've been using lately.

@Andy - The point is that it really doesn't take much longer to do. Much like commenting your code, do you do this? Of course you do because it's second nature now and really doesn't slow you down at all.

Your markup is totally clean, so if you need to make a change over many many many templates...you can do it in 1 .js fie.

As Todd said, the 'PC' isn't the only internet enabled device out there :)

Accessibility isn't given enough attention by developers and not just javascript.
# Posted By Michael Sharman | 3/18/08 5:24 PM
btw the reason I use id's instead of classes in my element is that they perform better, but sometimes you need to use classes if you want an operation performed on multiple elements.

jQuery has amazing dom traversal methods to use, combine this with the dom:ready (or dom loaded in Prototype) and everything is 'parsed' pre-image loading...super quick :)
# Posted By Michael Sharman | 3/18/08 5:26 PM
C Todd, that was what I was saying some time ago on your blog when claiming not everything worked when browsing with Javascript OFF - as I usually do. Good to read that you've joined the PE-clan. It makes for a better Internet ;-)
# Posted By Sebastiaan | 3/19/08 2:07 PM

Calendar

Sun Mon Tue Wed Thu Fri Sat
      1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30       

Subscribe

Enter your email address to subscribe to this blog.

Tags

actionscript ajax blogging cfsnippets coldfusion flash forms flex funny stuff misc model-glue off topic personal project learn slidesix sql

Recent Comments

Ajax Form Submission Revisited And Advice Needed
Hammo777 said: To solve your original problem: document.formname.onsubmit(); just call the onsubmit method yourse... [More]

Editing A Query In A SQL Server DTS Package
JD said: Thanks for your post. Never unlike Microsoft to hide stuff in the hardest part time find. [More]

Mashing Spry Effects With CF8 Ajax Goodness
Mark Pitts said: I have had moderate success implementing Spry Accordian. Sadly the part that does is not working wil... [More]

Chinese Birth Calendar Accuracy Test
Toni Lehman said: This calendar was accurate for both my daughters and 4 grandchildren. I tried it for 11 of my other ... [More]

Virtual Memory - Am I The Last To Know?
Larry Miller said: The authors friend was right. Windows virtual memory system was designed by experts and they fully u... [More]

RSS


coldfusionbloggers

FullAsAGoog MXNA

Consumed By Feed-Squirrel.com