cf9 query grid values SQL - ajax

I have a grid bound to a cfc, populating an id column and a checkbox (boolean) column. onLoad, i want to get a list of ID values, but only those having checkbox = 1
Here is the working code, with great help from Jan S
<script language="JavaScript">
function init(){
var grid = ColdFusion.Grid.getGridObject('testgrid');
var gs = grid.getStore();
var records = gs.getRange();
var filteredRecords = [];
for (i = 0; i < records.length; i++) {
// note: for CF, you MUST capitalize grid header names
if (records[i].get('SELECT') === 1) {
var thisID = records[i].get('ID');
filteredRecords.push(thisID);
}
}
document.getElementById('idList').value=filteredRecords;
}
ColdFusion.Event.registerOnLoad(init,null,false,true);
</script>
<cfset items=QueryNew("id,Description,Select")>
<cfset Temp=QueryAddRow(items,1)>
<cfset Temp=QuerySetCell(items,"id","11")>
<cfset Temp=QuerySetCell(items,"Description","Some item")>
<cfset Temp=QuerySetCell(items,"Select",1)>
<cfset Temp=QueryAddRow(items)>
<cfset Temp=QuerySetCell(items,"id","22")>
<cfset Temp=QuerySetCell(items,"Description","Some other item")>
<cfset Temp=QuerySetCell(items,"Select",1)>
<cfset Temp=QueryAddRow(items)>
<cfset Temp=QuerySetCell(items,"id","33")>
<cfset Temp=QuerySetCell(items,"Description","A third item")>
<cfset Temp=QuerySetCell(items,"Select",0)>
<cfform>
<cfgrid name="testgrid" format="html" query="items">
<cfgridcolumn name="id" header="ID" select="no">
<cfgridcolumn name="Description" header="Description" select="no">
<cfgridcolumn name = "Select" header="Select" select="yes" type="boolean">
</cfgrid>
<br>
<!--- populate this with list of ID's having the checkbox checked, here: 11,22 --->
<input type="text" name="idList" id="idList"> <input type="button" name="getList" value="Get List" onClick="init()">
</cfform>
Basically I need to translate into AJAX this SQL statement:
select stringColumn where booleanColumn = 1 from myGrid
I'm using Cold Fusion 9 which is based on js ext 3.1 i believe

to get an array with all a field from all records where another field is 1:
isData.on('load', function(store, records){
// create a new array with all records where the 'booleanColumn' is = 1
var filteredRecords = [];
for (i = 0; i < records.length; i++) {
if (records[i].get('booleanColumn') === 1) {
filteredRecords.push(records[i]);
}
}
console.log(filteredRecords);
// create a new array of 'stringColumn' values
var filteredValues = [];
for (i = 0; i < filteredRecords.length; i++) {
filteredValues[i] = filteredRecords[i].get('stringColumn');
}
console.log(filteredValues);
// proceed with filtered values...
});

Related

EventListener wont recognise new buttons

I have only started js recently, so i hope this makes sense..
I have made a simple dynamic form which starts with a table of 4 rows. After the heading row, the remaining 3 rows have +/- symbols which are to add or delete rows as necessary.
The add row functionality is currently working, however, even after assigning the correct class to the new row, the event listener wont work for the new buttons (even thought i have re-assigned the number of buttons within that class).
After adding the row, i re-assign btnAddRows and when logged to the console it is increasing with each row added. I can't figure out why it wont get captured in the for loop?
Thanks
//select elements on DOM
let btnAddRows = document.querySelectorAll('.btnADD');
let myTable = document.querySelector('#SecProp');
let myTableRows = document.querySelector('.tableRows');
for (let i = 0; i < btnAddRows.length; i++) {
btnAddRows[i].addEventListener('click', function () {
console.log(btnAddRows.length);
btnAddRows = document.querySelectorAll('.btnADD');
const rowNum = Number(btnAddRows[i].id.slice(6));
// console.log(rowNum);
// if (rowNum === myTableRows.length - 1) {
// addTableRow(rowNum);
// } else {
addTableRow(rowNum);
// }
btnAddRows = document.querySelectorAll('.btnADD');
myTable = document.querySelector('#SecProp');
myTableRows = document.querySelector('.tableRows');
});
}
const addTableRow = function (rowNum) {
//insert row into table
const addRw = myTable.insertRow(rowNum + 1);
const newRow = myTable.rows[rowNum + 1];
newRow.className = 'tableRows';
console.log(myTable.rows[rowNum + 1], typeof myTable.rows[rowNum + 1]);
const cell1 = newRow.insertCell(0);
const cell2 = newRow.insertCell(1);
const cell3 = newRow.insertCell(2);
const cell4 = newRow.insertCell(3);
const cell5 = newRow.insertCell(4);
cell1.className = 'column1';
cell2.className = 'coordsColumn';
cell3.className = 'coordsColumn';
cell4.className = 'buttons';
cell5.className = 'buttons';
cell1.innerHTML = `<td> ${rowNum + 1}</td>`;
cell2.innerHTML = '<td ><input type = "text" name="" value = ""/><td>';
cell3.innerHTML = '<td ><input type = "text" name="" value = ""/><td>';
cell4.innerHTML = `<td ><button class="btnADD btn btn-success" id="btnADD${
rowNum + 1
}"> + </button>`;
cell5.innerHTML = `<td ><button id="btnDEL${
rowNum + 1
}" class="btnDEL btn btn-success"> -</button>`;
};

