How to build ajax paths for .cfc files in a plugin? - coldfusion

In my plugin, I have defined a custom remote function. Here are the contents:
<cfcomponent output="false">
<cffunction name="getPersons" access="remote" output="false">
<cfset var local = {}>
<cfquery name="local.getPersons" >
SELECT FirstName FROM Persons
</cfquery>
<cfreturn local.getPersons>
</cffunction>
</cfcomponent>
This function is defined in file mytest.cfc that is stored under displayObjects/cfc directory.
In my main.cfm file that is stored in displayObjects directory, I need to make a ajax call to the function in mytest.cfc.
I am wondering how can I build proper path.
Calling #$.getCurrentURL()# returns:
http://localhost:8888/mymura/default/
However, I would somehow need to build and specify the following specify URL path to my ajax query:
http://localhost:8888/mymura/plugins/MyTest1_2/displayObjects/cfc/mytest.cfc
Can someone please tell me what is the recommended way to store plugin-specific .cfc files and how to properly build full path to access them?
Thank you in advance for your help.
Regards,
Peter
EDIT: I am almost there:
<cfoutput>
My other file
</cfoutput>
This returns:
http://localhost:8888/mymura/default/MyTest1_2/displayObjects/cfc/mytest.cfc
Instead of:
http://localhost:8888/mymura/plugins/MyTest1_2/displayObjects/cfc/mytest.cfc
Method $.currentURL() returns localhost:8888/mymura/default. I just need to find a way to traverse up one directory and append "plugins."
Any help is appreciated.

You can construct the URL using the domain setting of the site:
$.siteConfig().getDomain() & "/plugins/" & pluginConfig.getDirectory() & /displayObjects/cfc/myquery.cfc

Related

File Not Found in CF11 even though file exists

Well, I have been experiencing same error as discussed here:
Coldfusion 10 File Not Found Error
I am using Coldfusion 11 , developer edition on my Laptop , Windows 8.1 Pro (OS).
People have suggested two approaches over there to overcome this problem:
1) Setting Missing Template in CFAdmin
2) Setup onMissingTemplate function in Application.cfc
I am basically not sure with any of the approaches , however, I would like to go with the first approach. Could anyone tell me how should I set up missing template in CFadmin?
Why do you prefer letting the server handle the missing template? Myself, I like to handle it on a per-application basis. Some applications should never have links leading to non-existing files, others may operate on that as part of its core.
Straight from adobe's docs, you can use (I accidentally pulled this info from cf8 docs, but the link is to current docs and the result is largely the same.
<cffunction name="onMissingTemplate" returnType="boolean">
<cfargument type="string" name="targetPage" required=true/>
...
<cfreturn BooleanValue />
</cffunction>
For a few of my projects, I've written a CMS (Content-Management-System) that stores all the content in the database in fashion.
CMSPages
------------
PID PTitle PFile PContent
1 Home /index.cfm <b>Hey!</b> Welcome to our gollygizmo website.
And then I (my real code actually uses cfincludes rather than directly in the document. You can do it either way, but it was easiest for me to demonstrate with inline code).
<cffunction name="onMissingTemplate" returnType="boolean">
<cfargument type="string" name="targetPage" required=true/>
<cftry>
<cfquery name="FindPage">
select * from CMSPages
where pFile = <cfqueryparam cfsqltype="nvarchar" value="#Arguments.targetPage#">
</cfquery>
<cfif FindPage.recordcount eq 1>
<cfoutput query="FindPage">show page stuffs</cfoutput>
<cfreturn true>
<cfelse>
<!--- Page not found, log some stuff or email stuff
include cgi data so you know where the link came from --->
Hey, this page doesn't exist, sorry about that.
<cfreturn true>
</cfif>
<cfcatch>
<!--- Something went wrong, log/email error info and --->
<cfreturn false>
<!--- We return false here to pass it back to the default error handler, which can be a handler set in cfadmin. --->
</cfcatch>
</cftry>
</cffunction>
In such a scenario, it's probably beneficial to cache queries based on query name, you can do something like
<cfquery name="local.FindPage#hash(arguments.targetpage)#" cachedWithin="...">
...
</cfquery>
<cfset request.FindPage=local["Findpage#hash(arguments.targetpage)#"]>
So that the queries are cached by unique names, even though it's easily accesible in your document by a common name.
However, if you still prefer server-centric missing template handling, a simple search for cold fusion admin missing template will bring you here.
In the ColdFusion Administrator, click on "Settings" to view the "Server Settings" page
Specify the absolute path that ColdFusion will use to find your error handling template

Setting session variables with JavaScript in 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.

How do I access a UDF library in the APPLICATION scope using a shortened name?

