Event-Beans and the Form tag

11 views
Skip to first unread message

Mike Rogers

unread,
Nov 2, 2009, 4:29:38 PM11/2/09
to Mach-II for CFML
All,

I'm having some problems coming up with a clean way to handle the
following event flow:

1. A user fills out a form and hits 'submit' (here I would like to use
the Form taglib)
2. A filter is called to validate the data (the data are pulled from
the 'event' object at this point)
3. If the validator doesn't pass, redirect the user to the form and
populate the inputs with the whatever the user supplied; also, show
error messages (the former is impossible with the Form taglib and the
filter approach in 2)
4. If the validator passes muster, populate a bean with the event-bean
command
5. Run the listener that interacts with the bean

Is this a pretty common flow? How do most people solve items 2 and 3
while using filters, event-beans and the Form taglib?

Would there be any value (or anti-value) in having the form:input tag
try to populate the input's value first from the bean path ascribed to
it, and failing that, probe the event object? For example, I have
<form:input path="firstName"> -- should it first check
user.getFirstName() and if that's blank, check event.getArg
("firstName")?

How do you handle this in your Mach-II project?

Thanks,

--Mike Rogers

Summer Wilson

unread,
Nov 2, 2009, 4:43:00 PM11/2/09
to mach-ii-for...@googlegroups.com
In the config, I have an event-bean in the event that loads the survey form. This automatically puts the bean into the event args. On my form, I have <cfset survey = event.getArg("surveyBean") /> at the top, then I just call the respective get methods for each property to do the form values (such as #survey.getRecommend()#). On a fresh load, the bean is empty, so they are blank or at whatever default values there are.

In the event that fires when the survey is submitted, I again use the event-bean, now adding in the fields to let it know which form fields should be used to populate the bean. I then notify my listener and let it handle the rest. I'm not using the taglib, so I can't answer for differences there, and I am using the listener instead of a filter, but hopefully helps some.

--
~~ Summer

Peter J. Farrell

unread,
Nov 2, 2009, 4:57:50 PM11/2/09
to mach-ii-for...@googlegroups.com
The flow we use at my company is as follows:

1. A user fills out a from and hits "submit" (using the M2 form tag library)
2. The event-bean and inner-bean is populated. This requires that all
setters/getters for form data are set to "string" datatypes since all
"form" data are strings and are typeless. At this point, these beans
have a "dirty" context (i.e. not validated)
3. A listener method is called and the service layer "saveUser" is called.
4. In the "saveUser" method in our service layer, we always assume dirty
data in the passed in bean. A validate method is called (many validate
methods could be calls depending on the composition of the beans).
5. If the bean passes muster, then the data is saved in the DB. If it
fails, error messages are returned from the saveUser method in the
service. We always return a MessageHandler.cfc in this situation.
6. Back in our listener, a simple messageHandler.hasErrors() in a
conditional checks whether the user was saved or there was some error.
If there were errors, we redirect back to the form. If there were no
errors, we redirect to the "successful" page.

I personally prefer to work with beans versus trying to validate data in
the event *before* populating the bean(s). The above outline is a bit
more complicated because in our applications we have three levels of
messages -- one level is an "information" level which doesn't get
counted in the messageHandler.hasErrors() boolean. Mix in a bit of i18n
and some message translation (our messages are just key references in
the messageHandler -- we use the key to match to an English or language
phrase that is loaded in and managed elsewhere).

Best,
.Peter

P.s. Suffice it to say that the Validation package we're planning for
1.9 is probably going to work similar to the process above.


Mike Rogers said the following on 11/02/2009 03:29 PM:

Mike Rogers

unread,
Nov 2, 2009, 5:22:12 PM11/2/09
to Mach-II for CFML
Peter,

How do you deal with the dichotomy of data? I mean, if I need a
birthdate from my users, how does that go from a 'string' in the event-
bean to a 'date' in the database? Are you using ORM?

Also, do you think you could expound a little more (or maybe point me
toward a wiki page) on how you're handling I18N? It's kind of off-
topic for this thread, but it's an area of professional curiosity for
me.

Also again, is there an M2SFP or some wiki page for validation that we
could read and comment on?

Thanks for the quick reply!

-Mike

Peter J. Farrell

unread,
Nov 2, 2009, 5:52:16 PM11/2/09
to mach-ii-for...@googlegroups.com
Mike Rogers said the following on 11/02/2009 04:22 PM:

> Peter,
>
> How do you deal with the dichotomy of data? I mean, if I need a
> birthdate from my users, how does that go from a 'string' in the event-
> bean to a 'date' in the database?
There is just a bit of ad-hoc code to handle dates. Since everything in
our beans (unless it's not publicly exposed) are strings, validation is
simple as this in some our applications:

<cffunction name="validateTestee" access="public" returntype="any"
output="false"
hint="Validates a testee.">
<cfargument name="testee" type="testee" required="true" />
<cfargument name="eH" type="myApp.model.sys.error.errorHandler"
required="false" />

<cfif NOT StructKeyExists(arguments, "eH")>
<cfset arguments.eH = CreateObject("component",
"myApp.model.sys.error.errorHandler").init() />
</cfif>

<cfif NOT ListFindNoCase("active|archived",
arguments.testee.getStatuscode(), "|")>
<cfset arguments.eh.setError("statuscode",
"testee.statuscode.invalid") />
</cfif>
<cfif Len(arguments.testee.getFirstName()) LT 2>
<cfset arguments.eh.setError("firstName",
"testee.firstName.invalid") />
</cfif>
<cfif Len(arguments.testee.getFirstName()) GT 50>
<cfset arguments.eh.setError("firstName",
"testee.firstName.tooLong") />
</cfif>
<cfif Len(arguments.testee.getLastName()) LT 2>
<cfset arguments.eh.setError("lastName",
"testee.lastName.invalid") />
</cfif>
<cfif Len(arguments.testee.getLastName()) GT 50>
<cfset arguments.eh.setError("lastName",
"testee.lastName.tooLong") />
</cfif>
<cfif Len(arguments.testee.getSuffix())
AND NOT ListFindNoCase("Jr|Sr|II|III|IV",
arguments.testee.getSuffix(), "|")>
<cfset arguments.eh.setError("suffix",
"testee.suffix.invalid") />
</cfif>
<cfif
REFindNoCase("^(0[13578]|1[02])/(0[1-9]|[12][0-9]|3[01])|(0[469]|11)/(0[1-9]|[12][0-9]|30)|(02)/(0[1-9]|[12][0-9])$",
arguments.testee.getBirthDate()) EQ 0>
<cfset arguments.eh.setError("birthDate",
"testee.birthDate.invalid") />
</cfif>
<cfif Len(arguments.testee.getEmployeeNumber()) GT 20>
<cfset arguments.eh.setError("employeeNumber",
"testee.employeeNumber.tooLong") />
</cfif>

<cfreturn eH />
</cffunction>

Basically, the error handler (message handler is more aptly named) has
structs for error message keys to be set and offers a nice API
(hasErrors(), hasInformations(), setError(), getError()). The keys
(i.e. "testt.employeeNumber.tooLong") are used to look up the messages
as defined by our content editors. The messages are housed in a
home-grown XML file and loader. We use custom tags to look things up.
You'll see similar ideas for the validation framework package we're
going to introduce in Mach-II 1.9.
> Are you using ORM?
>
No, we're using mostly generated hand tweaked DAOs/Gateways to work with
our database.


> Also, do you think you could expound a little more (or maybe point me
> toward a wiki page) on how you're handling I18N? It's kind of off-
> topic for this thread, but it's an area of professional curiosity for
> me.
>

Yeah, an area I'm struggle with at the moment -- it's kludge of
different things from a home grown message key translation system to
using things like resource bundles (Java). That's from the page view
perspective. Organizing a site with i18n is different beast. Questions
like reusing events and setting the language or having separate events
for each language. Depends on the application -- for pure translations
of "brochure" stuff that is not managed in a CMS we opted for a new
event-handler for each language (we name events then using that language
or setup Mach-II URL routes). For app like pages such as basic forms
where we can get away with simple translations of labels, we used a
single event and set a language for it.

i18n is something we're discussing for 1.9 as that ties into the
validation stuff (we want validation to by i18n ready).


> Also again, is there an M2SFP or some wiki page for validation that we
> could read and comment on?
>

An area where the wiki needs some help to be honest. It's more of an
how can I do validation in OO CFML applications than specific Mach-II
issue and which is why it hasn't gotten the attention it needs by the
team. Care to start developing one?

Best,
.Peter

Sumit Verma

unread,
Nov 22, 2009, 12:09:54 AM11/22/09
to Mach-II for CFML
Hi Peter,

So how will this work with ORM? I'm using the same flow as yours but I
don't think it will work with ORM on the "Add New" forms. Since the
event-bean calls setter on primary key as well, which is of type
"generator", entitySave() will always perform update instead of
insert.

Ideally, I would like to keep it as simple as this:

Cofing:
<event-handler event="users.save" access="public">
<event-mapping event="error" mapping="users.edit" />
<event-mapping event="success" mapping="users.list" />
<event-bean name="user" type="com.model.users.users"
fields="userID,firstname,lastname,email,password" />
<notify listener="UserListener" method="saveUser" />
</event-handler>

Listner:
<cffunction name="saveUser" access="public" returntype="void"
output="true">
<cfargument name="event" type="MachII.framework.Event"
required="yes">

<cfset local.user = arguments.event.getArg("user") />

<cfset local.error = variables.usersService.saveUser
(local.user).getErrorBean() />
<cfset arguments.event.setArg("errorBean",local.error) />

<cfif local.error.hasErrors()>
<cfset arguments.event.setArg("initData",false) />
<cfset announceEvent("error",event.getArgs()) />
<cfelse>
<cfset redirectEvent("success") />
</cfif>
</cffunction>

Can we, somehow, specify the event-bean to not call setter on primary
key if it is blank?

Thanks,
Sumit

Matthew Woodward

unread,
Nov 22, 2009, 12:29:38 PM11/22/09
to mach-ii-for...@googlegroups.com
On Sat, Nov 21, 2009 at 9:09 PM, Sumit Verma <su...@blogonria.com> wrote:
So how will this work with ORM? I'm using the same flow as yours but I
don't think it will work with ORM on the "Add New" forms. Since the
event-bean calls setter on primary key as well, which is of type
"generator", entitySave() will always perform update instead of
insert.

Unless I'm misunderstanding, you're going to have this problem whether you're using Mach-II or not because of the way CF handles nulls. Your safest and easiest bet is to provide default values for all your bean properties so things don't "disappear" if they're null. You could also not use event bean. I know, more work, but then you have total control over what setters you call based on the state of the form data when it hits the listener.

Is it possible to set defaults on PKs with CF 9's Hibernate wrapper? Seems like that's pretty critical since CF doesn't deal with nulls well.
 
Can we, somehow, specify the event-bean to not call setter on primary
key if it is blank?

Is it possible? Sure. Is it the right thing to do? Not in my opinion, at least not right now.

Peter and I have been discussing how we're going to handle things like this when we add validation to Mach-II, but making this change would change the behavior of existing applications, so we need to evaluate this more.

Also, quite frankly I feel like by doing this we're coding to a bug in CF. OpenBD handles nulls differently (correctly, IMO), and I'm not sure what Railo does (my guess is it mimics CF), so we can't change things to satisfy how CF works without also investigating how the other CFML engines work. And we certainly don't want to start writing CF 9 specific code based on how the Hibernate wrapper works because, again, the other engines may wind up implementing ORM differently.

I realize that may not be the answer you wanted to hear, but I hope you understand there are a lot of ins and outs to this issue, most of which can't be resolved intelligently at this time because we don't have a full picture of how all the aspects are going to play out.

In short, we may handle things differently when we introduce validation (which will also add i18n to the framework), but this behavior will remain as is for Mach-II 1.8. We'll have the 1.8 RC out this week so it's just too late to make a change that would potentially break existing applications.

As for the issue with CF making variables magically disappear, I'd set defaults for all your instance variables (if you can; I haven't played with the ORM in CF 9 yet). That's a common thing to do anyway, because without defaults there's essentially no way to create an empty bean (i.e. call a no-arg constructor), which seems odd to me.

When we have the specs for validation written up we'll post to the list and we'll love to hear everyone's feedback at that point. But for 1.8 at least, this behavior will remain as is.

--
Matthew Woodward
ma...@mattwoodward.com
http://mpwoodward.posterous.com
identi.ca/Twitter: @mpwoodward

Please do not send me proprietary file formats such as Word, PowerPoint, etc. as attachments.
http://www.gnu.org/philosophy/no-word-attachments.html

Kurt Wiersma

unread,
Nov 22, 2009, 12:48:19 PM11/22/09
to mach-ii-for...@googlegroups.com
I haven't gotten a change to try out the new event-bean command with a
CF 9 ORM CFC yet, however, I think it will work just fine. Maybe today
I think throw a quick example together using AppBooster.

There are a couple of ways that the primary key can be handled. The
first idea would be use the ability to ignore certain event args when
binding to a bean in the event-bean command. You could ignore the
primary key in order that Hibernate handle generating it for you or in
order to allow a notify or call-method command prior to the event-bean
command to load up a User object from ORM for you. The second idea
would be to configured our User CFC to have a default of 0 for the
primary key using the default attributes in the cfproperty tag. You
can then check for this in your service CFC to ensure the User object
gets created in the DB when it is saved rather then saved with a
primary key of zero Hibernate should fill in the primary key based on
how you configured it in the cfproperty tag.

Would you be willing to help us test out the event-bean command with
CF 9's ORM features? It would be great to get some help testing out
all the different situations. :)

