Use of Valuelist in cfscript is not working - coldfusion

I'm using the iCalUS function from cflib.org which uses cfscript to generate iCal for a calendar. It is possible that an activity has more then one location. The locations are coming from a different table, so I want to use a Valuelist to get a comma separated list.
My code:
<cfquery datasource="#application.dsn#" name="getEvents">
select project.project_id,omschrijving, project.naam, project.start,project.eind,room, meeting_url
from project, lp_booking, lp_locaties
where export = <cfqueryparam value="1" cfsqltype="cf_sql_bit">
and keuren is null
and project.datum >= <cfqueryparam value="#createODBCDate(Now())#" cfsqltype="cf_sql_date">
and lp_locaties.room_id = lp_booking.room_id
and lp_booking.project_id = project.project_id
order by project.datum
</cfquery>
<cfscript>
eventStr = StructNew();
function iCalUS(stEvent) {
var vCal = "";
var CRLF=chr(13)&chr(10);
var date_now = Now();
vCal = vCal & writeoutput('BEGIN:VCALENDAR'& CRLF);
vCal = vCal & writeoutput('VERSION:2.0'& CRLF);
vCal = vCal & writeoutput('PRODID:Name'& CRLF);
vCal = vCal & writeoutput('X-WR-CALNAME:SOMENAME' CRLF);
vCal = vCal & writeoutput('CALSCALE:GREGORIAN'& CRLF);
vCal = vCal & writeoutput('METHOD:PUBLISH'& CRLF);
for(i=1; i LTE getEvents.RecordCount; i=i+1){
vCal = vCal & writeoutput('BEGIN:VEVENT'& CRLF);
vCal = vCal & writeoutput('SUMMARY:'&getEvents.naam[i]& CRLF);
vCal = vCal & writeoutput('UID:'&getEvents.project_id[i]& CRLF);
vCal = vCal & writeoutput('SEQUENCE:0'& CRLF);
vCal = vCal & writeoutput('STATUS:CONFIRMED'& CRLF);
vCal = vCal & writeoutput('TRANSP:OPAQUE'& CRLF);
vCal = vCal & writeoutput('DTSTART;TZID=Europe/Amsterdam:'&reReplace(getEvents.start[i],"[-:]","","all")& CRLF);
vCal = vCal & writeoutput('DTEND;TZID=Europe/Amsterdam:'&reReplace(getEvents.eind[i],"[-:]","","all")& CRLF);
vCal = vCal & writeoutput('DTSTAMP:'&reReplace(getEvents.start[i],"[-:]","","all")& CRLF);
vCal = vCal & writeoutput('LOCATION:'&getEvents.room[i]& CRLF);
vCal = vCal & writeoutput('DESCRIPTION:'&getEvents.omschrijving[i]&'\n\n\n'&getEvents.meeting_url[i]& CRLF);
vCal = vCal & writeoutput('URL:'&application.webUrl& CRLF);
vCal = vCal & writeoutput('END:VEVENT'& CRLF);
}
vCal = vCal & writeoutput('END:VCALENDAR'& CRLF);
return Trim(vCal);
}
</cfscript>
<cfcontent type="text/calendar" reset="Yes">
<cfheader name="Content-Disposition" value="inline; filename=churchbookAgenda.ics"> <cfoutput>#iCalUS(eventStr)#</cfoutput>`
Now I want to use:
vCal = vCal & writeoutput('LOCATION:'&valuelist(getEvents.room[i])& CRLF);
When I do I get an error: variable [SomeRoomName] doesn't exist. When I remove the [i] it works, but I get all the locations found in the query.
vCal = vCal & writeoutput('LOCATION:'&valuelist(getEvents.room)& CRLF);

Too long for a comment.
This would be easier if you used tags instead of script. That would allow you to do this:
<cfoutput query = "getEvents" group = "a suitable field from your query">
code for stuff you only want to appear once
<cfoutput>
generate your list of rooms here
</cfoutput>
</cfoutput>
I don't know if there is a simple script equivalent of this.
Edit Begins Here
If you want to use script and valuelist, you can use queryExecute with dbtype="query" inside your loop. You can find a very simple example here. All you have to do is apply that general idea to your own situation.

