First Attempt At Excel Document Formatting Hooks Using ColdFusion And POI

Posted February 15, 2007 at 2:30 PM by Ben Nadel

Tags: ColdFusion

As I stated before, I have been trying to come up with a very easy way to generate Excel document with good formatting by using ColdFusion and POI at a high level. I am not talking about a simple wrapper for POI that makes it ColdFusion friendly. That kind of low-level, very granular stuff has been done before. I am talking about making it very easy for people to pass in a query or an array or a CSV value and convert that into a very nicely formatted Excel document with just a few lines of code.

I am not posting the implementation yet, as it is very early, but I thought I would share the thoughts to date. I have taken my POI Utility ColdFusion component and broken it out into a few objects:

  • POIUtility.cfc
  • POISheet.cfc
  • POIHelper.cfc

The POI Helper ColdFusion component just has some utility functions such as CSV parsing and CSS stacking (read: cascading of CSS rules). The POI Sheet ColdFusion component houses the information about a given sheet that needs to be created. Then the POI Utility ColdFusion component is the glue that brings it all together.

Let's run through an example of where I am now:

  • <!---
  • Let's start out by creating an array of arrays that will
  • act as the data for our Excel sheet. Behind the scenes,
  • this array is actually getting converted to a query (but
  • that is not important at this time).
  • --->
  • <cfset arrData = ArrayNew( 1 ) />
  • <cfset arrData[ 1 ] = ListToArray( "Azure,9.0,10.0,Brunette" ) />
  • <cfset arrData[ 2 ] = ListToArray( "Sarah,9.0,9.0,Brunette" ) />
  • <cfset arrData[ 3 ] = ListToArray( "Kit,7.0,7.0,Brunette" ) />
  • <cfset arrData[ 4 ] = ListToArray( "Kim,8.0,n/a,Black" ) />
  • <cfset arrData[ 5 ] = ListToArray( "Annie,7.0,9.0,Brunette" ) />
  • <cfset arrData[ 6 ] = ListToArray( "India,6.0,5.0,Blonde" ) />
  • <cfset arrData[ 7 ] = ListToArray( "Cici,5.0,3.0,Blonde" ) />
  •  
  •  
  • <!---
  • Now, let's create an instance of the POI Utility. This call
  • assumes that the POIUtility.cfc is in the same directory as
  • the calling code.
  • --->
  • <cfset objPOI = CreateObject(
  • "component",
  • "POIUtility"
  • ).Init()
  • />
  •  
  •  
  • <!---
  • Get a POI Sheet object from the POI Utility. The sheet
  • object is in now way tied to the POI Utility. This is
  • just a comvenience method. You could just have easily
  • called the CreateObject() method and instantiated the
  • sheet yourself.... that is why CreateSheet() returns
  • an uninitialized sheet (so that you can pass in the
  • good stuff).
  •  
  • When initializing, we can pass in the Name of the sheet,
  • the data array we created above, the column names (for
  • the header row) and the default CSS information).
  • --->
  • <cfset objSheet = objPOI.CreateSheet().Init(
  • Name = "Hot Chicks",
  • DataArray = arrData,
  • ColumnNames = "Name,Hotness,Coolness,Hair Color",
  • HeaderCSS = "font: italic 16pt verdana ; background: lime ; color: white ; border-bottom: 3px solid green ;",
  • RowCSS = "border-bottom: 1px solid gold ; font-size: 12pt ;",
  • AltRowCSS = "background-color: lemon_chiffon ;"
  • ) />
  •  
  •  
  • <!---
  • Now, here's where it gets exciting. Let's format cells in
  • the Excel file using conditions and CSS.
  • --->
  •  
  •  
  • <!---
  • Here, we are are going to find any cell whose value is
  • "n/a" and update the CSS with font and color.
  • --->
  • <cfset objSheet.AddFormatting(
  • Condition = "{value} EQ 'n/a'",
  • CSS = "font-style: italic ; color: GREY_40_PERCENT ;"
  • ) />
  •  
  • <!---
  • Here, we are going to find any value in the 2nd column
  • whose value is greater than or equal to 9 and we are
  • going to update the CSS with color and font.
  • --->
  • <cfset objSheet.AddFormatting(
  • Condition = "({column} EQ 2) AND (Val( {value} ) GTE 9)",
  • CSS = "color: red ; font-weight: bold ;"
  • ) />
  •  
  • <!---
  • Here, wer are going ot find any value in the 3rd column
  • whose value is greater than or equal to 9 and update
  • the CSS with color.
  • --->
  • <cfset objSheet.AddFormatting(
  • Condition = "({column} EQ 3) AND (Val( {value} ) GTE 9)",
  • CSS = "color: red ;"
  • ) />
  •  
  •  
  • <!---
  • Now that our data and formatting are set, we are going to
  • write the Excel file by sending in the file path and the
  • sheet object we created above.
  • --->
  • <cfset objPOI.WriteExcel(
  • FilePath = ExpandPath( "./test.xls" ),
  • Sheets = objSheet
  • ) />

