Printing The Entire jQuery API As A PDF (Using CFDocument And XML Parsing)
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.
Want to use code from this post? Check out the license.
Reader Comments
Thanks for doing this. My one complaint about that visualjquery site is the lack of a search. A PDF will fix that.
Glad to help. Let me know if you see any way I can improve the PDF.
don't forget about the jQuery cheat sheets:
http://rip747.wordpress.com/2006/11/30/jquery-cheat-sheets/
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.
@Matt: If you need a searchable version of the API, then go here:
http://jquery.bassistance.de/api-browser/
Rey...
@Ben: Thanks for doing this man. Its a big help :)
@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 :)
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. =)
@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:
www.bennadel.com/index.cfm?dax=blog:961.view
But yes, I agree, getting the documentation from the live site is the most useful.
Thank for your PDF it helpful.
This is a huge help, thanks. I think it's a terrible oversight to not have a downloadable version of the docs!
thanks was looking for this... your a life saver :D
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!
@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.
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?
@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.
jQuery is the shizzle.