--Kurt
> --
> You received this message because you are subscribed to Mach-II for CFML
> list.
> To post to this group, send email to mach-ii-for...@googlegroups.com
> To unsubscribe from this group, send email to
> mach-ii-for-coldf...@googlegroups.com
> For more options, visit this group at
> http://groups.google.com/group/mach-ii-for-coldfusion?hl=en
> SVN: http://greatbiztoolsllc.svn.cvsdude.com/mach-ii/
> Wiki / Documentation / Tickets:
> http://greatbiztoolsllc.trac.cvsdude.com/mach-ii/

Sumit Verma

unread,
Nov 22, 2009, 1:53:56 PM11/22/09
to mach-ii-for...@googlegroups.com
Hi Matt,

Yes, not the answer I wanted to hear, even though I knew that's what I will get :o) I fully understand the reasoning as well.

Few things for you guys to ponder:

They way Hibernate works, you can't even default your primary key in constructor. Any time you set the primary key after object creation, Hibernate will perform update. Can we set the default in the cfproperty for PK? I'm not sure. I will try later today, but that doesn't seem like the right thing to do.

I was thinking of may be having an additional attribute in the ignore field option of the event-bean to specify "ignore values". So, in that case we can specify, for what values the field will be ignored instead of always ignoring it. Also, the field will always be ignored if the value attribute is not specified. This way we can call the setter for edit forms and ignore for "add" forms.