You are referencing the room variable incorrectly. The cfdocs say (see valueList() at cfdocs.org): The cfml function valueList() returns "A delimited list of the values of each record returned from an executed query column" as a STRING. But you are trying to reference it with room[i] as being an array at index position i.
Take a look at the following query example and see the code comments in between the lines:
<cfscript>
// create and populate a query
myquery = queryNew("id,room","integer,varchar", [
{"id":1,"room":"Room Oscar"},
{"id":2,"room":"Room John"},
{"id":3,"room":"Room Pablo"},
{"id":4,"room":"Room Juliet"},
{"id":5,"room":"Room Victor"}
]);
// get a list of all values of the room column
roomList=valueList(myquery.room);
/* because the list is a string, a single room can't be referenced
* with an index position, example roomList[i]. To make this work,
* you'd need to convert the list to an array first */
roomArray=listToArray(roomList);
writedump(roomArray);
writedump(roomList);
//reference with index
i=4;
writeOutput( roomArray[i] );
</cfscript>
Instead, it would make more sense to use the function valueArray() then, like so:
vCal = vCal & writeoutput('LOCATION:' & valueArray(getEvents.room)[i] & CRLF);
Unfortunately, I still can't follow what you are exactly trying to achieve. You've said you want to use vCal = vCal & writeoutput('LOCATION:'&valuelist(getEvents.room[i])& CRLF);, without specifying exactly where you want to use that line and what output you'd like to have. Also, it would have been better if you would have provided a query value dump as an data example. From what I understand, you just want to use an additional line in the same for-loop - but I'm not sure - then it would make more sense to just not use the valueList() function, like so:
vCal = vCal & writeoutput('LOCATION:'& getEvents.room[i] & CRLF);

As the others stated, you need to provide a dump so we can see the data struct.
A LOOK INTO MY CRYSTAL BALL
You haven't said this directly, but my wild guess is that your query returns multiple rows per event, if the event has multiple locations.
I then assume that you're trying to produce one iCal entry per event ... and you were hoping that the valuelist function was somehow going to grab the room column from all rows in the query for that event.
However, your query isn't using a group by. If an event had 3 locations, you're going to end up with 3 iCal entries for that event -- one for each location.
SUGGESTIONS
Combine the Locations in the DB Query
Not sure what DBMS you're using ... but maybe you can deal with this in your query by using GROUP BY to get one query row per event and some function to concatenate the location values. Looks like MySQL has one called GROUP_CONCAT https://www.geeksforgeeks.org/mysql-group_concat-function/.
CFOUTPUT Group
Instead of doing this in CFSCRIPT, do this as a tried and true CFOUTPUT with the group attribute.
https://cfdocs.org/cfoutput
The last example on this page is pretty spot on. Of course, you'd likely need to define the function using the cffunction tag and do the output within a CFSAVECONTENT or something.
Come up with some reworked approach using either queryfilter or queries of queries
Not much to say here other than the title. There are all sorts of query manipulation functions available, depending on what version of ACF/LUCEE you're on.
Maybe use QoQ to pull a distinct list of events, and use that query as the basis of your for loop. Then when you need the locations, maybe a queryfilter against the original query to pull the locations that match that event.
The examples on the https://cfdocs.org/queryfilter page show this quite clearly.

Related

How to show full number in Excel sheet?

I'm exporting a query to an Excel file using cfspeadsheet. It's working and creating the Excel sheet. However, the problem is that one of the columns, ie card_number, contains a 15 digit number, which is displayed like this: 4.5421E+15. Is there a way I can display the full number instead: 4254218068670980 ?
<!--- create manual query for demo --->
<cfset qData = queryNew("")>
<cfset queryAddColumn(qData, "NumericCol", "BigInt",["4254218068670980"])>
<cfset queryAddColumn(qData, "StringCol", "Varchar",["4254218068670980"])>
<cfset queryAddColumn(qData, "DecimalCol", "Decimal",["4254218068670980"])>
<!--- export to file --->
<cfspreadsheet action="write"
filename="c:/path/to/myFile.xls"
query="qData"
overwrite="true">
You need to define and use a format for the cell to show complete number. Below is a sample code snippet for your code:
<cfscript>
theFile=GetDirectoryFromPath(GetCurrentTemplatePath()) & "new_data.xls";
//Create a new Excel spreadsheet object.
theSheet = SpreadsheetNew("Expenses");
//Set the value a cell.
SpreadsheetSetCellValue(theSheet,"4254218068670980",1,4);
//Set value into another cell.
SpreadsheetSetCellValue(theSheet,"4254218068670980",2,4);
// Define a format class for for number.
longNum=StructNew();
longNum.dataformat = "0";
//Now use this class to format cell
SpreadsheetFormatCell(theSheet,longNum,2,4);
</cfscript>
There are many supported formats available; for a complete list you may check here.
Also, just like SpreadsheetFormatCell you may want to use SpreadsheetFormatColumn or other related functions.
(Too long for comments...)
FWIW, CFSpreadsheet is designed for very simple exports, without a lot of bells and whistles. If you need special formatting, you must use spreadsheet functions instead.
The closest equivalent to your current code is probably the SpreadsheetAddRows(sheet, query) function. It populates a worksheet with the data in the supplied query object. As Viv's answer mentions, you can then format the columns as desired. For example, if you want the value to be treated as text, use {dataformat = "#"}:
<cfscript>
SpreadsheetAddRows(theSheet, qData);
SpreadsheetFormatColumns(theSheet, {dataformat = "#"}, "1-3");
SpreadSheetWrite(theSheet, "c:/path/to/myFile.xls", true);
</cfscript>
As an aside, the examples in the documentation are not always the best or cleanest. Consider them a starting point, rather than using the code exactly "as is" ..

