Writing a spreadsheet using CFspreadsheet to avoid error this file might be corrupted - coldfusion

I am trying to figure out how to add my html table to a CFspreadsheet to show in excel. All the examples online that I have found are not as crazy as mine (only a simple one basic query). Any help with this would be greatly appreciated. This is what I have been able to figure out so far for my spreadsheet:
<cfset objSpreadsheet = SpreadsheetNew()>
<cfset filename = expandPath("./myexcel.xls")>
<!--- Create and format the header row. --->
<cfset SpreadsheetAddRow( objSpreadsheet, "Associate Name,Location,Checklists Generated by Associate,Checklists Generated by Selected Location(s),Associate Percentage of Location Total" )>
<cfset SpreadsheetFormatRow( objSpreadsheet, {bold=true, alignment="center"}, 1 )>
<cfheader name="Content-Disposition" value="attachment; filename=#filename#">
<cfcontent type="application/vnd.ms-excel" variable="#SpreadsheetReadBinary( objSpreadsheet )#">
My table trying to convert:
<table class="table table-hover">
<thead>
<th><strong>Associate Name</strong></th>
<th><strong>Location</strong></th>
<th><strong>Checklists Generated by Associate</strong></th>
<th><strong>Checklists Generated by Selected Location(s)</strong></th>
<th><strong>Associate Percentage of Location Total</strong></th>
</thead>
<tbody>
<cfoutput query="GetEmployeeInfo">
<tr>
<td><cfif rnA EQ 1><strong>#assoc_name#</strong></cfif></td>
<td><cfif rnL EQ 1>#trans_location#</cfif></td>
<td>#checklistsByAssocLoc#</td>
<td>#assocChecklistsByLoc#</td>
<td>#DecimalFormat(totalChecklistsByAssocLocPct)# %</td>
<!---<td> rnA: #rnA# | rnL: #rnL# | rnTotAssoc: #rnTotAssoc# </td> --->
</tr>
<cfif rnTotAssoc EQ 1>
<tr>
<td>Associate Total</td>
<td></td>
<td>#totalChecklistsByAssoc#</td>
<td>#totalAssocChecklistsByAllFilteredLoc#</td>
<td>#DecimalFormat(totalChecklistsByLocPct)# %</td>
</tr>
</cfif>
</cfoutput>
</tbody>
</table>
My crazy queries!:
<cfquery datasource="#application.dsn#" name="GetEmployeeInfo">
SELECT s4.associate /* Associate's ID */
, s4.assoc_name /* Associate's Name */
, s4.trans_location /* Associate's Location */
, s4.checklistsByAssocLoc /* Gives you a count of Checklists by Associate for a specific Location. */
, s4.assocChecklistsByLoc /* Gives you a count of Total Checklists by All Associates in a Location. */
, s4.totalChecklistsByAssoc /** Gives you a count of Total Checklists by Specific Associate in All Locations. */
, s4.totalAssocChecklistsByAllFilteredLoc /* Gives you a count of Total Checklists by Specific Associates in All Locations. */
, CASE WHEN ( coalesce(s4.assocChecklistsByLoc,0) > 0 ) THEN (CAST(s4.checklistsByAssocLoc AS decimal(8,2))/s4.assocChecklistsByLoc) * 100 ELSE 0 END AS totalChecklistsByAssocLocPct /* This gives you a percent of associate location checklists over count of checklists by Associate in a Location. */
, CASE WHEN ( coalesce(s4.totalAssocChecklistsByAllFilteredLoc,0) > 0 ) THEN (CAST(s4.totalChecklistsByAssoc AS decimal(8,2))/s4.totalAssocChecklistsByAllFilteredLoc) * 100 ELSE 0 END AS totalChecklistsByLocPct /* This gives you a percent of Total Associate Checklists in All Locations over count of Checklists by All Associate in All Locations. */
, s4.rnA /* Placeholder for a record to display the Associate Name. */
, s4.rnL /* Placeholder for a record to display the Location. */
, s4.rnTotAssoc /* Placeholder for the last Associate Location row. The next row should be an Associate Total. */
FROM (
SELECT s3.*
, SUM(s3.assocChecklistsByLoc) OVER (PARTITION BY s3.associate) AS totalAssocChecklistsByAllFilteredLoc /* Gives you a count of Total Checklists by Specific Associates in All Locations. */
FROM (
SELECT s2.*
FROM (
SELECT a.assoc_name
, s1.associate
, s1.trans_location
, s1.checklistsByAssocLoc
, s1.assocChecklistsByLoc
, s1.totalChecklistsByAssoc
, ROW_NUMBER() OVER (PARTITION BY s1.associate ORDER BY s1.associate, s1.trans_location) AS rnA /* Placeholder for a record to display the Associate Name */
, ROW_NUMBER() OVER (PARTITION BY s1.associate, s1.trans_location ORDER BY s1.associate, s1.trans_location) AS rnL /* Placeholder for a record to display the Location */
, ROW_NUMBER() OVER (PARTITION BY s1.associate ORDER BY s1.trans_location DESC) AS rnTotAssoc /* Placeholder for the last Associate Location row. The next row should be an Associate Total. */
FROM (
SELECT c.associate
, c.trans_location
, COUNT(*) OVER (PARTITION BY c.associate, c.trans_location) AS checklistsByAssocLoc /* Gives you a count of Checklists by Associate for a specific Location. */
, COUNT(*) OVER (PARTITION BY c.associate) AS totalChecklistsByAssoc /* Gives you a count of Total Checklists by Associate in All Locations. */
, COUNT(*) OVER (PARTITION BY c.trans_location) AS assocChecklistsByLoc /* Gives you a count of Total Checklists by All Associates in a Location. */
FROM cl_checklists c
LEFT OUTER JOIN tco_associates a ON c.associate = a.assoc_id
AND a.assoc_id IN ( <cfqueryparam value="#FORM.EmployeeName#" cfsqltype="cf_sql_varchar" list="true" /> ) /* SELECTED ASSOCIATE IDs */
WHERE c.[DATE] >= <cfqueryparam value="#date1#" cfsqltype="cf_sql_timestamp" /> /* SELECTED DATES */
AND c.[DATE] <= <cfqueryparam value="#date2#" cfsqltype="cf_sql_timestamp" />
AND c.trans_location IN ( <cfqueryparam value="#locList#" cfsqltype="cf_sql_varchar" list="true" /> ) /* SELECTED LOCATIONS */
) s1
INNER JOIN tco_associates a ON s1.associate = a.assoc_id
AND a.assoc_id IN ( <cfqueryparam value="#FORM.EmployeeName#" cfsqltype="cf_sql_varchar" list="true" /> ) /* SELECTED ASSOCIATE IDs */
) s2
WHERE s2.rnA = 1 OR s2.rnL = 1 /* There will be a final Location (rnL=1 and rnTotAssoc=1). This is the final row. */
) s3
) s4
ORDER BY s4.assoc_name, s4.trans_location
</cfquery>
This is the path I was thinking but I truly dont understand calling the rows and columns. Do I even have the right idea or am I way off?
<cfoutput query="GetEmployeeInfo">
<cfif rnA EQ 1><cfset SpreadsheetSetCellValue( objSpreadsheet, #assoc_name#, 2, 1) ></cfif>
<cfif rnL EQ 1><cfset SpreadsheetSetCellValue( objSpreadsheet, #trans_location#, 2, 1) ></cfif>
<cfset SpreadsheetSetCellValue( objSpreadsheet, #checklistsByAssocLoc#, 2, 1) >
<cfset SpreadsheetSetCellValue( objSpreadsheet, #assocChecklistsByLoc#, 2, 1) >
<cfset SpreadsheetSetCellValue( objSpreadsheet, #DecimalFormat(totalChecklistsByAssocLocPct)# %, 2, 1) >
<cfif rnTotAssoc EQ 1>
<cfset SpreadsheetSetCellValue( objSpreadsheet, 'Associate Total', 2, 1) >
<cfset SpreadsheetSetCellValue( objSpreadsheet, '', 2, 1) >
<cfset SpreadsheetSetCellValue( objSpreadsheet, #totalChecklistsByAssoc#, 2, 1) >
<cfset SpreadsheetSetCellValue( objSpreadsheet, #totalAssocChecklistsByAllFilteredLoc#, 2, 1) >
<cfset SpreadsheetSetCellValue( objSpreadsheet, #DecimalFormat(totalChecklistsByLocPct)# %, 2, 1) >
</cfif>
</cfoutput>
Also tried:
<cfoutput query="GetEmployeeInfo">
<cfset SpreadsheetAddRow( objSpreadsheet, "<cfif rnA EQ 1>#assoc_name#</cfif>,<cfif rnL EQ 1>#trans_location#</cfif>,#checklistsByAssocLoc#,#assocChecklistsByLoc#,#DecimalFormat(totalChecklistsByAssocLocPct)# %" )>
<cfif rnTotAssoc EQ 1>
<cfset SpreadsheetAddRow( objSpreadsheet, "Associate Total,'',#totalChecklistsByAssoc#,#totalAssocChecklistsByAllFilteredLoc#,#DecimalFormat(totalChecklistsByLocPct)# %" )>
</cfif>
</cfoutput>

For your final snippet ColdFusion tags will not be evaluated in a string literal. For if else statements, you could use the ternary operator to toggle some of the output. Also, if any of your data contains comma/s it'll split the data between cells. To combat this, try wrapping each cell value in quotes. This may keep your text in one cell. Others still have had issues with this fix.
<cfset rowNumber = 0 />
<cfoutput query="GetEmployeeInfo">
<cfset rowNumber++ />
<cfset rowList = "'#(rnA eq 1)?assoc_name:''#', '#(rnl eq 1)?trans_location:''#', '#checklistsByAssocLoc#,#assocChecklistsByLoc#', '#DecimalFormat(totalChecklistsByAssocLocPct)# %'">
<cfset SpreadsheetAddRow( objSpreadsheet, rowList)>
<cfset spreadsheetFormatCell( objSpreadsheet, {bold: true}, rowNumber, 1 )>
<cfif rnTotAssoc EQ 1>
<cfset rowNumber++ />
<cfset rowList = "'Associate Total','','#totalChecklistsByAssoc#','#totalAssocChecklistsByAllFilteredLoc#','#DecimalFormat(totalChecklistsByLocPct)# %'" >
<cfset SpreadsheetAddRow( objSpreadsheet, rowList )>
</cfif>
</cfoutput>
Personally, due to slow rendering time of using many (few hundred+) spreadsheetAddRows to create a spread sheet, and working with lists of string data can be a pain, I almost always place my spreadsheet data into a query object. Once the data is in a query object, it takes one call to spreadsheetAddRows to get the data into the spreadsheet.
qReportData = queryNew("Name, Age",
"varchar, integer",
[{name: "Tom", age: 25},{name: "Dick", age: 40},{name: "Harry", age: 55}]
);
sheet = SpreadsheetNew(false);
//Add and format headers
bold = {bold: true};
spreadsheetAddRow(sheet, "Name, Age");
spreadsheetFormatRow(sheet, bold, 1);
spreadsheetAddRows(sheet, qReportData);
Given that some of your report data can be on multiple rows, under specific situations, you won’t be able to just export your report query, so we'll have to build a new one with code. We'll iterate over the report and generate rows in our spreadsheet query. In my example, I'll add an extra row any time the person is over the age of 40.
qSheetOutput = queryNew("Name, Age");
for(row in qReportData){
queryAddRow(qSheetOutput, {
name: row.name,
age: row.age
});
if(row.age > 40){
queryAddRow(qSheetOutput, {
name: row.name & " is over 40"
});
}
}
// now writing the generated query to the spreadsheet
spreadsheetAddRows(sheet, qSheetOutput);
The last step will be to iterate and format the cells of the spreadsheet. As I iterate over the output I have to offset the row I'm working with by the count of headers in the sheet, witch in this example is 1. Also for this example, the extra row for a person over the age of 40 will not be bolded, and will span 2 cells.
for(row in qSheetOutput){
if(!len(row.age)){
spreadsheetFormatCell(sheet, {dataformat="#", alignment="center"}, qSheetOutput.currentRow + 1, 1);
spreadsheetFormatCell(sheet, qSheetOutput.currentRow + 1, qSheetOutput.currentRow + 1, 1, 2);
}
else{
spreadsheetFormatCell( sheet, bold, qSheetOutput.currentRow + 1, 1 );
}
}
If looking at the output is too difficult to determine the correct format/s needed for the row, you could iterate over an array/s of row numbers that require a specific format/s. Once again notice I'm using the header count as an offset again.
dataRows = [];
messageRows = [];
for(row in qReportData){
queryAddRow(qSheetOutput, {
name: row.name,
age: row.age
});
arrayAppend(dataRows, qSheetOutput.recordCount + 1);
if(row.age > 40){
queryAddRow(qSheetOutput, {
name: row.name & " is over 40"
});
arrayAppend(messageRows, qSheetOutput.recordCount + 1);
}
}
...
for(rowNumber in dataRows){
spreadsheetFormatCell( sheet, bold, rowNumber, 1 );
}
for(rowNumber in messageRows){
spreadsheetFormatCell(sheet, {dataformat="#", alignment="center"}, rowNumber, 1);
spreadsheetFormatCell(sheet, rowNumber, rowNumber, 1, 2);
}
Here is the complete working code on TryCF.com

In honour of your perseverance, here is one that I did a couple of days ago. The visitData, headers, columns and title variables were set earlier in the program because they also applied to html output.
<cfscript>
filePath = "d:\dw\dwweb\work\";
fileName = title & " " & getTickCount() & ".xlsx";
sheet = spreadSheetNew("data", true);
HeaderFormat = {};
HeaderFormat.bold = true;
spreadSheetAddRow(sheet, headers);
SpreadSheetFormatRow(sheet, HeaderFormat, 1);
SpreadSheetAddFreezePane(sheet, 0,1);
for (queryRow = 1; queryRow <= visitData.recordcount; queryRow ++) {
rowNumber = queryRow + 1;
for (columnNumber = 1; columnNumber <= listLen(columns); columnNumber ++) {
thisColumn = listGetAt(columns, columnNumber);
thisValue = visitData[thisColumn][queryrow];
SpreadSheetSetCellValue(sheet, thisValue, rowNumber, columnNumber);
} // columns
} // rows
SpreadSheetWrite(sheet,filePath & fileName, true);
</cfscript>
<cfheader name="content-disposition" value="Attachment;filename=#fileName#">
<cfcontent file="#filePath & fileName#" type="application/vnd.ms-excel">
Note the variables that I use in the last two tags. The <cfheader> tag has only the name of the file, but not the path. A mistake I was making earlier was to use just one variable which had both. The result was undesireable filenames being sent to the user.

Related

Breaking a Value List to show 5 records in one Line and then move to Next line and so on

I have the following code where i am tryin to use mod operator to list 5 items on the line and then move to next line, On the single line it should display 5 items and then in next line, it should display the remaining items, if the remaining items are more than 5, it should go to 3rd line then
i am trying this code: but it not doing anything
<cfset items = "1,2,3,4,5,6,7,8,9,0">
<cfif listLen(items) mod 5>
<cfoutput>
#items##Chr(10)##chr(13)#TEST
</cfoutput>
</cfif>
it is displaying all in one line
There are a couple of things wrong with your code.
You are not looping over the list to display each item.
Chr(10) and Chr(13) (linefeed and carriage return) do not display in HTML and your browser.
I modified your code like this:
<cfset counter = 0>
<cfset items = "1,2,3,4,5,6,7,8,9,0,a,b,c">
<cfloop index="thisItem" list="#items#">
<cfset counter = counter + 1>
<cfif counter mod 5>
<cfoutput>#thisItem#, </cfoutput>
<cfelse>
<cfoutput>#thisItem#<br></cfoutput>
</cfif>
</cfloop>
Try it here
and here is an example of that same logic using cfscript syntax:
<cfscript>
counter = 0;
items = "1,2,3,4,5,6,7,8,9,0,a,b,c";
for (counter = 1; counter lte listlen(items); counter++) {
if (counter mod 5) {
writeOutput('#listGetAt(items,counter)#, ');
} else {
writeOutput('#listGetAt(items,counter)#<br>');
}
}
</cfscript>
Try it here
The code I have given you here can be cleaned up a bit but hopefully it is easy to understand for you.
Here is another approach if you are on CF10+:
<cfscript>
// Items List
items_list = "1,2,3,4,5,6,7,8,9,0,a,b,c";
// Convert to array
items_array = items_list.listToArray( "," );
// Item Count
itemCount = arrayLen( items_array );
// Display
for ( i = 1; i <= itemCount; i += 5) {
writeOutput( items_array.slice( i, i + 5 - 1 > itemCount ? itemCount % 5 : 5 ).toList( "," ) & "<br>" );
}
</cfscript>
Here is the TryCF.
Here is another approach.
<cfset items = "1,2,3,4,5,6,7,8,9,0,a,b,c">
<cfoutput>
<cfloop from="1" to="#listLen(items)#" index="i">
#listGetAt(items,i)#
<cfif i mod 5 eq 0>
<br>
<cfelseif i neq listLen(items)>
,
</cfif>
</cfloop>
</cfoutput>
Results in
1 , 2 , 3 , 4 , 5
6 , 7 , 8 , 9 , 0
a , b , c

How to output the values of a query depending on the value of row?

If I have a query result like this
type_name | count
-----------------
test1 | 5
test2 | 4
test4 | 7
How to output the count base on the value of the column type_name?
From the table the row value 'test3' doesn't exist, but it doesn't mean it wont exist later after a refresh.
With the below code I will only get the value but looping 3 times since test3 value doesn't exist.
<cfoutput name="query">
<table>
<tr><td><cfif query.type_name eq 'test1'>#query.count#</cfif></td></tr>
<tr><td><cfif query.type_name eq 'test2'>#query.count#</cfif></td></tr>
<tr><td><cfif query.type_name eq 'test3'>#query.count#</cfif></td></tr>
<tr><td><cfif query.type_name eq 'test4'>#query.count#</cfif></td></tr>
</cfoutput>
<cfset testResults = [0,0,0,0]>
<cfloop query="query">
<cfset testResults[right(query.type_name, 1)] = query.count>
</cfloop>
<table>
<cfloop array="#testResults#" index="count">
<td>#count#</td>
</cfloop>
</table>
You should see
5
4
0
7
If you need a more dynamic approach, you can transform your full query result (all columns) to an array of structs:
<!---
<!--- test data --->
<cfset query = queryNew("type_name,count", "VARCHAR,INTEGER")>
<cfset queryAddRow(query, 3)>
<cfset querySetCell(query, "type_name", "test1", 1)>
<cfset querySetCell(query, "count", "5", 1)>
<cfset querySetCell(query, "type_name", "test2", 2)>
<cfset querySetCell(query, "count", "4", 2)>
<cfset querySetCell(query, "type_name", "test4", 3)>
<cfset querySetCell(query, "count", "7", 3)>
--->
<!--- number of tests to list --->
<cfset expectedNumberOfRows = 4>
<!--- remember all columns while transforming query to array of structs --->
<cfset queryColumns = listToArray(query.columnList)>
<!--- initialize array of structs --->
<cfset testResults = arrayNew(1)>
<cfloop from="1" to="#expectedNumberOfRows#" index="i">
<cfset blankResult = structNew()>
<!--- initialize all columns for each row --->
<cfloop array="#queryColumns#" index="columnName">
<cfset blankResult[columnName] = "">
</cfloop>
<!--- default values for specific columns --->
<cfset blankResult["type_name"] = "test#i#">
<cfset blankResult["count"] = "0">
<cfset testResults.add(blankResult)>
</cfloop>
<!--- transfer cell values from each query row to array of structs --->
<cfset queryRowIndex = 1>
<cfloop query="query">
<!--- extract possible row index --->
<cfset testNumber = trim( reReplace(query.type_name, "[^0-9]*([0-9]+)$", "\1") )>
<!--- transfer cells --->
<cfif reFind("^[0-9]+$", testNumber)>
<cfloop array="#queryColumns#" index="columnName">
<cfset testResults[int(testNumber)][columnName] = query[columnName][queryRowIndex]>
</cfloop>
</cfif>
<cfset queryRowIndex++>
</cfloop>
<cfoutput>
<table>
<cfloop array="#testResults#" index="testResult">
<tr>
<td>#testResult.type_name#</td>
<td>#testResult.count#</td>
<!--- add additional columns if desired --->
</tr>
</cfloop>
</table>
</cfoutput>

How Can I Output a Remark Column with rowspan Without Duplicates for same group in Coldfusion (Re formated)

I am writing a Software for a company. Please friends, I want you to help me. This company is a paint company that has different Product Type and Paint Type and colors. A way bill will be generated showing the item no, the description, the quantity, and the remark. I am sorting by the Product Type and the Paint Type. I am also grouping the output.
Here, I want the remark to rowspan all the paint with the same Product Type and Paint Type. For instance, if the Product Type is Honey, and the Paint Type is Texture. If there are 5 Honey Texture with a quantity of 20, The remark should rowspan all the 5 honey texture and write "Total of 20 quantity for Honey Texture". I tried looping but the values for quantity, description and item are repeated.
I don't want ITEM, QUANTITY, DESCRIPTION to be repeated. Only the Remark Column should rowspan the rows with the same product type and paint type. The Values in the ITEM, DESCRIPTION and QUANTITY should not be looped. See Picture for more explanation. I will be very grateful with any help.
Please, don't mind my html
<cfquery datasource="ysr" name="getlistofbuyings">
SELECT p.*, pt.paintcode, pt.*, pp.producttypename, pts.paintype, l.*
FROM purchase p, paint pt, producttype pp, painttype pts, litre l
WHERE p.transactionid = #transactionid#
AND p.paintid = pt.paintid
AND pt.producttypeid = pp.producttypeid
AND pt.painttypeid = pts.painttypeid
AND pt.litreid = l.litreid
ORDER BY pp.producttypename, pts.paintype ASC
</cfquery>
<table id="items" bgcolor="">
<a name="afteradding">
<tr bgcolor="#ccccee">
<th>ITEM</th>
<th>QUANTITY</th>
<th class="blank" colspan="3">DESCRIPTION</th>
<th>REMARKS</th>
</tr>
</a>
<cfoutput group="producttypename" query="getlistofbuyings">
<cfoutput group="paintype">
<cfoutput group="litrename">
<cfquery datasource="ysr" name="numvb">
SELECT p.*, COUNT(purchaseid) as pan, SUM(quantity) as quani, pa.*, py.*, pp.producttypename, l.litrename
FROM purchase p, paint pa, painttype py, producttype pp, litre l
WHERE p.transactionid = #transactionid#
AND p.paintid = pa.paintid
AND pa.painttypeid = py.painttypeid
AND pa.litreid = l.litreid
AND pa.producttypeid = #producttypeid#
AND py.painttypeid = #painttypeid#
AND pa.producttypeid = pp.producttypeid
AND l.litreid = #litreid#
</cfquery>
<cfquery datasource="ysr" name="kilop">
SELECT COUNT(purchaseid) as newpur
FROM purchase
WHERE transactionid = #transactionid#
</cfquery>
<cfset rowss = #numvb.pan#>
<cfset tot = #numvb.quani#>
<cfset arrOfUsers = ArrayNew(1)>
<cfoutput>
<cfset ArrayAppend(arrOfUsers,'<td>'&getlistofbuyings.quantity&getlistofbuyings.producttypeid&getlistofbuyings.painttypeid&'</td>')>
</cfoutput>
<cfloop from="1" to="#ArrayLen(arrOfUsers)#" index="i">
<tr class="item-row">
<cfset curm = #getlistofbuyings.currentrow#>
<th><cfoutput>#curm#</cfoutput></th>
<th>#getlistofbuyings.quantity#</th>
<th colspan="3" class="description"><span> #producttypename# #paintype# #paintcolor# #paintcode#</span></th>
<cfif i EQ 1>
<th nowrap="nowrap" rowspan="#ArrayLen(arrOfUsers)#" >#tot# bags of #producttypename# #paintype# with #litrename#</th>
</cfif>
</tr>
</cfloop>
</cfoutput>
</cfoutput>
</cfoutput>][1]
Please try the following:
<!--- pseudo query --->
<cfscript>
report = queryNew("productTypeName,paintType,paintColor,paintCode,quantity,litreName");
queryAddRow(report, [["Honey","Texture","Brilliant White",1700,3,"20 litres"]]);
queryAddRow(report, [["Honey","Texture","Off White",1701,8,"20 litres"]]);
queryAddRow(report, [["Magic","Texture","Off White",1701,21,"20 litres"]]);
queryAddRow(report, [["Magic","Texture","Brilliant White",1700,8,"20 litres"]]);
queryAddRow(report, [["Princess","Gloss","Brilliant White",9102,9,"4 litres"]]);
queryAddRow(report, [["Princess","Texture","Rose Pink",1712,3,"20 litres"]]);
queryAddRow(report, [["Princess","Texture","Ivory",1704,1,"20 litres"]]);
queryAddRow(report, [["Princess","Texture","Off White",1701,3,"20 litres"]]);
queryAddRow(report, [["Princess","Texture","Off White",1701,3,"20 litres"]]);
</cfscript>
<!--- add groupRowspan and groupTotalQuantity columns --->
<cfscript>
queryAddColumn(report, "groupRowspan", "integer", []);
queryAddColumn(report, "groupTotalQuantity", "integer", []);
if(report.RecordCount) {
lastQueryRowToUpdate = 0;
lastProductType = lastPaintType = lastLitreName = "";
groupRowspan = 0;
groupTotalQuantity = 0;
for(rowNum=1; rowNum<=report.RecordCount; rowNum++) {
if((report.productTypeName[rowNum] is not lastProductType) or (report.paintType[rowNum] is not lastPaintType) or (report.litreName[rowNum] is not lastLitreName)) {
if(lastQueryRowToUpdate) {
querySetCell(report, "groupRowspan", groupRowspan, lastQueryRowToUpdate);
querySetCell(report, "groupTotalQuantity", groupTotalQuantity, lastQueryRowToUpdate);
}
lastQueryRowToUpdate = rowNum;
lastProductType = report.productTypeName[rowNum];
lastPaintType = report.paintType[rowNum];
lastLitreName = report.litreName[rowNum];
groupRowspan = 0;
groupTotalQuantity = 0;
}
groupRowspan++;
if(isValid("integer", report.quantity[rowNum])) {
groupTotalQuantity += report.quantity[rowNum];
}
if((rowNum is report.RecordCount) and lastQueryRowToUpdate) {
querySetCell(report, "groupRowspan", groupRowspan, lastQueryRowToUpdate);
querySetCell(report, "groupTotalQuantity", groupTotalQuantity, lastQueryRowToUpdate);
}
}
}
</cfscript>
<!--- table w/ rowspan --->
<table>
<tr style="background-color:#cce;">
<th>ITEM</th>
<th>QUANTITY</th>
<th>DESCRIPTION</th>
<th>REMARKS</th>
</tr>
<cfloop query="report">
<tr style="background-color:#ccc;">
<cfoutput>
<td>#report.CurrentRow#</td>
<td>#report.quantity#</td>
<td>#report.productTypeName# #report.paintType# #report.paintColor# #report.paintCode#</td>
<cfif isValid("integer", report.groupRowspan)>
<td rowspan="#report.groupRowspan#">#report.groupTotalQuantity# bags of #report.productTypeName# #report.paintType# with #report.litreName#</td>
</cfif>
</cfoutput>
</tr>
</cfloop>
</table>
Thanks!,
-Aaron
This looks like dead code
<cfset ArrayAppend(arrOfUsers ...
The <cfloop> and <tr> are not balanced
<tr class="item-row">
<cfset curm = #getlistofbuyings.currentrow#>
<cfloop from="1" to="#ArrayLen(arrOfUsers)#" index="i">
...
</tr>
</cfloop>
It would be useful to use http://validator.w3.org

shorten numbering e.g., 1,2,5,6,7 becomes 1,2,5-7

I have been tasked with shortening a sequence of numbers by replacing internal sequences of 3 or more with a dash for the internal numbers (as in, 3,5,6,7,8,10 would be 3,5-8,10)
Pretty similar to the above, just using lists.
<cfscript>
a = listToArray('3,5,6,7,8,10,11,15,16,17,18');
writeOutput( fNumSeries(a) );
string function fNumSeries( array a) output="false" {
var tempList = '';
var outputList = '';
for ( var i in a ) {
if( listLen(tempList) == 0 ) {
// Nothing in the list
tempList = i;
} else if ( listLen(tempList) ) {
if ( listLast(tempList)+1 == i) {
// i is next in order from the previous list item. Just add to temp list.
tempList = listAppend(tempList, i);
} else if (listLen(tempList) >= 3) {
// current item is not the next item, and the tempList is more than 2 items long, so abbreviate and set templist to the current item.
outputList = listAppend(outputList, listFirst(tempList) & '-' & listLast(tempList));
tempList = i;
} else {
// tempList is less than 3 items long, just append to output and set tempList to the current item.
outputList = listAppend(outputList, tempList);
tempList = i;
}
}
}
// Clean up the remaining item(s) in tempList. If 3 or more, abbreviate, else just append to output.
outputList = listLen(tempList) >= 3 ? listAppend(outputList, listFirst(tempList) & '-' & listLast(tempList)) : listAppend(outputList, templist);
return outputList;
}
</cfscript>
I put the following function together, plus some demo output. It assumes the numbers are in an ordered, non-repeating array. (CF list functions help get to this point.) Is there a more elegant way of doing this?
<cffunction name="fNumSeries" output="1" hint="parses array of numbers for display: 1|2|3|6|7|9|10|11|12 would be 1-3, 6, 7, 9-12 ">
<cfargument name="raNums" required="1" type="array">
<cfset raLen=arraylen(raNums)>
<cfif raLen eq 0><CFRETURN "">
<cfelseif raLen eq 1><CFRETURN raNums[1]>
<cfelseif raLen eq 2><CFRETURN raNums[1] & "," & raNums[2]>
</cfif>
<cfset numSeries="">
<cfset numHold=raNums[1]>
<cfset seqHold=raNums[1]>
<cfset numSeries=raNums[1]><!--- raNums[1]always output the first number --->
<cfloop from="1" to="#raLen#" index="idxItem">
<cfif idxItem gt 1><!--- wait for 2nd element to decide output --->
<cfif numHold+1 neq raNums[idxItem]><!--- reason to output and seqHold+1 neq numHold--->
<cfif seqHold eq numHold>
<cfset numSeries=numSeries & ",#raNums[idxItem]#">
<cfelse>
<cfif seqHold+1 eq numHold><cfset numSeries=numSeries & ","><!--- and numHold+1 neq raNums[1] and seqHold+1 eq numHold --->
<cfelse><cfset numSeries=numSeries & "-">
</cfif>
<cfset numSeries=numSeries & "#numHold#,#raNums[idxItem]#">
</cfif>
<cfset seqHold=raNums[idxItem]>
</cfif>
</cfif>
<cfset numHold=raNums[idxItem]>
</cfloop>
<cfif seqHold neq numHold><!--- display the last element --->
<cfif seqHold+1 eq numHold or raLen eq 2><cfset numSeries=numSeries & ",">
<cfelse><cfset numSeries=numSeries & "-">
</cfif>
<cfset numSeries=numSeries & "#numHold#">
</cfif>
<CFRETURN numSeries>
</cffunction>
<cfset raRa=[
[2,12],
[2,3,12],
[2,4,12],
[2,3,4,12],
[2,8,9,11,12],
[2,9,11,12],
[2,3,8,10,11,12]
]>
<cfoutput>
<cfloop array="#raRa#" index="ra">
#arrayToList(ra)#<br />
#fNumSeries(ra)#<hr>
</cfloop>
</cfoutput>

CFML - query row to structure

I would like to handle a row from a query by a function, where I pass the row as a structure.
ideally...
<cfloop query="myquery">
#myfunction(#row#)#
</cfloop>
I could set it up like this too...
<cfloop query="myquery">
#myfunction(#col1#,#col2#,#col3#,#col4#)#
</cfloop>
but I don't want to. I haven't been able to finda simple way of extracting a row, But I thought I'd ask.
found a little more elegant looking solution, for single row
<cfscript>
function GetQueryRow(query, rowNumber) {
var i = 0;
var rowData = StructNew();
var cols = ListToArray(query.columnList);
for (i = 1; i lte ArrayLen(cols); i = i + 1) {
rowData[cols[i]] = query[cols[i]][rowNumber];
}
return rowData;
}
</cfscript>
Adobe ColdFusion 11 introduced QueryGetRow which converts a row from a query into a struct.
Ben Nadel has posted a blog post about this where he gives an example UDF that converts a query to a struct, and it accepts an optional row argument that allows you to turn an single row in that query to a struct. Take a look here.
This is the Class from Ben's site without the comments
<--- --------------------------------------------------------------------------------------- ----
Blog Entry:
Ask Ben: Converting A Query To A Struct
Author:
Ben Nadel / Kinky Solutions
Link:
http://www.bennadel.com/index.cfm?event=blog.view&id=149
Date Posted:
Jul 19, 2006 at 7:32 AM
---- --------------------------------------------------------------------------------------- --->
<cffunction name="QueryToStruct" access="public" returntype="any" output="false"
hint="Converts an entire query or the given record to a struct. This might return a structure (single record) or an array of structures.">
<cfargument name="Query" type="query" required="true" />
<cfargument name="Row" type="numeric" required="false" default="0" />
<cfscript>
var LOCAL = StructNew();
if (ARGUMENTS.Row){
LOCAL.FromIndex = ARGUMENTS.Row;
LOCAL.ToIndex = ARGUMENTS.Row;
} else {
LOCAL.FromIndex = 1;
LOCAL.ToIndex = ARGUMENTS.Query.RecordCount;
}
LOCAL.Columns = ListToArray( ARGUMENTS.Query.ColumnList );
LOCAL.ColumnCount = ArrayLen( LOCAL.Columns );
LOCAL.DataArray = ArrayNew( 1 );
for (LOCAL.RowIndex = LOCAL.FromIndex ; LOCAL.RowIndex LTE LOCAL.ToIndex ; LOCAL.RowIndex = (LOCAL.RowIndex + 1)){
ArrayAppend( LOCAL.DataArray, StructNew() );
LOCAL.DataArrayIndex = ArrayLen( LOCAL.DataArray );
for (LOCAL.ColumnIndex = 1 ; LOCAL.ColumnIndex LTE LOCAL.ColumnCount ; LOCAL.ColumnIndex = (LOCAL.ColumnIndex + 1)){
LOCAL.ColumnName = LOCAL.Columns[ LOCAL.ColumnIndex ];
LOCAL.DataArray[ LOCAL.DataArrayIndex ][ LOCAL.ColumnName ] = ARGUMENTS.Query[ LOCAL.ColumnName ][ LOCAL.RowIndex ];
}
}
if (ARGUMENTS.Row){
return( LOCAL.DataArray[ 1 ] );
} else {
return( LOCAL.DataArray );
}
</cfscript>
</cffunction>
usage...
<!--- Convert the entire query to an array of structures. --->
<cfset arrGirls = QueryToStruct( qGirls ) />
<!--- Convert the second record to a structure. --->
<cfset objGirl = QueryToStruct( qGirls, 2 ) />
Another solution would be:
function QueryToStruct(query){
var cols = ListToArray(query.columnList);
var salida = query.map(function(v=0,i,a){
return {'#cols[1]#':v};
});
return ValueArray(salida,'#cols[1]#');
}