Problems accessing VARIABLES scope in CFC
|
|
Thread rating:  |
mate of the state - 26 Aug 2005 15:54 GMT I've got a little CFC that is basically like a cart. When trying to call the "add" method via a URL, I get an error saying my storage variable is undefined in the CFCs variables scope, even though I set it in the init() method. The relevant code has been attached.
<!--- THE COMPONENT ---> <CFcomponent> <CFfunction name="init" output="false" returntype="coupon-book"> <CFscript> variables.coupons = arrayNew(1); return this; </CFscript> </CFfunction> <CFfunction name="getMyCoupons" output="false" returntype="array"> <CFreturn variables.coupons> </CFfunction> <CFfunction name="add" output="false" returntype="void" access="remote"> <CFargument name="id" type="numeric" required="yes"> <CFscript> var arrayList = arrayToList(getMyCoupons()); if (not listFind(arrayList,arguments.id)) { variables.coupons = arrayAppend(getMyCoupons(),arguments.id); } </CFscript> <CFlocation url="#cgi.http_referer#" addtoken="no"> </CFfunction> </CFcomponent>
<!--- THE RELEVANT VIEW CODE ---> <CFif not isDefined('session.couponBook')> <CFset session.couponBook = createObject('component',request.config.paths.cfcroot & 'coupon-book').init()> </CFif>
BSterner - 26 Aug 2005 22:35 GMT Am I understanding you right in that you're instantiating the object in the session scope, and then trying to post to it using the "?method=add" url query param? If, so, I don't think that will work, because you're not posting to the session object, but rather a new instance of it.
mate of the state - 26 Aug 2005 23:12 GMT "Am I understanding you right in that you're instantiating the object in the session scope, and then trying to post to it using the "?method=add" url query param? If, so, I don't think that will work, because you're not posting to the session object, but rather a new instance of it."
Yes, of course! Thank you for clearing that up.
Your suggested workaround #1 is what I was wanting to avoid. I'd never tried to implement CFC methods via the query string before, so I wanted to try that, but also any sort of handler page would be so small I wanted to avoid it.
I agree that #2 is bad design. This project is already impure enough, with CFCs referencing properties and config values in the request scope.
I'm gettin' there... ;)
BSterner - 26 Aug 2005 22:50 GMT Put another way, you can't do a form post to an object stored in a persistant scope. Only to pages or cfc files. Couple workarounds.
a) Post to another component, page or the current page, and have it call the session.couponBook method 'add', passing it the form data.
b) Have the cfc you're posting to check for the existence of the object in the session scope and pass it along. This is bad design if you ask me.
BSterner - 26 Aug 2005 23:36 GMT As an ancillary note, you can also specify the method using the following...
<input type="hidden" name="method" value="myCFCMethodToPostTo" />
I personally, use a cfc controller that invokes the session object in the servicing method of the form post.
mate of the state - 30 Aug 2005 20:41 GMT Okay, I've moved the access to a 'helper' page as suggested.
However, now I'm getting type errors I don't follow. This 'cart' of mine holds a single value which is a single-dimension array. On the CFCs init() method, I initialize that value:
variables.coupons = arrayNew(1);
Later, the add() and delete() methods use a helper method, getMyCoupons(), to get the current value of #variables.coupons# at any time.
The CFC init()s fine, but the first attempt to call getMyCoupons() throws an exception saying the value returned is not of type array.
Attached is the updated CFC code. I can post the 'helper page' code if necessary, though I don't know if it's relevant.
<CFcomponent> <CFfunction name="init" output="false" returntype="coupon-book"> <CFscript> variables.coupons = arrayNew(1); return this; </CFscript> </CFfunction> <CFfunction name="getMyCoupons" output="false" returntype="array"> <CFreturn variables.coupons> </CFfunction> <CFfunction name="add" output="false" returntype="void"> <CFargument name="id" type="numeric" required="yes"> <CFscript> var arrayList = arrayToList(getMyCoupons()); if (not listFind(arrayList,arguments.id)) { variables.coupons = arrayAppend(variables.coupons,arguments.id); } </CFscript> <CFlocation url="#cgi.http_referer#" addtoken="no"> </CFfunction> <CFfunction name="delete" output="false" returntype="void"> <CFargument name="id" type="numeric" required="yes"> <CFscript> var currentCoupons = getMyCoupons(); for (i=1; i lt arrayLen(currentCoupons); i=i+1) { if (arguments.id is i) { variables.coupons = arrayDeleteAt(variables.coupons,i); } } </CFscript> <CFlocation url="#cgi.http_referer#" addtoken="no"> </CFfunction> </CFcomponent>
BSterner - 31 Aug 2005 04:48 GMT Please post your "helper" code. The following returned an empty array ok for me.
<cfset obj = createObject('component', 'coupon-book').init() /> <cfdump var="#obj.getMyCoupons()#" />
mate of the state - 31 Aug 2005 05:32 GMT The main view template receives a request like:
?action=add&coupon=3
Based on the action param, a method is called in the coupon-book. The error WAS being thrown from the module invoked at the end of the attached code. I reworked some things, and now no error is being thrown, but nothing is being added to the array. I know it's hitting the method call because it is redirecting back to the referer. I also did a CFabort in the method call to make sure.
<CFparam name="url.action" default=""> <CFparam name="url.coupon" default="0">
<CFif not isNumeric(url.coupon) or url.coupon lt 0> <CFset url.action = ''> </CFif>
<CFswitch expression="#url.action#"> <CFcase value="add"> <CFset session.couponBook.add(url.coupon)> </CFcase> <CFcase value="delete"> <CFset session.couponBook.delete(url.coupon)> </CFcase> </CFswitch>
<CFmodule template="/system/taglibs/coupons/coupon-book.cfm" standalone="true"
BKBK - 31 Aug 2005 08:38 GMT Just two things.
1) For better or worse, I have the "I started, so I'll finish" mentality. I wouldn't do
<CFlocation url="#cgi.http_referer#" addtoken="no">
just before the finish-line, </cffunction>. Instead, I would cflocate on the cfm page that invokes the cfc, giving the function the opportunity to finish gracefully.
2) I find this construction, a recursive instantiation of a component within itself, unnecessarily complicated:
<CFcomponent> <CFfunction name="init" output="false" returntype="coupon-book"> <CFscript> variables.coupons = arrayNew(1); return this; </CFscript> </CFfunction> ... ...etc.
I would replace this excerpt with
<CFcomponent> <cfset variables.coupons = arrayNew(1)>
Wherever you need to have an instance of the component, you only have to create an object, outside the component itself, the way BSterner does.
Good luck.
mate of the state - 31 Aug 2005 15:55 GMT BK,
Re: 1) You may have a good point there. It doesn't seem to matter for the code, but there is an element of cleanliness I may look into further.
Re: 2) The way CF parses non-method code within a CFC is not the same as a constructor function and more importantly, it is an insufficient replacement. If I want to customize new instances of an object, I need a method I can pass parameters to. When the constructor code is as simple as mine, sure, you don't NEED the constructor function, but it's part of my desire for consistency that I do it.
Kronin555 - 31 Aug 2005 16:04 GMT mate of the state,
Here's something you can check. You're storing the component on the session, and you're using cflocations with addToken="no". Are you using cookies to track the session? Are you sure you're getting the same instance of the component when you come back and the coupon array is empty? Maybe you're getting a new session, which is instantiating a new instance of CouponComponent, which has a blank array...
mate of the state - 31 Aug 2005 17:46 GMT Kronin,
Yes, I had assumed cookies were being set, but it seems maybe they aren't. If I add a call to the add() method right after the coupon-book object is created in Application.cfm, the first view shows the added value. So, I think you may be on to something. How can I ensure I stay within a single session scope? I'd prefer to avoid the dirty URL scenario.
BSterner - 31 Aug 2005 17:47 GMT Originally posted by: Kronin555 mate of the state,
Are you sure you're getting the same instance of the component when you come back and the coupon array is empty? Maybe you're getting a new session, which is instantiating a new instance of CouponComponent, which has a blank array...
Put some kind of output in the following code to check that the coupon object is not repeatedlty being recreated...
<!--- THE RELEVANT VIEW CODE ---> <CFif not isDefined('session.couponBook')> <CFset session.couponBook = createObject('component',request.config.paths.cfcroot & 'coupon-book').init()> <b>New coupon session object created.</b> </CFif>
If you see the 'New coupon session object created.' each time, then you've got a problem w/your session storage. Are all these files in the same directory? I'm assuming you're using the <cfapplication> tag w/i Application.cfm.
|
|
|