I agree, this is somehow related to CF not being able to handle null. But sadly that's not going to change and I think framework should offer some work around. Otherwise, we won't be able to use the awesome event-bean feature.

Thanks,
Sumit



Sumit Verma

unread,
Nov 22, 2009, 2:02:03 PM11/22/09
to mach-ii-for...@googlegroups.com
Hi Kurt,

If we use the ignore option then we will have to put if/else logic in listner to set the PK in case of "edit" forms. I will be happy to help test and find the best solution. Let me know what you want me to do. Here is what I ended up doing currently:

I extend all my entity with base entity and perform the init in base entity. I added the code to exclude the PK setter for empty value. I think this may cause a little performance drop because it will show down the bean creation a little bit. 

function init() {
properties = {data=GetMetaData(this).properties};
for(item in arguments){
var propertyArray = structFindValue(properties,item);
if(ArrayLen(propertyArray) && (arguments[item] NEQ "" || !ArrayLen(structFindKey(propertyArray[1].owner,"fieldtype")) || propertyArray[1].owner.fieldtype NEQ "id")){
evaluate("this.set#item#(arguments[item])");
}
}
}

Best,
Sumit

Peter J. Farrell

unread,
Nov 22, 2009, 2:48:39 PM11/22/09
to mach-ii-for...@googlegroups.com
I think the easiest workaround is to just define a concrete setter in your bean to override the generated setter.  You would just not set a value if the incoming argument is equal "" (basically ignoring the setter call).