The AddFormatting() method is what I am all jazzed up about now. You use that to add conditional formatting based on a expression that will get Evaluated during the Excel creation. Right now, there are three "place holders" that you can use in the conditional expression:

{column}

The current column index (1-based like ColdFusion).

{row}

The current row index (1-based like ColdFusion). This offset does NOT include the header row.

{value}

The actual data value of the cell.

The Condition expression actually gets evaluated using the ColdFusion method Evaluate() so you can use any methods or expression available to a standard ColdFusion expression. Even things like this are totally valid:

  • "FindNoCase( 'sexy', {value} )"

As long as the condition evaluates to TRUE, the CSS is stacked on top of the existing CSS for that cell. Formatting conditions are applied in the order in which they were added to the sheet. The CSS formatting rules will cascade as much like standard CSS as I could manage (I am only human). If you look at the Excel below, you will notice that all of our conditions stacked appropriately with the RowCSS and AltRowCSS passed into the Sheet constructor.

Here is the Excel sheet that was produced with the above code:


 
 
 

 
ColdFusion And POI Using Formatting Hooks For CSS  
 
 
 

NOTE: I resized the columns manually. Currently, the columns widths cause a bit of an issue (since I am trying to get text wrapping to apply).

How easy was that? I LOVE being able to use standard (and fuzzy) CSS rules to format a cell. CSS is awesome and natural and easy to read and understand. If you ain't using CSS, you'ze a sqaure.

As it stands right now, here are the methods available to the POISheetObject():

Init()
AddFormatting()
GetAltRowCSS()
GetColumnList()
GetColumnNames()
GetDataQuery()
GetFormattingConditions()
GetHeaderCSS()
GetName()
GetRowCSS()
SetAltRowCSS()
SetColumnList()
SetColumnNames()
SetCSV()
SetDataArray()
SetDataQuery()
SetHeaderCSS()
SetName()
SetRowCSS()

I am not going to detail them at this point as I feel many of them are self explanatory. They are pretty much getters and setters. Behind the scenes, every data value gets converted to a ColdFusion query. Data Arrays and CSV values all get converted to an array behind the scene. I just find queries easy to work with and they make for a very nice, uniform interface regardless of the data origin.

So anyway, that's the first go-round on this idea. Any thoughts?

And remember, I am not trying to make a ColdFusion API for the POI project. I am trying to make a very easy to use Data-to-Excel translation. I am not trying to solve very detailed problems here (think cell functions, regions, printable areas). At least not yet; all that stuff will come with time. What I am trying to do at first is accomplish one goal: Make it easy to create formatted Excel files based on simple sets of data.

While this sounds simple, it is proving harder than expected. I believe that the low-level stuff (setting functions, splitting screens) is actually going to be easier as less "magic" will be happening behind the scenes as the defining of such things will probably require reaching more directly into the POI API.




Reader Comments

Feb 15, 2007 at 5:47 PM // reply »
18 Comments

Ben,

Have you also looked at JExcelAPI?

It would be interesting to see a comparison with POI.

http://jexcelapi.sourceforge.net/

Sami


Feb 15, 2007 at 5:53 PM // reply »
10,640 Comments

Sami,

I looked at JExcel very briefly. I actually tried working with that before I even knew about POI. I kept trying to load it as a JAR file without putting it in the class paths (I like the idea of having stuff very modular - another conversation all together). I could read fine, but writing seemed be throwing errors.

I talked to Mark Mandell about it and he seemed to think it was a ClassLoader conflict. I should try it again using his Java class loader.

Long story short though, I was not able to get very far with JExcel without installed it. POI, on the other hand is already installed, so I went starting going that route.


Feb 16, 2007 at 6:04 PM // reply »
16 Comments

Ben,

We have written some custom Java stuff to 'read and write' excel files that piggybacks off of POI.

In our world (mortgage rates, etc...) on import we are reading 'range specific locations' on an excel worksheet and parse that information.

For export, we drop blocks of data into a 'pre formatted' excel sheet at different range locations/worksheets. While we're not handling the formatting directly, it allows for easy reading of specific locations and writing of data to specific locations... in a 'pretty' but templated excel workbook.

