Earlier today I read an interesting blog entry on jQuery Mobile and Rails. Now - let me start off with saying I'm not a huge fan of Ruby's syntax. I've got nothing against it and Rails. I did notice though that some of the template examples in the post seemed unnecessarily complex and verbose when it came to HTML. A few folks on Twitter informed me that was more his use of HAML than Ruby. Fair enough. That being said, the author did some very interesting things with his templates and I thought I'd work on a ColdFusion version. Credit for the coolness though goes squarely to the original author, Ken Collins.So - creating a ColdFusion custom tag to handle layouts is not something new. You've been able to build custom tag wrappers since version 4, approximately 200 years ago. But if you look at Ken's code, even if you can't read the Ruby/HAML, you will see two very cool things he is doing with his templates. First - he is simplifying the creation of page IDs. jQuery Mobile requires your pages to use unique IDs. His template makes that automatic. I decided for my code I'd make it automatic, but let you supply one if you wanted. Secondly - jQuery Mobile pages loaded after the initial request do not need to return the entire HTML block. Ie the HTML tag, BODY, etc. His code intelligently checks for Ajax requests and minimizes what is returned when it can. That quick little tweak will add just a bit more speed to your pages and is easy enough to add in our code as well. Here's my first custom tag, page.cfm.
<!--- Courtesy Dan Switzer, II: http://blog.pengoworks.com/index.cfm/2009/4/9/ColdFusion-UDF-for-detecting-jQuery-AJAX-operations ---> <cffunction name="isAjaxRequest" output="false" returntype="boolean" access="public"> <cfset var headers = getHttpRequestData().headers /> <cfreturn structKeyExists(headers, "X-Requested-With") and (headers["X-Requested-With"] eq "XMLHttpRequest") /> </cffunction> <cfif thisTag.executionMode is "start"> <cfparam name="attributes.title" default=""> <cfparam name="attributes.customscript" default=""> <cfif not structKeyExists(attributes, "pageid")> <!--- Make a page based on request. ---> <cfset attributes.pageid = replace(cgi.script_name, "/","_","all")> </cfif> <cfparam name="attributes.theme" default=""> <cfif not isAjaxRequest()> <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <cfoutput> <title>#attributes.title#</title> <link rel="stylesheet" href="http://code.jquery.com/mobile/1.0b2/jquery.mobile-1.0b2.min.css" /> <script src="http://code.jquery.com/jquery-1.6.2.min.js"></script> <cfif len(attributes.customscript)> <srcript src="#attributes.customscript#"></script> </cfif> </cfoutput> <script src="http://code.jquery.com/mobile/1.0b2/jquery.mobile-1.0b2.min.js"></script> </head> <body> </cfif> <cfoutput><div data-role="page" id="#attributes.pageid#" data-title="#attributes.title#" <cfif len(attributes.theme)>data-theme="#attributes.theme#"</cfif>></cfoutput> <cfelse> </div> <cfif not isAjaxRequest()> </body> </html> </cfif> </cfif>For the most part I'm going to assume this is easy enough to read. If you've never seen a custom tag "wrapper" before, check out this blog entry I wrote back in 2007. Basically ColdFusion passes to your custom tag whether or not the execution is at the beginning or end of the tag. Notice the check for the pageid value. If it doesn't exist, we create it based on the request path. Again, this is based on Ken's template. The other interesting part is the isAjaxRequest UDF. It's taken from code Dan Switzer wrote (and something I blogged about in the past as well). If we detect an Ajax request, we will suppress everything but the code div. Finally - I added support for passing in the URL to a custom script and a theme selection as well. Here's an example of how you could call this:
<cf_page title="Home Page"> </cf_page>Or you can use cfimport:
<cfimport prefix="jqm" taglib="jqm"> <jqm:page title="Home Page"> </jqm:page>Next up - I decided to quickly add support for 3 main jQuery Mobile UI items - header, footer, and content. This allows me to complete a page like so:
<cfimport prefix="jqm" taglib="jqm"> <jqm:page title="Home Page"> <jqm:header>Welcome!</jqm:header> <jqm:content> This is my main page content. Go to <a href="test2.cfm">next</a>. </jqm:content> <jqm:footer>Copyright © 2014</jqm:footer> </jqm:page>Each of these three new tags are the exact same, so I'll share one of them here (content.cfm):
<cfif thisTag.executionMode is "start"> <cfparam name="attributes.theme" default=""> <div data-role="content" <cfif len(attributes.theme)>data-theme="#attributes.theme#"</cfif>> <cfelse> </div> </cfif>And here is another example:
<cfimport prefix="jqm" taglib="jqm"> <jqm:page title="Home Page" id="page2" theme="e"> <jqm:header>Welcome!</jqm:header> <jqm:content> This is my second page content. Go to <a href="index.cfm">home</a>. </jqm:content> <jqm:footer theme="b">Copyright © 2014</jqm:footer> </jqm:page>By the way, I'm not normally a cfimport fan myself. I use it every now and then when the mood hits me. If it scares you, here's the above example with just cf_ syntax.
<cf_page title="Home Page" id="page2" theme="e"> <cf_header>Welcome!</cf_header> <cf_content> This is my second page content. Go to <a href="index.cfm">home</a>. </cf_content> <cf_footer theme="b">Copyright © 2014</cf_footer> </cf_page>I did some quick testing with my Chrome Net panel open, and can confirm that when I requested my second page (and clicked the link back home), the template correctly noted it as an Ajax request and suppressed the unnecessary HTML. If you want to play with these custom tags I've included them as a zip to this blog entry.