Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at CFUNITED 2009 (Lansdowne, VA) with: Bruno Lopes

Printing The Entire jQuery API As A PDF (Using CFDocument And XML Parsing)

By Ben Nadel on

I am just starting to really get into jQuery, the JavaScript API that makes writing sweet-ass JavaScript easy and fun. As with ColdFusion, I like to really dive into it by plowing through the documentation (yeah, I actually do like reading documentation so I can see what the possibilities are!). jQuery.com and VisualjQuery.com provide great documentation and visual representations of the API, but I wanted something that I could print out and take with me on the train. I am sure that I could find something somewhere, but seeing as VisualjQuery.com runs off of an XML API definition file, I thought I would give it a go with ColdFusion XML and PDF generation (what else would I do for fun???).

The first step was downloading the XML file for the documentation. I got my XML file from Visual jQuery:

http://visualjquery.com/index.xml

Once I had that, it was just a matter of parsing it in with CFXML and outputting a formatting HTML document (that could then be consumed by CFDocument). Ok, maybe that was not quite as easy as it sounds. For one thing, it took me a while to figure out all the little nuances of the XML document (such as what the heck the SEE node was). After a few hours of trial an error, I finally came up with something that was "good enough":

  • <!---
  • Create a ColdFusion XML document based on the jQuery XML
  • documentation available at:
  •  
  • http://visualjquery.com/index.xml
  • --->
  • <cfxml variable="xmlDoc">
  • <cfinclude template="./documentation.xml" />
  • </cfxml>
  •  
  •  
  • <!---
  • Get the root XML node of the XML document. This will
  • contain all the categories for the documentation.
  • --->
  • <cfset xmlRoot = XmlDoc.XmlRoot />
  •  
  •  
  • <!--- Output basic CSS for the document. --->
  • <style type="text/css">
  •  
  • body {
  • font: 11px verdana ;
  • }
  •  
  • h2.categoryname {
  • border-bottom: 2px solid #333333 ;
  • font-size: 24px ;
  • }
  •  
  • h2.subcategoryname {
  • border-bottom: 1px solid #333333 ;
  • font-size: 18px ;
  • }
  •  
  • h3 {
  • border-bottom: 1px solid #999999 ;
  • font-size: 14px ;
  • padding-bottom: 3px ;
  • padding-top: 15px ;
  • }
  •  
  • h4 {
  • font-style: italic ;
  • margin: 0px 0px 5px 0px ;
  • }
  •  
  • h5 {
  • font-size: 10px ;
  • margin: 0px 0px 5px 0px ;
  • }
  •  
  • p,
  • ol,
  • ul {
  • margin: 0px 0px 12px 0px ;
  • }
  •  
  • div.methodbody {
  • margin-left: 20px ;
  • }
  •  
  • div.indent {
  • margin-left: 20px ;
  • }
  •  
  • </style>
  •  
  •  
  • <cfoutput>
  •  
  • <h1>
  • jQuery API Documentation
  • </h1>
  •  
  • <!---
  • Get the sections/categories that the documentation is
  • broken up into. Be careful; the documentation has nested
  • categories. By doing a // search, we will be finding all
  • categories regardless of nesting.
  • --->
  • <cfset arrCategories = XmlSearch( xmlRoot, "//cat" ) />
  •  
  •  
  • <!--- Loop over all the sections / categories in the documentation. --->
  • <cfloop
  • index="intCategory"
  • from="1"
  • to="#ArrayLen( arrCategories )#"
  • step="1">
  •  
  • <!--- Get category short hand. --->
  • <cfset xmlCategory = arrCategories[ intCategory ] />
  •  
  • <!--- Get methods for this category. --->
  • <cfset arrMethods = XmlSearch( xmlCategory, "./method" ) />
  •  
  • <!---
  • Get all the sub categories. Remember, the documentation
  • has categories within categories (at times).
  • --->
  • <cfset arrSubCategories = XmlSearch( xmlCategory, "./cat" ) />
  •  
  •  
  • <!---
  • Check to see if we are in a sub category. We will
  • know this is true if the node name of the current
  • node matches the node name of the parent node.
  • --->
  • <cfif (xmlCategory.XmlParent.XmlName EQ xmlCategory.XmlName)>
  •  
  • <!--- We are in a sub category. --->
  • <cfset blnSubCategory = true />
  •  
  • <cfelse>
  •  
  • <!--- We are NOT in a sub category. --->
  • <cfset blnSubCategory = false />
  •  
  • </cfif>
  •  
  •  
  • <!--- Output the category name. --->
  • <h2 class="<cfif blnSubCategory>sub</cfif>categoryname">
  • #xmlCategory.XmlAttributes.Value#
  • </h2>
  •  
  •  
  • <!--- Loop over methods. --->
  • <cfloop
  • index="intMethod"
  • from="1"
  • to="#ArrayLen( arrMethods )#"
  • step="1">
  •  
  • <!--- Get method short hand. --->
  • <cfset xmlMethod = arrMethods[ intMethod ] />
  •  
  • <!--- Get method attributes. --->
  • <cfset arrDescription = XmlSearch( xmlMethod, "./desc" ) />
  • <cfset arrSee = XmlSearch( xmlMethod, "./see" ) />
  • <cfset arrParams = XmlSearch( xmlMethod, "./params" ) />
  • <cfset arrExamples = XmlSearch( xmlMethod, "./examples" ) />
  • <cfset arrBefore = XmlSearch( xmlMethod, "./before" ) />
  • <cfset arrCode = XmlSearch( xmlMethod, "./code" ) />
  • <cfset arrResult = XmlSearch( xmlMethod, "./result" ) />
  • <cfset arrOptions = XmlSearch( xmlMethod, "./options" ) />
  •  
  •  
  • <!--- Ouptut the method name (with param names). --->
  • <h3>
  • #xmlMethod.XmlAttributes.Name#(
  •  
  • <!--- Ouput parameters. --->
  • <cfloop
  • index="intParam"
  • from="1"
  • to="#ArrayLen( arrParams )#"
  • step="1">
  •  
  • <!--- Check to see if this parameter has a name. --->
  • <cfif StructKeyExists( arrParams[ intParam ].XmlAttributes, "Name" )>
  • #arrParams[ intParam ].XmlAttributes.Name#
  • </cfif>
  •  
  • <!---
  • Check to see if we have more than one parameter.
  • We only need a comma for mulitple params.
  • --->
  • <cfif (intParam LT ArrayLen( arrParams ))>
  • ,
  • </cfif>
  •  
  • </cfloop>
  • )
  • </h3>
  •  
  •  
  • <div class="methodbody">
  •  
  • <!--- Check to see if we have a metho description. --->
  • <cfif ArrayLen( arrDescription )>
  •  
  • <p class="methoddescription">
  • #ToString( HtmlEditFormat( arrDescription[ 1 ].XmlText ) ).ReplaceAll(
  • "(\r?\n){2,}",
  • "</p><p class=""methoddescription"">"
  • )#
  • </p>
  •  
  • </cfif>
  •  
  •  
  • <!--- Check to see if we have any parameters. --->
  • <cfif ArrayLen( arrParams )>
  •  
  • <h4>
  • Parameters
  • </h4>
  •  
  • <div class="indent">
  •  
  • <p>
  • <cfloop
  • index="intParam"
  • from="1"
  • to="#ArrayLen( arrParams )#"
  • step="1">
  •  
  • <!--- Get a short hand for the parameters. --->
  • <cfset xmlParameter = arrParams[ intParam ] />
  •  
  • <!--- Get description. --->
  • <cfset arrParamDescription = XmlSearch( xmlParameter, "./desc" ) />
  •  
  • <p>
  •  
  • <cfif StructKeyExists( xmlParameter.XmlAttributes, "Name" )>
  • <strong>#xmlParameter.XmlAttributes.Name#</strong>:
  • </cfif>
  •  
  • <cfif StructKeyExists( xmlParameter.XmlAttributes, "Type" )>
  • ( #xmlParameter.XmlAttributes.Type# ):
  • </cfif>
  •  
  • <cfif ArrayLen( arrParamDescription )>
  • #HtmlEditFormat( arrParamDescription[ 1 ].XmlText )#
  • </cfif>
  •  
  • </p>
  •  
  • </cfloop>
  • </p>
  •  
  • </div>
  •  
  • </cfif>
  •  
  •  
  • <!--- Check to see if we have any method options. --->
  • <cfif ArrayLen( arrOptions )>
  •  
  • <h4>
  • Hash Options
  • </h4>
  •  
  • <div class="indent">
  •  
  • <!--- Loop over options. --->
  • <cfloop
  • index="intOption"
  • from="1"
  • to="#ArrayLen( arrOptions )#"
  • step="1">
  •  
  • <!--- Get a short hand for the option. --->
  • <cfset xmlOption = arrOptions[ intOption ] />
  •  
  • <!--- Get description. --->
  • <cfset arrOptionDescription = XmlSearch( xmlOption, "./desc" ) />
  •  
  • <p>
  •  
  • <cfif StructKeyExists( xmlOption.XmlAttributes, "Name" )>
  • <strong>#xmlOption.XmlAttributes.Name#</strong>:
  • </cfif>
  •  
  • <cfif StructKeyExists( xmlOption.XmlAttributes, "Type" )>
  • ( #xmlOption.XmlAttributes.Type# ):
  • </cfif>
  •  
  • <cfif ArrayLen( arrOptionDescription )>
  • #HtmlEditFormat( arrOptionDescription[ 1 ].XmlText )#
  • </cfif>
  •  
  • </p>
  •  
  • </cfloop>
  •  
  • </div>
  •  
  • </cfif>
  •  
  •  
  • <!--- Check to see if are method has a return type. --->
  • <cfif StructKeyExists( xmlMethod.XmlAttributes, "Type" )>
  •  
  • <h4>
  • Returns
  • </h4>
  •  
  • <div class="indent">
  •  
  • <p>
  • #xmlMethod.XmlAttributes.Type#
  • </p>
  •  
  • </div>
  •  
  • </cfif>
  •  
  •  
  • <!--- Check to see if we have any examples. --->
  • <cfif ArrayLen( arrExamples )>
  •  
  • <!--- Loop over examples. --->
  • <cfloop
  • index="intExample"
  • from="1"
  • to="#ArrayLen( arrExamples )#"
  • step="1">
  •  
  • <!--- Get short hand to example. --->
  • <cfset xmlExample = arrExamples[ intExample ] />
  •  
  • <!--- Get the example attributes. --->
  • <cfset arrExampleDesc = XmlSearch( xmlExample, "./desc" ) />
  • <cfset arrExampleBefore = XmlSearch( xmlExample, "./before" ) />
  • <cfset arrExampleCode = XmlSearch( xmlExample, "./code" ) />
  • <cfset arrExampleResult = XmlSearch( xmlExample, "./result" ) />
  •  
  • <h4>
  • Example
  • </h4>
  •  
  • <div class="indent">
  •  
  • <cfif ArrayLen( arrExampleDesc )>
  •  
  • <p>
  • #HtmlEditFormat( arrExampleDesc[ 1 ].XmlText )#
  • </p>
  •  
  • </cfif>
  •  
  • <cfif ArrayLen( arrExampleCode )>
  •  
  • <p>
  • #HtmlEditFormat( arrExampleCode[ 1 ].XmlText )#
  • </p>
  •  
  • </cfif>
  •  
  • <cfif ArrayLen( arrExampleBefore )>
  •  
  • <h5>
  • Before:
  • </h5>
  •  
  • <p>
  • #HtmlEditFormat( arrExampleBefore[ 1 ].XmlText )#
  • </p>
  •  
  • </cfif>
  •  
  • <cfif ArrayLen( arrExampleResult )>
  •  
  • <h5>
  • Result:
  • </h5>
  •  
  • <p>
  • #HtmlEditFormat( arrExampleResult[ 1 ].XmlText )#
  • </p>
  •  
  • </cfif>
  •  
  • </div>
  •  
  • </cfloop>
  •  
  • </cfif>
  •  
  •  
  • <!--- Check to see if we have any "See Also" examples. --->
  • <cfif ArrayLen( arrSee )>
  •  
  • <h4>
  • See Also
  • </h4>
  •  
  • <div class="indent">
  •  
  • <p>
  • <cfloop
  • index="intSee"
  • from="1"
  • to="#ArrayLen( arrSee )#"
  • step="1">
  •  
  • #arrSee[ intSee ].XmlText#<br />
  •  
  • </cfloop>
  • </p>
  •  
  • </div>
  •  
  • </cfif>
  •  
  • </div>
  •  
  • </cfloop>
  •  
  • </cfloop>
  •  
  • </cfoutput>

I didn't comment the above code that much as I wasn't really that interested in demoing the code. I just wanted to get it working so that I could get my PDF going. Now, once I had that code up and running and producing a nice HTML document, I used the CFDocument to generate that PDF. Assuming the above code was stored in a file named, "parse.cfm", generating the PDF was a sinch!

  • <!--- Create a PDF of the parse jQuery documentation. --->
  • <cfdocument
  • format="PDF"
  • pagetype="letter"
  • orientation="portrait"
  • unit="in"
  • encryption="none"
  • fontembed="Yes"
  • backgroundvisible="No">
  •  
  • <!--- Include the parsed jQuery XML. --->
  • <cfinclude template="./parse.cfm" />
  •  
  • </cfdocument>

And that's that! If you want to see the PDF, feel free to download it here.




Reader Comments

Thanks for doing this. My one complaint about that visualjquery site is the lack of a search. A PDF will fix that.

Reply to this Comment

Good work mate, I have been using jQuery for ages and wouldn't use anything else - it is so good. This is really handy as an alterative to the online API documentation.

Reply to this Comment

@Matt: If you need a searchable version of the API, then go here:

http://jquery.bassistance.de/api-browser/

Rey...

Reply to this Comment

@Tony,

Those cheat sheets look awesome! Thanks for pointing me (and everyone else) to those. Once I get through the documentation and get a real taste of everything that is possible, the cheat sheets will provide an invaluable quick look up!

@Rey,

My pleasure dude, jQuery just rocks :)

Reply to this Comment

@Sam,

When I wrote this post, I was just learning jQuery and I needed something that I could print out and bring home with me; I don't have internet at home, so hand-held documentation is very useful.

It was also an exploration of the use of XML on a large scale document. This later became an experimentation in XSLT:

http://www.bennadel.com/index.cfm?dax=blog:961.view

But yes, I agree, getting the documentation from the live site is the most useful.

Reply to this Comment

This is a huge help, thanks. I think it's a terrible oversight to not have a downloadable version of the docs!

Reply to this Comment

This may be slighly off topic but... Is there any way this could parse a dynamic page? For example pass it something like htmlpage.aspx?i=1. Where i is an id used in the .net page to create a dynamic html webpage? Thanks!

Reply to this Comment

@Mike,

Because the jQuery only gets the data that is delivered by the server, it is shielded from the inner workings of the page creation. You could have the page as dynamic as you want.

Reply to this Comment

Ben,

Right that is the trick. If I do this:

<cfdocument format="pdf" localUrl="yes"
src="http://report1.aspx?i=1366" />

An error: ERROR encountered loading http://report1.aspx?i=1366: java.io.IOException: Server returned HTTP response code: 500 for URL: http://report1.aspx?i=1366 occured during the rendering process of this document.

How do you actually invoke jQuery to return the html generated from http://report1.aspx?

Reply to this Comment

@Mike,

Ahhh, you can't really do that since the CFDocument doesn't load the target HTML in a browser; as such, the Javascript never has a chance to execute.

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.