I am using ColdFusion 8.0.1.
I created a UDF library and put it in a CFC. I load the library in the APPLICTION scope like this:
// CREATE STRUCTURE OBJECTS
if (not isDefined("APPLICATION.AppOBJ") or not isStruct(APPLICATION.AppOBJ)) {
APPLICATION.AppOBJ = structNew();
APPLICATION.AppOBJ.udf_library = createObject("component", "udf.udf_library");
}
The library works great! But I want to reduce the code needed to access the functions, to shorten the reference. Currently, I have to access the functions like this:
APPLICATION.AppOBJ.udf_library.myFunction();
I want to be able to reference this library object as "UDF", like this:
UDF.myFunction();
In another ColdFusion 9 project (Again, this is a CF8 project!), I am able to do this right after I create the ojbect
<cfset udf = APPLICATION.AppOBJ.udf_library>
In the current project, this doesn't work in the application.cfm file. It DOES however, work when I put it on the page that it is being used.
My question is how far upstream can I put this last line of code to have the variable available on any page in the application? Is there a difference between CF8 and CF9 for this type of thing? Is the difference because I am working in application.CFM versus application.CFC?
Thanks!!!
-- EDIT -- MORE INFORMATION ---
The files that I am trying to access the APPLICATION.AppOBJ.udf_library object are within a custom tag. Might that matter?
-- ANSWER -- THANKS TO MICAH AND BEN NADEL ---
I haven't tried this yet but I think it should work as the idea comes from Ben Nadel's blog entry entitled Creating Globally Accessible User Defined Functions In ColdFusion (Safer Version)
<cfcomponent output="false" hint="I define the application settings and event handlers.">
<!--- Define the application. --->
<cfset this.name = "TestApp" >
<cfset this.applicationTimeout = createTimeSpan( 0, 0, 5, 0 ) >
<!---
Add all of our "global" methods to the URL scope. Since
ColdFusion will automatically seach the URL scope for
non-scoped variables, it will find our non-scoped method
names.
--->
<cfset structAppend( url, createObject( "component", "udf.udf_library" ) ) >
</cfcomponent>
You should now be able to access MyFunction() globally.
If you want to access the function as UDF.MyFunction() then I think you should be able modify Ben's example to the following:
<cfset UDF = StructNew() >
<cfset structAppend( UDF, createObject( "component", "udf.udf_library" ) ) >
<cfset structAppend( url, UDF ) >

How to include UDF_library in application.cfm?

I am using ColdFusion 8.0.1
I am working on an existing application that has thousands of pages. I am trying to include a library of new UDFs in my application.cfm file.
I addedd this line to application.cfm:
<!--- UDF library include --->
<cfinclude template="UDF/udf_library.cfm">
The UDF library includes other files that contain UDFs, like this:
<cfinclude template="udf_powerreview.cfm">
I have functions in the udf_powerreview.cfm file, such as:
// CREATE POWER REVIEWS SNIPPET
function createPRSnippet(Page_ID) {
LOCAL.Page_ID = ARGUMENTS.Page_ID;
if (isNumeric(LOCAL.Page_ID) && LOCAL.Page_ID > 0) {
LOCAL.Snippet = "<div class='pr_snippet_product'><script type='text/javascript'>var pr_snippet_min_reviews = 0; POWERREVIEWS.display.snippet(document, { pr_page_id : '#LOCAL.Page_ID#' });</script></div>";
} else {
LOCAL.Snippet = "";
}
return LOCAL.Snippet;
}
The debugging tool says that UDF/udf_library.cfm and udf_powerreview.cfm are being successfully included.
The problem is when I call the function in another page, I get an error that says that function doesn't exist. When I can copy the function and put it directly into the page that it is used in and it works just fine. And, I do not get the error "routines can not be declared twice".
In every site that I build, I create a udf_library.cfm or udf_library.cfc in the exact same manner. They always work fine.
What might prevent the functions from being available and accessed? Is there an application setting that needs to be set?
It's a page scope issue. Don't think of the Application.cfm as an include on all your pages, just know that it runs first. Somethings it initializes will carry over to your existing page scope and some things won't. Using an Application.cfc instead of an application.cfm takes care of much of the ambiguity.
To make your UDF's available to your whole application, I would suggest using a "Singleton" Design pattern. First take your UDF's and put them in a CFC format. This will make them more portable.
in your application.cfm you could put the following lines:
<cfif NOT isdefined('session.udf_powerreview') or isdefined('url.resetudf')>
<cfset session.udf_powerreview = createobject('Component','udf.udf_powerreview')/>
<!--- this 'udf.udf_powerreview' represents the physical path udf/udf_powerreview.cfc --->
</cfif>
I'm stuffing it in the session scope instead of the application scope, becuase you won't have an good way of resetting the application scope if you modify your UDF's.
Either way, once this is in your application.cfm you should be able to see your functions on any page.
<cfdump var="#session.udf_powerreview#">
Here is one strategy that I use. This basically calls the UDFs "on demand". It won't reimport the UDFs if it already exists. You do however have to have named arguments however, otherwise you'd have to strip out the UDFName out of the argument collection. I'm worried however that argument order might not be preserved, I haven't investigated that.
application.cfm
<cfapplication
name="udftest_001" />
<cffunction name="udf">
<cfargument name="udfname" type="string" required="true">
<cfif NOT isDefined(udfname)>
<cfinclude template='./udfs/#udfname#.cfm'>
</cfif>
<cfset tempfunc = variables[udfname]>
<cfreturn tempfunc(argumentCollection=arguments)>
</cffunction>
index.cfm
<cfoutput>
#udf(udfname='testUDF',firstname='John',lastname='Smith')#<br/>
#udf(udfname='testUDF',firstname='Betty',lastname='Ford')#<br/>
</cfoutput>
/udfs/testudf.cfm
<cfscript>
function testUDF() {
return 'Hello ' & arguments.firstname & ' #arguments.lastname#';
}
</cfscript>
I suspect something is up with relative paths.
Can you make "UDF" a mapping? Then you can do
<cfinclude template="/UDF/udf_library.cfm">

