<cfscript> // Step 1: Gather the raw data from somewhere (probably your database). users = [ { id: 1, name: "Sarah ""Stubs"" Smith", role: "Admin", joinedAt: createDate( 2020, 1, 13 ) }, { id: 2, name: "Tom Titto", role: "Manager", joinedAt: createDate( 2021, 3, 4 ) }, { id: 3, name: "Kit Caraway", role: "Manager", joinedAt: createDate( 2019, 10, 27 ) }, { id: 4, name: "Allan Allure, Jr.", role: "Designer", joinedAt: createDate( 2020, 8, 22 ) } ]; // Step 2: Prepare the raw data for encoding. This usually means adding a HEADER row // and encoding non-string values as strings (such as formatting dates). rows = [ [ "ID", "Name", "Role", "Joined At" ] ]; for ( user in users ) { rows.append([ user.id, user.name, user.role, user.joinedAt.dateFormat( "yyyy-mm-dd" ) ]); } // Step 3: Serialize the row data as a CSV (Comma Separated Value) payload. csvContent = encodeCsvData( rows = rows, rowDelimiter = chr( 10 ), fieldDelimiter = chr( 9 ) ); csvFilename = ( "users-" & now().dateFormat( "yyyy-mm-dd" ) & ".csv" ); // Step 4: Serve-up that sweet, sweet encoded data! header name = "content-disposition" value = getContentDisposition( csvFilename ) ; // NOTE: By converting the CSV payload into a binary value and using the CFContent // tag, ColdFusion will add the content-length header for us. Furthermore, the // CFContent tag will also halt any subsequent processing of the request. content type = "text/csv; charset=utf-8" variable = charsetDecode( csvContent, "utf-8" ) ; // ------------------------------------------------------------------------------- // // ------------------------------------------------------------------------------- // /** * I encoded the given two-dimensional array as a CSV (Comma Separated Value) payload. * Rows and fields are serialized as lists using the given delimiters. All fields are * wrapped in double-quotes. * * CAUTION: All values are assumed to have ALREADY BEEN STRINGIFIED. As such, if you * want to have control over how things such as Dates are serialized, then that should * have already been done prior to calling this function. * * @rows I am the two-dimensional collection being encoded. * @rowDelimiter I am the delimiter used to serialized the encoded rows. * @fieldDelimiter I am the delimiter used to serialize the encoded fields. */ private string function encodeCsvData( required array rows, required string rowDelimiter, required string fieldDelimiter ) { var encodedData = rows .map( ( row ) => { return( encodeCsvDataRow( row, fieldDelimiter ) ); } ) .toList( rowDelimiter ) ; return( encodedData ); } /** * I encode the given collection of values for use in a CSV (Comma Separated Value) * row. The values are then serialized as a list using the given delimiter. * * @row I am the collection being encoded. * @fieldDelimiter I am the list delimiter used in the encoding. */ private string function encodeCsvDataRow( required array row, required string fieldDelimiter ) { var encodedRow = row .map( encodeCsvDataField ) .toList( fieldDelimiter ) ; return( encodedRow ); } /** * I encode the given value for use in a CSV (Comma Separated Value) field. The value * is wrapped in double-quotes and any embedded quotes are "escaped" using the * standard "doubling" syntax. * * @value I am the field value being encoded. */ private string function encodeCsvDataField( required string value ) { var encodedValue = replace( value, """", """""", "all" ); return( """#encodedValue#""" ); } /** * I get the content-disposition (attachment) header value for the given filename. * * @filename I am the name of the file to be saved on the client. */ private string function getContentDisposition( required string filename ) { var encodedFilename = encodeForUrl( filename ); return( "attachment; filename=""#encodedFilename#""; filename*=UTF-8''#encodedFilename#" ); } </cfscript>