How to get sum of array for specific position?

I have an array:
<cfset fullarray = listtoArray (listofcollumnvalues)>
And it had for example:
fullarray[1]=20
fullarray[2]=11
fullarray[3]=4
fullarray[4]=12.2
etc.
And I wanted to add the sum of the the X position below number:
for example if I wanted the sum of the second element and below
2 + 3 + 4= 27.2
ColdFusion 10 introduced the ArraySlice function. It returns an array when you give it an array, a starting location, and an optional length. So ArraySlice(myArray,3,4) would return a "sub-array" with the elements that start at position 3 and includes 4 elements.
Based on your example:
mySum = ArraySum(ArraySlice(fullarray,2))
If you're on CF 9 or below, you can use a UDF. At CFLib.org there is
arraySlice
arraySlice2
Warning! arraySlice uses "start" and "end" element arguments, while arraySlice2 uses "start" and "length" arguments, like the built-in CF10 function.
Here's something that will work on older versions.
<cfscript>
function listSum(listStr)
{
var delim = ",";
if(ArrayLen(Arguments) GTE 2)
delim = Arguments[2];
return ArraySum(ListToArray(listStr, delim));
}
</cfscript>
Simply envoke it as #listSum(MyListOrArray)#
This is a formatted comment. You define your array as this:
<cfset fullarray = listtoArray (listofcollumnvalues)>
The variable, listofcollumnvalues looks like it might represent results of a query object. If that's the case, You might be doing more work than necessary, because each column of a query is effectively an array. Consider for example this query:
<cfquery name = "queryName">
select field1, numericalField
from etc
</cfquery>
You can actually do stuff like this:
numericalFieldSum = ArraySum(queryName['numericalField'];
It's not exactly what you want, but it illustrates that you might not need your initial command.
Continuing with the assumption that you started with a query, a query of queries might give you what you want.
<cfquery name = "mySum" dbtype = "query">
select sum(numericalField) theSumIWant
from queryName
where your conditions are met
</cfquery>
Of course, if listofcollumnvalues does not come from a query, you should declare this answer to be useless twaddle and downvote accordingly.

Coldfusion-9 Trim Values

I am trying to write a code that takes a URL that has 3 parts (www).(domainname).(com) and trim the first part out completely.
So far I have this code that checks if on the left side I don't have a 'www' or 'dev'
go in and set siteDomainName = removecharsCGI.SERVER_NAME,1,2);
if (numHostParts eq 3 and listfindnocase('www,dev',left(CGI.SERVER_NAME,3)) eq 0) {
siteDomainName = removecharsCGI.SERVER_NAME,1,2);
The problem with the code above is that is deleting only 2 characters where I need it to delete ALL characters until numHostParts eq 2 or at least until the first "."
Another example would be:
akjnakdn.example.com I need the code to delete the first part of the URL with the dot included (akjnakdn.)
This code will help some of the queries that i have on the site to stop crushing because they are related with the #URL# and when the #URL# is fake I am getting cform query returned zero records error that is causing my contact forms to stop working.
You can just use listRest. It returns all the elements in a list, except the first one. Documentation is here http://help.adobe.com/en_US/ColdFusion/9.0/CFMLRef/WSc3ff6d0ea77859461172e0811cbec22c24-6d87.html
Example:
<cfscript>
name = cgi.server_name;
if (listlen(name,".") gte 3) {
name = listRest(name,".");
}
</cfscript>
You could do something like this:
<cfscript>
local.nameArr = ListToArray(CGI.SERVER_NAME, '.');
if (ArrayLen(local.nameArr) gt 2) {
ArrayDeleteAt(local.nameArr, 1);
}
siteDomainName = ArrayToList(local.nameArr, '.');
</cfscript>
I've split the server name into array elements with a period as the delimiter. If the number of elements is greater than two, remove the first element. Then convert it back to a list with the period as a delimiter.
UPDATE
As suggested by Robb, this could be more concise and perform better by skipping the array conversion process:
<cfscript>
siteDomainName = CGI.SERVER_NAME;
if (ListLen(siteDomainName, '.') gt 2) {
siteDomainName = ListDeleteAt(siteDomainName, 1, '.');
}
</cfscript>
I would use a regular expression, since you only want to "trim" certain subdomains (www,dev).
<cfset the_domain = REReplaceNoCase(cgi.SERVER_NAME, "(www|dev)\.", "") />
Just use a |-delimited list of subdomains you want to trim within the parentheses.

