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 »
11,246 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 »
11,246 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 »
11,246 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 »
11,246 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 »
11,246 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 »
40 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
Ben Nadel's Company - Epicenter Consulting Recent Blog Comments
May 25, 2013 at 10:08 AM
Using "//" And ".//" Expressions In XPath XML Search Directives In ColdFusion
@Ben, my question is that i want the current node with its tag and its parent node. i just want only that data. So, give me the solution for that. and remember solution is working on " xpath 1.0 ... read »
May 25, 2013 at 10:01 AM
Using "//" And ".//" Expressions In XPath XML Search Directives In ColdFusion
hey ben, i want get my current node tag and also want the root node tag withing. So, how can i fix it.. ! ... read »
May 24, 2013 at 5:39 PM
Ask Ben: Manually Enforcing Basic HTTP Authorization In ColdFusion
@Adam Oops! My mistake! I hadn't gotten that far in my testing - I'm still baby stepping my way through the process. ... read »
May 24, 2013 at 5:13 PM
Ask Ben: Manually Enforcing Basic HTTP Authorization In ColdFusion
Hi Jason, Thanks for checking up on that, but I still stand firm on my position. :) There are actually two listLast()'s in use, and you're right that the one using a space as a delimiter is fine. ... read »
May 24, 2013 at 4:45 PM
Ask Ben: Manually Enforcing Basic HTTP Authorization In ColdFusion
@Ben I have been lurking your site for quite some time, and haven't stepped up to comment until today. Thanks for all the great info - keep it up! @Adam I believe you are mistaken... as the commen ... read »
May 24, 2013 at 11:21 AM
Strange Interaction Between DeserializeJson(), ArrayContains(), And Database Values In ColdFusion
@WebManWalking, Ha ha, let's us never speak of justifying "##" notation again :P ... read »
May 24, 2013 at 11:18 AM
Strange Interaction Between DeserializeJson(), ArrayContains(), And Database Values In ColdFusion
@Ben, Ah, so it was indeed how I vaguely remembered it to be: A direct assignment value = users.id[ i ] causes value to retain the sticky datatype of the query column. Although unnecessary in ... read »
May 24, 2013 at 9:11 AM
Preventing Links In Standalone iPhone Applications From Opening In Mobile Safari
@Brandon, Hi, No, I haven't been able to do that. I have just kept it as it is. ... read »
InVision App - Prototyping Made Beautiful With Prototyping Tools