Set the sourceRange of Data Validation to an array of values

I'm creating a social media outreach tracker. I want to create a drop-down list of the contact name. The problem is that I have two sources of names on two different sheets.
I wrote a script that pulls the names from the two different sources and combines them to a single array.
I was hoping to set the source range as that array.
Here is my code:
function setDataValid_(range, sourceRange) {
var rule = SpreadsheetApp.newDataValidation()
.requireValueInRange(sourceRange, true)
.build();
range.setDataValidation(rule);
}
function onEdit() {
var auditionsSheet = SpreadsheetApp.getActiveSpreadsheet();
var castingDirectorsTab = auditionsSheet.getSheetByName("Casting Directors");
var contactsTab = auditionsSheet.getSheetByName("Contacts");
var socialMediaOutreachTab = auditionsSheet.getSheetByName("Social Media Outreach");
var lastRowCD = castingDirectorsTab.getLastRow();
var lastRowContacts = contactsTab.getLastRow();
var activeCell = socialMediaOutreachTab.getActiveCell();
var activeColumn = activeCell.getColumn();
// get data
var castingDirectorNameData = castingDirectorsTab.getRange(2, 1, lastRowCD, 1).getValues();
var contactNameData = contactsTab.getRange(2, 1, lastRowContacts, 1).getValues();
//get name data to a single arrays
var castingDirectorName = [];
castingDirectorNameData.forEach(function(yr) {
castingDirectorName.push(yr[0]);
});
var contactName = [];
contactNameData.forEach(function(yr) {
contactName.push(yr[0]);
});
// get rid of the empty bits in the arrays
for (var x = castingDirectorName.length-1; x > 0; x--) {
if ( castingDirectorName[x][0] === undefined ) {
castingDirectorName.splice( x, 1 )
}
}
for (var x = contactName.length-1; x > 0; x--) {
if ( contactName[x][0] === undefined ) {
contactName.splice( x, 1 )
}
}
//combine two data sources for data validation
var combinedNames = [];
combinedNames.push(castingDirectorName + contactName);
Logger.log (combinedNames);
Logger.log( typeof combinedNames);
// data validation set up and build
if (activeColumn == 1 && auditionsSheet.getName() == "Social Media Outreach") {
var range = auditionsSheet.getRange(activeCell.getRow(), activeColumn +1);
var sourceRange = combinedNames;
setDataValid_(range, sourceRange)
}
}
When I enter a date in Col A on Social Media Outreach, nothing happens in Col 2.
I was using an existing working nested data validation script I have but the sourceRange pulls from a sheet based on the value in the active cell. Here is that code:
function setDataValid_(range, sourceRange) {
var rule = SpreadsheetApp.newDataValidation()
.requireValueInRange(sourceRange, true)
.build();
range.setDataValidation(rule);
}
function onEdit() {
var aSheet = SpreadsheetApp.getActiveSheet();
var aCell = aSheet.getActiveCell();
var aColumn = aCell.getColumn();
// data validation for Auditions Tab Projet Type to Project Details
if (aColumn == 9 && aSheet.getName() == 'Auditions') {
var range = aSheet.getRange(aCell.getRow(), aColumn + 1);
var sourceRange = SpreadsheetApp.getActiveSpreadsheet().getRangeByName('RefTables!' + aCell.getValue())
setDataValid_(range, sourceRange)
}
}
For this script when I select from the data validation drop-down, a new data validation comes up in the next col with the appropriate secondary data validation.
So the question is, can the source range be set to an array or do I need to put the names back into my sheet to reference a la the second script.
I've looked through the documentation and searched and can't find an answer. I'm relatively new to GAS and am not sure of all the inner workings of the data validation builder.