<cfcomponent>

    <cfproperty name="id=" ... yadda yadda ... />

    <cffunction name="setId">
       <cfargument name="id" />
       <cfif Len(arguments.id)
            <cfset variables.id = argurments.id />
       </cfif>
    </cffunction>

</cfcomponent>

While I believe this will be something which we're ponder a solution for 1.9, but it's too late in the 1.8 release cycle to get it in this release.  I believe the above solution offers a low-impact work around for the time being that isn't too difficult to implement.  Also, it will let you work in the manner you defined in your question email.

Another way is (if it's possible -- I haven't tried) is to override EntitySave() in a bean.  You could set a default of "0" for the PK and in the entitySave do something like this:

<cffunction name="save">
    <cfif getId() EQ 0">
        <!--- Code to reset the Id to something hibernate will like so you have an insert --->
    </cfif>

     <cfset entitySave() />
</cffunction>

This would be possible since it sounds like you have base class that you are using for all of your beans.

Honestly, ORM aside -- this is similar to how most people might handle this when ORM isn't involved or use "0" (in this case of a PK) to indicate an "insert" instead of an "update".

Hope I that one of the solutions I offered will prove to be an useful workaround for you.

Best,
.Peter

Sumit Verma said the following on 11/22/2009 01:02 PM:

Sumit Verma

