Just a bunch of snippets
Example using Dependency Injection with a Car-Instance. It will include the Engine
Comnponent, the GasolineEngine
and DieselEngine
implementations and the WireBox configuration.
Engine.cfc
component {
public function start() {
// This method will be implemented by subclasses
}
}
GasolineEngine.cfc
component extends="path.to.Engine" {
public function start() {
writeOutput("Gasoline engine started.<br>");
}
}
DieselEngine.cfc
component extends="path.to.Engine" {
public function start() {
writeOutput("Diesel engine started.<br>");
}
}
Car.cfc
component {
property name="engine" inject="id:engine"; // Specify injection here
public function init() {
return this; // Return the instance
}
public function drive() {
writeOutput("Driving the car...<br>");
engine.start(); // Call the start method on the injected engine
}
}
WireBox.cfc
component extends="coldbox.system.ioc.config.Binder" {
public function configure() {
// Map the Engine implementations
map("engine", "path.to.GasolineEngine").asSingleton(); // Use GasolineEngine as singleton
// Alternatively, you could switch to DieselEngine by changing this mapping
// map("engine", "path.to.DieselEngine").asSingleton();
// Map the Car component
map("Car", "path.to.Car");
}
}
Usage Example
car = wirebox.getInstance("Car"); // Automatically injects GasolineEngine
car.drive(); // Outputs: Driving the car... Gasoline engine started.
-
Interface Definition: The
Engine
interface defines a common contract for all engine types with a methodstart()
. -
Implementation Classes: Both
GasolineEngine
andDieselEngine
extend from theEngine
interface and implement their specific versions of thestart()
method. -
Injection in Car: The
Car
component has a property for an engine. When instantiated, it will automatically receive an instance of whatever is mapped to"engine"
. -
Configuration in WireBox: The
WireBox.cfc
file contains mappings that tell WireBox how to instantiate components. Here, we specify that"engine"
should resolve to a singleton instance ofGasolineEngine
. -
Automatic Resolution: When we request an instance of
Car
, WireBox automatically injects the appropriate engine based on our configuration.
This CFScript version of the car and engine example demonstrates how to implement Dependency Injection using WireBox in ColdFusion. This allows for clear and concise definitions of components while still leveraging WireBox's powerful DI capabilities.
Injected properties are not available within init() method, so you need to use the onDICompletr()
function to assign injected properties to a component instance. For more details see https://stackoverflow.com/a/53214982/2645359
==============================================================================================================================================================================================================
ant -buildfile="D:\workspace-lucee-scriptrunner\script-runner" -DluceeVersion="6.1.0.235-SNAPSHOT" -Dwebroot="D:\workspace_luceedocs\lucee-docs" -Dexecute="/build-all.cfm"
Let’s say you have a setup like this:
- a database named database.accdb with a table named table1 placed at
pathToYourLucee\lucee-express-5.3.10.97\database.accdb
,- a webroot served at
pathToYourLucee\lucee-express-5.3.10.97\webapps\ROOT
Step 1: Download Lucee Express Version 5.3.10.97 and unzip it.
Step 2: Download the following OSGI compliant dependencies to your pathToYourLucee\lucee-express-5.3.10.97\lib
:
- commons-lang3-3.12.0.jar
- ucanaccess-5.0.0.jar
- jackcess-4.0.4.jar
- hsqldb-2.7.1.jar
- commons-logging-1.2.jar
Step 3: Create an Application.cfc at pathToYourLucee\lucee-express-5.3.10.97\webapps\ROOT\Application.cfc
with the following code:
//Application.cfc
component {
this.Name = "MSAccessExample";
this.dataBasePath=expandPath("../../") & "database.accdb";
this.datasources["msAccessDB"] = {
class: "net.ucanaccess.jdbc.UcanaccessDriver",
connectionString: "jdbc:ucanaccess:///" & this.dataBasePath
};
}
Step 4: Create an index.cfm with the following code at pathToYourLucee\lucee-express-5.3.10.97\webapps\ROOT\index.cfm
with the following code:
<!--- index.cfm --->
<cfquery name="myquery" datasource="msAccessDB" >
select * from table1;
</cfquery>
<cfdump var="#myquery#">
Step 5: Run the Lucee server from your Lucee Express Version and execute the index.cfm file.
Function to sign a Kraken API call with CFML. This function returns a base 64 string of the signed Kraken Api Call as specified in the KrakenAPI docs. This is also my answer posted at Stackoverflow.
<cfscript>
/**
* Returns a base 64 string of the signed Kraken Api Call as specified in the docs https://docs.kraken.com/rest/#section/Authentication/Headers-and-Signature
* in CFML
* Author: Andreas RĂĽger 2022
* License: MIT
*/
public string function getKrakenSignature( urlpath, postdata, nonce, secretAsBase64) localmode=true {
// assign arguments to local variables
urlpath= arguments.urlpath;
nonce= arguments.nonce;
postdata = arguments.postdata;
secretAsBase64= arguments.secretAsBase64;
// convert urlpath to a binary Hex representation
urlpathBinary= toBinary( toBase64( urlpath ));
urlpathBinaryAsHex= BinaryEncode( urlpathBinary, "HEX");
// convert secret to binary
secretBinary= ToBinary( arguments.secretAsBase64 );
// concatenate nonce and postdata
noncePostdata = nonce & postdata;
//get binary digest as Hex representation
noncePostdataDigestBinaryAsHex= hash( noncePostdata, "SHA-256" );
// concatenate urlPath binary (hex) and noncePostDara binary (hex)
messageBinaryAsHex= urlpathBinaryAsHex & noncePostdataDigestBinaryAsHex;
// convert message hex representation to binary
messageBinary= BinaryDecode( messageBinaryAsHex, "HEX");
// sign the message with hmac function
messageHmacDigestBinaryAsHex = hmac( messageBinary, secretBinary, "HMACSHA512");
messageHmacDigestBinary=BinaryDecode( messageHmacDigestBinaryAsHex, "HEX");
return binaryEncode( messageHmacDigestBinary, "base64" );
}
encodedPayLoad="nonce=1616492376594&ordertype=limit&pair=XBTUSD&price=37500&type=buy&volume=1.25";
nonce="1616492376594";
api_sec = "kQH5HW/8p1uGOVjbgWA7FunAmGO8lsSUXNsu3eow76sz84Q18fWxnyRzBHCd3pd5nE9qa99HAZtuZuj6F1huXg==";
urlpath="/0/private/AddOrder";
signature = getKrakenSignature( urlpath, encodedPayLoad, nonce, api_sec);
writeoutput( signature );
</cfscript>
<cfscript>
/**
* Sorts a struct recursively
*/
public struct function sortNestedStruct( struct datastruct ) localmode=true {
// define sorted struct
sortedStruct = [:];
// Get the keys of the struct and sort them
keys = structKeyArray( arguments.datastruct ).sort( "textnocase" );
// Iterate over the sorted keys
for (var key in keys) {
value = arguments.datastruct[ key ];
// If the value is a nested struct, recursively sort it
if ( isStruct( value ) ) {
value = sortNestedStruct( value );
}
// Add the key-value pair to the sorted struct
sortedStruct[ key ] = value;
}
return sortedStruct;
}
</cfscript>
Sometimes you're not sure where the web/server context is, e.g. when you've moved the context out of the document root. Throw this snippet somewhere into your code and run it!
<cfscript>
/**
* returns a struct with the server/web context information that is bound to this template.
*/
public struct function getServerWebContextInfoAsStruct(){
//get pageContext/CFMLFactoryConfig of actual template
local.pageContext=getpagecontext();
local.pageCFMLFactory=local.pageContext.getCFMLFactory();
local.pageCFMLFactoryConfig=local.pageCFMLFactory.getConfig();
//get the Servlets configuration and initial Parameters (e.g. set in Tomcats conf/web.xml)
local.servletConfig = getpagecontext().getServletConfig();
local.servletInitParamNames = servletConfig.getInitParameterNames();
// populate struct with gathered information
local.info={
"context-label" : getpagecontext().getCFMLFactory().getLabel(),
"configFileLocation" : pageCFMLFactoryConfig.getConfigFile(),
"servletInitParameters": [:]
};
// if available, iterate enum of InitParamNames and get the values
cfloop( collection="#servletInitParamNames#" item="item" ){
structInsert( local.info["ServletInitParameters"] , item, local.servletConfig.getInitParameter( item.toString() ) );
};
return local.info;
}
writedump(var="#getServerWebContextInfoAsStruct()#");
</cfscript>
Because this also updates the task engine, the programmatically way is much better than using the Lucee admin (e.g. call it on ApplicationStart())
<cfscript>
public query function createMyScheduledTask(){
cfschedule(
action="update"
task="myTestTask"
operation="HTTPRequest"
startDate="1/1/2022"
startTime="4:30 AM"
url="http://localhost:8888/?schedulerTime"
port="8888"
interval="70")
schedules=getMyScheduledTask();
return schedules;
}
public query function getMyScheduledTask(){
cfschedule(
action="list"
result = "schedules"
);
```
<cfquery name="myTestTask" dbtype="query">
SELECT * from schedules
WHERE task='myTestTask';
</cfquery>
```
return myTestTask;
}
dump( createMyScheduledTask() );
</cfscript>
Just download tika-app-2.6.0.jar from https://repo1.maven.org/maven2/org/apache/tika/tika-app/2.6.0/tika-app-2.6.0.jar to your classpath ( drop it to your Tomcat lib directory /or lucee-server/bundle/ and run the following script
<cfscript>
/**
*
* Retrieve MP3 Data with TIKA Java & CFML: Download https://repo1.maven.org/maven2/org/apache/tika/tika-app/2.6.0/tika-app-2.6.0.jar
* and drop it to your classpath/bundle or Tomcat Lib directory.
*
**/
public struct function getMP3Info( required string filename ) localmode = true {
result = {
"error" = "",
"info" = {}
};
if ( FileExists( filename ) ) {
file = createObject( "java", "java.io.File" ).init( arguments.filename );
fileInputStream = createObject( "java", "java.io.FileInputStream" ).init( file );
bodyContentHandler = CreateObject( "java", "org.apache.tika.sax.BodyContentHandler", "../../lib/tika-app-2.6.0.jar" );
metaData = CreateObject( "java", "org.apache.tika.metadata.Metadata", "../../lib/tika-app-2.6.0.jar" );
pcontext = CreateObject( "java", "org.apache.tika.parser.ParseContext", "../../lib/tika-app-2.6.0.jar" );
mp3Parser = CreateObject( "java", "org.apache.tika.parser.mp3.Mp3Parser", "../../lib/tika-app-2.6.0.jar" );
try {
mp3Parser.parse( fileInputStream, bodyContentHandler, metaData, pcontext );
//dump( metaData.names() );
durationInSec = parseNumber( metaData.get( "xmpDM:duration" ) );
hours = int( durationInSec / 60 / 60 );
restTimeSec = durationInSec - ( hours * 60 * 60 );
minutes = int( restTimeSec / 60 );
restTimeSec = restTimeSec - ( minutes * 60 );
seconds = restTimeSec;
result.info = {
"artist" = metaData.get( "xmpDM:artist" ),
"album" = metaData.get( "xmpDM:album" ),
"trackNumber" = metaData.get( "xmpDM:trackNumber" ),
"duration" = numberformat( hours, "00" )
& ":" & numberformat( minutes, "00" )
& ":" & numberformat( seconds, "00" ),
"genre" = metaData.get( "xmpDM:genre"),
"comment" = metaData.get( "xmpDM:logComment" )
}
} catch ( any error ) {
result[ "error" ] = error;
}
fileInputStream.close();
} else {
result [ "error" ] = "File not found";
}
return result;
}
mp3File = expandPath( "../../" ) & "song.mp3";
dump( getMP3Info( mp3File ) );
</cfscript>
Get a struct of all available two-letter language codes and their target language names of the underlying Java.util.Locale Class
Lucee uses the underlying Java locale from Java.util.Locale for ls-Functions. As long as the language is supported in Java, the locale can also be used in Lucee CFML. Some Java version just don't support all Java Locales (e.g. Java 8 doesn't support the Locale "Filipino ( Philippines )", but AdoptOpenJDK 11.0.4 does. This script returns all availabe 2-letter language codes and their language names in their respective language (different from the getDisplayName() function which returns the name in the OS language ).
<cfscript>
/**
* returns as struct of all available 2-letter codes of the underlying java.util with the referring Language DisplayName (target language)
*/
public struct function getAvailableLanguageJavaLocalesAsStruct(){
// Get Locale List
local.JavaLocale = CreateObject("java", "java.util.Locale");
local.availableJavaLocalesArray=JavaLocale.getAvailableLocales();
// initialize an ordered struct with shorthand [:]
local.availableJavaLocalesStruct =[:];
cfloop( array= "#availableJavaLocalesArray#" item="itemLocale" index="i"){
if( len( itemLocale.toLanguageTag() ) == 2 ){
local.displayNameTargetLanguage=itemLocale.info();
local.availableJavaLocalesStruct[itemLocale.toLanguageTag()] = {
"targetLanguage": UcFirst( local.displayNameTargetLanguage["display"]["language"] ),
"displayName": UcFirst( itemLocale["displayName"] ),
"ISO639-2": local.displayNameTargetLanguage["iso"]["Language"],
"ISO639-1": local.displayNameTargetLanguage["Language"]
}
}
}
return local.availableJavaLocalesStruct;
}
writeDump(var="#getAvailableLanguageJavaLocalesAsStruct()#");
</cfscript>
As example: Download the OSGI compliant Bundle from https://mvnrepository.com/artifact/org.mariadb.jdbc/mariadb-java-client/2.6.1 to commandbox server instance \lucee-x.x.x.x\WEB-INF\lib:
Sample code testDSN.cfm
<cfscript>
classLoader = createObject("java", "org.mariadb.jdbc.Driver");
driverManager = createObject("java","java.sql.DriverManager");
connector = driverManager.getConnection("jdbc:mariadb://localhost:3306/test?user=root&password=mypassword");
connectionString = connector.createStatement();
result = connectionString.ExecuteQuery("SHOW TABLES FROM TEST;");
writeDump( "#result#" );
writeDump( "#classLoader#" );
</cfscript>
<cfscript>
myTransportations="bicycle,bus,foot,car,train,airplane";
// two expressions, lexical scoping
finallist="";
cfloop( list="#myTransportations#", item="element", index="index") {
finallist = finallist.listAppend(
"#index#:" & element.listLast(",").uCFirst();
)
}
writedump(var="#[finallist]#");
</cfscript>
vs.
<cfscript>
myTransportations="bicycle,bus,foot,car,train,airplane";
// one expression with closure function
finallist=listMap( myTransportations, ( element, index, list) => {
return "#index#:" & element.listLast(",").uCFirst();
}
);
writedump(var="#[finallist]#");
</cfscript>
Further example:
<cfscript>
myTransportationSequence="car,bicycle,bus,foot,car,train,airplane,bus,foot";
speedSequence = listMap(
myTransportationSequence,
( element, index, list) => {
switch(element){
case "bicycle": return "#index#:" & "slow"
case "bus": return "#index#:" & "normal"
case "foot": return "#index#:" & "very slow"
case "car": return "#index#:" & "fast"
case "train": return "#index#:" & "very fast"
case "airplane": return "#index#:" & "ultra fast"
default: return "don't know the transportation speed";
}
}
);
writedump(var="#[speedSequence]#");
</cfscript>
listReduce: Use always if you need to reuse a calculated value to recursively pass it again to the closure (through accumulator acc)
<cfscript>
initialAmount=1000;
transferedQuarterList="120,140,123,90";
listOfTransactions="";
interestPerQuarter=0.12;
cummulatedAmount = listReduce(
transferedQuarterList,
( acc, element ) => {
var newAmount = acc + ( acc + element ) * interestPerQuarter ;
var calculationString = " #acc# + " & ( acc + element ) & " * " & interestPerQuarter & " = " & acc + ( acc + element ) * interestPerQuarter ;
listOfTransactions = listOfTransactions.listAppend(
calculationString
);
return newAmount;
}
, initialAmount
);
writedump(var="#[initialAmount:initialAmount, transferedQuarterList:transferedQuarterList,listOfTransactions:listOfTransactions,cummulatedAmount:cummulatedAmount]#");
</cfscript>
<cfscript>
fruitsQuery = queryNew(
"id, fruit , price" , "numeric, varchar , numeric" ,
{
id: [1,2,3,4,5,6,7,8,9],
fruit: [ "Bananas" , "Kiwis", "Apples", "Oranges", "Peaches", "Bananas" , "Kiwis", "Apples", "Uchuvas" ],
price: [ 1.99 , 0.99 , 2.99, 3.99, 6.99, 2.99, 3.99, 6.99, 5.00 ]
}
);
distinctFruitsArray = valueList( fruitsQuery.fruit ).listReduce(
( acc, element ) => {
if( not acc.contains( element) ){
acc.append( element );
}
return acc;
}
, [] , ","
);
dump(distinctFruitsArray);
</cfscript>
<cfscript>
/**
* @hint check if passed file extensiona and mimeType matches the allowed mapped file-extensions/mimeTypes combinations, usable for fileUploads/imageMagick verbose identify ;
*/
public boolean
function matchesAllowedMimeTypesAndFileExtensions(
string fileExtension required,
string mimeType required
) {
variables.allowedFileExtensionsAndMimeTypes = {
jpg: 'image/jpeg',
jpeg: 'image/jpeg',
png: 'image/png'
}
variables.isFileExtensionsAndMimeTypesAllowed = false;
variables.fileExtension = lcase( arguments.fileExtension );
variables.mimeType = lcase( arguments.mimeType );
variables.allowedFileExtensionsAndMimeTypes.reduce( ( result, allowedExtension, allowedMimeType ) => {
if (
variables.fileExtension == arguments.allowedExtension &&
variables.mimeType == arguments.allowedMimeType ) {
variables.isFileExtensionsAndMimeTypesAllowed = true;
}
return result
}, "" );
return variables.isFileExtensionsAndMimeTypesAllowed
}
writedump( matchesAllowedMimeTypesAndFileExtensions("jpg","image/jpeg") );
writedump( matchesAllowedMimeTypesAndFileExtensions("jpg","text/html") );
</cfscript>
JfreeChart: A collection of commands to directly invoke the jfreeChart java package shipped with Lucee as an alternative to cfml's cfchart.
<cfscript>
// create all objects
ObjChartFactory = CreateObject("java", "org.jfree.chart.ChartFactory");
ObjChartOrient = CreateObject("java", "org.jfree.chart.plot.PlotOrientation");
ObjChartUtil = CreateObject("java", "org.jfree.chart.ChartUtilities");
ObjXYLineAndShapeRenderer = CreateObject("java", "org.jfree.chart.renderer.xy.XYLineAndShapeRenderer");
ObjBasicStroke = CreateObject("java", "java.awt.BasicStroke");
ObjChartColor = CreateObject("java", " org.jfree.chart.ChartColor");
ObjRectangleInsets = CreateObject("java", "org.jfree.ui.RectangleInsets");
ObjShapeUtilities = CreateObject("java", "org.jfree.util.ShapeUtilities");
ObjXYSeriesCollection = CreateObject("java", "org.jfree.data.xy.XYSeriesCollection");
ObjXYDataset= CreateObject("java", "org.jfree.data.xy.XYDataset");
ObjXYSeries= CreateObject("java", "org.jfree.data.xy.XYSeries");
ObjNumberAxis= CreateObject("java", "org.jfree.chart.axis.NumberAxis");
ObjNumberTickUnit= CreateObject("java", "org.jfree.chart.axis.NumberTickUnit");
ObjStandardXYItemLabelGenerator = CreateObject("java", "org.jfree.chart.labels.StandardXYItemLabelGenerator ");
ObjXYItemLabelGenerator = CreateObject("java", "org.jfree.chart.labels.XYItemLabelGenerator");
ObjNumberFormat= CreateObject("java", "java.text.NumberFormat");
ObjFont=CreateObject("java", "java.awt.Font");
// Initialize Series
XYSeries=ObjXYSeries.init("series1");
// add data to series
XYSeries.add(1,0.9);
XYSeries.add(2,0.902);
XYSeries.add(3,0.903);
XYSeries.add(4,0.906);
XYSeries.add(5,0.904);
// Initialize second series
XYSeries2=ObjXYSeries.init("series2");
// add data to series
XYSeries2.add(1,0.906);
XYSeries2.add(2,0.904);
XYSeries2.add(3,0.900);
XYSeries2.add(4,0.901);
XYSeries2.add(5,0.904);
// initialize XYSeriesCollection
XYDataset=ObjXYSeriesCollection.init();
//add both series to collection
XYDataset.addSeries(XYSeries);
XYDataset.addSeries(XYSeries2);
// Set Chart as createXYLineChart
Chart = ObjChartFactory.createXYLineChart ("This is some Title", "Name 1", "Name 2", XYDataset, ObjChartOrient.VERTICAL, true, true, true);
// Set Range of Range Axis (y)
Chart.getPlot().getRangeAxis().setRange(0.887, 0.908);
// Set Range of Domain Axis (x)
Chart.getPlot().getDomainAxis().setRange(1, 5);
// Force Domain Axis to show values as Integer
Chart.getPlot().getDomainAxis().setStandardTickUnits(ObjNumberAxis.createIntegerTickUnits());
// Define Steps of Values of Domain Axis (x)
Chart.getPlot().getDomainAxis().setTickUnit(ObjNumberTickUnit.init(0.5));
// Define Steps of Values of Range Axis (y)
Chart.getPlot().getRangeAxis().setTickUnit(ObjNumberTickUnit.init(0.005));
// Remove Legend
Chart.removeLegend();
// Init
barrenderer = ObjXYLineAndShapeRenderer.init();
//define Color for each serie
barrenderer.setSeriesPaint(0,ObjChartColor.RED);
barrenderer.setSeriesPaint(1,ObjChartColor.LIGHT_GREEN);
//define Stroke for each serie
barbasicstroke1 = ObjBasicStroke.init(1);
barbasicstroke2 = ObjBasicStroke.init(10);
barrenderer.setSeriesStroke(0,barbasicstroke1);
barrenderer.setSeriesStroke(1,barbasicstroke2);
//define Type of Markers for each serie
triangle = ObjShapeUtilities.createDownTriangle(5);
diamond = ObjShapeUtilities.createDiamond(8);
barrenderer.setSeriesShape(0, triangle);
barrenderer.setSeriesShape(1, diamond);
barrenderer.setShapesFilled(true);
barrenderer.setShapesVisible(true);
// Set color of LabelValues
barrenderer.setBaseItemLabelPaint(ObjChartColor.WHITE);
// Set format for LabelValues and limit showing digits for use in Label-Genertator
NumberFormatY = ObjNumberFormat.getNumberInstance();
NumberFormatY.setMaximumFractionDigits(2);
NumberFormatX = ObjNumberFormat.getNumberInstance();
NumberFormatX.setMaximumFractionDigits(4);
// initialize XYItemLabelGenerator: it sets the content and the format of the shown data. ("Some text {datasetIndex}",format)
tmpgenerator=ObjStandardXYItemLabelGenerator.init("Y={2} / X={1} ", NumberFormatY, NumberFormatX);
// initialize font with ObjFont.init( FontFamily, Style( bitwise: 0=normal, 1=bold, 2=italic, 3=bold|italic), size )
font=ObjFont.init("Verdana",1,12);
//Set font for ItemLables
barrenderer.setBaseItemLabelFont(font);
//Generate ItemLables
barrenderer.setBaseItemLabelGenerator(tmpgenerator);
barrenderer.setBaseItemLabelsVisible( true );
Chart.getPlot().setRenderer(barrenderer);
// Set Margins (top, left, bottom, right)
newrectangle=ObjRectangleInsets.init(20,0,0,50);
Chart.getPlot().setAxisOffset(newrectangle);
// Change Background Color
Chart.getPlot().setBackgroundPaint( ObjChartColor.BLUE );
// Change GridlineColors for Domain Axis (x)
Chart.getPlot().setDomainGridlinePaint(ObjChartColor.WHITE);
// Change GridlineColors for Range Axis (y)
AWTColor = CreateObject("java", "java.awt.Color");
// Set a color with AWTColor(redPercentage, greenPercentage, bluePercentage) as FLOAT
// lime(0,255,0) or magenta/fuchsia(255,0,255) with 255 being 1:
LIMECOLOR= AWTColor.init(0,1,0);
MAGENTACOLOR= AWTColor.init(1,0,1);
Chart.getPlot().setBackgroundPaint( MAGENTACOLOR );
// Prepare for Output
ChartImage = Chart.createBufferedImage(500, 500);
ImageFormat = createObject("java", "org.jfree.chart.encoders.ImageFormat");
EncoderUtil = createObject("java", "org.jfree.chart.encoders.EncoderUtil");
ChartImgInBytes = EncoderUtil.encode( ChartImage, ImageFormat.PNG);
</cfscript>
<!--- display in browser --->
<cfoutput>
<img src="data:image/*;base64,#toBase64( ChartImgInBytes )#" />
</cfoutput>