extract and resuse values from a structure

I have a page called "shopping_cart_qry.cfm", that does a series of SELECT queries from various tables. It extracts data and populates a single structure called shopping. This structure contains around 50 parameters, like:
shopping.company_id
shopping.brand_id
shopping.cost_Price
shopping.expiry_dt
shopping.user_id
shopping.item_name
shopping.item_cost
...
I only need 15 out of the 50 parameters (shopping.item_name , shopping.item_cost, etc) for a different task. So I am calling "shopping_cart_qry.cfm" as <cfinclude> in a new file named "item_info.cfm".
In this file when I do an <cfdump> of the structure, I see all 50 parameters, including the 15 parameters I need. But when I try to assign new names to the 15 parameters I need like this:
<cfset itemName = "shopping.item_name">
<cfset itemCost = "shopping.item_Cost">
<cfset itemDt = "shopping.item_Dt">
And then use <cfdump> to see I was able to do successfully, I am seeing the variable names (itemName, itemCost, etc..) but no values.
<cfdump var="#shopping.item_name#">
<cfdump var="#shopping.item_Cost#">
<cfdump var="#shopping.item_Dt#">
Should I use <script>?
You should remove the quotation marks, ex:
<cfset itemName = shopping.item_name>
See Adobe docs on cfset.

How to format spreadsheet columns using ColdFusion?

I am using SpreadsheetFormatColumns() to format the columns in a spreadsheet to "text", but I don't know how to do this, all the formats in the livedocs are for numbers, currency or dates... is there something like
SpreadsheetFormatColumns(mySpreadsheet, {dataFormat="text"}, "1-15")
out there? this is really bugging me...
Thanks
In ColdFusion 9.0.1 (i.e. updater 1), if you use SpreadsheetSetCellValue() it will respect the format you have previously set. So to force a column to be text when populating a sheet you can use a 3-step process:
Populate the spreadsheet, ignoring the incorrectly interpreted number values.
Format the column you want as text.
Replace the incorrect value in each row of the column with the correct value, which will now be treated as text.
Here's an example which you can copy into a .cfm and run as-is (requires CF9.0.1)
<cfscript>
// Create a 2 column, 2 row query. The first column contains numbers or possible numbers we want formatted as text in our spreadsheet
q = QueryNew( "" );
QueryAddColumn( q,"NumbersAsText","VarChar",[ 01050094071094340000,"743059E6" ] );
QueryAddColumn( q,"Text","VarChar",[ "abc","def" ] );
// Get the column names as an array so we can get at them more easily later
columns = q.getMetaData().getColumnLabels();
// Create a new spreadsheet object
sheet = SpreadSheetNew( "test" );
// specify the column we want formatted as text
forceTextColumnNumber = 1;
// Use the query column names as column headers in our sheet
SpreadSheetAddRow( sheet,q.columnList );
// Add the data: the numbers will be inserted as numeric for now
SpreadSheetAddRows( sheet,q );
// Now we format the column as text
SpreadSheetFormatColumn( sheet,{ dataformat="text" },forceTextColumnNumber );
// Having formatted the column, add the column from our query again so the values correct
while( q.next() )
{
// Skip the header row by adding one
rownumber = ( q.currentrow + 1 );
// Get the value of column at the current row in the loop
value = q[ columns[ forceTextColumnNumber ] ][ q.currentrow ];
// replace the previously added numeric value which will now be treated as text
SpreadsheetSetCellValue( sheet,value,rownumber,forceTextColumnNumber );
}
// Download the object as a file
sheetAsBinary = SpreadSheetReadBinary( sheet );
filename = "test.xls";
</cfscript>
<cfheader name="Content-Disposition" value="attachment; filename=#Chr(34)##filename##Chr(34)#">
<cfcontent type="application/msexcel" variable="#sheetAsBinary#" reset="true">
By default, both of the values in the first column of my query would be treated as numbers (the second as a HEX). Using this method both preserve their original value as text.
According to this chart use '#' (without quotes) for the text placeholder.