I am using Ajax with ModelGlue in a ColdFusion Application. I want to make an Ajax call to return a value. I do not want to render any view. I just want a database interaction and bring back a value.
My Ajax call:
new Ajax.Request(root+'test.testFunction',{
method: 'post',
parameters: {param1:paramval},
onSuccess: function(response){
alert(response.responseText);
var myresult = response.responseText;
}
});
my modelglue event :
<event-handler name="test.testFunction">
<broadcasts>
<message name="testFunction" />
</broadcasts>
</event-handler>
and my controller function:
<cffunction name="testFunction" returnType="any" output="true" >
<cfargument name="event" type="any" required="true">
<cfset justtest = 1>
<cfreturn justtest>
</cffunction>
I am using prototype as my ajax library.
When i alert the responseText i am getting null value. Is this bcoz i have not included the view part in the event handler? If i included the view part then i wud have to create a new page which i dont want to do. Is it possible to get just a server value by ajax call without rendering any view?
I want to have myresult value as 1 according to the above scenario.
Pls help. Thnx for any help.
When you say you "just want to bring back a value" -- that's your "view". What you want to do is use a special view for your remote (ajax) event that just spits out the value. For example, if you want it to return JSON, you might do this:
Event Configuration:
<event-handler name="test.testFunction">
<broadcasts>
<message name="testFunction" />
</broadcasts>
<views>
<include name="body" template="renderJson.cfm" />
</views>
</event-handler>
Controller function:
<cffunction name="testFunction" returnType="any" output="true" >
<cfargument name="event" type="any" required="true">
<cfset event.setValue('justtest', 1) />
</cffunction>
renderJson.cfm:
<cfoutput>#serializeJson(event.getValue('justtest'))#</cfoutput>
If you're using Model-Glue 3, you can use the new Event Formats feature to piggy-back this ajax view on an existing event that does the same thing for a different view-format.
Try using this at the end of your controller function:
<CFCONTENT TYPE="text" RESET="Yes"><CFOUTPUT>#serializeJSON(justTest)#
<cfset request.modelGlueSuppressDebugging = true />
<cfsetting showdebugoutput="false" /></CFOUTPUT><cfabort>
So like this:
<cffunction name="testFunction" returnType="any" output="true" >
<cfargument name="event" type="any" required="true">
<cfset justtest = 1>
<CFCONTENT TYPE="text" RESET="Yes"><CFOUTPUT>#serializeJSON(justTest)#
<cfset request.modelGlueSuppressDebugging = true />
<cfsetting showdebugoutput="false" /></CFOUTPUT><cfabort>
</cffunction>
This will keep your current view and return 'justTest' as a json.
If you are using firefox you should be able to see the reponse from the server.
WARNING: if Coldfusion 8, and have OnRequest() in application.cfc, there may be a related known cf8 bug. For a workaround, see
http://www.coldfusionjedi.com/index.cfm/2008/3/19/Ask-a-Jedi-Ajaxbound-requests-and-onRequest
In this situation, you should really be calling the remote proxy of your service, bypassing the MVC framework. :)
Oh, and don't forget you can use <cfajaxproxy> if you're using CF8.
Related
We’ve recently moved to cf11 for a project and have hit on an unusual problem:
When a user lets their session timeout, and they try to login back in it takes two attempts for them to successfully log in.
When a user manually logs out, they have no problems logging in.
This problem didn’t happen in CF8. I’ve examined user scopes and cannot see a difference. I’ve tried adding the logout code before sign-in in the hope I can make the state the same. Neither has worked. Is this a known problem? And do you have a suggestion to what I can try?
EDIT:
I have an Application.cfc and result.cfm in the root of the project.
I have a signin/ folder for pages that are not logged in. That contains signin.cfm and onsignin.cfm that handles the authentication.
When running the code, wait for the session to time out before logging in again with the same user name again.
signin/signin.cfm
<form action="onsignin.cfm" method="POST" >
User name: <input name="login" type="text" />
<input type="submit" value="Login" name="btnSubmit" >
</form>
signin/onsignin.cfm
<cffunction name="authenticate" access="public" returntype="void">
<cfargument name="login" />
<cflogin idletimeout="3600">
<cfloginuser name="#arguments.login#" password="1234AbCd" roles="admin,developer,login_session,login_signoff" />
</cflogin>
<cfreturn />
</cffunction>
<cfscript>
authenticate(trim(Login));
writeOutput("At /signin/onsignin.cfm<br/>");
writeOutput("IsUserLoggedIn: #IsUserLoggedIn()#<br /><hr/>");
</cfscript>
<cflocation url="../result.cfm" addToken="no" /><!--- before we get to result.cfm onRequestStart() in Application.cfc is triggered. --->
Application.cfc
<cfcomponent>
<cfset THIS.Name = "LoginTest" />
<cfset THIS.SessionManagement = true />
<cfset THIS.ClientManagement = false />
<cfset THIS.LoginStorage = "session" />
<cfset THIS.setClientCookies = false />
<cfset This.sessiontimeout= createTimeSpan(0,0,0,20)/><!--- 20second timeout to show the session problem --->
<cffunction name="outputCurrentLoginState" access="private">
<cfargument name="currentFunction" type="string" required="true"/>
<cfargument name="TargetPage" type="string" required="true"/>
<cfscript>
writeOutput("In function: #arguments.currentFunction# state is <br />");
writeOutput("TargetPage: #arguments.TargetPage# <br />");
writeOutput("GetAuthUser: #GetAuthUser()#<br />");
writeOutput("GetUserRoles: #GetUserRoles()#<br />");
writedump(session);
writedump(cookie);
writeOutput("IsUserLoggedIn: #IsUserLoggedIn()#<br /><hr />");
/*use writeLog() to view to Console */
</cfscript>
</cffunction>
<cffunction name="OnRequestStart" access="public" returntype="boolean">
<cfargument name="TargetPage" type="string" required="true"/>
<cfset outputCurrentLoginState("OnRequestStart",arguments.TargetPage)/>
<cfif not IsDefined("Cookie.CFID")>
<CFLOCK SCOPE="SESSION" TYPE="exclusive" TIMEOUT="5">
<cfcookie name="CFID" value="#SESSION.CFID#" secure="false" httpOnly="true" />
<cfcookie name="CFTOKEN" value="#SESSION.CFTOKEN#" secure="false" httpOnly="true" />
</CFLOCK>
</cfif>
<cfreturn true />
</cffunction>
<cffunction name="OnRequest" access="public">
<cfargument name="TargetPage" type="string" required="true"/>
<cfset outputCurrentLoginState("OnRequest",arguments.TargetPage)/>
<cfif findNoCase("signin/", arguments.TargetPage)>
<cflogout/>
</cfif>
<cfinclude template="#ARGUMENTS.TargetPage#" />
</cffunction>
</cfcomponent>
result.cfm
<cfscript>
writeOutput("GetAuthUser: #GetAuthUser()#<br />");
writeOutput("GetUserRoles: #GetUserRoles()#<br />");
writeOutput("--- IsUserLoggedIn: #IsUserLoggedIn()#<br />");
writeOutput("At /result.cfm<hr/>");
</cfscript>
Update: Now I have the test code above that fails for cf11, I tried it on a cf8 server and in cf8 it works as I expect it to. When the session times out, the user does not have any problem creating a new session. It is only in cf11 were it fails.
I have a work around that will be good enough for my use case.
Old code:
<cflogin idletimeout="3600">
<cfloginuser name="#yourlogin#" password="#yourpassword#" roles="#yourroles#" />
</cflogin>
New code:
<cflogin idletimeout="3600" allowconcurrent="false">
<cfloginuser name="#yourlogin##createUUID()#" password="#yourpassword#" roles="#yourroles#" />
</cflogin>
Adding a random value to the cfloginuser name attribute should make everyone unique, even if they use the same credentials when you validate them against a database.
It’s a bit of a hack, but it gets around the issue.
Thank you for everyone’s help… Especially Miguel for picking up on allowconcurrent.
I've added the following comment on 3839458:
Another workaround is duplicate the cflogin. Example:
<cflogin ..>
<cflogin ..>
When both have allowconcurrent=true (the default), then both will run. isUserLoggedIn() returns YES after the 1st, but the 1st login actually fails. The 2nd runs and logs in correctly.
Repro attached as Application.cfc
When THIS.loginStorage="cookie", then the issue does not reoccur if an old cfauthorization cookie is still present.
Thanks!,
-Aaron
Update
There is already a bug opened with Adobe about this issue - Bug 3839458. I suggest you add your experience and vote on that bug. It currently has a status of "ToTrack" with a reason of "PRNeedInfo". I have voted for it as well and added a reference to this question for more info.
Promoted from the comments
I think the problem is that you have set the idletimeout attribute of the <cflogin> tag to a value (3600 seconds) which is longer than your session timeout value (20 seconds). That is not a valid approach. I realize that you have set the session timeout at 20 seconds for ease of testing. However, it does not make sense to have a longer idletimeout than session timeout when you are setting your loginStorage to be session. The <cflogin> tag is attempting to keep the user's session active but the session itself is being torn down outside of it's control. That is asking for trouble in my opinion. Keep the idletimeout setting less than or at least equal to the session timeout setting.
I suspect the difference you are seeing between ColdFusion versions might have to do with a new attribute that was added to the <cflogin> tag in ColdFusion 11. The allowconcurrent attribute (documentation reference). By default that setting is set to true meaning concurrent login sessions should be allowed. Try setting that attribute to false, as in, <cflogin idletimeout="3600" allowconcurrent="false">. I believe that will make the <cflogin> tag work like it used to in ColdFusion 8.
You mention in the comments that setting allowconcurrent to false is not an acceptable solution. Why? That is how it is working for you on ColdFusion 8. I feel that the behavior you are seeing is "working as designed". However, if you feel that this is a bug then please enter a bug for it with Adobe.
As an aside, I also noticed that you are setting ClientManagement and setClientCookies both to false. Yet in your OnRequestStart method you are setting those cookies. Why? They are not needed for session management. The ColdFusion server will handle that for you.
I am using Ajax on a CFC file. I can't get the result from the function that I am calling from Ajax. I set a debug message (Alert()), but can't see it. Any suggestion?
function getDelegateChartAcct(LocFund){
alert("#Arguments.PIUniversalID#");
alert($F("DelegateFund"));
new Ajax.Request( "?method=displayDelegateChartAcct",
{
parameters : {
PIUniversalID: "#Arguments.PIUniversalID#",
PILocFund: $F("DelegateFund")
},
method : "post"
}
);
}
<cffunction name="displayDelegateChartAcct" access="remote" output="true"
returntype="void">
<CFArgument name="PIUniversalID" type="string" required="true" />
<CFArgument name="LocFund" required="true" type="String" />
<CFSET var chartacctlist = runChartAcctDelegationQuery
(#Arguments.PIUniversalID#, #Arguments.LocFund#)>
<CFContent type="text/x-javascript">
alert(“Hi”);
// delegateChartAcctList();
// $("DelegateChartAcct").
// <CFOutput query="chartacctlist">
// $("DelegateChartAcct").insert( new Element(
"option", { value : "#JSStringFormat( chart_acct )#", selected :
// "selected" } ).update( "#JSStringFormat( chart_acct )#" ) );
// </CFOutput>
</cffunction>
Thanks,
Kefang
You are not returning anything from the CFC (and you can't run JavaScript inside the function you are calling remotely, either).
You have 2 ways you can handle this:
Load the form (or element) you want by making a remote call to a .cfm file that builds the form (or element) you want. You can use $("{place form will be displayed}").load("{url to .cfm page}) and jQuery will make an HTTP request adn load the result in the DOM element that matches the selector.
Change your CFC to return the query and populate the select box on the client side using JavsScript. (This is what I would do)
Your code would look like this:
<cffunction name="displayDelegateChartAcct" access="remote" output="true" returntype="query">
<cfargument name="PIUniversalID" type="string" required="true" />
<cafrgument name="LocFund" required="true" type="String" />
<cfset var chartacctlist = runChartAcctDelegationQuery (#Arguments.PIUniversalID#, #Arguments.LocFund#)>
<cfreturn chartacctlist />
</cfcomponent>
You could then use the following to load that data (looks like Prototype based on the syntax):
new Ajax.Request( "?method=displayDelegateChartAcct&returnFormat=json",
{
parameters : {
PIUniversalID: "#Arguments.PIUniversalID#",
PILocFund: $F("DelegateFund")
},
method : "post",
onSuccess: function(response) {
// code in here will populate select
}
}
);
The 'returnFormat=json' tells ColdFusion to return the results as JSON.
You would just need a JS handler to take that data and use it to populate the SELECT box.
I ran into an issue this morning after deploying of some files to a ColdFusion website/application.
I updated an existing CFC with some new code. The CFC has an init() method that returns the instantiated Object:
Original MyObject.cfc:
<cfscript>
VARIABLES.MyParam = "";
</cfscript>
<cffunction name="init" returntype="MyObject" output="false">
<cfargument name="MyParam" type="String" required="true" />
<cfscript>
VARIABLES.MyParam = ARGUMENTS.MyParam;
return THIS;
</cfscript>
</cffunction>
New MyObject.cfc:
<cfscript>
VARIABLES.MyParam = "";
</cfscript>
<cffunction name="init" returntype="MyObject" output="false">
<cfargument name="MyParam" type="String" required="true" />
<cfscript>
setMyParam(ARGUMENTS.MyParam);
return THIS;
</cfscript>
</cffunction>
<cffunction name="setMyParam" output="false" returntype="Void">
<cfargument name="MyParam" type="String" required="true" />
<cfset VARIABLES.MyParam = Trim(ARGUMENTS.MyParam) />
</cffunction>
<cffunction name="getMyParam" output="false" returntype="String">
<cfreturn VARIABLES.MyParam />
</cffunction>
Any time an Object that extended this CFC called init(), it was throwing an exception:
"The value returned from the init function is not of type MyObject."
This issue did not occur in any of the other environments in which this change was deployed - only in Production.
The only thing that fixed it was clearing the template cache in ColdFusion Administrator.
So, I'm either looking for a way to prevent this from happening in the future and/or a way to automatically clear the template cache when I deploy files.
FYI, I currently deploy files using Tortoise SVN.
In your init() (or more preferably, in another reload-style method), programmatically call the Admin API's clearTrustedCache() method:
<cfscript>
// Login is always required (if the administrator password
// is enabled in the ColdFusion Administrator).
// This example uses two lines of code.
adminObj = createObject("component","cfide.adminapi.administrator");
adminObj.login("admin");
// Instantiate the runtime object.
myObj = createObject("component","cfide.adminapi.runtime");
// clear cache
myObj.clearTrustedCache();
// Stop and restart trusted cache. However, only the clearTrustedCache function needs to be called.
myObj.setCacheProperty("TrustedCache", 0);
myObj.setCacheProperty("TrustedCache", 1);
</cfscript>
This functionality's been in place as far back as CF7 (Source). Note that you will need the CF Admin password for this.
I would also recommend clearing the component cache, if you have that option enabled in your admin:
myObj.clearComponentCache();
I'm new to wheels (and I'm sure I'll be posting here a lot) so bear with me.
I have two forms "register" and "login" under the controller of "user". So my URL's look like.
/user/register/
/user/login/
At the moment in my models folder I simply have user.cfc with validation for the "register" page inside the init method - this works just fine.
So essentially...my question is...regarding validation for my login form; do I have to always place validation into the init method or in a different one? If so, how do I do this? Each form of course has different fields...so I need to know some logic on detecting what form is currently in play.
Hope this makes sense. For reference, my user.cfc model currently looks like this:
<cfcomponent extends="Model" output="true">
<cffunction name="init">
<cfset validate( property='userName', method='validateAlphaNumeric') />
<cfset validatesPresenceOf( properties='userName') />
<cfset validatesUniquenessOf( properties='userName') />
<cfset validatesFormatOf( property='userEmail', type='email', message="Email address is not in a valid format.") />
<cfset validatesPresenceOf( properties='userEmail') />
<cfset validatesUniquenessOf( properties='userEmail') />
<cfset validatesPresenceOf( properties='userPassword') />
<cfset validatesConfirmationOf( property='userPassword') />
<cfset validatesLengthOf( property="userToken", allowBlank=true) />
</cffunction>
<cffunction name="validateAlphaNumeric" access="private">
<cfif REFind("[^A-Za-z0-9]", this.userName, 1)>
<cfset addError( property="userName", message="User name can only contain letters and numbers." ) />
</cfif>
</cffunction>
</cfcomponent>
Thanks,
Michael.
Michael,
You do need to put model validations in your init() method; Wheels requires that. However, I am not sure you would want or need to use model validation for the login page/call.
Unless I am missing something, you're not actually altering the model (i.e., creating a new or updating an existing user) when a user logs into a site. You are simply authenticating them (checking their username/password combo) against your DB values.
If it were me, I'd use client-side validation for login (fields are completed, etc.) and model validation for register.
HTH!
Craig
Go to this URL and scroll down to: Use "when", "condition", or "unless" to Limit the Scope of Validation
http://cfwheels.org/docs/1-1/chapter/object-validation
For your case, you could probably go with when="onCreate"
In my "link" model I have some basic validation, one of which is to check if the link the user is submitting is already in the database.
If the link IS already submitted to the database, I want to let them know that and forward them to the link that was previously submitted (to a URL basically).
How can I do this? My model looks like this so far:
<cfcomponent extends="Model" output="true">
<cffunction name="init">
<cfset validatesPresenceOf( property='linkURL') />
<cfset validatesFormatOf( property='linkURL', type='url', message="Your link isn't a valid URL.") />
<cfset validatesUniquenessOf( property='linkURL') />
</cffunction>
</cfcomponent>
Very basic. The validatesUniquenessOf() works great, but I'd like to do a bit more in my validation logic. If I was doing it without the framework...I'd of course do some standard logic, but I'd like to work the way wheels needs me to.
Thanks again CFWHEELS!
This falls out of the general use case for validatesUniquenessOf(), but there are ways around that using addError and errorsOn.
I would do this in the model:
<cfcomponent extends="Model">
<cffunction name="init">
<cfset validatesPresenceOf( property='linkURL') />
<cfset validatesFormatOf( property='linkURL', type='url', message="Your link isn't a valid URL.") />
<cfset validate("validateUniqueUrl") />
</cffunction>
<cffunction name="validateUniqueUrl" access="private">
<cfscript>
if (this.exists(where="linkURL='#this.linkURL#'")) {
this.addError(property="linkURL", name="linkExists", message="The link you entered already exists.");
}
</cfscript>
</cffunction>
</cfcomponent>
The reason I would do that is so that you'll have a named error to check for in the controller (called linkExists).
Then in your controller:
<cfcomponent extends="Controller">
<cffunction name="create">
<cfscript>
link = model("link").new(params.link);
local.linkExistsErrors = link.errorsOn(property="linkURL", name="linkExists");
if (link.save()) {
// Whatever you want to do on success
}
else if (ArrayLen(local.linkExistsErrors)) {
flashInsert(error=local.linkExistsErrors[1].message);
Location(url=link.linkURL, addToken=false); // Need to use Location or cflocation for hard-coded URLs
}
else {
// Whatever you want to do on other types of validation failures
}
</cfscript>
</cffunction>
</cfcomponent>
API Resources
errorsOn: http://cfwheels.org/docs/function/errorson
validate: http://cfwheels.org/docs/function/validate
addError: http://cfwheels.org/docs/function/adderror
Could you not supply the submitted url as a link in the message attribute of validatesUniquenessOf()? This way the user will get the error and will be able to follow the link in the message. Otherwise, I think you will need to use cflocation to send the user to the linUrl value if the validatesUniquenessOf() functions returns false.