Merry Christmas
I just wanted to say Merry Christmas and Happy Holidays to everyone!
Yes Virginia, there is a Santa Claus
.. in the middle of the night
I just wanted to say Merry Christmas and Happy Holidays to everyone!
Yes Virginia, there is a Santa Claus
A reader named Murray happened to ask if there is a way to determine which jar file a class was loaded from. It is an invaluable bit of information when working with new jars. Nothing worse than spending an hour trying to debug code, only to find out the real problem was you were working with the wrong version of a class all along.
In case you are not already aware of it, an old entry on Brandon Purcell's blog provides an excellent tip on how to retrieve this information: Identifying Which Jar file a class was loaded from in ColdFusion MX . It uses getClassLoader() to retrieve information about the parent jar. Now since it uses the default class loader, it works perfectly well for jars in the CF classpath. But as I belatedly remembered, the javaLoader uses a separate class loader (duh!). So if you are checking a class that was loaded by the javaLoader you will need to make a few tweaks.
Here is a quick example using one of the POI classes. The code is simple and essentially does the same thing as Brandon's function. The only difference being it should work with objects loaded by a different class loader as well.
...Read More
<cfscript>
// get a reference to the javaLoader
javaLoader = server[ application.myJavaLoaderKey ];
// create test workbook object
wb = javaLoader.create("org.apache.poi.hssf.usermodel.HSSFWorkbook");
// get the class name (without the package) ie "HSSFWorkbook.class"
className = listLast( wb.getClass().getName(), ".") &".class";
resourceURL = wb.getClass().getResource( className );
if ( isDefined("resourceURL") ) {
WriteOutput(resourceURL);
}
else {
WriteOuput("Could not resolve resource");
}
</cfscript>
I needed to create a basic Gantt Chart from a query. So once again I dipped into the webcharts3d utility, borrowing code from a great entry about charts on Raymond Camden's blog.
I made some small modifications. Using the webcharts utility, I selected the XMLStyle tab and replaced a few dynamic values with {placeholders} . Then saved only the style information to an xml file. That way I could read in the style information directly, without having to extract it from a .wcp file.
Next, I created two functions. One to convert my query into webchart's xml format. The other to encapsulate Raymond Camden's code to generate the chart html. Since I needed some flexibility with the formatting, the functions have a number of optional settings for things like date formats and locale. But even with the minimal settings it produces a nice and simple Gantt Chart.
Unfortunately, the one thing I could not figure out was how to localize the dates in the chart tool tips. The webcharts3d documentation shows how you can use patterns to format tool tip values. Patterns definitely come in handy. Especially date patterns.
<!--- example of using patterns in chart style xml file --->
<![CDATA[
$(prevValue;EEE MMMM d, yyyy)
$(nextValue;EEE MMMM d, yyyy)
]]>
Updated: November 19 - Corrected copy / paste error Updated: March 1, 2010 - Modified date/time formatting
<!--- CREATE QUERY WITH SAMPLE DATA ---> <cfset rangeStart = createDate(2008,10,1)> <cfset rangeEnd = createDate(2008,10,31)> <cfset numOfDays = 31> <cfset qTasks = QueryNew("TaskName,Type,StartDate,EndDate")> <cfloop from="1" to="6" index="t"> <cfloop list="Testing,Development" index="type"> <cfset row = queryAddRow(qTasks, 1)> <cfset d1 = randRange(1, 31)> <cfset d2 = randRange(1, 31)> <cfset taskStart = createDate(2008, 10, min(d1, d2))> <cfset taskEnd = createDate(2008, 10, max(d1, d2))> <cfset querySetCell(qTasks, "TaskName", "Task "& t, row)> <cfset querySetCell(qTasks, "Type", type, row)> <cfset querySetCell(qTasks, "StartDate", taskStart, row)> <cfset querySetCell(qTasks, "EndDate", taskEnd, row)> </cfloop> </cfloop> <!--- GENERATE CHART HTML ---> <cfset chartProp = { stylePath = ExpandPath("./simpleGanttStyle.xml"), query = qTasks, nameCol = "TaskName", typeCol = "Type", startCol = "StartDate", endCol = "EndDate", height = 400, width = 800 }> <cfset chartHTML = generateGantt( argumentCollection = chartProp )> <!--- display chart ---> <cfoutput>#chartHTML#</cfoutput>
<?xml version="1.0" encoding="UTF-8"?> <gantt> <xAxis isMultiline="true"> <dateTimeStyle majorUnit="Day" minorUnit="Hour" /> <groupFormat style="DateTimePattern" pattern="{groupDatePattern}"> <locale lang="{language}" country="{country}" variant=""/> </groupFormat> <labelFormat style="DateTimePattern" pattern="{labelDatePattern}"> <locale lang="{language}" country="{country}" variant=""/> </labelFormat> </xAxis> <dataLabels style="Value"/> <popup background="#C8FFFFFF" foreground="#333333" isMultiline="true"/> <paint palette="Dawn" paint="Plain" max="100"/> <![CDATA[ $(rowLabel) / $(colLabel) $(prevValue;{tipDatePattern}) $(nextValue;{tipDatePattern}) ]]> </gantt>
<cffunction name="generateGantt" returntype="string"> <cfargument name="stylePath" type="string" required="true" hint="Absolute path the xml chart style"> <cfargument name="query" type="query" required="true"> <cfargument name="nameCol" type="string" required="true"> <cfargument name="typeCol" type="string" required="true"> <cfargument name="startCol" type="string" required="true"> <cfargument name="endCol" type="string" required="true"> <cfargument name="width" type="numeric" required="true"> <cfargument name="height" type="numeric" required="true"> <cfargument name="format" type="string" required="false" default="png"> <cfargument name="language" type="string" required="false" default=""> <cfargument name="country" type="string" required="false" default=""> <cfargument name="groupDatePattern" type="string" required="false" default="MMM-yyyy"> <cfargument name="labelDatePattern" type="string" required="false" default="E dd"> <cfargument name="tipDatePattern" type="string" required="false" default=""> <cfset var Local = {} > <cfscript> Local.Locale = createObject("java", "java.util.Locale"); // Create a Locale using the given language / country if ( len( arguments.language ) and len( arguments.country ) ) { Local.dateLocale = Local.Locale.init( arguments.language, arguments.country ); } // Use the current Locale else { Local.dateLocale = getPageContext().getResponse().getLocale(); if ( not structKeyExists(Local, "dateLocale") ) { Local.dateLocale = Local.Locale.getDefault(); } } arguments.dateLocale = Local.dateLocale; arguments.language = Local.dateLocale.getLanguage(); arguments.country = Local.dateLocale.getCountry(); arguments.model = queryToGanttModel( argumentCollection= arguments ); Local.chartHTML = generateGanttHTML( argumentCollection = arguments ); </cfscript> <cfreturn Local.chartHTML> </cffunction> <cffunction name="generateGanttHTML" returntype="string"> <cfargument name="stylePath" type="string" required="true"> <cfargument name="model" type="string" required="true"> <cfargument name="height" type="numeric" required="true"> <cfargument name="width" type="numeric" required="true"> <cfargument name="format" type="string" required="false" default="png"> <cfargument name="language" type="string" required="true" default="en"> <cfargument name="country" type="string" required="false" default="US"> <cfargument name="groupDatePattern" type="string" required="false" default="MMM-yyyy"> <cfargument name="labelDatePattern" type="string" required="false" default="EEE dd"> <cfargument name="tipDatePattern" type="string" required="false" default=""> <cfset var Local = {}> <cfscript> // Read in the xml styles and set the desired date patterns // Note, the date patterns _are_ case sensitive Local.style = FileRead( arguments.stylePath ); // SOURCE: Raymond Camden blog entry // SOURCE: http://www.coldfusionjedi.com/index.cfm/2008/1/18/Coolest-CFCHART-Trick-Ever Local.style = replaceNoCase( Local.style, "{groupDatePattern}", arguments.groupDatePattern ); Local.style = replaceNoCase( Local.style, "{labelDatePattern}", arguments.labelDatePattern ); Local.style = replaceNoCase( Local.style, "{language}", arguments.language, "all" ); Local.style = replaceNoCase( Local.style, "{country}", arguments.country, "all" ); Local.style = replaceNoCase( Local.Style, ";{tipDatePattern}", ";"& arguments.tipDatePattern, "all" ); Local.MxServerComponent = createObject( "java", "com.gp.api.jsp.MxServerComponent" ); Local.context = getPageContext().getServletContext(); Local.chartServer = Local.MxServerComponent.getDefaultInstance( Local.context ); Local.chart = Local.chartServer.newImageSpec(); Local.chart.width = arguments.width; Local.chart.height = arguments.height; Local.chart.type = arguments.format; Local.chart.style = Local.style; Local.chart.model = arguments.model; // Get host information if ( getPageContext().getRequest().isSecure() ) { Local.baseURL = "https://"& getPageContext().getRequest().getHeader('Host') &"/"; } else { Local.baseURL = "http://"& getPageContext().getRequest().getHeader('Host') &"/"; } </cfscript> <!--- Create html tag set ---> <cfsavecontent variable="Local.chartHTML"> <cfoutput>#Local.chartServer.getImageTag( Local.chart, Local.baseURL & "CFIDE/GraphData.cfm?graphCache=wc50&graphID=")#</cfoutput> </cfsavecontent> <!--- Good old Webcharts loves to add an extra /Images/ to the URL ---> <cfset Local.chartHTML = replace( Local.chartHTML, Local.baseURL & "Images/", Local.baseURL , "all" )> <cfreturn Local.chartHTML> </cffunction> <cffunction name="queryToGanttModel" returntype="string"> <cfargument name="query" type="query" required="true"> <cfargument name="nameCol" type="string" required="true"> <cfargument name="typeCol" type="string" required="true"> <cfargument name="startCol" type="string" required="true"> <cfargument name="endCol" type="string" required="true"> <cfargument name="dateLocale" type="any" required="true"> <cfset var Local = {}> <cfscript> // use formatter to convert date objects into strings // Note: This constructor may not support all locales // Note: Date patterns _are_ case sensitive Local.datePattern = "yyyy-MM-dd HH:mm:ss"; Local.SimpleDateFormat = createObject("java", "java.text.SimpleDateFormat"); Local.formatter = Local.SimpleDateFormat.init( Local.datePattern, arguments.dateLocale ); Local.formatter.setLenient( false ); // prepare to generate the xml Local.newline = createObject("java", "java.lang.System").getProperty("line.separator"); Local.sb = createObject("java", "java.lang.StringBuffer").init(); // generate the xml headers // note, newlines are added for 'readability' Local.sb.append('<?xml version="1.0" encoding="UTF-8"?>'); Local.sb.append( Local.newline ); Local.xmlLocale = arguments.dateLocale.getLanguage() &"-"& arguments.dateLocale.getCountry(); Local.sb.append('<xml pattern="'& Local.datePattern &'" locale="'& Local.xmlLocale &'">'); Local.sb.append( Local.newline ); // generate the xml chart content using the query data for ( Local.row = 1; Local.row <= arguments.query.recordCount; Local.row++) { // extract the time (ie number of milliseconds) from the date column values Local.startDate = arguments.query[arguments.startCol][Local.row].getTime(); Local.endDate = arguments.query[arguments.endCol][Local.row].getTime(); // construct an xml element for each row in the query Local.sb.append('<item name="'& arguments.query[arguments.nameCol][Local.row] &'"'); Local.sb.append(' type="'& arguments.query[arguments.typeCol][Local.row] &'"'); Local.sb.append(' from="'& Local.formatter.format( Local.startDate ) &'"'); Local.sb.append(' to="'& Local.formatter.format( Local.endDate ) &'" />'); Local.sb.append( Local.newline ); } // append closing tag Local.sb.append( '</xml>' ); </cfscript> <cfreturn Local.sb.toString()> </cffunction>...Read More
I was never a big fan of the old html cfforms, and consequently never learned much about them. Though they have probably improved over time. While googling I came across the old question of why the
The common response was along the lines of "You should not expect it to work. Something is always selected if the list size is one". Technically, that is true. But to me it comes down to the fact that the validation was implemented differently than some people might expect, given how they use select lists. The validation code in /CFIDE/scripts/cfform.js is clearly geared towards multiple selection lists, not single.
// if this form field is a select list
if(_c=="SELECT"){
// verify at least one item was selected
for(i=0;i<_b.length;i++){
if(_b.options[i].selected){
return true;
}
}
return false;
}
...
Now I saw a few examples of tweaking cfform.js to produce the desired results. But they were not exactly what I had in mind. If I were to change the script, I would use "" as the default to represent no-selection. But have an optional attribute called "noSelection". So you could use other values like zero (0), etcetera when needed.
/*
REPLACEMENT JAVASCRIPT
*/
if( _c == "SELECT" ) {
var idx = _b.selectedIndex;
var isValid = false;
var _defaultValue = "";
// use the supplied default value
if ( document.getElementById && _b.getAttribute("noSelection") ) {
_defaultValue = _b.getAttribute("noSelection").toLowerCase();
}
if ( _b.type == "select-one" ) {
// something other than the default value is selected
isValid = ( _b.options[idx].value.toLowerCase() != _defaultValue );
}
else {
// multiple list: at least one item is selected
isValid = ( idx >= 0 );
}
return isValid;
}
...
<!---
Use the default "" to represent no selection
--->
<cfform name="someForm" format="xml" skin="basic">
<cfformgroup type="horizontal">
<cfselect name="Company" label="Company" required="true">
<option value="">-- select ---</option>
<option value="1">Company A</option>
<option value="2">Company B</option>
</cfselect>
<cfinput type="submit" name="submitBtn" value="Submit" >
</cfformgroup>
</cfform>
<!---
Use "0" to represent no selection
--->
<cfform name="someForm" format="xml" skin="basic">
<cfformgroup type="horizontal">
<cfselect name="Company" label="Company" required="true" noSelection="0"
message="Do not pass go. Do not collect $200. Not until you select a Company.">
<option value="0">-- select ---</option>
<option value="1">Company A</option>
<option value="2">Company B</option>
</cfselect>
<cfinput type="submit" name="submitBtn" value="Submit" >
</cfformgroup>
</cfform>
Now I do not really have a need for this. But if I ever do, I now know where to look ;)
As far as google doodles go .. pretty sharp. "Now that's what I'm talking about".
http://www.google.com/logos/halloween08.gif
Whenever I run the webcharts3d to search for one thing, I invariably end up finding other small tweaks that might come in handy later. So with that in mind, I am writing up these small tweaks for future use.
SkipLabels
For when the Y-Axis labels get too crowded, the skipLabels attribute can be used to reduce the number of labels displayed, without changing the number of gridlines. For example, the chart on the right displays every other label by using skipLabels="1".
Formatting Y-Axis Dates
ColdFusion seems to display Y-Axis dates in MM/d/yy format. I have yet to figure out a way to change the format except using styles. Here are a few attributes that can be useful when working with dates on the Y-Axis.
LabelFormat:
Used to change the display format of Y-Axis dates. Warning, the patterns are case sensitive.
<labelFormat style="DateTimePattern" pattern="MM.dd.yyyy"/>
IsReversed:
Used to reverse the direction of the Y-Axis values. Can also be applied to other types, not just dates.
<yAxis type="DateTime" isReversed="true">
ScaleMin/ScaleMax:
Can be used to force the chart to display a "fixed" date range. The parseFormat pattern tells webcharts the format of the scaleMin/scaleMax dates. Again, the date patterns are case sensitive.
<yAxis type="DateTime" scaleMin="10/01/2008" scaleMax="10/31/2008"> <parseFormat style="DateTimePattern" pattern="MM/dd/yyyy"/> </yAxis>
MajorUnit/MajorStep:
Similar to SkipLabels, but more suitable for date/time ranges
<dateTimeStyle majorUnit="Day" majorStep="1"/>
Choice LabelFormat:
It turns out there is an interesting label format called "Choice". You can use it to display text instead of a numeric value. For example, display "YES" instead of 1. Just supply the number to text translation separated by a "#". Use a "|" to separate each set. Choice seems to require a little more tweaking than other types. But still an interesting option.
<labelFormat style="Choice" pattern="1#YES|0#NO"/>
Orientation/isMultilevel
A combination of orientation and isMultilevel can also be used to create more readable labels. The image on the left shows how the IsMultilevel setting displays the labels on different lines.
<labelstyle ismultilevel="true" orientation="Parallel">
Examples:
The base file was created by copying the default style (ie C:\ColdFusion8\charting\styles\default.xml ) and modifying only the <yAxis> section. For brevity, only the <yAxis> section of the xml is posted below. The {placeholders} to represent the dynamic values, which are inserted when the charts are generated.
SkipLabels
<!---
FILE: skipLabelsStyle.xml
--->
<yAxis scaleMin="0">
<titleStyle font="Arial-12-bold"/>
<dateTimeStyle majorUnit="Year" minorUnit="Month"/>
<labelFormat style="Pattern" pattern="#,##0.########"/>
<groupStyle skipLabels="{skipLabels}"/>
</yAxis>
<!---
SKIP LABELS EXAMPLE
--->
<cfset chartStyle = FileRead( ExpandPath("./skipLabelsStyle.xml") )>
<cfset chartStyle = replaceNoCase(chartStyle, "{skipLabels}", "1")>
<cfchart format="png" style="#chartStyle#">
<cfchartseries type="bar">
<cfchartdata item="A" value="26">
<cfchartdata item="B" value="15">
<cfchartdata item="C" value="75">
<cfchartdata item="D" value="49">
<cfchartdata item="E" value="90">
<cfchartdata item="F" value="25">
<cfchartdata item="G" value="95">
</cfchartseries>
</cfchart>
<!---
SAMPLE VALUES for line charts
--->
<cfset q1 = queryNew("ChartItem,ChartValue", "varchar,date")>
<cfset q2 = queryNew("ChartItem,ChartValue", "varchar,date")>
<cfloop from="1" to="8" index="x">
<cfloop from="85" to="90" index="y">
<cfset row = queryAddRow(q1, 1)>
<cfset q1["ChartItem"][row] = chr(y)>
<cfset q1["ChartValue"][row] = createDate(2008, 10, randRange(1,25))>
<cfset row = queryAddRow(q2, 1)>
<cfset q2["ChartItem"][row] = chr(y)>
<cfset q2["ChartValue"][row] = createDate(2008, 10, randRange(1,25))>
</cfloop>
</cfloop>
<!---
FILE 1: yAxisDateStyle.xml
--->
<yAxis type="DateTime">
<titleStyle font="Arial-12-bold"/>
<labelFormat style="DateTimePattern" pattern="{labelDateFormat}"/>
</yAxis>
<!---
EXAMPLE 1: FORMAT Y-AXIS DATES (MM.dd.yyy)
--->
<cfset chartStyle = FileRead( ExpandPath("./yAxisDateStyle.xml") )>
<!--- date patterns _are_ case sensitive --->
<cfset chartStyle = ReplaceNoCase( chartStyle, "{labelDateFormat}", "MM.dd.yyyy")>
<cfchart format="png" style="#chartStyle#">
<cfchartseries type="line">
<cfloop query="q1">
<cfchartdata item="#ChartItem#" value="#q1.ChartValue[currentRow].getTime()#">
</cfloop>
</cfchartseries>
<cfchartseries type="line">
<cfloop query="q2">
<cfchartdata item="#ChartItem#" value="#q2.ChartValue[currentRow].getTime()#">
</cfloop>
</cfchartseries>
</cfchart>
<!---
FILE 2: yAxisReverseDateStyle.xml
--->
<yAxis type="DateTime" isReversed="true">
<titleStyle font="Arial-12-bold"/>
<labelFormat style="DateTimePattern" pattern="{labelDateFormat}"/>
</yAxis>
<!---
EXAMPLE 2: REVERSE AND FORMAT Y-AXIS DATES
--->
<cfset chartStyle = FileRead( ExpandPath("./yAxisReverseDateStyle.xml") )>
<!--- date patterns _are_ case sensitive --->
<cfset chartStyle = ReplaceNoCase( chartStyle, "{labelDateFormat}", "MM.dd.yyyy")>
<cfchart format="png" style="#chartStyle#">
<cfchartseries type="line">
<cfloop query="q1">
<cfchartdata item="#ChartItem#" value="#q1.ChartValue[currentRow].getTime()#">
</cfloop>
</cfchartseries>
<cfchartseries type="line">
<cfloop query="q2">
<cfchartdata item="#ChartItem#" value="#q2.ChartValue[currentRow].getTime()#">
</cfloop>
</cfchartseries>
</cfchart>
<!---
FILE 3: yAxisReverseDateStyle.xml
--->
<yAxis type="DateTime" scaleMin="{dateMin}" scaleMax="{dateMax}">
<titleStyle font="Arial-12-bold"/>
<labelFormat style="DateTimePattern" pattern="{labelDateFormat}"/>
<parseFormat style="DateTimePattern" pattern="{inputDateFormat}"/>
<dateTimeStyle majorUnit="{dateUnit}" majorStep="{dateStep}"/>
</yAxis>
<!---
EXAMPLE 3: SET SCALE W/SKIP LABELS
--->
<cfset chartStyle = FileRead( ExpandPath("./yAxisDateRangeStyle2.xml") )>
<!--- date patterns _are_ case sensitive --->
<!--- set the input format of min/max dates --->
<cfset chartStyle = ReplaceNoCase( chartStyle, "{inputDateFormat}", "MM/dd/yyyy")>
<cfset chartStyle = ReplaceNoCase( chartStyle, "{dateMin}", "10/01/2008")>
<cfset chartStyle = ReplaceNoCase( chartStyle, "{dateMax}", "10/31/2008")>
<!--- set the display format of Y-Axis dates --->
<cfset chartStyle = ReplaceNoCase( chartStyle, "{labelDateFormat}", "MM.dd.yyyy")>
<!--- display a date label every three (3) Days --->
<cfset chartStyle = ReplaceNocase( chartStyle, "{dateUnit}", "Day" ) >
<cfset chartStyle = ReplaceNocase( chartStyle, "{dateStep}", "3" ) >
<cfchart format="png" style="#chartStyle#">
<cfchartseries type="line">
<cfloop query="q1">
<cfchartdata item="#ChartItem#" value="#q1.ChartValue[currentRow].getTime()#">
</cfloop>
</cfchartseries>
<cfchartseries type="line">
<cfloop query="q2">
<cfchartdata item="#ChartItem#" value="#q2.ChartValue[currentRow].getTime()#">
</cfloop>
</cfchartseries>
</cfchart>
...Read More
<!---
FILE: choiceStyle.xml
--->
<yAxis scaleMin="0">
<titleStyle font="Arial-12-bold"/>
<dateTimeStyle majorUnit="Year" minorUnit="Month"/>
<labelFormat style="Choice" pattern="{choicePattern}"/>
</yAxis>
<!---
CHOICE EXAMPLE
--->
<cfset chartStyle = FileRead( ExpandPath("./choiceStyle.xml") )>
<cfset choiceText = "0##|2##Bored to Tears | 4##Jaded | 6##Intrigued">
<cfset chartStyle = ReplaceNoCase( chartStyle, "{choicePattern}", choiceText)>
<cfchart format="png" style="#chartStyle#" scalefrom="0" scaleto="6">
<cfchartseries type="bar">
<cfchartdata item="Group A" value="2">
<cfchartdata item="Group B" value="2">
<cfchartdata item="Group C" value="4">
<cfchartdata item="Group D" value="6">
<cfchartdata item="Group E" value="2">
</cfchartseries>
</cfchart>
So I am cleaning up some data from an old MS SQL database today. I needed to split a column on what appeared to be a space character. But obviously not because SELECT CHARINDEX( char(32), MyColumn ) returned zero.
No problem, I thought. It is probably a tab character or something. So I decided to check the ascii values. In a less than brilliant moment, I decided to copy and paste one of the values from grid view and use it for testing. Imagine my surprise when SELECT ASCII( SUBSTRING('some value', 5, 1) ) told me the character in question really was a space. Oookaay... then why does SELECT CHARINDEX( char(32), MyColumn ) return zero?
It took me a few minutes before I realized the problem. The data actually contained char(13) + char(10), but Studio Express grid view helpfully translated the new lines into spaces for my viewing pleasure. That is what I get for not checking the underlying data with sql.
Really I know better. When it comes to whitespace, never trust the graphical tools. They all lie.
Rey Bango posted a recent entry called "Meme(me)" that gave me a chuckle. Now I know what you are thinking .. but the funny part was not the pictures. It was some of the comments:
".. careful w/ those bag monsters. They're dangerous!"
- Rey Bango
"There are enough horrible images available on the web- I don't think it needs pictures of me making things worse. :)"
- Mark Osbun
"Oh my-- cheesy E-mail fowards just met the blogosphere. :)"
- Brad Wood
(I am with Mark on this one ;)
Here is the complete code from my previous entry
Detailed Instructions
Using the JavaLoader.cfc and compiling the Java class with Eclipse
(The instructions for compiling the jar should be the same. Only the java code is different)
Invoke CFFunction from PdfPageEvent Example (MX7+)
Path: {wwwroot}\dev\iText\myTestPage.cfm
<h1>Invoke CFFunction from PdfPageEvent Example (MX7+)</h1>
<cfscript>
savedErrorMessage = "";
fullPathToOutputFile = ExpandPath("./CFToPDFPageEventResult.pdf");
// get instance of javaLoader stored in the server scope
javaLoader = server[application.MyUniqueKeyForJavaLoader];
document = javaLoader.create("com.lowagie.text.Document").init();
try {
outStream = createObject("java", "java.io.FileOutputStream").init(fullPathToOutputFile);
writer = javaLoader.create("com.lowagie.text.pdf.PdfWriter").getInstance(document, outStream);
// 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 );
// 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
);
writer.setPageEvent( eventUtil );
// step 4: open the document and add a few sample pages
phrase = javaLoader.create("com.lowagie.text.Phrase");
totalPages = 10;
document.open();
for (i = 1; i LTE totalPages; i = i + 1) {
document.add( phrase.init("The best way to be boring is to leave nothing out. ") );
document.add( phrase.init("The best way to be boring is to leave nothing out. ") );
document.add( phrase.init("The best way to be boring is to leave nothing out. ") );
if (i LT totalPages) {
document.newPage();
}
}
}
catch (Exception e) {
savedErrorMessage = e;
}
// close document and output stream objects
if ( structKeyExists(variables, "document") ) {
document.close();
}
if ( structKeyExists(variables, "outStream") ) {
outStream.close();
}
WriteOutput("Done!");
</cfscript>
<!--- show any errors --->
<cfif len(savedErrorMessage) gt 0>
Error. Unable to create file
<cfdump var="#savedErrorMessage#">
</cfif>
<!---
PDFPageEventHandler.cfc
@author http://cfsearching.blogspot.com/
@version 1.0, September 22, 2008
--->
<cfcomponent output="false">
<cfset variables.instance = structNew()>
<cffunction name="init" returntype="PDFPageEventHandler" access="public" output="false">
<cfargument name="javaLoader" type="any" required="true" hint="Instance of the javaLoader.cfc">
<cfargument name="utilityClass" type="string" default="itextutil.CFPDFPageEvent" hint="Dot notation path for the java utility class">
<cfset variables.instance.javaLoader = arguments.javaLoader>
<cfset variables.instance.utilityClass = arguments.utilityClass>
<cfreturn this>
</cffunction>
<cffunction name="getJavaLoader" returntype="any" access="private" output="false">
<cfreturn variables.instance.javaLoader>
</cffunction>
<cffunction name="getUtilityClass" returntype="any" access="private" output="false">
<cfreturn variables.instance.utilityClass>
</cffunction>
<cffunction name="createEventUtility" returntype="any" access="public" output="false" hint="Returns a new instance of the java utility class">
<!--- create an instance of the java utility class --->
<cfreturn getJavaLoader().create( getUtilityClass() ).init() >
</cffunction>
<cffunction name="link" returntype="void" access="public" output="false" hint="Links a CFFunction to a PDFPageEvent">
<cfargument name="eventUtility" type="any" required="true" hint="Instance of the java utility class">
<cfargument name="eventName" type="string" required="true" hint="Name of the desired PDFPageEvent">
<cfargument name="functionContext" type="any" required="true" hint="Page context for the CFFunction. ie GetPageContext()">
<cfargument name="functionObject" type="any" required="true" hint="Instance of the desired CFFunction">
<cfargument name="functionArguments" type="struct" default="#structNew()#" hint="Any arguments to pass to the CFFunction">
<cfset var Local = structNew()>
<!--- the method name used to call the CF function internally --->
<cfset Local.internalMethod = "invoke">
<!--- define paramter types required to call the CF function internally --->
<cfset Local.Class = createObject("java", "java.lang.Class")>
<cfset Local.paramTypes = arrayNew(1)>
<cfset Local.paramTypes[1] = Local.Class.forName("java.lang.Object")>
<cfset Local.paramTypes[2] = Local.Class.forName("java.lang.String")>
<cfset Local.paramTypes[3] = Local.Class.forName("java.lang.Object")>
<cfset Local.paramTypes[4] = Local.Class.forName("java.util.Map")>
<!--- define arguments required to call the CF function internally --->
<!--- [1] instance, [2] function name, [3] parent, [4] function arguments --->
<cfset Local.methodArgs = arrayNew(1)>
<cfset Local.methodArgs[1] = arguments.functionContext.getFusionContext()>
<cfset Local.methodArgs[2] = getMetaData(arguments.functionObject).name>
<cfset Local.methodArgs[3] = arguments.functionContext.getPage()>
<cfset Local.methodArgs[4] = arguments.functionArguments >
<!--- using the java class, link the CF function to the specified page event --->
<cfset arguments.eventUtility.link ( arguments.eventName,
arguments.functionObject,
arguments.functionArguments,
Local.internalMethod,
Local.paramTypes,
Local.methodArgs
)>
</cffunction>
</cfcomponent>
<cfcomponent output="false">
<cfset variables.instance = structNew()>
<cffunction name="init" returntype="MyPageEventFunctions" access="public" output="false">
<cfargument name="javaLoader" type="any" required="true">
<cfset variables.instance.javaLoader = arguments.javaLoader>
<cfreturn this>
</cffunction>
<!--- this is required for event handler --->
<cffunction name="getContext" returntype="any" access="public" output="false">
<cfreturn getPageContext()>
</cffunction>
<cffunction name="getJavaLoader" returntype="any" access="private" output="false">
<cfreturn variables.instance.javaLoader>
</cffunction>
<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.HELVETICA,
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>
Local.writer = arguments.CF_PDF_EVENT.EVENT_WRITER;
Local.document = arguments.CF_PDF_EVENT.EVENT_DOCUMENT;
Local.textSize = 12;
Local.Color = createObject("java", "java.awt.Color");
Local.textColor = Local.color.decode("##cc0000");
// TEST: change the text color to blue
//Local.textColor = Local.color.decode("##0000ff");
// retrieve the textFont from the event handler object
Local.textFont = arguments.CF_PDF_EVENT.EVENT_PARENT.getProp("textFont");
Local.cb = Local.writer.getDirectContent();
Local.cb.saveState();
Local.cb.beginText();
Local.cb.setColorFill(Local.textColor);
Local.cb.setFontAndSize( Local.textFont, Local.textSize);
Local.cb.setTextMatrix( Local.document.left(), Local.document.bottom() - 10);
Local.text = arguments.footerText & Local.writer.getPageNumber();
// TEST: change the footer text
//Local.text = "www.cluelesscorp.com - What page is this? ["& Local.writer.getPageNumber() &"]";
Local.cb.showText( Local.text );
Local.cb.endText();
Local.cb.restoreState();
</cfscript>
</cffunction>
</cfcomponent>
...Read More
/**
* Generic helper class used to invoke a ColdFusion function
* from java when a PDFPageEvent occurs
*
* @author http://cfsearching.blogspot.com
* @version 1.0
*/
package itextutil;
import java.lang.reflect.Method;
import java.util.Hashtable;
import java.util.Map;
import com.lowagie.text.Document;
import com.lowagie.text.ExceptionConverter;
import com.lowagie.text.Paragraph;
import com.lowagie.text.Rectangle;
import com.lowagie.text.pdf.PdfPageEventHelper;
import com.lowagie.text.pdf.PdfWriter;
public class CFPDFPageEvent extends PdfPageEventHelper {
public static final double version = 1.0;
// Keys representing the different pdf page events
public static final String ON_END_PAGE = "onEndPage";
public static final String ON_START_PAGE = "onStartPage";
public static final String ON_CLOSE_DOCUMENT = "onCloseDocument";
public static final String ON_OPEN_DOCUMENT = "onOpenDocument";
public static final String ON_CHAPTER = "onChapter";
public static final String ON_CHAPTER_END = "onChapterEnd";
public static final String ON_GENERIC_TAG = "onGenericTag";
public static final String ON_PARAGRAPH = "onParagraph";
public static final String ON_PARAGRAPH_END = "onParagraphEnd";
public static final String ON_SECTION = "onSection";
public static final String ON_SECTION_END = "onSectionEnd";
public static final String CF_FUNCTION_CLASS = "CF_FUNCTION_CLASS";
public static final String CF_METHOD_NAME = "CF_METHOD_NAME";
public static final String CF_METHOD_PARAMTER_TYPES = "CF_METHOD_NAME";
public static final String CF_METHOD_ARGUMENTS = "CF_METHOD_ARGUMENTS";
// Key for event data passed to the CF functions
public static final String CF_PDF_EVENT = "CF_PDF_EVENT";
// Key for event values passed to the CF functions
public static final String EVENT_PARENT = "EVENT_PARENT";
public static final String EVENT_WRITER = "EVENT_WRITER";
public static final String EVENT_DOCUMENT = "EVENT_DOCUMENT";
public static final String EVENT_POSITION = "EVENT_POSITION";
public static final String EVENT_TITLE = "EVENT_TITLE";
public static final String EVENT_DEPTH = "EVENT_DEPTH";
public static final String EVENT_TEXT = "EVENT_TEXT";
public static final String EVENT_RECTANGLE = "EVENT_RECTANGLE";
private Map props;
private Map eventMap;
public CFPDFPageEvent(){
super();
this.props = new Hashtable();
this.eventMap = new Hashtable();
}
public void onOpenDocument(PdfWriter writer, Document document) {
EventFunction cfData = getEventFunction(ON_OPEN_DOCUMENT);
// If there is a CF function mapped to this event
if (cfData != null) {
// Add the event data to the CF function arguments
Map event = createEvent();
event.put(EVENT_WRITER, writer);
event.put(EVENT_DOCUMENT, document);
cfData.addFunctionArg(CF_PDF_EVENT, event);
invokeCFFunction(cfData);
}
}
public void onCloseDocument(PdfWriter writer, Document document) {
EventFunction cfData = getEventFunction(ON_CLOSE_DOCUMENT);
// If there is a CF function mapped to this event
if (cfData != null) {
// Add the event data to the CF function arguments
Map event = createEvent();
event.put(EVENT_WRITER, writer);
event.put(EVENT_DOCUMENT, document);
cfData.addFunctionArg(CF_PDF_EVENT, event);
invokeCFFunction(cfData);
}
}
public void onStartPage(PdfWriter writer, Document document) {
EventFunction cfData = getEventFunction(ON_START_PAGE);
// If there is a CF function mapped to this event
if (cfData != null) {
// Add the event data to the CF function arguments
Map event = createEvent();
event.put(EVENT_WRITER, writer);
event.put(EVENT_DOCUMENT, document);
cfData.addFunctionArg(CF_PDF_EVENT, event);
invokeCFFunction(cfData);
}
}
public void onEndPage(PdfWriter writer, Document document) {
EventFunction cfData = getEventFunction(ON_END_PAGE);
// If there is a CF function mapped to this event
if (cfData != null) {
// Add the event data to the CF function arguments
Map event = createEvent();
event.put(EVENT_WRITER, writer);
event.put(EVENT_DOCUMENT, document);
cfData.addFunctionArg(CF_PDF_EVENT, event);
invokeCFFunction(cfData);
}
}
public void onChapter(PdfWriter writer, Document document, float position, Paragraph title) {
EventFunction cfData = getEventFunction(ON_CHAPTER);
// If there is a CF function mapped to this event
if (cfData != null) {
// Add the event data to the CF function arguments
Map event = createEvent();
event.put(EVENT_WRITER, writer);
event.put(EVENT_DOCUMENT, document);
event.put(EVENT_POSITION, String.valueOf(position));
event.put(EVENT_TITLE, title);
cfData.addFunctionArg(CF_PDF_EVENT, event);
invokeCFFunction(cfData);
}
}
public void onChapterEnd(PdfWriter writer, Document document, float position) {
EventFunction cfData = getEventFunction(ON_CHAPTER_END);
// If there is a CF function mapped to this event
if (cfData != null) {
// Add the event data to the CF function arguments
Map event = createEvent();
event.put(EVENT_WRITER, writer);
event.put(EVENT_DOCUMENT, document);
event.put(EVENT_POSITION, String.valueOf(position));
cfData.addFunctionArg(CF_PDF_EVENT, event);
invokeCFFunction(cfData);
}
}
public void onGenericTag(PdfWriter writer, Document document, Rectangle rect, String text) {
EventFunction cfData = getEventFunction(ON_GENERIC_TAG);
// If there is a CF function mapped to this event
if (cfData != null) {
// Add the event data to the CF function arguments
Map event = createEvent();
event.put(EVENT_WRITER, writer);
event.put(EVENT_DOCUMENT, document);
event.put(EVENT_RECTANGLE, rect);
event.put(EVENT_TEXT, text);
cfData.addFunctionArg(CF_PDF_EVENT, event);
invokeCFFunction(cfData);
}
}
public void onParagraph(PdfWriter writer, Document document, float position) {
EventFunction cfData = getEventFunction(ON_PARAGRAPH);
// If there is a CF function mapped to this event
if (cfData != null) {
// Add the event data to the CF function arguments
Map event = createEvent();
event.put(EVENT_WRITER, writer);
event.put(EVENT_DOCUMENT, document);
event.put(EVENT_POSITION, String.valueOf(position));
cfData.addFunctionArg(CF_PDF_EVENT, event);
invokeCFFunction(cfData);
}
}
public void onParagraphEnd(PdfWriter writer, Document document, float position) {
EventFunction cfData = getEventFunction(ON_PARAGRAPH_END);
// If there is a CF function mapped to this event
if (cfData != null) {
// Add the event data to the CF function arguments
Map event = createEvent();
event.put(EVENT_WRITER, writer);
event.put(EVENT_DOCUMENT, document);
event.put(EVENT_POSITION, String.valueOf(position));
cfData.addFunctionArg(CF_PDF_EVENT, event);
invokeCFFunction(cfData);
}
}
public void onSection(PdfWriter writer, Document document, float position, int depth, Paragraph title) {
EventFunction cfData = getEventFunction(ON_SECTION);
// If there is a CF function mapped to this event
if (cfData != null) {
// Add the event data to the CF function arguments
Map event = createEvent();
event.put(EVENT_WRITER, writer);
event.put(EVENT_DOCUMENT, document);
event.put(EVENT_POSITION, String.valueOf(position));
event.put(EVENT_DEPTH, String.valueOf(depth));
event.put(EVENT_TITLE, title);
cfData.addFunctionArg(CF_PDF_EVENT, event);
invokeCFFunction(cfData);
}
}
public void onSectionEnd(PdfWriter writer, Document document, float position) {
EventFunction cfData = getEventFunction(ON_SECTION_END);
// If there is a CF function mapped to this event
if (cfData != null) {
// Add the event data to the CF function arguments
Map event = createEvent();
event.put(EVENT_WRITER, writer);
event.put(EVENT_DOCUMENT, document);
event.put(EVENT_POSITION, String.valueOf(position));
cfData.addFunctionArg(CF_PDF_EVENT, event);
invokeCFFunction(cfData);
}
}
protected void invokeCFFunction(EventFunction cfData) {
try {
// Get the CF function's java class
Class cfClass = cfData.getFunction().getClass();
// Locate the internal method used to invoke the function
Method cfMethod = cfClass.getMethod( cfData.getMethodName(), cfData.getParamTypes() );
// Finally, invoke call the CF function
cfMethod.invoke( cfData.getFunction(), cfData.getMethodArgs());
}
catch (Exception e) {
// convert checked exception into an unchecked exception.
throw new ExceptionConverter( new CFPDFFunctionException(e) );
}
}
protected Map createEvent() {
Map event = new Hashtable();
event.put(EVENT_PARENT, this);
return event;
}
public void link(String eventName, Object cfFuncObj, Map cfFuncArgs,
String methodName, Class[] paramTypes, Object[] methodArgs) {
// create a new function object
EventFunction func = new EventFunction(cfFuncObj, cfFuncArgs, methodName, paramTypes, methodArgs);
// link the function to the specified event
getEventMap().put(eventName, func);
}
public Object getProp(String key) {
return (this.props.containsKey(key) ? this.props.get(key) : "");
}
public void setProp(String key, Object value) {
this.props.put(key, value);
}
protected Map getEventMap() {
return this.eventMap;
}
protected EventFunction getEventFunction(String eventName) {
return (EventFunction)getEventMap().get(eventName);
}
public static void main(String[] args) {
}
/**
* Custom exception designed to make it easier to detect and catch errors from CF
*/
class CFPDFFunctionException extends Exception {
public CFPDFFunctionException(Exception e) {
super(e);
}
}
/**
* This class represents the information required to call a CF Function
*/
class EventFunction {
private Object funcInstance;
private Map funcArgs;
private String methodName;
private Class[] paramTypes;
private Object[] methodArgs;
public EventFunction( Object funcInstance, Map funcArgs, String methodName,
Class[] paramTypes, Object[] methodArgs) {
this.funcInstance = funcInstance;
this.funcArgs = funcArgs;
this.methodName = methodName;
this.paramTypes = paramTypes;
this.methodArgs = methodArgs;
}
public Object getFunction() {
return this.funcInstance;
}
public Map getFunctionArgs() {
return this.funcArgs;
}
public void addFunctionArg(Object key, Object value) {
this.funcArgs.put(key, value);
}
public String getMethodName() {
return this.methodName;
}
public Class[] getParamTypes() {
return this.paramTypes;
}
public Object[] getMethodArgs() {
return this.methodArgs;
}
}
}
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>
<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>
<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>
<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>
<!--- this is required for event handler --->
<cffunction name="getContext" returntype="any" access="public" output="false">
<cfreturn getPageContext()>
</cffunction>
public void link(String eventName, Object cfFuncObj, Map cfFuncArgs,
String methodName, Class[] paramTypes, Object[] methodArgs) {
// create a new function object
EventFunction func = new EventFunction(cfFuncObj, cfFuncArgs, methodName, paramTypes, methodArgs);
// link the function to the specified event
getEventMap().put(eventName, func);
}
public void onOpenDocument(PdfWriter writer, Document document) {
EventFunction cfData = getEventFunction(ON_OPEN_DOCUMENT);
// If there is a CF function mapped to this event
if (cfData != null) {
// Add the event data to the CF function arguments
Map event = createEvent();
event.put(EVENT_WRITER, writer);
event.put(EVENT_DOCUMENT, document);
cfData.addFunctionArg(CF_PDF_EVENT, event);
invokeCFFunction(cfData);
}
}
protected void invokeCFFunction(EventFunction cfData) {
try {
// Get the CF function's java class
Class cfClass = cfData.getFunction().getClass();
// Locate the internal method used to invoke the function
Method cfMethod = cfClass.getMethod( cfData.getMethodName(), cfData.getParamTypes() );
// Finally, invoke call the CF function
cfMethod.invoke( cfData.getFunction(), cfData.getMethodArgs());
}
catch (Exception e) {
// convert checked exception into an unchecked exception.
throw new ExceptionConverter( new CFPDFFunctionException(e) );
}
}
<cffunction name="createEventUtility" returntype="any" access="public" output="false" hint="Returns a new instance of the java utility class">
<!--- create an instance of the java utility class --->
<cfreturn getJavaLoader().create( getUtilityClass() ).init() >
</cffunction>
<cffunction name="link" returntype="void" access="public" output="false" hint="Links a CFFunction to a PDFPageEvent">
<cfargument name="eventUtility" type="any" required="true" hint="Instance of the java utility class">
<cfargument name="eventName" type="string" required="true" hint="Name of the desired PDFPageEvent">
<cfargument name="functionContext" type="any" required="true" hint="Page context for the CFFunction. ie GetPageContext()">
<cfargument name="functionObject" type="any" required="true" hint="Instance of the desired CFFunction">
<cfargument name="functionArguments" type="struct" default="#structNew()#" hint="Any arguments to pass to the CFFunction">
<cfset var Local = structNew()>
<!--- the method name used to call the CF function internally --->
<cfset Local.internalMethod = "invoke">
<!--- define paramter types required to call the CF function internally --->
<cfset Local.Class = createObject("java", "java.lang.Class")>
<cfset Local.paramTypes = arrayNew(1)>
<cfset Local.paramTypes[1] = Local.Class.forName("java.lang.Object")>
<cfset Local.paramTypes[2] = Local.Class.forName("java.lang.String")>
<cfset Local.paramTypes[3] = Local.Class.forName("java.lang.Object")>
<cfset Local.paramTypes[4] = Local.Class.forName("java.util.Map")>
<!--- define arguments required to call the CF function internally --->
<!--- [1] instance, [2] function name, [3] parent, [4] function arguments --->
<cfset Local.methodArgs = arrayNew(1)>
<cfset Local.methodArgs[1] = arguments.functionContext.getFusionContext()>
<cfset Local.methodArgs[2] = getMetaData(arguments.functionObject).name>
<cfset Local.methodArgs[3] = arguments.functionContext.getPage()>
<cfset Local.methodArgs[4] = arguments.functionArguments >
<!--- using the java class, link the CF function to the specified page event --->
<cfset arguments.eventUtility.link ( arguments.eventName,
arguments.functionObject,
arguments.functionArguments,
Local.internalMethod,
Local.paramTypes,
Local.methodArgs
)>
</cffunction>
I came across a nice debugging tip on Dan Vega's blog. He shows how you can display the contents of most all scopes using getPageContext().getBuiltInScopes()
Obviously the usual cautions apply. 1) It is an undocumented function, so use it at your own risk. 2) It really does dump all scopes. So it is not suitable for production environments where you might accidentally be displaying sensitive information.
Most of us love cfqueryparam .. except when it comes to debugging. Unfortunately, when you use cfqueryparam, the basic CF debugging template displays parameter values separate from the actual sql statements. Understandable, given how queries are handled behind the scenes. But as a developer you typically want to see human readable queries, with the parameter values.
This limitation sent me searching for a new debugging tool last week. The search led me to the fantastic Firefox plugin ColdFire (by Raymond Camden, Adam Podolnick and Nathan Mische). It has been around for a while, but I only started using it a few days ago. So far I think it is an amazing tool!
Now since my main interest was database queries, I installed it and jumped right to the DB Queries tab. At first glance the results looked perfect: sql you can copy and paste into an query window. After running a few more queries I noticed a few small things I wanted to tweak. (Yes I know ... give them the moon and they still want more).
<cfset parameters[x][1] = ListFind(cfsqltypes,attr.sqltype)>
formatParamValue: function( param ) {
var type = param[0];
var value = param[1];
var tmpVal = value;
if (value.length == 0) {
// treat empty strings as NULL
tmpVal = "NULL";
} else if (type == 2){
// ... etcetera
},
...
<cfif isBinaryType AND isBinary(attr.value)>
<cfset parameters[x][2] = "0x"& binaryEncode(attr.value, "hex")>
<cfelse>
<cfset parameters[x][2] = attr.value>
</cfif>
Hardly a week goes by without seeing a question that starts with "My query is not returning the right results..." and ends with ".. Oh yeah, and the values in the ColumnXYZ are a list like 1,5,6,18,97. So can anyone tell me why my query does not work?".
{Sigh} The question inevitably elicits a slew of responses about proper database modeling and normalization. Followed by a predictable set of responses from the OP. In confusion they ask "what do you mean by junction table?" Then in disbelief: "how can creating more tables and more records be better?".
You know ... it is almost as if they think you are trying to trick them. As if the fact that they already spent the better part of two hours, working on a single a query, was not clue enough that maybe .. just maybe.. they were headed down the wrong path.
Usually after a few well worded explanations, most are ready to be "saved" and embark on the higher path to database enlightenment. I am a convert myself. But occasionally you run across a true die-hard disbeliever. They scoff at the mere idea of database modeling. Standing unrepentant (and unfortunately .. very vocal) in their ignorance. I suspect they have business cards and t-shirts emblazoned with their name and title:
A reader named Turnbull was asking about PDFTables and I realized I did not have any examples on tables posted. So here are the first sections from from the iText tutorial on tables.
<h1>My First PdfPTable (MX7 compatible)</h1>
Use a PdfPTable to add a table to a PDF document
<cfscript>
savedErrorMessage = "";
pathToOutputFile = ExpandPath("MyFirstTable.pdf");
try {
// step 1: creation of a document-object
document = createObject("java", "com.lowagie.text.Document").init();
// step 2:
// we create a writer that listens to the document
// and directs a PDF-stream to a file
PdfWriter = createObject("java", "com.lowagie.text.pdf.PdfWriter");
FileOutputStream = createObject("java", "java.io.FileOutputStream");
outStream = FileOutputStream.init( pathToOutputFile );
writer = PdfWriter.getInstance(document, outStream);
// step 3: we open the document
document.open();
// create reusable object
Paragraph = createObject("java", "com.lowagie.text.Paragraph");
PdfPCell = createObject("java", "com.lowagie.text.pdf.PdfPCell");
Color = createObject("java", "java.awt.Color");
// create a table with three columns
PdfPTable = createObject("java", "com.lowagie.text.pdf.PdfPTable");
table = PdfPTable.init( javacast("int", 3) );
// create a header cell
cell = PdfPCell.init( Paragraph.init("header with colspan 3") );
// force header cell to span all three columns
cell.setColspan( javacast("int", 3) );
// add the header to the table
table.addCell( cell );
// add some cells with plain data
table.addCell("1.1");
table.addCell("2.1");
table.addCell("3.1");
table.addCell("1.2");
table.addCell("2.2");
table.addCell("3.2");
// add some formatted cells
cell = PdfPCell.init( Paragraph.init("cell test1") );
cellColor = Color.init( javacast("int", 255), javacast("int", 0), javacast("int", 0) );
cell.setBorderColor( cellColor );
table.addCell( cell );
cell = PdfPCell.init( Paragraph.init("cell test2") );
cell.setColspan( javacast("int", 2) );
// converted java values to decimal RGB values using:
// value = inputBaseN("0xC0", 16);
cellColor = Color.init( javacast("int", 192), javacast("int", 192), javacast("int", 192) );
cell.setBackgroundColor( cellColor );
table.addCell( cell );
// Finally, add the table to the document
document.add(table);
}
catch (com.lowagie.text.DocumentException de) {
savedErrorMessage = de;
}
catch (java.io.IOException ioe) {
savedErrorMessage = ioe;
}
// step 5: always close document and output stream
document.close();
if ( IsDefined("outStream") ) {
outStream.close();
}
WriteOutput("<hr>Finished!");
</cfscript>
<!--- show any errors --->
<cfif len(savedErrorMessage)>
Error creating document
<cfdump var="#savedErrorMessage#">
</cfif>
<h1>TableWidthAlignment (MX7 compatible)</h1>
Changing the width and the alignment of the complete table
<cfscript>
savedErrorMessage = "";
pathToOutputFile = ExpandPath("TableWidthAlignment.pdf");
try {
// step 1: creation of a document-object
PageSize = createObject("java", "com.lowagie.text.PageSize");
document = createObject("java", "com.lowagie.text.Document").init(PageSize.A4);
// step 2:
// we create a writer that listens to the document
// and directs a PDF-stream to a file
PdfWriter = createObject("java", "com.lowagie.text.pdf.PdfWriter");
FileOutputStream = createObject("java", "java.io.FileOutputStream");
outStream = FileOutputStream.init( pathToOutputFile );
writer = PdfWriter.getInstance(document, outStream);
// step 3: we open the document
document.open();
// create reusable object
Paragraph = createObject("java", "com.lowagie.text.Paragraph");
PdfPCell = createObject("java", "com.lowagie.text.pdf.PdfPCell");
Color = createObject("java", "java.awt.Color");
PdfTable = createObject("java", "com.lowagie.text.pdf.PdfPTable");
// step4
// create a table with three columns
table = PdfTable.init( javacast("int", 3) );
// create a cell to contain the header
cell = PdfPCell.init( Paragraph.init("header with colspan 3") );
// set the header to span all three columns
cell.setColspan( javacast("int", 3) );
// add the header to the table
table.addCell( cell );
// add some cells with plain data
table.addCell("1.1");
table.addCell("2.1");
table.addCell("3.1");
table.addCell("1.2");
table.addCell("2.2");
table.addCell("3.2");
// add some formatted cells
cell = PdfPCell.init( Paragraph.init("cell test1") );
cellColor = Color.init( javacast("int", 255), javacast("int", 0), javacast("int", 0) );
cell.setBorderColor( cellColor );
table.addCell( cell );
cell = PdfPCell.init( Paragraph.init("cell test2") );
cell.setColspan( javacast("int", 2) );
cellColor = Color.init( javacast("int", 192), javacast("int", 192), javacast("int", 192) );
cell.setBackgroundColor( cellColor );
table.addCell( cell );
// step 5: add multiple copies of the table
// to the document to demonstrate different widths and alignment
// add the table to the document at its original size
document.add( table );
// add another copy of the table at 100% width
table.setWidthPercentage( javacast("float", 100) );
document.add( table );
// add another copy at 50% width, and align it to the right
table.setWidthPercentage( javacast("float", 50) );
table.setHorizontalAlignment( PdfTable.ALIGN_RIGHT);
document.add( table );
// add another copy and align it to the left
// note, the table width is still set at 50%
table.setHorizontalAlignment( PdfTable.ALIGN_LEFT );
document.add( table );
}
catch (java.lang.Exception de) {
savedErrorMessage = de;
}
// step 5: always close document and output stream
document.close();
if ( IsDefined("outStream") ) {
outStream.close();
}
WriteOutput("<hr>Finished!");
</cfscript>
<!--- show any errors --->
<cfif len(savedErrorMessage)>
Error creating document
<cfdump var="#savedErrorMessage#">
</cfif>
...Read More
<h1>TableSpacing (requires newer version of iText)</h1>
Defining the spacing between the table and other content
<cfscript>
savedErrorMessage = "";
pathToOutputFile = ExpandPath("TableSpacing.pdf");
javaLoader = server[application.MyUniqueKeyForJavaLoader];
try {
// step 1: creation of a document-object
PageSize = javaLoader.create("com.lowagie.text.PageSize");
document = javaLoader.create("com.lowagie.text.Document").init(PageSize.A4);
// step 2:
// we create a writer that listens to the document
// and directs a PDF-stream to a file
PdfWriter = javaLoader.create("com.lowagie.text.pdf.PdfWriter");
FileOutputStream = createObject("java", "java.io.FileOutputStream");
outStream = FileOutputStream.init( pathToOutputFile );
writer = PdfWriter.getInstance(document, outStream);
// step 3: we open the document
document.open();
// create reusable object
Paragraph = javaLoader.create("com.lowagie.text.Paragraph");
PdfPCell = javaLoader.create("com.lowagie.text.pdf.PdfPCell");
Color = createObject("java", "java.awt.Color");
PdfTable = javaLoader.create("com.lowagie.text.pdf.PdfPTable");
// step4
// create a table with three columns
table = PdfTable.init( javacast("int", 3) );
// create a cell to contain the header
cell = PdfPCell.init( Paragraph.init("header with colspan 3") );
// set the header to span all three columns
cell.setColspan( javacast("int", 3) );
// add the header to the table
table.addCell( cell );
// add some cells with plain data
table.addCell("1.1");
table.addCell("2.1");
table.addCell("3.1");
table.addCell("1.2");
table.addCell("2.2");
table.addCell("3.2");
// add some formatted cells
cell = PdfPCell.init( Paragraph.init("cell test1") );
cellColor = Color.init( javacast("int", 255), javacast("int", 0), javacast("int", 0) );
cell.setBorderColor( cellColor );
table.addCell( cell );
cell = PdfPCell.init( Paragraph.init("cell test2") );
cell.setColspan( javacast("int", 2) );
cellColor = Color.init( javacast("int", 192), javacast("int", 192), javacast("int", 192) );
cell.setBackgroundColor( cellColor );
table.addCell( cell );
// step 5: add multiple copies of the table to the document
// to demonstrate different spacing options
table.setWidthPercentage( javacast("float", 50) );
document.add( Paragraph.init("We add 2 tables:") );
document.add( table );
document.add( table );
document.add( Paragraph.init("They are glued to eachother") );
document.add(table );
document.add( Paragraph.init("This is not very nice. Turn to the next page to see how we solved this") );
document.newPage();
document.add( Paragraph.init("We add 2 tables, but with a certain 'SpacingBefore':") );
table.setSpacingBefore( javacast("float", 15) );
document.add( table );
document.add( table );
document.add( Paragraph.init("Unfortunately, there was no spacing after.") );
table.setSpacingAfter( javacast("float", 15) );
document.add( table );
document.add( Paragraph.init("This is much better, don't you think so?") );
}
catch (java.lang.Exception de) {
savedErrorMessage = de;
}
// step 5: always close document and output stream
document.close();
if ( IsDefined("outStream") ) {
outStream.close();
}
WriteOutput("<hr>Finished!");
</cfscript>
<!--- show any errors --->
<cfif len(savedErrorMessage)>
Error creating document
<cfdump var="#savedErrorMessage#">
</cfif>
© Blogger templates The Professional Template by Ourblogtemplates.com 2008
Header image adapted from atomicjeep