unread,
Nov 22, 2009, 3:07:13 PM11/22/09
to mach-ii-for...@googlegroups.com
Yes, overriding the setter in the bean is an option. I was trying to not add any method in the bean itself and keep only table definition, but I think the setter for ID is needed. It will allow me to remove the check from the constructor in base entity.

From what I have tested so far, I don't think we can reset the ID in save method because it looks like cf/hibernate expects PK (identity) to be null but there no way to set it to null!! I'm not sure if setting it to a java null will work.

Peter J. Farrell

unread,
Nov 22, 2009, 3:25:38 PM11/22/09
to mach-ii-for...@googlegroups.com
Sumit Verma said the following on 11/22/2009 02:07 PM:
> Yes, overriding the setter in the bean is an option. I was trying to
> not add any method in the bean itself and keep only table definition,
> but I think the setter for ID is needed. It will allow me to remove
> the check from the constructor in base entity.
Well, over time your beans will be more than table definitions -- or
you'll have a pretty anemic domain model. I wouldn't worry about having
to define an occasional setter/getter/other method. No bean is going to
always be the same so an exception to the normal rules is fine. If your
PK is always going to be ID, then you define the setId() method in your
base class.
>
> From what I have tested so far, I don't think we can reset the ID in
> save method because it looks like cf/hibernate expects PK (identity)
> to be null but there no way to set it to null!! I'm not sure if
> setting it to a java null will work.
Well, nulls in Java and nulls in Adobe CF are the same thing unless they
are doing something weird with the ORM implementation. I didn't test
this, but I would think you should be able to do:

