Monday, September 22, 2008

Experiment with Calling CFFunctions from PDFPageEvents - Part 2

In Part 1 I covered the basics of my java and cfc utilities. In Part 2 I will show an example of them in action. I thought it would be interesting to contrast the differences between the old and new technique. So I used a modified version of the example from Using iText's PdfPageEventHelper with ColdFusion to test the code. (If you have not read the previous entry already, it is worth a quick read. If only so the descriptions below will make more sense ;).

Anyway, my first step was converting the java methods into a CFC with two functions: one that will be called onOpenDocument and the other onEndPage. Both functions have an argument called CF_PDF_EVENT. It is a structure of information that is passed in automatically from the java utility. It contains details about the PDFPageEvent that occurred (writer, document, paragraph position, etcetera).


<cffunction name="initDocument" returntype="void" access="public" output="false">
<cfargument name="CF_PDF_EVENT" type="struct">
...
</cffunction>

Since different page events generate different information, the structure contents vary depending on which event occurred. However, the structure always contains a key named EVENT_PARENT. The value is just a reference to the java utility object. It comes in handy when you need to make properties available to other event functions. Just use the setProp() method to add a property, and getProp() to retrieve it.

For example, the initDocument function below adds a property called "textFont". This property is later used by the addFooter function when generating the footer text.

    <cffunction name="initDocument" returntype="void" access="public" output="false">
<cfargument name="CF_PDF_EVENT" type="struct">
<cfset var Local = structNew()>
<cfscript>
// create a font object to use for the page footer text
Local.BaseFont = getJavaLoader().create("com.lowagie.text.pdf.BaseFont");
Local.textFont = Local.BaseFont.createFont( Local.BaseFont.COURIER_BOLD,
Local.BaseFont.WINANSI,
Local.BaseFont.EMBEDDED
);
// store the font in the event handler object
arguments.CF_PDF_EVENT.EVENT_PARENT.setProp("textFont", Local.textFont);
</cfscript>
</cffunction>

<cffunction name="addFooter" returntype="void" access="public" output="false">
<cfargument name="footerText" type="string">
<cfargument name="CF_PDF_EVENT" type="struct">
<cfset var Local = structNew()>

<cfscript>
...
// retrieve the textFont from the event handler object
Local.textFont = arguments.CF_PDF_EVENT.EVENT_PARENT.getProp("textFont");
...
</cfscript>
</cffunction>

All that remains ...


The final step was to modify the original CF example which generated a pdf with page footers. After instantiating the writer, I create a few instances of my components. Both components use the JavaLoader.cfc, so I am passing in an instance as a parameter.


<cfscript>
...

// get an instance of the CFC containing my cf event functions
eventFuncs = createObject("component", "MyPageEventFunctions").init( javaLoader=javaLoader );

// get an instance of the page event component
eventHandler = createObject("component", "PDFPageEventHandler").init( javaLoader=javaLoader );
<cfscript>


Next I create an instance of the java utility and link my two functions to the onOpenDocument and onEndPage events. Finally, I register the utility object with the PDFWriter. The rest of the code is unchanged.


<cfscript>
...
// create an instance of the java utility class
eventUtil = eventHandler.createEventUtility();

// link the "initDocument" function to the "onOpenDocument" event
eventHandler.link( eventUtility = eventUtil,
eventName = eventUtil.ON_OPEN_DOCUMENT,
functionContext = eventFuncs.getContext(),
functionObject = eventFuncs.initDocument
);


// link the "addFooter" function to the "onEndPage" event
functionArgs.footerText = "BOREDOM ALERT! BOREDOM ALERT! Page number ";
eventHandler.link( eventUtility = eventUtil,
eventName = eventUtil.ON_END_PAGE,
functionContext = eventFuncs.getContext(),
functionObject = eventFuncs.addFooter,
functionArguments = functionArgs
);

// finally, register the page event with the pdfWriter
writer.setPageEvent( eventUtil );

...
<cfscript>

It is worth noting that most of the parameters are objects, not strings. So for instance I am passing in a function object, not a name. I think the parameters are pretty self-explanatory, with the possible exception of "functionContext". In short it accepts the results of the getPageContext() function. When the function is called from java, we need to to provide CF with the context for executing the function. So the link() function extracts the required information from getPageContext() and passes it to the java class. Since I could not find a direct way to access the context from outside a cfc, I added a small helper method to my cfc that returns the context object.

<!--- this is required for event handler --->
<cffunction name="getContext" returntype="any" access="public" output="false">
<cfreturn getPageContext()>
</cffunction>


Are we there yet??

The final test was to see if all this dynamic stuff actually worked. So I ran the updated code and it produced the same silly pink footers as in the original example.



Now with just a slight change to my CF code, I was able to generate an entirely different footer. I could have also hooked into other events on-the-fly. No recompile of the java class required. Dynamic page events. Pretty cool stuff.




Wrap up ....

There are several ways I could have designed the PDFPageEventHandler.cfc. But I did not want to place a lot of restrictions on how it could be used. So the cfc could be streamlined or tweaked to suit your own needs. Now keep in mind the code is barely tested, so feel free to play around with it. If you have any comments or suggestions on how to improve it I would love to hear them.

A big thank you to Murray for coming up with such an interesting idea!

0 comments:

  © Blogger templates The Professional Template by Ourblogtemplates.com 2008

Header image adapted from atomicjeep