Uploading Multiple Files

I have a form where users are to upload at least three documents and up to seven. In its current state, I have all the error checking and validation functioning. What I want to happen is for the visitor's forms to get renamed a specified name once they are uploaded and placed into a specified directory. My code is here: http://pastebin.com/V5ThWe7M
I believe the issue occurs around line 456. I believe I need to have the file name stored in a variable then use the variable to process the renaming function. The first file gets uploaded but not the second as they are assigned the same names. I'm trying to figure out how to use the variables that store the individual file names and then use that variable to rename the file. I'd like to have another set of eyes check it out for me and point me in the right direction.
Thank you
As already mentioned, the CFFILE structure is overwritten each time you upload. So any values you wish to preserve, you must save to another variable. But since you are already saving the full file names to a variable, you could easily extract their extensions using list functions. For example:
<cfset nomExt = listLast(clientNominationLetter, ".")>
A few other observations
Consider a more unique naming scheme for your folders than "/firstName_lastName/". Otherwise, you may end up overwriting someone's files if you receive multiple submissions under the same name, like two different "John Smith's".
The cffile values are separated into two categories: cffile.serverXX and cffile.clientXX (ie user system). They are not interchangeable. So be sure you are using the correct variables and be consistent.
FILE is deprecated. Use CFFILE or the result attribute instead.
EDIT: Adding some new code into the mix... :)
Here's some functions, just slap these in the top of your page...
<cffunction name="uploadFile">
<cfargument name="formField" hint="Form field name that holds the file to be uploaded" required="yes">
<cfargument name="renameTo" hint="What to rename the file, ex: 01_nominationLetter" required="yes">
<cfargument name="uploadErrorMessage" required="no" default="Error uploading file"/>
<cfargument name="allowedExtensions" required="no" default="doc,docx,pdf,txt,rt">
<cfargument name="extensionErrorMessage" required="no" default="Only doc, docx, pdf, txt, and rtf file formats are accepted">
<cfset var dir = expandPath("./nominationUploads/#trim(form.fname)#_#trim(form.lname)#/")>
<cfparam name="request.filesUploaded" default="#arrayNew(1)#">
<cftry>
<cffile action="upload" filefield="#arguments.formField#" nameconflict="makeunique" destination="#dir#">
<cfcatch type="any"><cfset ArrayAppend(arrErrors, arguments.uploadErrorMessage )></cfcatch>
</cftry>
<cfif not listFindNoCase(arguments.allowedExtensions, cffile.ServerFileExt)>
<cfset ArrayAppend(arrErrors, arguments.extensionErrorMessage )>
</cfif>
<cffile action="rename" file="#dir##cffile.serverFile#" destination="#dir##renameTo#.#cffile.ServerFileExt#">
<cfset ArrayAppend(request.filesUploaded, dir & arguments.renameTo & "." & cffile.ServerFileExt )>
</cffunction>
<cffunction name="removeFilesOnError">
<cfloop from="1" to="#arrayLen(request.filesUploaded)#" index="i">
<cftry><cffile action="delete" file="#request.filesUploaded[i]#"/><cfcatch type="any"></cfcatch></cftry>
</cfloop>
</cffunction>
Then in your existing validation script, get rid of all the file stuff, instead you'll only need to use those functions above, something like this :
<cfset uploadFile('myFileField', 'renameToThis' )>
<cfset uploadFile('myFileField2', 'renameToThat' , 'My custom upload error!')>
<cfset uploadFile('anotherFile', 'differentName', 'Another custom upload msg!', 'doc,docx', 'This one only lets you upload word docs!')>
<cfif arrayLen( arrErrors ) >
<cfset removeFilesOnError()>
</cfif>
I don't have time to test the above, but I believe it to be frighteningly close. If you encounter an issue, let me know and I'll help you debug it out. :)