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

Posted February 22, 2007 at 3:00 PM by Ben Nadel

Tags: ColdFusion, Javascript / DHTML

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

Feb 22, 2007 at 4:14 PM // reply »
27 Comments

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


Feb 22, 2007 at 4:17 PM // reply »
10,638 Comments

Glad to help. Let me know if you see any way I can improve the PDF.


Feb 22, 2007 at 6:37 PM // reply »
56 Comments

don't forget about the jQuery cheat sheets:

http://rip747.wordpress.com/2006/11/30/jquery-cheat-sheets/


Feb 22, 2007 at 7:37 PM // reply »
76 Comments

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.


Feb 22, 2007 at 11:29 PM // reply »
28 Comments

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

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

Rey...


Feb 22, 2007 at 11:33 PM // reply »
28 Comments

@Ben: Thanks for doing this man. Its a big help :)


Feb 23, 2007 at 7:19 AM // reply »
10,638 Comments

@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 :)


Sam
Nov 24, 2007 at 4:31 AM // reply »
1 Comments

Ben, everything is ok, but why you don`t get the documentation from
http://docs.jquery.com/Main_Page ???
As i think it will be better and more fresh.
And there are posted problems and bugs...
Best regards from Russia. =)


Dec 6, 2007 at 8:19 AM // reply »
10,638 Comments

@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.


Dec 19, 2007 at 12:27 AM // reply »
1 Comments

Thank for your PDF it helpful.


Jun 26, 2008 at 5:43 AM // reply »
1 Comments

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


Oct 4, 2008 at 1:48 AM // reply »
1 Comments

thanks was looking for this... your a life saver :D


Aug 15, 2009 at 12:01 AM // reply »
1 Comments

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!


Aug 17, 2009 at 2:02 PM // reply »
10,638 Comments

@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.


Aug 17, 2009 at 8:38 PM // reply »
1 Comments

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?


Sep 6, 2009 at 1:52 PM // reply »
10,638 Comments

@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.


Dec 19, 2009 at 11:26 PM // reply »
32 Comments

jQuery is the shizzle.



Post A Comment

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.

Please review the following issues:

Author Name:


Author Email:

Author Website:

Comment:

Supported HTML tags for formatting: <strong>bold</strong>   <em>italic</em>   <code>code</code>







  • Help Wanted - Find Your Next ColdFusion Job
InVision App - Prototyping Made Beautiful With Prototyping Tools Ben Nadel's Company - Epicenter Consulting Recent Blog Comments
Feb 3, 2012 at 10:49 PM
How I Got Node.js Running On A Linux Micro Instance Using Amazon EC2
Wow this was really helpful! Only thing I would add is you need to update your .bash_profile after you edit the secure_path. This is what I did: $ . ~/.bash_profile Otherwise, NPM won't be found. ... read »
Feb 3, 2012 at 10:14 PM
Pushing Base64-Encoded Images Over HTML5 WebSockets With Pusher And ColdFusion
@Ben, Just wanted to let you know that pusher are soon to start limiting sizes on messages. This was the detail that came through in the Feb dispatch: "However, we will soon be limiting the s ... read »
Feb 3, 2012 at 5:05 PM
Regular Expressions Make CSV Parsing In ColdFusion So Much Easier (And Faster)
I tried using your RegEx in my C# program, but it was matching an extra empty-string at the end and so I would end up with an extra field that doesn't exist, so I changed it to this: (^|,)("(?: ... read »
Feb 3, 2012 at 3:47 PM
ColdFusion Supports HTTP Verbs PUT And DELETE (As Well As GET And POST)
Josh Cyr posted this on Twitter just a little bit ago. Thought it was appropriate. http://stackoverflow.com/questions/1619152/how-to-create-rest-urls-without-verbs/1619677#1619677 ... read »
Feb 3, 2012 at 2:28 PM
Changing The Execution Context Of Your Self-Executing Function Blocks In JavaScript
@Michael, You definitely make a good point (and extra points for quoting movies - I love movies). When you use a return() statement to define the object's public API, it does provide a consistent a ... read »
Feb 3, 2012 at 2:04 PM
Changing The Execution Context Of Your Self-Executing Function Blocks In JavaScript
To quote Jurassic Park: "Just because you can doesn't mean you should". I completely, utterly disagree with the thought that this is more readable. Consider the current module pattern: if ... read »
Feb 3, 2012 at 1:10 PM
REST API Design Rulebook By Mark Masse
@Jordan, Yeah, WRML was created by Mark Masse (author of the book). I also found it to be a bit convoluted. I suppose it is intended to allow the Client to be able to programmaticaly respond to cha ... read »
Feb 3, 2012 at 1:08 PM
ColdFusion Supports HTTP Verbs PUT And DELETE (As Well As GET And POST)
@Jason, To be honest, I don't have good answers for that kinds of stuff. And, to the point, that is specifically why I *really* liked the REST API Design Rulebook by Mark Masse - he just cuts throu ... read »