There are some limitations, of course. But if you think this might be a workable solution for you, please drop me a line.


Feb 16, 2007 at 6:16 PM // reply »
10,640 Comments

Alan,

That sounds freakin' brilliant! It never in a million years would have occurred to me to read in an existing file that had formatting. You magnificent bastard! I will give that a go to experiment.

Thanks!


Feb 19, 2007 at 8:55 AM // reply »
10,640 Comments

Alan,

You are a genius:

http://www.bennadel.com/index.cfm?dax=blog:532.view

Thanks.


Tom
Mar 15, 2007 at 11:07 AM // reply »
15 Comments

How funny I found two solutions I needed from your web site, two days apart!

I also wanted to offer you my condolences on your father's passing. I'm sure you are making him proud.

Now to my question, can you post what became of your inquiry to Alan about feeding the formatted xls (see two or three comments up).


Mar 15, 2007 at 11:13 AM // reply »
10,640 Comments

@DL,

Glad to help, and thank you very much for your thoughts of my father. The preformatted XLS stuff is actually in the link right above your comment... but the link is not working for some reason. Here is a working link :)

http://www.bennadel.com/index.cfm?dax=blog:532.view


May 31, 2007 at 2:23 PM // reply »
1 Comments

Ben,

Great work. Thanks for everything so far.
Whee can we get a copy of the POISheet.cfc and POIHelper.cfc??
Maybe I am not seeing another post where you posted these files..

David Sterling


May 31, 2007 at 3:06 PM // reply »
10,640 Comments

@Dave,

I have not posted it anywhere. It's kind of on the back-burner for the moment. I will try and give it some time/effort. Thanks for checking in.


Mar 24, 2011 at 9:28 PM // reply »
1 Comments

Hey Ben,

Love the POIUtility.cfc... it's adding an extra dimension to my perspective of Excel compatibility with CF.

I've got a bit of a conundrum here... Is there any way that POI is capable of detecting specific formatting (bold, italic, underline) and thus allowing formatting via CF to HTML equivalents through Replace()... For example, a cell containing:

BLAH

will render out as:

  • <B>BLAH</B>

when spat out by CF?

Appreciate your thoughts...


Post A Comment

Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.

Please review the following issues:

Author Name:


Author Email:

Author Website:

Comment:

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







  • Help Wanted - Find Your Next ColdFusion Job
InVision App - Prototyping Made Beautiful With Prototyping Tools Ben Nadel's Company - Epicenter Consulting Recent Blog Comments
Feb 10, 2012 at 7:21 PM
jQuery AJAX Strips Script Tags And Inserts Them After Parent-Most Elements
Update! Instead of $(eval(options.insertAfter)).after(data['insertData']); I now use: var ajaxNode = document.createElement('span'); var parent = $(eval(options.insertAfter))[0].parentNode; ... read »
Feb 10, 2012 at 6:18 PM
jQuery AJAX Strips Script Tags And Inserts Them After Parent-Most Elements
encountered this same, what I consider, jQuery bug last week. I'm building a site in which I load some content via AJAX. This content contains Linkedin share button placeholders which Linkedin API ne ... read »
Feb 10, 2012 at 11:30 AM
Cross-Origin Resource Sharing (CORS) AJAX Requests Between jQuery And Node.js
After you understand the concepts here, this is an awesome cheatsheet for enabling CORS in just about anything http://enable-cors.org/ ... read »
JM
Feb 10, 2012 at 9:10 AM
My Safari Browser SQLite Database Hello World Example
@Amy, Here is a very good tutorial on how to use JOIN: http://www.sqltutorial.org/sqljoin-innerjoin.aspx ... read »
Feb 10, 2012 at 4:42 AM
Building A Twitter-Inspired RESTful API Architecture In ColdFusion
This is great, very useful Ben. I spotted a small typo in the api.cgm listing: <cfthrow type="Unauthroized" /> Cheers Stefan ... read »
Feb 9, 2012 at 10:35 PM
CFDirectory Filtering Uses Pipe Character For Multiple Filters (Thanks Steve Withington)
I was wondering if there would be a filter you could apply so that you got everything but what you included in the filter. As in show me all docs that are not a .pdf. ... read »
Feb 9, 2012 at 10:29 PM
Learning ColdFusion 9: Application-Specific Data Sources
@Ben, No offence, but if people were really wanting advanced features they would be using a platform like ASP.NET MVC. CFML is so structurally compromised as a tag-based scripting language that ... read »
Feb 9, 2012 at 10:03 PM
Subversion - Cleanup Failed To Process The Following Paths
@Leviaguirre, do you still have problems with this? ... read »