Setting session variables with JavaScript in ColdFusion - coldfusion

I have a website with multiple tabs. Each tab runs a separate report based on a set of filters that take their values from session variables.
How things work now:
While the user is inside a report tab they can open a filter menu to select the options that they need to run their report (doctor names, locations, date, etc) and then they can hit the run button to get their report. When the user clicks "run" the form is saving the variables inside the session where they are available to run other reports without having to click "run" or define them again and again.
What I am trying to do:
Instead of having only a "run" button inside the form I need an "Apply" button that will set the session variables from the form without running the current report. This way the user can pre-define their variables without being forced to run a report they don't need.
I tried using ajax that calls a function outside my application which is setting up variables based on the user's selection.
My challenge is to get those variables back from the function in some format where I could use them in updating the current session variables.
This is a sample of my code:
The Apply button:
Apply
My Ajax Function:
function setSession(){
var formData = $('form').serialize();
$.ajax({
url:'/mod_example/components/exampleCFCs/xUtility.cfc?method=setSessionVariables',
data: formData
});
};
And part of my function:
<cfcomponent output="no">
<cffunction name="setSessionVariables" access="remote" returntype="any">
<cfargument name="docid" type="string" required="no">
<cfif isDefined('docid')>
<cfset session.doctorids = docid>
</cfif>
<cfif isDefined('docid')>
<cfreturn session.doctorids>
<cfelse>
<cfreturn 0>
</cfif>
</cffunction>
</cfcomponent>
What I need is to get the value of session.doctorids to be able to update my session variables with the new value.

