I want to be able to dynamically write a set of getters and setters in CFML/LUCEE components ( No hardcoded cfproperty tags).
<!--- MyComp.cfc --->
<cfcomponent displayname="MyComp" hint="MyComp" accessors="true">
<cffunction name="init">
<cfargument name="dynamicprops" type="array">
<cfloop array="#dynamicprops#" index="item">
<!---
Now what? I cannot do a cfsavecontent and write props here.
It demands cfproperty just after the cfcomponent begins. I
tried to do with closures but they are not acually setters
and getters. Does anyone know how to better do it?
--->
</cfloop>
</cffunction>
</cfcomponent>
<!--- example call --->
<cfset mc = CreateObject("component","MyComp").init( [{"name"="a","default"=1}] ) />
Then I want to be able to call mc.setA( 100 ) and mc.getA(). But does not happen.
So my humble question is how can I dynamically write setters and getters on component?
PS: Please remeber that I have tried the closure way:
variables[item.name] = item.default;
variables["set"&item.name] = function(_val){ variables[item.name] =_val; }
variables["get"&item.name] = function(){ return variables[item.name; }
Couldn't get worked. How can I do it?
Thanks :)
You could use onMissingMethod() for this.
component name="myComponent" hint="myComponent.cfc"{
function init( struct dynamicProperties={} ){
dynamicProperties.each( function( name, default ){
variables[ name ] = default;
} );
return this;
}
function onMissingMethod( name, args ){
if( name.Left( 3 ) IS "get" )
return get( name );
if( ( name.Left( 3 ) IS "set" ) AND ( args.Len() IS 1 ) )
return set( name, args[ 1 ] );
cfthrow( type="NonExistentMethod", message="The method '#name#' doesn't exist" );
}
public any function get( required string accessorName ){
var propertyName = parsePropertyName( accessorName );
if( !variables.KeyExists( propertyName ) )
cfthrow( type="NonExistentProperty", message="The property '#propertyName#' doesn't exist" );
return variables[ propertyName ];
}
public void function set( required string accessorName, required any value ){
var propertyName = parsePropertyName( accessorName );
if( !variables.KeyExists( propertyName ) )
cfthrow( type="NonExistentProperty", message="The property '#propertyName#' doesn't exist" );
variables[ propertyName ] = value;
}
private string function parsePropertyName( accessorName ){
return accessorName.RemoveChars( 1, 3 );
}
}
Pass it your struct of property names/default values and it will "listen" for getters/setters that match. Any that don't will result in an exception.
<cfscript>
myDynamicProperties = { A: 0, B: 0 }; // this is a struct of names and default values
mc = new myComponent( myDynamicProperties );
mc.setA( 100 );
WriteDump( mc.getA() ); // 100
WriteDump( mc.getB() ); // 0
WriteDump( mc.getC() ); // exception
</cfscript>
UPDATE 1: Property name array replaced with name/default value struct as init argument to allow default values to be set.
UPDATE 2: If you want to pass an array of structs containing your name/default value pairs e.g.
dynamicProperties = [ { name: "A", default: 1 }, { name: "B", default: 2 } ];
then the init() method would be:
function init( array dynamicProperties=[] ){
dynamicProperties.each( function( item ){
variables[ item.name ] = item.default;
} );
return this;
}
UPDATE 3: If you must use tags and <cfloop> to set your dynamic properties then this is all you need in your init method:
<cfloop array="#dynamicProperties#" item="item">
<cfset variables[ item.name ] = item.default>
</cfloop>
onMissingMethod won't fire if you try to invoke the dynamic methods as properties like this:
method = mc[ "set#property#" ];
method( value );
Instead just make the set() and get() methods in the component public and invoke them directly:
mc.set( property, value );
mc.get( property );
Consider using accessors
<cfcomponent displayname="MyComp" hint="MyComp" accessors="true">
Source: https://helpx.adobe.com/coldfusion/cfml-reference/coldfusion-tags/tags-c/cfcomponent.html
Related
I have a function utilizing an include, of a JSP file, to retrieve thread information which is then converted into a query object. The function returns an empty query Lucee, but it executes properly in ColdFusion.
CFML:
<cffunction name="mainThreads" output="false" returntype="query" access="public">
<cfargument name="filterPages" type="boolean" required="true">
<cfscript>
var threadStackDump = "";
var thread = 0;
var stackTrace = "";
request.threads = arraynew(1);
GetPageContext().include("putParentThreadInRequestScope.jsp");
ThreadQuery = QueryNew("id, name, group, stacktrace, alive", "Integer, VarChar, VarChar, VarChar, Bit");
QueryAddRow(ThreadQuery, arrayLen(request.threads));
for ( thread = 1; thread lte arrayLen(request.threads); thread = thread + 1 )
{
QuerySetCell(ThreadQuery, "id", request.threads[thread].getId(), thread);
QuerySetCell(ThreadQuery, "name", request.threads[thread].getName(), thread);
QuerySetCell(ThreadQuery, "group", request.threads[thread].getThreadGroup().getName(), thread);
QuerySetCell(ThreadQuery, "alive", request.threads[thread].isAlive(), thread);
threadStackDump = "";
stackTrace = request.threads[thread].getStackTrace();
for ( element = 1; element lte arrayLen(stackTrace); element = element + 1 )
if ( arguments.filterPages )
{
if ( findNoCase('runPage',stackTrace[element]) neq 0 or findNoCase('runFunction',stackTrace[element]) neq 0 )
threadStackDump = threadStackDump & stackTrace[element] & "#chr(13)#";
}
else
threadStackDump = threadStackDump & stackTrace[element] & "#chr(13)#";
QuerySetCell(ThreadQuery, "stacktrace", threadStackDump, thread);
}
return ThreadQuery;
</cfscript>
</cffunction>
JSP
<%
ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
Thread threadList[]=new Thread[Thread.activeCount()];
threadGroup.enumerate(threadList);
request.setAttribute("threads", threadList);
%>
The code is not working in Lucee, but I'm not sure why. Does it have something to do with the java versions?
I'd guess you forgot to enable handling of .jsp in your web.xml, but ... you're not aware of it because the problem code executes inside a cffunction that suppresses all output!
Take a leaf out of Troubleshooting 101 and test the problem code in small chunks. Start by executing the JSP include separately. If it displays the JSP code on screen, instead of executing it, then you know JSP handling isn't enabled, and that's your problem.
<cfscript>
GetPageContext().include("putParentThreadInRequestScope.jsp");
writeDump( request );
</cfscript>
I would like to loop over query and compare column values. Here is example of CFML code:
<cfquery name="qryUserPerm" datasource="#Application.dsn#">
SELECT AccessType, AccessLevel, State, City, Building
FROM Permissions
WHERE AccountID = <cfqueryparam cfsqltype="cf_sql_integer" value="#trim(session.AccountID)#">
</cfquery>
<cfset local.permissionType = "">
<cfset local.permissionLevel = "">
<cfset local.permissionList = "">
<cfif qryUserPerm.AccessLevel EQ "S">
<cfset local.permissionType = qryUserPerm.AccessType>
<cfset local.permissionLevel = qryUserPerm.AccessLevel>
<cfset local.permissionList = qryUserPerm.State>
<cfelseif qryUserPerm.AccessLevel EQ "C">
<cfset local.permissionType = qryUserPerm.AccessType>
<cfset local.permissionLevel = qryUserPerm.AccessLevel>
<cfset local.permissionList = ListRemoveDuplicates(ValueList(permissionList,qryUserPerm.City))>
<cfelseif qryUserPerm.AccessLevel EQ "B">
<cfset local.permissionType = qryUserPerm.AccessType>
<cfset local.permissionLevel = qryUserPerm.AccessLevel>
<cfset local.permissionList = ListRemoveDuplicates(ValueList(permissionList,qryUserPerm.Building))>
</cfif>
Code above should be translated to cfscript, I got this far but can't figure it out how to access column values.
<cfscript>
public string function permissionList(required string AccountID) {
local.fnResults = "";
local.permissionList = "";
try{
local.qryPermissions = new Query();
local.qryPermissions.setDatasource("#Application.dsn#");
local.qryPermissions.setSQL("SELECT AccessType, AccessLevel, State, City, Building FROM Permissions WHERE AccountID = :AccountID");
local.qryPermissions.addParam(name="AccountID",value="#trim(arguments.AccountID)#",cfsqltype="cf_sql_idstamp");
local.qryRes = qryPermissions.execute();
for ( i = 1 ; i <= qryRes.getResult().recordCount ; i++ ) {
if(qryRes["AccessLevel"][i] EQ "S"){
local.permissionList = "";
}else if(qryRes["AccessLevel"][i] EQ "S"){
local.permissionList = ListRemoveDuplicates(ValueList(qryRes.Agency,","));
}else if(qryRes["AccessLevel"][i] EQ "C"){
local.permissionList = ListRemoveDuplicates(ValueList(qryRes.District,","));
}else if(qryRes["AccessLevel"][i] EQ "B"){
local.permissionList = ListRemoveDuplicates(ValueList(qryRes.Building,","));
}
}
local.fnResults = permissionList;
}catch(any e){
local.fnResults = e.message;
//writeOutput(e.message);
}
return fnResults;
}
writeOutput(permissionList(AccountID));
</cfscript>
If anyone can help please let me know.
(From comments ...)
The issue is local.qryRes doesn't actually contain a query object. Confusingly, calling execute() doesn't return a query, but calling execute().getResult() does. Try changing the assignment from:
local.qryRes = qryPermissions.execute();
To:
local.qryRes = qryPermissions.execute().getResult();
A few other observations:
It is important to local scope ALL function variables, including your loop index i. Otherwise, you may get some bizarre and unpredictable results if the component is stored in a shared scope.
Although I don't think a loop is necessary, if you do loop, consider the simpler for..in syntax, instead of an indexed loop:
for (local.row in local.qryPermissions ) {
if (local.row.AccessType eq "S") {
//... code here
}
....
}
Since the access fields are so closely related, I'd probably have the function return a structure containing all three keys (AccessType, AccessLevel, PermissionList) rather than having three separate functions.
Rather than using a loop, consider going with one of the suggestions on your other thread,
Best way to store permissions for the user account?
You can also use :
local.qryPermissions = queryExecute(
"SELECT AccessType, AccessLevel, State, City, Building
FROM Permissions
WHERE AccountID = :AccountID" ,
{AccountID={value="#trim(arguments.AccountID)#", cfsqltype="cf_sql_idstamp"}} // Or "?" and "[value=xxx,cfsqltype=xxx]"
) ;
And then just build out your permissions pieces without the loop:
local.permissionType = qryPermissions.AccessType ;
local.permissionLevel = qryPermissions.AccessLevel ;
switch( qryPermissions.AccessLevel ) {
case "S" : local.permissionList = qryPermissions.State ;
break ;
case "C" : local.permissionList = ListRemoveDuplicates(ValueList(qryPermissions.City)) ;
break ;
case "B" : local.permissionList = ListRemoveDuplicates(ValueList(qryPermissions.Building)) ;
break ;
}
Also see my notes on the other question about potential for unintentional, semi-related data.
We are using Coldfusion 9.
Is there a simple way to know if enablecfoutputonly has been set to true during a particular request?
I cannot test with CF9 right now, but in CF10 it is accessible from getPageContext() by checking the output object:
<cfscript>
out = getPageContext().getOut();
// Is the cfsetting enablecfoutputonly value currently true?
isSettingEnabled = out.getDisableCount() > 0;
WriteOutput("isSettingEnabled="& isSettingEnabled &"<br>");
// Is output currently allowed?
isOuputtingEnabled = out.getDisableCount() == 0 || out.getOutputCount() > 0;
WriteOutput("isOuputtingEnabled="& isOuputtingEnabled &"<br>");
</cfscript>
.. or using reflection:
<cfscript>
out = getPageContext().getOut();
internalMethod = out.getClass().getDeclaredMethod("isOutputEnabled", []);
internalMethod.setAccessible( true );
isOuputtingEnabled = internalMethod.invoke( out, [] );
// is output currently allowed?
WriteOutput("isOuputtingEnabled="& isOuputtingEnabled);
</cfscript>
I have many structures, I'm using StructFindValue() to determine if a a key occurs many times.
I get the expected array returned for "singles", however I get an empty array for "doubles" and "triples" - this code is actually inside a different code segment, -my is a structure in itself...
If I try ANY of the lines "x= StructFindValue( y, 3, 'all' );" in a stand alone template - CF finds the '2' and '3' values just fine - I get an array with the data - but inthe above code - ONLY the values of '1' return anything...
I'm confused.
UPDATE
OK, in response to the request for more information, my server details are:
Version ColdFusion 10,282462
Edition Developer
Operating System Windows XP
Java Version 1.6.0_29
OS Version 5.1
Update Level /C:/ColdFusion10/cfusion/lib/updates/chf10000002.jar
Adobe Driver Version 4.1 (Build 0001)
As you can see from the code example below, my array has a few different names, of differing repetitive nature. I want to know which values repeat a specified number of times. As stated above, the structkeyFind() works IF (and only) on the key value of 1. It doesn't work with the key values of 2, 3 (or 4 or 5 etc, not included here for brevity).
<cfscript>
_myArry = listToArray('bob,bob,bob,joe,jane,jane,john,john,john,alex,greg');
_myStats = getDataStats( _myArry );
writeDump( _myStats );
</cfscript>
<cffunction name="getDataStats">
<cfargument name="data" required="yes" type="array" >
<cfscript>
var _hv = {};
// default some values
_hv.vals = {};
_hv.threes = false;
_hv.twos = false;
_hv.ones =false;
// loop the data put it into separate containers
for ( var i=1; i LTE arrayLen( arguments.data ); i++ ) {
switch ( lcase( arguments.data[i] ) ) {
case 'bob': // bob
if ( structKeyExists( _hv.vals, 'bob' ) ) { _hv.vals.bob = _hv.vals.bob + 1; }
else { _hv.vals.bob = 1; }
break;
case 'joe': // joe
if ( structKeyExists( _hv.vals, 'joe' ) ) { _hv.vals.joe = _hv.vals.joe + 1; }
else { _hv.vals.joe = 1; }
break;
case 'jane': // jane
if ( structKeyExists( _hv.vals, 'jane' ) ) { _hv.vals.jane = _hv.vals.jane + 1; }
else { _hv.vals.jane = 1; }
break;
case 'john': // john
if ( structKeyExists( _hv.vals, 'john' ) ) { _hv.vals.john = _hv.vals.john + 1; }
else { _hv.vals.john = 1; }
break;
case 'alex': // alex
if ( structKeyExists( _hv.vals, 'alex' ) ) { _hv.vals.alex = _hv.vals.alex + 1; }
else { _hv.vals.alex = 1; }
break;
case 'greg': // greg
if ( structKeyExists( _hv.vals, 'greg' ) ) { _hv.vals.greg = _hv.vals.greg + 1; }
else { _hv.vals.greg = 1; }
break;
}
}
// give me a return struct for testing so i can 'see' where I'm at
var _thisReturn = {
'threes' = StructFindValue( _hv.vals, 3, 'all' ),
'twos' = StructFindValue( _hv.vals, 2, 'all' ),
'ones' = StructFindValue( _hv.vals, 1, 'all' ),
'values' = arguments.data
};
</cfscript>
<cfreturn _thisReturn />
</cffunction>
In an attempt to 'cast' the values, I have tried each of these variations. However the results are UNCHANGED from the original.
'ones' = StructFindValue( _hv.vals, '1', 'all' ),
'twos' = StructFindValue( _hv.vals, '2', 'all' ),
'threes' = StructFindValue( _hv.vals, '3', 'all' ),
And then
'ones' = StructFindValue( _hv.vals, val( 1 ), 'all' ),
'twos' = StructFindValue( _hv.vals, val( 2 ), 'all' ),
'threes' = StructFindValue( _hv.vals, val( 3 ), 'all' ),
The issue here appears to be how CF is storing / displaying / comparing the values.
Here is a simple demonstration of the problem:
<cfset Data =
{ Bob : 2
, Joe : 1+1
, Jane : "2"
, John : 2.0
, Alex : 4/2
} />
<cfdump var=#Data# />
<cfdump var=#StructFindValue(Data,2,"all")# />
<cfdump var=#StructFindValue(Data,"2","all")# />
<cfdump var=#StructFindValue(Data,2.0,"all")# />
The first dump displays all values as 2 except for John who is 2.0
However, the first two StructFindValue calls both only return Bob,Jane.
The third StructFindValue call returns Joe,John, Alex.
This basically demonstrates that CF's StructFindValue does a very crude comparison for checking equality, and basically isn't to be trusted when it comes to dealing with numbers.
(The issue doesn't exist with Railo, which probably uses the exact same comparison as it would when doing an EQ test, coercing types accordingly. Only tested on CF10,0,0,282462.)
To solve your problem, it seems you may need to manually walk the struct and replicate the behaviour of StructFindValue yourself.
I have been using ColdFusion 8 / 9 / 10 regularly. The code below works just great in CF9 and CF10. (I developed it in 9). It does NOT work in CF8 though.
If you run the code below (at the bottom) in CF9 and CF10, you should get the HTML results immediately below:
<select>
<option value="1">Option 1</option>
<option value="2">Option 2</option>
<option selected="" value="3">Option 3</option>
</select>
If you run the code below in CF8, you'll get this error:
The SELECTED parameter to the WrapOption function is required but was not passed in.
In CF8, how would I modify this code to make the "selected" parameter (or any other parameter) optional in CF8?
<cfscript>
Options = WrapOption("Option 1", 1);
Options = Options & WrapOption("Option 2", 2);
Options = Options & WrapOption("Option 3", 3, "Selected");
SelectBox = WrapSelect(Options);
writeOutput(SelectBox);
// WRAP OPTION
function WrapOption(Content, Value, Selected) {
LOCAL.Content = ARGUMENTS.Content;
LOCAL.Properties = " value='#ARGUMENTS.Value#'";
// SELECTED
if (structKeyExists(ARGUMENTS, "Selected")) {
LOCAL.Properties = LOCAL.Properties & " selected";
}
LOCAL.Item = "<option #LOCAL.Properties#>#LOCAL.Content#</option>";
return LOCAL.Item;
}
// WRAP SELECT
function WrapSelect(Options, Class, ID) {
LOCAL.Options = ARGUMENTS.Options;
LOCAL.Properties = "";
// CLASS
if (structKeyExists(ARGUMENTS, "Class")) {
LOCAL.Properties = LOCAL.Properties & " class='#ARGUMENTS.Class#'";
}
// ID
if (structKeyExists(ARGUMENTS, "ID")) {
LOCAL.Properties = LOCAL.Properties & " id='#ARGUMENTS.ID#'";
}
LOCAL.Item = "<select #LOCAL.Properties#>#LOCAL.Options#</select>";
return LOCAL.Item;
}
</cfscript>
In CFSCRIPT, named arguments are required unless provided with a default (which can't be done until CF9).
To do optional arguments in CFSCRIPT in ColdFusion 8 and below, you need to remove the argument from the function definition and check for its existence in the body of the function. You can do this by taking advantage of ColdFusion's handling of ordinal (ordered instead of named) arguments.
function WrapOption(Content, Value) {
if ( ArrayLen(Arguments) GTE 3 ) {
ARGUMENTS.Selected = ARGUMENTS[3];
}
LOCAL.Content = ARGUMENTS.Content;
LOCAL.Properties = " value='#ARGUMENTS.Value#'";
// SELECTED
if (structKeyExists(ARGUMENTS, "Selected")) {
LOCAL.Properties = LOCAL.Properties & " selected";
}
LOCAL.Item = "<option #LOCAL.Properties#>#LOCAL.Content#</option>";
return LOCAL.Item;
}
Sean is correct:
Names of the arguments required by the function. The number of
arguments passed into the function must equal or exceed the number of
arguments in the parentheses at the start of the function definition.
If the calling page omits any of the required arguments, ColdFusion
generates a mismatched argument count error.
Quoted from: http://livedocs.adobe.com/coldfusion/8/htmldocs/UDFs_03.html
I guess you can rewrite it in CFML then it'll work for sure.
// WRAP OPTION
<cffunction name="WrapOption" output="false">
<cfargument name="Content" required="true">
<cfargument name="Value" required="true">
<cfargument name="Selected">
<cfscript>
LOCAL.Content = ARGUMENTS.Content;
LOCAL.Properties = " value='#ARGUMENTS.Value#'";
// SELECTED
if (structKeyExists(ARGUMENTS, "Selected")) {
LOCAL.Properties = LOCAL.Properties & " selected";
}
LOCAL.Item = "<option #LOCAL.Properties#>#LOCAL.Content#</option>";
return LOCAL.Item;
<cfscript>
</cffunction>
Or alternatively as a workaround for CF8, do not define the selected value in the function declarator. Just check if arguments[3] is defined. Make sure you document what is expected for arguments[3] in the comment.
p.s. don't forget, you need to Make your own LOCAL scope in CF8... i.e. var LOCAL = {}