GmailApp - Inline images are attached, and not inline - send email with images in body

I created a Google Script to email several charts as images. When using MailApp it works, and the images are in the body of the email. When using GmailApp, the images are as attachments and not in the email body. I want to use GmailApp because I can use an alias, and because I figured out how to make the email send to a list of people. How do I make a change so that the GmailApp function sends the email with the charts contained in the body? Here is a link to a sample spreadsheet, and here is the code:
function SRpt2() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sstotalsheets = ss.getNumSheets();
var clientCode = ss.getSheetByName("Info").getRange("C4").getDisplayValue();
var progmgr = ss.getSheetByName("Info").getRange("D11").getDisplayValue();
var sheetcount = 0;
var token = ScriptApp.getOAuthToken();
var sheets = ss.getSheets();
var blobs = [];
var subject = "Daily " + clientCode +" Digest";
var body = "Your daily update";
var recips = progmgr + ", neill#momentum-behavioral.com";
var emailImages={};
var emailBody="Charts<br>";
for (var i = 5; i < sheets.length ; i++ ) {
var sheet = sheets[i];
var charts = sheet.getCharts();
if(charts.length > 0) {
var template = HtmlService.createTemplateFromFile("reportTemplate");
template.date = Utilities.formatDate(new Date(), Session.getScriptTimeZone(), "MM-dd-yyyy");
blobs[i] = charts[0].getBlob().getAs('image/png').setName("areaBlob");
emailBody= emailBody + "<img src='cid:chart"+i+"'><br>";
emailImages["chart"+i]= blobs[i];
}
}
var me = Session.getActiveUser().getEmail();
var aliases = GmailApp.getAliases();
if (aliases.length > 0) {
GmailApp.sendEmail(recips, subject, emailBody, {'from': aliases[0],inlineImages:emailImages});
}
MailApp.sendEmail({
to: recips,
subject: subject,
htmlBody: emailBody,
inlineImages:emailImages});
}
And here is the html it refers to:
<html>
<h1>Daily Update <?= date ?></h1>
<p>
<img src="cid:chart" />
</p>
</html>
The incorrectly formatted email body just says the actual html source code (I can't figure out how to paste it into the stackoverflow without it actually treating it as html).
Add the HTML to the options object. And make the body parameter and empty string.
var options;
options = {};//Assign an empty object to the variable options
//{'from': aliases[0],inlineImages:emailImages}
options.from = aliases[0];
options.inlineImages = emailImages;
options.htmlBody = emailBody;
if (aliases.length > 0) {
GmailApp.sendEmail(recips, subject, "", options);//Leave body an empty string
}

Kendoui Multiselect show text values selected on tooltip

I have a problem, I like to show a select text values of a MultiSelect control on a tooltip.
I only can show the value(numeric) from MultiSelect, this is my code:
var multiselect = $("#combo_multi").data("kendoMultiSelect");
value2 = multiselect.value(); //show only numeric values ->14376, etc.
Show the numeric values together without spaces. ->14376
I like to show the text value, not the numeric value.
I think I have to use an array for show the text value, but I donĀ“t know how do it.
If somebody have the response of this problem, I appreciate the solution. Thanks.
Maybe this could help you a bit
var multiselect = $("#combo_multi").data("kendoMultiSelect");
var value2 = multiselect.value();
var selectedValues = value2.split(",");
var multiSelectData = multiselect.dataSource.data();
for (var i = 0; i < multiSelectData.length; i++) {
var numberValue = multiSelectData[i].number;
for (var j = 0; j < selectedValues.length; j++) {
if (selectedValues[j] == numberValue) {
// here we get description for value
var desc = multiSelectData[i].description;
break;
}
}
}
Example other
$("#multiselect").kendoMultiSelect();
var multiselect = $("#CityTo").data("kendoMultiSelect");
var dataItem = multiselect.dataItems();
//***Debug
var CityArray = new Array();
CityArray = dataItem;
alert(JSON.stringify(CityArray));
//***End Debug
//**************** Applied example
var newHtml = "";
var item = dataItem;
$.each(item, function (index, item) {
newHtml += '<div class="panel panel-default" ><div class="panel-heading">' + item.City_Name + '</div><div class="panel-body">Panel Content</div></div>';
});
$("#CityCount").html(newHtml);
You can see the details "dataItem" using "item."
item.City_Name
I'm on a newer version of Kendo UI so possibly things have changed since you asked this. I'll give you an updated answer..
var multiselect = $("#combo_multi").data("kendoMultiSelect");
var selectedValues = multiselect.value();
var multiSelectData = multiselect.dataSource.data();
var count = selectedValues.length;
for (var i = 0; i < multiSelectData.length; i++) {
if (selectedValues.indexOf(multiSelectData[i].Value) >= 0 ) {
//found, do something
var selectedText = multiSelectData[i].Text;
count--;
}
if (count == 0) {
break;
}
}

How to sort an array of structs in ColdFusion

I have an array of structs in ColdFusion. I'd like to sort this array based on one of the attributes in the structs. How can I achieve this? I've found the StructSort function, but it takes a structure and I have an array.
If this is not possible purely in ColdFusion, is it possible in Java somehow (maybe using Arrays.sort(Object[], Comparator))?
Here is something that closely resembles the original StructSort(). It also supports the pathToSubElement argument.
<cffunction name="ArrayOfStructSort" returntype="array" access="public" output="no">
<cfargument name="base" type="array" required="yes" />
<cfargument name="sortType" type="string" required="no" default="text" />
<cfargument name="sortOrder" type="string" required="no" default="ASC" />
<cfargument name="pathToSubElement" type="string" required="no" default="" />
<cfset var tmpStruct = StructNew()>
<cfset var returnVal = ArrayNew(1)>
<cfset var i = 0>
<cfset var keys = "">
<cfloop from="1" to="#ArrayLen(base)#" index="i">
<cfset tmpStruct[i] = base[i]>
</cfloop>
<cfset keys = StructSort(tmpStruct, sortType, sortOrder, pathToSubElement)>
<cfloop from="1" to="#ArrayLen(keys)#" index="i">
<cfset returnVal[i] = tmpStruct[keys[i]]>
</cfloop>
<cfreturn returnVal>
</cffunction>
Usage / test:
<cfscript>
arr = ArrayNew(1);
for (i = 1; i lte 5; i = i + 1) {
s = StructNew();
s.a.b = 6 - i;
ArrayAppend(arr, s);
}
</cfscript>
<cfset sorted = ArrayOfStructSort(arr, "numeric", "asc", "a.b")>
<table><tr>
<td><cfdump var="#arr#"></td>
<td><cfdump var="#sorted#"></td>
</tr></table>
Result:
As usual, CFLib.org has exactly what you want.
http://cflib.org/udf/ArrayOfStructsSort
/**
* Sorts an array of structures based on a key in the structures.
*
* #param aofS Array of structures.
* #param key Key to sort by.
* #param sortOrder Order to sort by, asc or desc.
* #param sortType Text, textnocase, or numeric.
* #param delim Delimiter used for temporary data storage. Must not exist in data. Defaults to a period.
* #return Returns a sorted array.
* #author Nathan Dintenfass (nathan#changemedia.com)
* #version 1, December 10, 2001
*/
function arrayOfStructsSort(aOfS,key){
//by default we'll use an ascending sort
var sortOrder = "asc";
//by default, we'll use a textnocase sort
var sortType = "textnocase";
//by default, use ascii character 30 as the delim
var delim = ".";
//make an array to hold the sort stuff
var sortArray = arraynew(1);
//make an array to return
var returnArray = arraynew(1);
//grab the number of elements in the array (used in the loops)
var count = arrayLen(aOfS);
//make a variable to use in the loop
var ii = 1;
//if there is a 3rd argument, set the sortOrder
if(arraylen(arguments) GT 2)
sortOrder = arguments[3];
//if there is a 4th argument, set the sortType
if(arraylen(arguments) GT 3)
sortType = arguments[4];
//if there is a 5th argument, set the delim
if(arraylen(arguments) GT 4)
delim = arguments[5];
//loop over the array of structs, building the sortArray
for(ii = 1; ii lte count; ii = ii + 1)
sortArray[ii] = aOfS[ii][key] & delim & ii;
//now sort the array
arraySort(sortArray,sortType,sortOrder);
//now build the return array
for(ii = 1; ii lte count; ii = ii + 1)
returnArray[ii] = aOfS[listLast(sortArray[ii],delim)];
//return the array
return returnArray;
}
I don't have the reputation points to comment on #mikest34 post above but #russ was correct that this callback no longer works the way it was explained.
It was Adam Cameron who discovered that when using arraySort with a callback, it no longer requires a True/False response but rather:
-1, if first parameter is "smaller" than second parameter
0, if first parameter is equal to second parameter
1, first parameter is "bigger" than second parameter
So the correct callback is:
ArraySort(yourArrayOfStructs, function(a,b) {
return compare(a.struct_date, b.struct_date);
});
Testing and working in CF2016
The accepted solution (from CFLib.org) is NOT safe. I experimented with this for something I needed to do at work and found that it returns incorrect results when sorting numeric with floats.
For example if I have these structs: (pseudocode)
a = ArrayNew(1);
s = StructNew();
s.name = 'orange';
s.weight = 200;
ArrayAppend(a, s);
s = StructNew();
s.name = 'strawberry';
s.weight = 28;
ArrayAppend(a, s);
s = StructNew();
s.name = 'banana';
s.weight = 90.55;
ArrayAppend(a, s);
sorted_array = arrayOfStructsSort(a, 'weight', 'asc', 'numeric');
Iterate over the sorted array and print the name & weight.
It won't be in the right order, and this is a limitation of mixing
an arbitrary key with the value being sorted.
You can use the Underscore.cfc library to accomplish what you want:
arrayOfStructs = [
{myAttribute: 10},
{myAttribute: 30},
{myAttribute: 20}
];
_ = new Underscore();
sortedArray = _.sortBy(arrayOfStructs, function (struct) {
return struct.myAttribute;
});
Underscore.cfc allows you to define a custom comparator and delegates to arraySort(). You can use it for sorting arrays, structs, queries, or string lists, but it always returns an array.
(Disclaimer: I wrote Underscore.cfc)
I wanted to throw my two cents in here. I ran into a case where I needed to sort an array of structures using more than one key. I wound up using a constructed query to do my sorting. The function takes the array of structs as the first argument, and then an array of structs indicating the sort order, like this:
<cfset result = sortArrayOfStructsUsingQuery(myArrayOfStructs,[
{name = "price", type = "decimal", sortOrder = "asc"},
{name = "id", type = "integer", sortOrder = "asc"}
])>
Within the sortArrayOfStructsUsingQuery function, I construct a query based only on the keys I pass in, then sort that query. Then, I loop over the query, find the structure element from the array which matches the data at the current query row, and add that structure to the array I hand back.
It's entirely possible there's a gaping hole in this code that my testing hasn't uncovered (there haven't been a lot of use-cases for me yet), but in case it's useful to anybody, here it is. Hope it's useful, and if there are any glaring holes, I'm happy to hear about them.
(just a note: I use the "local" scope for all variables that will stay in the function, and the "r" scope for anything I intend to hand back, for whatever that's worth)
<cffunction name="sortArrayOfStructsUsingQuery" output="yes" returnType="array">
<cfargument name="array" type="array" required="true">
<cfargument name="sortKeys" type="array" required="true">
<cfset var local = {
order = {
keyList = "",
typeList = "",
clause = ""
},
array = duplicate(arguments.array),
newArray = []
}>
<cfset var r = {
array = []
}>
<cftry>
<!--- build necessary lists out of given sortKeys array --->
<cfloop array=#arguments.sortKeys# index="local.key">
<cfset local.order.keyList = listAppend(local.order.keyList, local.key.name)>
<cfset local.order.typeList = listAppend(local.order.typeList, local.key.type)>
<cfset local.order.clause = listAppend(local.order.clause, "#local.key.name# #local.key.sortOrder#")>
</cfloop>
<!--- build query of the relevant sortKeys --->
<cfset local.query = queryNew(local.order.keyList, local.order.typeList)>
<cfloop array=#arguments.array# index="local.obj">
<cfset queryAddRow(local.query)>
<cfloop list=#local.order.keyList# index="local.key">
<cfset querySetCell(local.query, local.key, structFind(local.obj, local.key))>
</cfloop>
</cfloop>
<!--- sort the query according to keys --->
<cfquery name="local.sortedQuery" dbtype="query">
SELECT *
FROM [local].query
ORDER BY #local.order.clause#
</cfquery>
<!--- rebuild the array based on the sorted query, then hand the sorted array back --->
<cfloop query="local.sortedQuery">
<cfloop from=1 to=#arraylen(local.array)# index=local.i>
<cfset local.matchP = true>
<cfloop list=#local.order.keylist# index="local.key">
<cfif structKeyExists(local.array[local.i], local.key)
AND structFind(local.array[local.i], local.key) EQ evaluate("local.sortedQuery.#local.key#")>
<cfset local.matchP = true>
<cfelse>
<cfset local.matchP = false>
<cfbreak>
</cfif>
</cfloop>
<cfif local.matchP>
<cfset arrayAppend(r.array, local.array[local.i])>
<cfelse>
<cfif NOT arrayContains(local.newArray, local.array[local.i])>
<cfset arrayAppend(local.newArray, local.array[local.i])>
</cfif>
</cfif>
</cfloop>
<cfset local.array = local.newArray>
</cfloop>
<!--- Outbound array should contain the same number of elements as inbound array --->
<cfif arrayLen(r.array) NEQ arrayLen(arguments.array)>
<!--- log an error here --->
<cfset r.array = arguments.array>
</cfif>
<cfcatch type="any">
<!--- log an error here --->
<cfset r.array = arguments.array>
</cfcatch>
</cftry>
<cfreturn r.array>
</cffunction>
It's actually even easier with the new CF Closure support.
Here's an example I worked on today where I wanted to sort an array of structs by a date stored in the struct. I was sorting in descending order.
ArraySort(yourArrayOfStructs, function(a,b) {
if ( DateCompare(a.struct_date, b.struct_date) == -1 ) {
return true;
} else {
return false;
}
});
I can't take total credit as I adapted this from Ray Camden's on Closures from 2012.
Here's a UDF based on Tomalak's answer that also supports custom objects (e.g., used by some Railo-based CMSs). This function is compatible with ColdFusion 9.
<cffunction name="sortStructArray" returntype="array" access="public">
<cfargument name="base" type="array" required="yes">
<cfargument name="sortType" type="string" required="no" default="text">
<cfargument name="sortOrder" type="string" required="no" default="ASC">
<cfargument name="pathToSubElement" type="string" required="no" default="">
<cfset var _sct = StructNew()>
<cfset var _aryKeys = ArrayNew(1)>
<cfset var arySorted = ArrayNew(1)>
<cfif IsStruct(base[1])>
<!--- Standard structure --->
<cfloop from="1" to="#ArrayLen(base)#" index="i">
<cfset _sct[i] = base[i]>
</cfloop>
<cfset _aryKeys = StructSort(_sct, sortType, sortOrder, pathToSubElement)>
<cfloop from="1" to="#ArrayLen(_aryKeys)#" index="i">
<cfset arySorted[i] = _sct[_aryKeys[i]]>
</cfloop>
<cfelse>
<!--- Custom object (e.g., Catalog) --->
<cfloop from="1" to="#ArrayLen(base)#" index="i">
<cfset _sct[i] = StructNew()>
<cfset _sct[i][pathToSubElement] = base[i][pathToSubElement]>
</cfloop>
<cfset _aryKeys = StructSort(_sct, sortType, sortOrder, pathToSubElement)>
<cfloop from="1" to="#ArrayLen(_aryKeys)#" index="i">
<cfset arySorted[i] = base[_aryKeys[i]]>
</cfloop>
</cfif>
<cfreturn arySorted>
</cffunction>
In case you don't want to use custom methods, Coldfusion has structSort method http://www.cfquickdocs.com/cf8/#StructSort . Yes it sorts structure with nested structures, BUT returns array so could be used to achieve same result.
Easy solution to sort an array of structures using more than one key using arraySort callback:
It takes array of structs to be sorted as first parameter and array of structs in format of sortkey/sortorder pair as second parameter e.g. [{sortkey: 'FirstName', sortorder: 'asc'}, {sortkey: 'LastName', sortorder: 'desc'}].
<cffunction name="arrayOfStructsSort" access="public" returntype="array" output="false" hint="This sorts an array of structures.">
<cfargument name="aOfS" type="array" required="yes" />
<cfargument name="key_sortOrder" type="array" required="yes" />
<cfscript>
arraySort(
aOfS,
function (a, b) {
for (var i = 1; i lte arrayLen(key_sortOrder); i = i + 1) {
var prop = key_sortOrder[i];
var key = prop.key;
var sortOrder = prop.sortOrder;
if (a[key] lt b[key]) {
if (sortOrder eq 'desc') {
return 1;
} else {
return -1;
}
}
if (a[key] gt b[key]) {
if (sortOrder eq 'desc') {
return -1;
} else {
return 1;
}
}
}
return 0;
}
);
return aOfS;
</cfscript>
</cffunction>
Simply call it with:
<cfset ArraySorted = arrayOfStructsSort(arrayToBeSorted,arrayOfSorkeys)>

Resources