When I run this code:
<cfscript>
flinstones = "fred,wilma,pebbles";
Workbook = Spreadsheetnew("Workbook");
for (i = 1; i lte listlen(flinstones); i ++) {
ThisFlinstone = ListGetAt(Flinstones, i);
if (ThisFlinstone == "wilma")
SpreadSheetAddRow(Workbook, "#ThisFlinstone#,barney");
else
SpreadSheetAddRow(Workbook, ThisFlinstone);
}
Format = {};
format.rightborder = "thin";
SpreadsheetFormatColumn(WorkBook, Format, 2);
MYfile = "d:\dw\dwtest\dan\abc.xls";
writedump(format);
</cfscript>
<cfspreadsheet action="write" filename="#MYFile#" name="Workbook"
sheet=1 sheetname="flinstones" overwrite=true>
I expect to see a worksheet with three rows. The second row will have two columns, with wilma and barney in the cells. So far, I see what I expect. I also expect to see Column B with a right hand border. I actually see cell B2 with a right hand border.
If I change this:
SpreadSheetAddRow(Workbook, ThisFlinstone);
to this
SpreadSheetAddRow(Workbook, "#ThisFlinstone#, ");
I see a right hand border for the first three rows of Column B.
Is there a way to have the right hand border apply to all of Column B?
Update:
As Dan mentioned in the comments, I misread the question. To clarify, adding a border to an entire column was not supported by POI last I checked. The only way to make the border appear on the entire column is to populate every single cell in that column first. Either by setting a value explicitly or using SpreadsheetFormatCellRange(sheet, format, 1, 2, maxRow, 2) with the maximum row number. While this does work, I would not recommend it as it has some undesirable side effects.
Is there a way to have the right hand border apply to all of Column B?
Short answer:
Yes, by doing what you already discovered: give all of the cells ie B1, B2 and B3 a value first. Even if it is just a blank or an empty string. This will create a border on all three cells.
Update 1: If you are using CF 9.0.1, another option is SpreadsheetFormatCellRange. Unlike most spreadsheet functions, it automatically creates non-existent cells first:
<cfset SpreadsheetFormatCellRange(Workbook, Format, 1, 2, 3, 2)>
Longer answer:
To apply a format, you need what POI calls a Cell object. When you create a new worksheet, it is completely blank, with no "cells" whatsoever. In most cases, CF only creates a Cell object when you set a value with one of the various functions ie SpreadSheetSetCellValue, SpreadSheetAddRow, etcetera. (See example below). Your original code only sets a one value in Column B. Hence it only creates a single Cell, ie B2. SpreadsheetFormatColumn only formats existing cells, that is why the border is only visible in B2.
<!--- blank sheet with no CELLS --->
<cfset Workbook = Spreadsheetnew() />
<cfset SpreadSheetWrite(Workbook, "c:/test.xls", true) />
<cfspreadsheet action="read" src="c:/test.xls" query="qValues" />
<cfdump var="#qValues#" label="No cells exist yet" />
<!--- setting one value, creates one CELL --->
<cfset Workbook = Spreadsheetnew() />
<cfset SpreadSheetAddRow(Workbook, "Fred") />
<cfset SpreadSheetWrite(Workbook, "c:/test.xls", true) />
<cfspreadsheet action="read" src="c:/test.xls" query="qValues" />
<cfdump var="#qValues#" label="Creates a single cell" />
<!--- setting two values, creates two CELL's --->
<cfset Workbook = Spreadsheetnew() />
<cfset SpreadSheetAddRow(Workbook, "Fred, ") />
<cfset SpreadSheetWrite(Workbook, "c:/test.xls", true) />
<cfspreadsheet action="read" src="c:/test.xls" query="qValues" />
<cfdump var="#qValues#" label="Creates two cells" />
Related
I have a word document with tables laid out to look like a form. I have placeholders like %firstName%, %lastName%, %birthdate%...etc.
When I use the replace() function, the %firstName%, %lastName%, %birthdate% and all of the other placeholder fields are replaced on the first and second page. After the second, nothing replaces. All the names of the placeholders on the 3rd and 4th pages are the same as the 1st and 2nd pages. I even copied and pasted the placeholder names and I've made sure there are no added spaced. Curious to know if anyone else has had this happen and can tell me what was done to fix it.
<cfset docPath = GetDirectoryFromPath(GetCurrentTemplatePath()) & "UserTemplate.rtf" />
<cflock name="UserTemp" type="exclusive" timeout="30">
<cfset rtf = FileRead(docPath) />
<cfquery name = "qUserFormData">
SELECT * FROM vUserFormData WHERE UserID = 3
</cfquery>
<cfset rtf = Replace(rtf,"%firstName%",#firstName#)/>
<cfset rtf = Replace(rtf,"%lastName%",#lastName#) />
<cfset rtf = Replace(rtf,"%birthday%",#birthday#) />
</cflock>
<cfheader name="content-disposition" value="filename=UserTemplate.doc" />
<cfcontent type="application/msword"><cfoutput>#rtf#</cfoutput>
There is a fourth (optional) parameter to the replace() method; scope.
Scope:
one: replaces the first occurrence (default)
all: replaces all occurrences
Notice that "one" is the default and that only replaces the first occurrence. Try adding that fourth parameter like this:
<cfset rtf = Replace(rtf,"%firstName%",firstName,"all") />
<cfset rtf = Replace(rtf,"%lastName%",lastName,"all") />
<cfset rtf = Replace(rtf,"%birthday%",birthday,"all") />
(The hash tags # are not necessary in this bit of code.)
Also be aware that the replace() method you are using is case sensitive.
I've been trying to add text watermark on images using imageDrawText tag in CF10.
here is some testing code
<cfset img = imageNew("",500,500,"rgb","blue")>
<cfset text = "This is just another test! See if text fits the imgage...">
<cfset buffered = ImageGetBufferedImage(img)>
<cfset context = buffered.getGraphics().getFontRenderContext()>
<cfset Font = createObject("java", "java.awt.Font")>
<cfset textFont = Font.init( "Arial", Font.BOLD, javacast("int", 40))>
<cfset textLayout = createObject("java", "java.awt.font.TextLayout").init( text, textFont, context)>
<cfset textBounds = textLayout.getBounds()>
<cfset textWidth = textBounds.getWidth()>
<cfset textHeight = textBounds.getHeight()>
<cfset attr = { font="Arial", size="40", style="bold" }>
<cfset x = (ImageGetWidth(img) / 2 - textWidth / 2)>
<cfset y = (ImageGetHeight(img) / 2 + textHeight / 2)>
<cfset imageSetDrawingColor(img,"black")>
<cfset imageDrawText(img,text, x, y, attr)>
<cfimage action="writeToBrowser" source="#img#">
the problem is that I do not know how to break lines and center the text...
on the left is what the code produces and on the right is what I would like to get
It is important to note that the font size and the number of characters will vary from one image to another and that's the main reason I do not know how to count it correctly.
My initial idea was to count characters and see how many will fit in image width though that is not possible due to the above reasons. So is there a one-liner for this or some sort of mathematical function is needed to manually split text width and break lines? also maybe I should use additional or x imageDrawText tags to display each line separately, though still need to split it somehow!
Wrapping text is definitely not a one-liner ;-) Unfortunately, you cannot simply count characters, because the sizes of the individual glyphs can vary, unless maybe you are using a mono-spaced font.
Essentially, the only way to accurately wrap text is to iterate through the string, measure the size of each word (in the current font) and see if it fits on the current line. If not, start a new line. There is a little more to it, but bottom line it involves two steps: 1) measure and split the text into lines 2) then draw the lines of text onto the image
There are different ways you could handle measuring and splitting the text into lines. My preference is using a LineBreakMeasurer because it does most of the hard work. You just give it the width of the wrapping area and it auto-magically calculates how much of the text can fit on the current line.
Many moons ago, I put together a small library for wrapping and scaling image text (old blog, not maintained). Give it a whirl. It is a little old, but I think it should do the trick.
UPDATE:
I just remembered the library does not center the text vertically, only horizontally. However, since it returns the dimensions of the wrapped text, you could easily calculate it. Here is a very quick and dirty example:
Code:
<cfset text = "If you're going through hell, keep going" />
<!--- overall image dimensions --->
<cfset imageWidth = 500 />
<cfset imageHeight = 500 />
<!--- desired wrapping area --->
<cfset textMargin = 25 />
<cfset wrapWidth = imageWidth - (textMargin*2) />
<cfset wrapHeight = 100 />
<!--- create a blank image with background --->
<cfset img = ImageNew("", imageWidth, imageHeight, "rgb") />
<cfset imageSetDrawingColor( img, "d9d9ff" ) />
<cfset imageDrawRect(img, 0, 0, imageWidth, imageHeight, true) />
<!--- measure dimensions of wrapped text --->
<cfset util = createObject("java", "org.cfsearching.image.WrapImageText") />
<cfset util.init( text, wrapWidth, wrapHeight ) />
<cfset util.setAlignment( util.CENTER_ALIGN ) />
<cfset util.setColor( "0000ff" ) />
<cfset util.setDrawText( false ) />
<cfset util.setX( textMargin ) />
<cfset util.setFont( "Arial", util.BOLD, 40) />
<!--- note: when disabled, text may overflow established wrap height
<cfset util.setAutoScale( false ) />
--->
<!--- use dimensions to center text VERTICALLY --->
<cfset dimen = util.wrapText( ImageGetBufferedImage(img) ) />
<cfset y = (imageHeight - dimen.height) / 2 />
<cfset util.setY( y ) />
<!--- draw the wrapped text --->
<cfset util.setDrawText( true ) />
<cfset dimen = util.wrapText( ImageGetBufferedImage(img) ) />
<!--- display results --->
<cfimage action="writeToBrowser" source="#img#" /> <br />
Result: (Using default font/auto scaling)
I'm in Coldfusion 8. I have a table that is produced by a loop. Very complex code but I put some of it here:
<cfloop array = #qrep.getColumnList()# index = "col">
<cfset l = l + 1>
<cfif l EQ tindx - 1>
<cfset prevcol= col>
</cfif>
<cfif linefold GT 0>
<cfset lmod = i%linefold>
<cfelse>
<cfset lmod = 1>
</cfif>
<!--- printing detail --->
<cfif l LE m AND repdetail NEQ 'n'>
<td class = "repsubthead"> Subtotal:
<b>#qrep[col][currentrow]#</b></td>
</cfif>
<!--- printing totals only; row labels --->
<cfif repdetail EQ 'n' AND l EQ tindx >
<cfset frowarr[footrow] = qrep[col][currentrow]>
<cfset footrow_1 = footrow - 1>
<cfif footrow EQ 1>
<td style = "font-size: 13px" > #qrep[col][currentrow]#</td>
<cfelseif frowarr[footrow] NEQ frowarr[footrow_1] >
<td style = "font-size: 13px;"> #qrep[col]currentrow]#</td>
<cfelse>
<cfset testrow = footrow>
<td class = "repsubthead" style = "padding-top: 10px"> Total #qrep[prevcol] currentrow]# </td>
</cfif>
.... lots more before we get to end of loop
This part of the code prints out a row label for each row. Further in the program there is a similar loop to print out the value for the row. Everything is working fine except for one problem I can't trace. An extra row is being inserted in one spot, with no data in it. Part of the table is here:
State: CT
AVS 25.00
COMB 15.00
Email2010 15.00
REF 75.00
STRLST01 22.00
extra row inserted here, height much smaller than other rows
STRLST04 50.00
Total CT 202.00
I have copied this table to a Libre Office document and zoomed in on the bad row. It is definitely there, and it contains a blinking item that looks like this: '
I cannot delete this item from the row in Libre Office, although I am able to delete the entire row. The blinking thing disappears when I put my cursor in another row.
I have checked both STRLST01 and STRLST04 in my MySQL database, and they seem fine, with no anomalies. I cannot find anywhere in my code where I would be inserting an extra row (altho admittedly the code is very complicated).
Has anyone seen something like this? Does anyone have a clue what might be causing this?
Just a shot in the dark here... but try sanitizing your table content. For example:
#htmlEditFormat(qrep[col][currentrow])#
This would be to rule out that the TR in "STRLST01" isn't getting processed as <TR>. I've seen dynamic tables like this one go haywire because the content gets interpreted as HTML.
MC
What is the generated HTML? Looking at the actual generated output rather than the rendered output could help you find if it's bad data being dumped into another row or if it's a bug in your HTML generation routine.
If it's an odd special character in your data, the HTMLEditFormat() or XMLFormat() functions should find it and deal with it. Or at least make it easier to troubleshoot.
I am creating a spreadsheet object using cfspreadsheet. Would like to make some of the individual cells as protected (read-only). Please let me know if anybody has tried this before.
I did try putting cell format as locked but it did not seems to work. Here is the sample code:
<cfset a = spreadsheetnew()>
<cfset format1 = structNew()>
<cfset format1.locked=true>
<cfset SpreadsheetFormatCell(a,format1,1,1)>
<cfspreadsheet action="write" filename="#expandpath('.')#/test.xls" name="a" overwrite="true">
Thanks.
Locking a cell does nothing unless the sheet is protected ie using cfspreadsheet's password attribute. But doing so has some negative side effects ...
Protecting the sheet locks all cells. That means you essentially have to "unlock" everything else by applying a format. In theory you could just unlock the entire sheet:
<cfset SpreadsheetFormatCellRange (sheet, {locked=false}, 1, 1, maxRow, maxCol)>
However, that has the nasty effect of populating every single cell in the sheet. So if you read the file into a query, the query would contain ~65,536 rows and 256 columns. Even if you only populated a few cells explicitly.
The lock feature is better suited to cases where you want everything to be locked except a few cells (not the reverse). Unless that is what you are doing, I probably would not bother with it, given all the negative side effects.
Side effect example
<cfset testFile = "c:/test.xls">
<cfset sheet = spreadsheetNew()>
<!--- only unlocking 100 rows to demonstrate --->
<cfset SpreadsheetFormatCellRange (sheet, {locked=false}, 1, 1, 100, 10)>
<!--- populate two cells --->
<cfset SpreadsheetSetCellValue(sheet,"LOCKED",1,1)>
<cfset SpreadsheetSetCellValue(sheet,"UNLOCKED",2,1)>
<!--- make one cell locked --->
<cfset SpreadsheetFormatCell(sheet, {locked=true}, 1, 1)>
<cfspreadsheet action="write"
name="sheet"
fileName="#testFile#"
password=""
overwrite="true" >
<!--- now see it is filled with empty cells --->
<cfspreadsheet action="read"
query="sheetData"
src="#testFile#" >
<cfdump var="#sheetData#" label="Lots of empty cells" />
I have a shopping cart array which holds the product information of items in the cart. Some products which are added will be at a special discount if purchased with the "main item".
If someone adds an item which has special offers associated to it, I set a key in the item structure called mainitem with a value of yes, all the subsequent special offer items associated to the main item have key mainitem set as no and have another key named mainitemid (this is the mainitem uid).
If some one deletes the mainitem I need to ensure any related special offer items are deleted as well. This is what I am having trouble with, can't quite work out how to find them all.
I am using form.itemID to supply the item id of the product being deleted. I need to make sure the item being deleted is a main item; if so, loop through the rest of the cart and find any item with mainitemid equal to form.itemid and remove them, then delete the mainitem.
mainitem is defined as session.mycart[i].mainitem
maintitenid is defined as session.mycart[i].mainitemid
<cfloop index="i" from="1" to="#arrayLen(session.mycart)#">
</cfloop>
Do I need to create two loops or could I do it with one? I'm not sure of my conditional statements.
Solution for OPs specific issue
Revised to offer a more complete solution
<!--- First of all, will need to go 1 to ArrayLen() to find the main item id. Doesn't matter what order you go here, but main items should be inserted before children so may as well start at the bottom --->
<cfloop index="i" from="1" to="#ArrayLen(session.mycart)#">
<!--- Is this the main item? --->
<cfif session.mycart[i].id EQ form.itemID>
<!--- It's found the item, is it a main item? We've found the item so entering here to break the loop --->
<cfif session.mycart[i].mainitem>
<!--- Go from ArrayLen() to 1, as if you go 1 to ArrayLen() when deleting you confuse Coldfusion --->
<cfloop index="j" from="#ArrayLen(session.mycart)#" to="1" step="-1">
<cfif session.mycart[j].id EQ form.itemID OR session.mycart[j].mainitemid EQ form.itemID>
<cfset ArrayDeleteAt(session.mycart,j)>
</cfif>
</cfloop>
</cfif>
<!--- This loop is done, so break out --->
<cfbreak>
</cfif>
</cfloop>
In your post, you state you are looping from index 1 to index ArrayLen(...). If you are deleting items from the array though, Coldfusion is a bit straightforward and doesn't really pay attention and so when you delete element 2 of a 5-element array, the array becomes 4 elements long (because you deleted one) and the element which was index 3 is now index 2, thus it is missed.
The way around this is to start at the end and work backwards. As long as you are deleting at most 1 record at a time, then this is perfectly valid as it will continue reducing the index it is currently checking until you get down to 1, which will be the first element.
This way you can go through element 5, 4, 3, 2, delete element 2, and it will then check index 1 which now will still be the same item as it was when you started the loop and thus no skipping is experienced.
Some blurb on how to deal with this
I misread the question, or it got edited while writing this, but the below is applicable so leaving it here still
Have you considered having special offer items as children to the main item, as then it encapsulates the entire offer together and deleting the parent deletes the child. I've a similar issue where items have options associated with them, and to allow hierarchy to be observed I decided on creating a tree combined with a reference array. Just as a rough example to show the principle, take a look at this
<cfscript>
// Create a main item structure
stcMainItem = {
id = CreateUUID(),
somekey = somevalue,
someotherkey = someothervalue,
cost = 123
};
// Create some child item structures, special offers for ex
stcSpecialOfferItem1 = {
id = CreateUUID(),
parent = stcMainItem.id,
cost = 45
};
stcSpecialOfferItem2 = {
id = CreateUUID(),
parent = stcMainItem.id,
cost = 45
};
// Each is added to a reference array
arrItemReference = [];
arrItemRefernce.add(stcMainItem);
arrItemRefernce.add(stcSpecialOfferItem1);
arrItemRefernce.add(stcSpecialOfferItem2);
// Now you decide to delete the main item and direct parents
strIDToDelete = stcMainItem.id;
for (i=ArrayLen(arrItemReference);i>=1;i--) {
if (
arrItemReference[i].id == strIDToDelete
||
arrItemReference[i].parent == strIDToDelete
) {
ArrayDeleteAt(arrItemReference,i);
}
}
</cfscript>
In my actual code I've done this by way of creating an Item.cfc with methods to deal with the above and to cascade up the tree deleting grandchildren etc, but the principle is sound. Essentially you have methods that allow the items to be exposed as both a flat array and as a hierarchy of parents, children, and siblings.
Informational point
As an aside, you keep interchanging "array" and "structure" when the two are slightly different unlike languages like PHP where array is used to refere to both terms. An array contains values with a numeric index from 1 up to n, while a structure is an object that holds values related to arbitary keys. The reason that there is a difference though is that they don't build off the same base java objects so some methods that work on one don't work on the other.
<cfloop index="i" from="1" to="#arrayLen(session.mycart)#">
<cfif session.mycart[i].mainitem eq "no" AND session.mycart[i].mainitemid EQ form.itemid>
<cfset arrayDeleteAt(session.myCart,i) />
<cfset i = i - 1 /><!--- this is necessary to keep you from running out of the end of the loop --->
</cfif>
</cfloop>
This isnt tested of course and you may have to work with it to put your exact variable names in, but I think it should get you there. Let me know if I have misunderstood the question.
I don't see a way to do this in one loop, as I view it as a two step process. First you have to see if the item id you deleted is a main item, then go back through the array and delete the rest of the associated items.
Add StructKeyExists() as necessary.
<!--- Find Main Item --->
<cfset isMainItem = false />
<cfloop from='1' to='#ArrayLen( Session.MyCart )#' index='item'>
<cfif Session.MyCart[ item ].ItemID EQ Form.ItemID AND Session.MyCart[ item ].MainItem EQ 'Yes'>
<cfset isMainItem = true />
<cfbreak />
</cfif>
</cfloop>
<cfif isMainItem>
<!--- Clean up special offer items --->
<cfloop from='1' to='#ArrayLen( Session.MyCart )#' index='item'>
<cfif Session.MyCart[ item ].MainItemID EQ Form.ItemID AND Session.MyCart[ item ].MainItem EQ 'No'>
<cfset ArrayDeleteAt( Sesion.MyCart, Item ) />
<cfset item-- />
</cfif>
</cfloop>
</cfif>