It sounds like you have this utility cfc in a shared directory and you are calling it directly. As you've noticed, the problem with that is that you end up with multiple sessions. You can get around this issue be setting up a Facade cfc within your application and make your ajax calls to that cfc.
If you only want to expose the setSessionVariables then you could use this cfc:
<cfcomponent output="no">
<cffunction name="setSessionVariables" access="remote" returntype="any">
<cfset var xUtility = createObject('component','mod_example.components.exampleCFCs.xUtility')>
<cfreturn xUtility.setSessionVariables(argumentCollection=ARGUMENTS)>
</cffunction>
</cfcomponent>
If you want to expose all methods of the utility cfc, then you can extend it:
<cfcomponent output="no" extends="mod_example.components.exampleCFCs.xUtility">
</cfcomponent>
This would allow you to call methods on the utility cfc while maintaining a single session scope (per user of course).
EDIT:
Been a while since i've worked in wheels...but i remember not liking AJAX in the wheels framework. If you create a new subfolder and call it 'remoting' and put the facade in there, and drop an application.cfc in there that looks like this:
<cfcomponent >
<cfset this.name = 'whatever_your_wheels_app_name_is'>
<cfset this.SessionManagement=true>
</cfcomponent>
You should be able to use that facade and this application.cfc will piggyback on the existing application with the same name. The problem with this approach would be if the application times out, and a remote call is the first request to the application, then the wheels application scope might not get set up properly.
It would be best if you could extend the root application.cfc and just override the onRequestStart method so that the framework will ignore the request. To do that you would need to make a mapping in the cfadmin to the root of your project and use this for your remoting/application.cfc
<cfcomponent extends="mappingName.Application">
<cffunction name="onRequestStart">
<cfargument name="requestname" required="true" />
<cfset structDelete(this,'onRequest')>
<cfset structDelete(this,'onRequestEnd')>
<cfset structDelete(VARIABLES,'onRequest')>
<cfset structDelete(VARIABLES,'onRequestEnd')>
<cfreturn true>
</cffunction>
</cfcomponent>
The way that wheels uses `cfinclude' all over the place, you may need to look at this post about extending the appliciation: http://techblog.troyweb.com/index.php/2011/09/cfwheels-workarounds-numero-uno-application-proxy/
There are some wheels plugins (http://cfwheels.org/docs/1-1/chapter/wheels-ajax-and-you) that allow you to use the controller actions / views / routes via ajax so you could look into those also.

Related

How to callback to a method from another CFC method that has finished successfully

I am submitting a form to a CFC which in turn invokes a method in a different CFC. The initial CFC will then send an e-mail to the user confirming its all done. Its a bit like this:
<cfcomponent>
<cffunction name="FinalDemand" access="remote">
<cfinvoke component="AnotherCFC" method="AddToAudit" argumentCollection="#Arguments#">
<!--- I need to wait for confirmation from AnotherCFC here before continuing --->
<cfset APPLICATION.SendMail.ThankYou()/>
<cfset var Success = true>
<cfreturn Success>
</cffunction>
</cfcomponent>
The AnotherCFC is doing an update to a database. I'd really like that method to be able to tell the calling CFC that its done its work. At the moment it just returns a boolean like the CFC above to say all went well. If it doesn't return the boolean then we know it went wrong.
Is this possible at all? I don't mind using JQuery to acomplish it if need be.

User data is getting mixed up some of the time

I am building a website where I have followed MVC to manage my code without using any frameworks. I have put all of my queries inside cfcs and am initializing them inside my Application.cfm, storing them in application variables like below:
<cfset aplication.customerProfileObject=
createObject("component","cfc.customerprofile").init()>
To perform any query operations, I have made a function and then call it anywhere like this:
<cfset selectedCustomerOb =
application.customerProfileObject.getContactCustomerProfileDetail(session.userid)>
I don't know what is causing the issue, but sometimes a user accesses another user's data. How is that possible? Is it assessing another user's session data or have I initialized the cfc wrong?
Application settings are below:
<cfapplication name="MyDataSourceName"
sessionmanagement="Yes"
setclientcookies="yes"
setdomaincookies="yes"
loginstorage="session"
sessiontimeout="#CreateTimeSpan(0, 2,0,0)#">
CustomerProfile.cfc
<cfcomponent>
<cffunction name="init">
<cfreturn this>
</cffunction>
<cffunction name="getContactCustomerProfileDetail" returntype="query"
description="Returns customer contact details by contactid"
access="public">
<cfargument name="ccId" type="numeric" required="yes">
<cfquery name="getContactCustomerProfileDetail"
datasource="#Application.ds#"
dbtype="ODBC"
username="#Application.UserName#"
password="#Application.Password#">
<!-------My query here--->
</cfquery>
<cfreturn getContactCustomerProfileDetail>
</cffunction>
</cfcomponent>
As Adam says you need to do this:-
<cffunction name="getContactCustomerProfileDetail" returntype="query"
description="Returns customer contact details by contactid"
access="public">
<cfargument name="ccId" type="numeric" required="yes">
<cfset var getContactCustomerProfileDetail = false>
<cfquery name="getContactCustomerProfileDetail"
datasource="#Application.ds#"
dbtype="ODBC"
username="#Application.UserName#"
password="#Application.Password#">
<!-------My query here--->
</cfquery>
<cfreturn getContactCustomerProfileDetail>
</cffunction>
The reason you are getting the problem is because your CFC instance is in a shared scope (application) and you have not var'd the query variable. This means that it is getting set into the variables scope of the CFC instance. Which means that multiple threads can overwrite this value. By just varring the variable as I have shown you make the variable local to the function and so each call to that function creates a localised and thus thread-safe variable.
Basically you should var all local variables in functions as a matter of habit. This code would never pass code review anywhere I have worked.
You're not actually including the relevant bit of the code to answer this... which would be the code within getCustomerProfileDetail().
However I would assume you don't have all your variables VARed in it, which means they go in the CFC's variables scope, which is shared with every user in the application.
But, as I say, you're not giving us the correct info to really answer this accurately. I suggest updating your question to include the relevant code.

How can I avoid using SESSION variables in CFCs when these are used for DataSource and Database Schemas?

I'm trying to refactor all of my CFCs to avoid using SESSION and APPLICATION variables (not an easy task).
However, in this application, SESSION variables are used in every database call, since different logged in users may be accessing different databases and schemas:
<cfquery name="qEmployees" datasource="#SESSION.DataSourceName#">
SELECT *
FROM #SESSION.DatabaseSchema#.Employees
</cfquery>
I don't want to go through the trouble of passing these two SESSION variables to every method call that accesses the database. This is especially the case since I don't want to pass DSNs and Schema Names in remote AJAX calls.
What is best practice for doing this - for all Scopes that shouldn't be used in CFCs?
I think that since the datasource truly is variable I'd pass it into every function as an optional parameter and set the default value to a variables scoped dsn attribute. I'd set the variables scoped DSN in the CFC's constructor. That way you only have to pass in the DSN for the AJAX calls.
<cffunction name="doFoo" access="remote"...>
<cfargument name="dsn" type="String" required="false" default="#variables.datasource#" />
</cffunction>
I'd use the session scope of your app to store the users dsn name and use that var to pass to the AJAX call.
You should create an "init" method that will serve as a constructor for your CFC. You can then instantiate the CFCs and store them in a shared scope, most likely the application scope. From here, to use this CFC via AJAX, I typically will create a remote facade. Basically this is another CFC that will directly access the CFC instance in the application scope. It will implement the methods you need to access via Ajax, expose them using access="remote" giving your application access to the access="public" methods from the actual CFC. In this case it is generally accepted that the remote facade can access the application scope directly as part of the design pattern.
A simple example:
example.cfc:
<cfcomponent output="false">
<cffunction name="init" access="public" output="false" returntype="any">
<cfargument name="dsn" type="string" required="true" />
<cfset variables.dsn = arguments.dsn />
<cfreturn this />
</cffunction>
<cffunction name="doStuff" access="public" output="false" returntype="query">
<cfset var q = "" />
<cfquery name="q" datasource="#variables.dsn#">
select stuff from tblStuff
</cfquery>
<cfreturn q />
</cffunction>
</cfcomponent>
In your Application.cfc onApplicationStart() method:
<cfset application.example = createObject("component","example").init(dsn = "somedsn") />
remote.cfc:
<cfcomponent output="false">
<cffunction name="doStuff" access="remote" returntype="query">
<cfreturn application.example.doStuff() />
</cffunction>
</cfcomponent>
Can you set your datasource variables in the onRequest or onRequestStart functions in your Application.cfc
<cffunction name="onSessionStart">
<cfset session.dsn = _users_personal_dsn_ />
</cffunction>
<cffunction name="onRequestStart" >
<cfset dsn = "#session.dsn#" />
</cffunction>
<cfquery name="qEmployees" datasource="#dsn#">
SELECT *
FROM #SESSION.DatabaseSchema#.Employees
</cfquery>
etc.
not sure if that will work [not tested - actually feels a bit sloppy]
-sean
The scope you choose (for any variation of this question, not just for DSNs) should be based on whether the lifetime of the value is the same as the lifetime of the scope.
In our application, the DSN is just set once in the lifetime of the application, so we have an application.config struct that gets created (parsed from a file) in onApplicationStart, and within it is application.config.dsn
If your value really does change between sessions, but not over the life of a session, go ahead and use the session scope.
If your value could change for any given request, but not in the middle of a request, put it in the request scope.
That said, still heed ryan's advice and add optional arguments that only default to this value: being flexible is always the best.
My suggestion for this is to create a base class and then have your components that need database access extend that component. It doesn't have to be in the immediate parent hierarchy but somewhere down the line.
They goal is to do two things, keep the cfc abstracted from the main program and keep it easily configurable. This accomplishes both.
So your CFC that queries the database would look something like this :
<cfcomponent extends="DataAccessBase">
<cffunction name="myFunction" access="public" returntype="string">
<cfquery datasource="#getDSN()#" name="qStuff">select * from table</cfquery>
</cffunction>
The key above is the extends="DataAccessBase" portion. This adds the layer of abstraction where you can control the data access at one configurable point, but it's not tied to the application itself, leaving the component abstracted from where it's implemented.
Your DataAccessBase.cfc could look something like this:
<cfcomponent>
<cffunction name="loadSettings">
<cfparam name="request.settings" default="#structNew()#">
<cfparam name="request.settigns.loaded" default="false">
<cfif request.settings.loaded eq false>
<!--- load settings from resource bundle etc --->
<cfset request.settings.dsn = 'myDSN'>
<cfset request.settings.loaded = true>
</cfif>
</cffunction>
<cffunction name="getDsn" access="public" returntype="string">
<cfset loadSettings()>
<cfreturn request.settings.dsn>
</cffunction>
You can of course get more intricate with how you configure and store the settings etc, but that's out of scope of the question I think. :)
I don't see any reason to pass the DSN with every method call. Yes, it works, but it's not necessary. The components are developed with a built-in assumption of the datastructure so you know that it is not going to change from a addItem() call to a updateItem() call, thus its duplication of work which means additional points of failure. :P
Make sense?

Is onApplicationStart is good Idea in ColdFusion?

I have to use a Variable(Query Resultset) in ColdFusion, which will get the results from Other Application DB, and stores in Coldfusion Application.
The main idea is that I need to call the other Application DB only at Server startup time and cache the results in local. And I need to read the variable in other pages in my Application. I won't overwrite that variable in any page.
On googling I found that 'onApplicationStart' is useful to assign the variables at Application Startup time.
Is using the onApplicationStart fine or is there any other way? We can assign a variable at startup time(one time).
If onApplicationStart is fine: how to use? Maybe any link where it is explained clearly is helpful.
Well, it depends. How often will this query data be updated? If it really is unchanging, then onApplicationStart() is a fine place to put it. However, if it will change every so often, you can just tell Coldfusion to cache the query for a certain period of time, then you don't need to mess with onApplicationStart(), but rather when you call the query it will return the cached result automatically (within your specified time period).
Regardless, I would write a custom function to retrieve the data. Then it will be trivial to call it from onApplicationStart() or elsewhere.
Startup.cfc: (Named whatever you like)
<!--- Replace the datasource name with your db name --->
<cffunction name="getStartupQuery" hint="Returns a query recordset for startup">
<cfargument name="datasource" required="no" type="string" default="OtherAppDB">
<!--- Init the query variable --->
<cfset var result = queryNew("id")>
<!-- Get the query dataset --->
<cfquery name="result" datasource="#arguments.datasource#">
YOUR QUERY HERE
</cfquery>
<cfreturn result>
</cffunction>
Application.cfc: (Just the important parts)
<cffunction name="onApplicationStart">
<!--- init the startup.cfc, then retrieve the data
and save it to the application scope. Remember the component name must match
your component above --->
<cfset var startup = createObject("component", "startup")>
<cfset application.varFromOtherDB = startup.getStartupQuery()>
<cfreturn true>
</cffunction>
Now, you should be able to access this variable from any CFM or CFC in your application using:
<cfset myNewVar = application.varFromOtherDB>
or
#application.varFromOtherDB#
IF you use the onApplicationStart() method, I highly recommend implementing a method to reinit the application. For an example, see this other discussion.

Global Variables in ColdFusion

2 Questions -
In CF8 in the application.cfm I cold set a global variable
like so
<cfset DSN = "dej6_42">
I am now trying to adjust to the Application.cfc in CF10/Lucee and can not figure out how to set this same variable.
Here is my current Application.cfc
<cfcomponent output="false">
<cfset This.name = "My Application">
<cfset This.clientmanagement="True">
<cfset This.loginstorage="Session">
<cfset This.sessionmanagement="True">
<cfset This.sessiontimeout="#createtimespan(0,0,10,0)#">
<cfset This.applicationtimeout="#createtimespan(5,0,0,0)#">
<cfset DSN = "dej6_42">
</cfcomponent>
I have tried
<cfset This.DSN = "dej6_42">
Then tried to call in a separate page
<cfoutput>#Applicaton.DSN#</cfoutput>
I think from my research I will need to use both application.cfc and application.cfm to accomplish the above. *edit - I tried to add an include at the end of the application.cfc file to applciation.cfm and it did not work.
2 Question.
When I place any of the standard functions in the Application.cfc my site turns to a blank page
Here is that Application.cfc - I if I remove everything below the DSN set then it will display the site.
<cfcomponent output="false">
<cfset This.name = "My Application">
<cfset This.clientmanagement="True">
<cfset This.loginstorage="Session">
<cfset This.sessionmanagement="True">
<cfset This.sessiontimeout="#createtimespan(0,0,10,0)#">
<cfset This.applicationtimeout="#createtimespan(5,0,0,0)#">
<cfset DSN = "dej6_42">
<cffunction name="onApplicationStart">
</cffunction>
<cffunction name="onApplicationEnd">
</cffunction>
<cffunction name="onRequestStart">
</cffunction>
<cffunction name="onRequest">
</cffunction>
<cffunction name="onRequestEnd">
</cffunction>
<cffunction name="onSessionStart">
</cffunction>
<cffunction name="onSessionEnd">
</cffunction>
<cffunction name="onError">
</cffunction>
</cfcomponent>
Your example doesn't set a global variable. It sets a variable in the variables scope: it will not be accessible to any CFC-based code nor any custom tags used within the request. It'll only be available in the Application.cfm, the file requested, files it includes, and OnRequestEnd.cfm
Application.cfc is a CFC (to state the obvious), so variables-scoped variables set within it are only available within it. If you want to set an application-wide variable, you need to put it in the application scope. Application scope variables should be set in the onApplicationStart) handler which is run once when the application starts, but not on every request. By way of comparison Application.cfm (which is misnamed) is run on every request. It should be called OnRequestStart.cfm.
So to be clear, setting an application-scoped variable in onApplicationStart would be as thus:
function onApplicationStart() {
application.DSN = "dej6_42";
}
If you use an onRequest() interceptor, and within that include the originally requested file, then the request will be run in the context of the Application.cfc instance, and variables set within onRequest will be available to the rest of the mainline request code, much like the way you set your variable in Application.cfm. Semantically though, if you mean a variable to exist for the life of the application (like a DSN), then putting it in the application scope is the best bet.
It sounds to me from the inferences one can make from your question that your app architecture might be languishing in the 1990s. I think you should read up on using a framework (eg: FW/1 or ColdBox) to better organise your code in a maintainable and scalable way.
Also you should read up on Application.cfc (and method reference). And probably CFCs in general: Using ColdFusion components-Developing guide.
You also might want to think about modernising your approach to writing CFML and spare the tags for view code, and otherwise using script. It makes the code easier to follow for both you and other developers who might end up needing to maintain it if the whole app isn't cluttered up with tags.
You need to set it into the application scope
<cfcomponent output="false">
<cfset This.name = "My Application">
<cfset This.clientmanagement="True">
<cfset This.loginstorage="Session">
<cfset This.sessionmanagement="True">
<cfset This.sessiontimeout="#createtimespan(0,0,10,0)#">
<cfset This.applicationtimeout="#createtimespan(5,0,0,0)#">
<cfset application.DSN = 'dej6_42'>
</cfcomponent>