<cfset variables.id = JavaCast("null", "") />

HTH,
.Peter

Sumit Verma

unread,
Nov 22, 2009, 4:00:05 PM11/22/09
to mach-ii-for...@googlegroups.com
On Sun, Nov 22, 2009 at 3:25 PM, Peter J. Farrell <pe...@mach-ii.com> wrote:
Sumit Verma said the following on 11/22/2009 02:07 PM:
> Yes, overriding the setter in the bean is an option. I was trying to
> not add any method in the bean itself and keep only table definition,
> but I think the setter for ID is needed. It will allow me to remove
> the check from the constructor in base entity.
Well, over time your beans will be more than table definitions -- or
you'll have a pretty anemic domain model.  I wouldn't worry about having
to define an occasional setter/getter/other method.  No bean is going to
always be the same so an exception to the normal rules is fine.  If your
PK is always going to be ID, then you define the setId() method in your
base class.

PK won't always be id, but I agree, exception to the normal rule is fine.
 
>
> From what I have tested so far, I don't think we can reset the ID in
> save method because it looks like cf/hibernate expects PK (identity)
> to be null but there no way to set it to null!! I'm not sure if
> setting it to a java null will work.
Well, nulls in Java and nulls in Adobe CF are the same thing unless they
are doing something weird with the ORM implementation.  I didn't test
this, but I would think you should be able to do:

<cfset variables.id = JavaCast("null", "") />

Well, no null in Adobe CF :o) From javacast documentation:

Note: Do not assign the results of JavaCast("null","") to a ColdFusion variable. Unexpected results will occur.

 

HTH,
.Peter


Thanks a lot you guys for being so responsive. Makes the learning curve much shorter and experience much better!
 
Best,
Sumit

Matthew Woodward

unread,
Nov 22, 2009, 4:27:29 PM11/22/09
to mach-ii-for...@googlegroups.com
On Sun, Nov 22, 2009 at 1:00 PM, Sumit Verma <su...@blogonria.com> wrote:
Well, no null in Adobe CF :o) From javacast documentation:

Note: Do not assign the results of JavaCast("null","") to a ColdFusion variable. Unexpected results will occur.


It's interesting they phrased it that way--I'd say the results are entirely expected. Not what *should* happen in my opinion, but the results are consistent and therefore aren't unexpected.

 

Thanks a lot you guys for being so responsive. Makes the learning curve much shorter and experience much better!



Thank you for helping us think through this issue. Chances are we'll come up with a solution for 1.9, and we'll definitely want everyone's feedback as we decide what to do, but sounds like you at least have a workaround in the mean time.

Peter J. Farrell

unread,
Nov 22, 2009, 4:49:27 PM11/22/09
to mach-ii-for...@googlegroups.com
FYI, You could set a null by using the return void exploit from a cffunction.  Adobe CF is the only engine that works like this -- Railo and Open BD work differently.

http://www.bennadel.com/blog/1654-Learning-ColdFusion-9-IsNull-And-Working-With-NULL-Values.htm

.pjf

Sumit Verma said the following on 11/22/2009 03:00 PM:

Peter J. Farrell

unread,
Nov 22, 2009, 4:53:33 PM11/22/09
to mach-ii-for...@googlegroups.com
Some other discourse on this is how to a value on a bean back to NULL --
like I suggested:

http://forums.adobe.com/thread/483080

.pjf

Sumit Verma

unread,
Nov 23, 2009, 12:37:17 AM11/23/09
to mach-ii-for...@googlegroups.com
Thanks Peter. I added the setter and it works fine (despite what documentation says).

public function setUserID(any id) {
if(len(id)){
variables.userID = id;
} else {
variables.userID = javacast("null","");
}
}


--
Reply all
Reply to author
Forward
0 new messages