Using object "getters" after form validation fails

7 views
Skip to first unread message

Michael Sharman

unread,
Sep 24, 2008, 1:27:50 AM9/24/08
to CFCDev
Hi guys,

I'm after some suggestions for a quite common scenario. I have a site
where users are registered (i.e. they have a profile) and have the
option of updating their details after logging in.

I load the "update" form's controls from a User.cfc

e.g. <input type="text" name="email" id="email"
value="#oUser.getEmail()#" />

Now when the form gets submitted validation takes place and if that
validation fails the update form is reloaded with an error message.
The problem is that I want to display the new information that the
user has entered even though it hasn't yet been updated in the
database.

I feel I can't update the User objects "state" if the validation fails
as the user may navigate away from the update form and as the rest of
the site depends on the User object's information this would be
potentially incorrect.

Would I be looking at some kind of memento pattern here? Some kind of
a global #request.input# struct? How do people get around this,
hopefully without complicating/messing up views with a cfparam for
each User property etc

Thanks

Mark Mandel

unread,
Sep 24, 2008, 1:56:21 AM9/24/08
to cfc...@googlegroups.com
Where is the validation failing? At a form level, or at the object level?

I will commonly have code that looks like:

obj.setFoo(value);
if(object.validate());
{
save(obj);
}

return obj;

Then if the object doesn't validate, I can pass it back to my view,
and it can display the invalid data just as it would do normally.

That make sense?

Mark

--
E: mark....@gmail.com
W: www.compoundtheory.com

Peter Bell

unread,
Sep 24, 2008, 3:14:32 AM9/24/08
to cfc...@googlegroups.com
Hey Mark,

That is what I would usually do, but I'm guessing from his email that
he's storing the user object in the session scope and is worried that
if they don't fix their user data up, the unvalidated, unsaved data
will now show on all other pages.

For example, you change your last name to
Chumbawumbachumbawumbachumbawumbachumbawumba but last name has a 20
character max length. If he does session.User.save(LastName ,
form.LastName), the session.User.isValid() will return false, but the
session.User.getLastName() used to welcome you to every page as hello
FirstName LastName will show the invalid version for the rest of the
session.

Michael - was that the concern you had? If so I'd consider instead of
editing (what I call) the SiteUser, just load and edit a User object
and then you can session.SiteUser =
UserService.getByID( session.SiteUser.getID() ); to reload any changes
to the site user in session scope once the new User object has been
successfully validated and saved.

Would that work?

Best Wishes,
Peter

Mark Mandel

unread,
Sep 24, 2008, 3:18:19 AM9/24/08
to cfc...@googlegroups.com
In that case, why not duplicate() (or write your own clone() method),
on the user, and populate THAT with data? :D

That way if it's invalid, you pass that back, otherwise, switch it out
in the session scope, or has a way to copy values b/w the objects.

Mark

Kevan Stannard

unread,
Sep 24, 2008, 9:11:27 AM9/24/08
to cfc...@googlegroups.com
Validation discussions frequently bring up the concept of populating beans
with potentially invalid data then calling a bean.validate() function, but
something doesn't quite feel right about it for me.

If I have a date field in my bean, then I would like it to be a real date
value (or an empty string meaning null), or if I have a numeric field in my
bean then I want it to be a real numeric value. Allowing them to be anything
entered by a user just doesn't feel right - but that is just where my head
is at right now.

I have been handling general add/edit forms as follows (it's a bit of a long
story ...)

I create a "FormHelper" object that is aware of all fields on the form
(including hidden, visible and dynamic fields). It knows how to transform
database data or objects into form field values and visa versa.

For a User add/edit form I would have a UserFormHelper with some functions
as follows:

* setField(name,value)
* getField(name)
Functions to set/get form field values.

* initialiseFields()
This initialises an internal struct with all of the fields in the form. It
sets each of the fields to their initial value. These values would be for
"add/new" forms. This function essentially defines the fields that are
permitted in the form. The field names exactly match the actual form field
names. This is called from the init() function and has code that looks like:

<cfset setField("userId",0)>
<cfset setField("firstName","")>
<cfset setField("lastName","")>

* loadByUsedId(userId)
This initialises the helper fields from the database. But this could also be
something like loadByUserBean(user). This calls the setField() function for
each field, but with the value from the database/bean. This is used for an
"edit" form.

* loadByEvent(event)
This initialises the helper fields from an "event", which is just the
combined form and url scopes. This is used when attempting to save a form.
The submitted form values are passed into the helper via this function.

* validate()
This validates the form data. This may also populate beans and call their
validate() functions, but this helper validation is aware of the entire form
which may span multiple bean types. This function sets errors inside the
helper.

* save()
Calls validate() and if no errors, performs the save (via
services/gateways). If errors during the save occur, then additional errors
may be set.

I would generally use the helper as follows:

* newUser event:

<cfset helper = createObject("component","UserFormHelper").init()>

In the form, use code such as:

<cfset userId = helper.getField("userId")>
<input type="hidden" name="userId" value="#userId#">

<cfset firstName = helper.getField("firstName")>
<input type="hidden" name="firstName" value="#userId#">

* editUser event:

<cfset userId = event.get("userId")>
<cfset helper = createObject("component","UserFormHelper").init()>
<cfset helper.loadByUserId(userId)>

Then the form display code is the same as above.

* saveUser event:

<cfset helper = createObject("component","UserFormHelper").init()>
<cfset helper.loadByEvent(event)>
<cfset helper.save()>
<cfif not helper.hasErrors()>
<cfset userId = helper.getField("userId")>
<cflocation
url="/some/dir/index.cfm?event=editUser&userId=#userId#">
</cfif>
<!---
Then display the same form again, but with error messages. The helper has a
function getErrors() which returns a collection of formfield error messages.
--->

The helper manages the data with the form display in mind. It holds any
submitted invalid data which is redisplayed exactly as entered back onto the
form when errors occur, it also prepares data for display - such as
converting datetime fields from the database into the correct display values
for the form. Essentially it is a representation of the form as the server
sees it - without the UI - just a bunch of name/value pairs.

I have a base FormHelper that all other form helpers extend, so there is not
much work in setting up new forms.

Overall this has been working out quite nicely for me, but would love to
hear others ideas.

Kevan

Michael Sharman

unread,
Sep 24, 2008, 9:32:38 AM9/24/08
to CFCDev
Thanks for your responses guys.

@Peter you are correct, I should have been a bit clearer there. The
User object is indeed stored in session and yes I am worried that
potentially "invalid" data could be displayed on other pages (as the
session.User data has been updated from the original form submission).

>If so I'd consider instead of editing (what I call) the SiteUser, just load and edit a User object
>and then you can session.SiteUser = UserService.getByID( session.SiteUser.getID() ); to reload any changes
>to the site user in session scope once the new User object has been successfully validated and saved.

That could work, but I wonder if it could get too complicated with
other potential "instance" data the User object might have (site
layout, current shopping cart composed objects etc).

@Mark
>In that case, why not duplicate() (or write your own clone() method),
>on the user, and populate THAT with data? :D

Could you explain this a bit further? Ideally the following would
happen:

- Form loads with current session.User data loaded into the form
controls
- User edits form and submits
- Validation fails -> View reloads with the data the user edited in
the form
- BUT the session.User still retains the original data

It's almost as if I shouldn't use:

<input type="text" name="email" id="email" value="#oUser.getEmail()#" /
>

but instead use:

<input type="text" name="email" id="email"
value="#request.input.email#" />

Then at the top of the "view" I can have something like:

<cfif structKeyExists(form, "submitted")>
<cfset session.User.save(form) />
<cfset request.input = form />
<cfelse>
<cfset request.input = structNew() />
<cfset request.input.email = session.User.getEmail() />
etc
</cfif>

@Kevan - wow, long post :)

Most of that sounds like what I've use a User object for. Think I'll
need to re-read in the morning to digest properly. Good food for
thought though!

Brian Kotek

unread,
Sep 24, 2008, 12:22:54 PM9/24/08
to cfc...@googlegroups.com
I have a Validator that first attempts to set the properties on the bean (I use a Transfer object that has been cloned so that the updates don't affect the real object until it is saved, but this could be done manually as well). If any properties fail to be set (argument type failure), I record those as validation errors and then move on. After the properties are set I run a validation on them to ensure that they conform to my rules (unique value, between two numbers, required, greater than/less than, etc.) and capture those as well. Only if the validator has no errors at this point do I save the object. So you don't have to type your arguments to any, you can just capture setter failures and treat them like any other validation failure.

David McGuigan

unread,
Sep 24, 2008, 1:31:29 AM9/24/08
to cfc...@googlegroups.com
Just drop the form object into the session scope queued for persistence after a successful authentication. Once the user authenticates, voila.

David McGuigan

unread,
Sep 24, 2008, 1:33:26 AM9/24/08
to cfc...@googlegroups.com
Sorry, I misread your post. Once the user successfully PASSES VALIDATION, voila. Persist the data.


On Tue, Sep 23, 2008 at 11:27 PM, Michael Sharman <sha...@gmail.com> wrote:

Mark Mandel

unread,
Sep 24, 2008, 6:48:49 PM9/24/08
to cfc...@googlegroups.com
> layout, current shopping cart composed objects etc).
>
> @Mark
>>In that case, why not duplicate() (or write your own clone() method),
>>on the user, and populate THAT with data? :D
>
> Could you explain this a bit further? Ideally the following would
> happen:
>
> - Form loads with current session.User data loaded into the form
> controls
> - User edits form and submits
> - Validation fails -> View reloads with the data the user edited in
> the form
> - BUT the session.User still retains the original data
>
> It's almost as if I shouldn't use:
>
> <input type="text" name="email" id="email" value="#oUser.getEmail()#" /
>>

I would see the flow as (or something similar) -
- put your current session.oUser in the request scope.
- Form loads with <input type="text" name="email" id="email"
value="#request.oUser.getEmail()#" />
- on submit duplicate() the session.oUser and put it in request scope
- load in the form data into the request scope's object (the copy)
- if validation fails, re-route back to the display page
- page still loads with <input type="text" name="email" id="email"
value="#request.oUser.getEmail()#" />
- session is still intact.

Make sense?

Peter Bell

unread,
Sep 26, 2008, 8:09:52 AM9/26/08
to cfc...@googlegroups.com
Hi Brian,

That is one solid approach. Another one (IMO no better or worse) is to type the arguments to any and to allow "invalid data" in the bean, running an isValid() on the bean to do all of the validations. There are two things I like about the typeless setters approach. One is that you can be much more granular in your error reporting, more easily. If you rely on a catch, all you really know is that the value entered wasn't a valid numeric or date. If you have a number of validations within the bean (probably composed in some kind of validator), you could be more granular in your checking and therefore in your error messages. I have come across cases where that was useful. I also personally use the bean to populate my form fields, so I can use the same code for populating the values for a first add form (using any defaults for a new bean instance), a second shot at an add (it shows exactly what you typed in - displayed in the fields again), or an edit (it shows the current values of the object). I find that works for me, but you do lose the documentation benefits of knowing what type all of your setter arguments are supposed to be. But I generate docs from my XML description of the object model (which also generates my db and synthesizes my code) and in most cases don't have the setter methods (or even the cfc's they're supposed to be in), so I gave up on making the code self documenting (as opposed to making the runtime system self documenting) a long time ago :-)

Best Wishes,
Peter

Brian Kotek

unread,
Sep 26, 2008, 10:10:41 AM9/26/08
to cfc...@googlegroups.com
Sure that's fine if you have control over the typing. I use Transfer which types all of the arguments automatically, so unless I manually override every single getter and setter to retype them to "any", that won't work for me. And of course, I'm not manually overriding every getter and setter. ;-)  I agree that if you're writing or generating your own beans, that typing to "any" is an option.

Peter Bell

unread,
Sep 26, 2008, 10:12:27 AM9/26/08
to cfc...@googlegroups.com
Ahhh, yeah, I can see how that wouldn't be worth the effort :-)

Kevan Stannard

unread,
Sep 26, 2008, 4:51:58 PM9/26/08
to cfc...@googlegroups.com

Hi Brian, Mark

 

If a form field contains invalid data that cannot be set on a bean due to a type failure, how would you handle redisplaying that original invalid data back on the form again?

 

Thanks

 

Kevan

 


Brian Kotek

unread,
Sep 26, 2008, 5:08:01 PM9/26/08
to cfc...@googlegroups.com
Well you have two options, one, is to create another CFC  that is typeless and return that. This could be done in a Generic CFC that uses onMissingMethod to store the properties and respond to getter requests. The second is to return some sort of structure of the original data along with the original User, and use that to populate the form. Basically, if you're populating the form from a CFC and you must redisplay the bad data, you either have to untype your arguments or you have to send something different back to the form so you can display the data.

Bob Silverberg

unread,
Sep 26, 2008, 5:08:14 PM9/26/08
to cfc...@googlegroups.com
I have yet to come up with a solution to that problem that I'm really
happy with. Currently my populate method, which loads data into the
business object and validates the datatypes, takes any invalid values
and stores them in "shadow" properties, from which I can retrieve them
when I need to display them. Unfortunately this technique requires a
custom getter for any properties that could have invalid values (most
are strings, so they don't need this functionality). Another way
around this is to use a generic getter that checks for the existence
of an invalid shadow property, and calls the standard getter if no
invalid value has been stored. But I'm not too keen on that either.

So, my solution works, allows me to use my business object directly in
my view (e.g., myObj.getName()), and also allows me to redisplay any
invalid values entered by a user, but I'm just not that keen on the
implementation. I hope to one day either hear of a better
implementation or come up with something on my own.


On Fri, Sep 26, 2008 at 4:51 PM, Kevan Stannard <ke...@stannard.net.au> wrote:

--
Bob Silverberg
www.silverwareconsulting.com

Bob Silverberg

unread,
Oct 2, 2008, 9:31:39 AM10/2/08
to cfc...@googlegroups.com
I spent some time discussing this with Paul Marcotte last week and we
came up with an idea that I think is similar to what Brian was
suggesting. Here's what we came up with:

Keep business object exactly as it is, with typed getters and setters.
Include the "shadow property" logic to store invalid data that cannot
be stored by a setter. As the business object is created by a
business object factory, wrap the business object in a generic
wrapper. That wrapper would use onMM to pass all setters to the
underlying business object, and to allow all getters to first check
for a shadow property and if one does not exist, then pass the getter
to the underlying business object.

This would allow one to keep the API of the business object, without
having to resort to generic getters and setters, but would remove the
previous requirement (in my implementation) of having to write a
custom getter for any properties that need to be able to store invalid
data. You could then use your business object directly in your view
and would always get back the data that the user submitted, valid or
not.

I haven't actually written this yet (I will), but in theory it sounds
pretty good.

Comments?

--
Bob Silverberg
www.silverwareconsulting.com

Dan O'Keefe

unread,
Oct 8, 2008, 9:26:42 AM10/8/08
to cfc...@googlegroups.com
Brian,

Just to confirm, you clone a copy of the transfer object and then
attempt to set each of the properties with a <cftry><cfcatch> around
it so if any of the properties fails to set you note it, and if no
errors were detected you make a second pass at each property for the
additional rules? If any errors were detected on either pass you
return the cloned object to the form to re-populate the form. If no
errors were detected, save the cloned transfer object, if saved
successfully, update your original object from the clone?

Sorry if that is a little incoherent, but I want to make sure I am
following you from form submission and back to the form if errors
occur, and if not, persisting to the DB. I have read the other posts
in the group here regarding changing the type to "Any" or not and I
think that is what you were addressing here. Also the issue of making
sure the session scope object in in sync as to what is in the DB.

Thanks,

Dan

Dan O'Keefe

unread,
Oct 9, 2008, 2:59:09 PM10/9/08
to cfc...@googlegroups.com
Bob,

Was wondering if you gave this idea a spin and have prototyped it -
and if so, did it work as expected for you.

Thanks,

Dan

Bob Silverberg

unread,
Oct 9, 2008, 8:56:17 PM10/9/08
to cfc...@googlegroups.com
I tried it and it worked like a charm. In fact, I have incorporated
it into a "validations framework" that I've been working on for the
past several weeks. I will be discussing and documenting the
framework on my blog over the next while, and hope to have a demo up
any day now. So far I've just written an introductory post about the
goals of the framework. If you're interested you can find that post
here:

http://www.silverwareconsulting.com/index.cfm/2008/10/6/ValidateThis--An-Object-Oriented-Approach-to-Validations

Bob

--
Bob Silverberg
www.silverwareconsulting.com

Baz

unread,
Oct 10, 2008, 2:26:38 PM10/10/08
to cfc...@googlegroups.com
All the techniques presented here are quite intelligent and elegant but I want to throw out an idea. Whether someone uses a helper component, shadow properties or a validator, at the root there is always a need for some sort of USER api to store and manage the data. Is that not the purpose of having a USER class to begin with? To have a definition of an api from which many different instances for all types of purposes can be created? It *feels* like if 99% of an interface needs to be duplicated, then the class definition is too tightly coupled to a specific implementation and that one would be better off dumbing down the original class in favor of more composition/inheritance for the added functionality.

To go back to the original example, Michael instantiated one instance of the USER class to store the logged-in user's information. Later on, he may instantiate more user objects to display query results or show someone's manager. They all share a common api but serve very different purposes with their own set  of unique rules and constraints to follow. So why not just have another instance that is specifically designed to handle dirty data for validation?

The way I would solve the problem most closely resembles Peter's technique. I would have a LOGINUSER stored in session (for things like displaying their name at the top of the site) - and if someone wanted to edit that USER, I would create a new instance specifically for that unique purpose. That new instance might interact with my session instance (to populate itself perhaps), but it is still a separate special instance whose surrounding code is specifically designed to handle and expect potentially dirty data. So the form that is being used for editing, would only interact with that instance. It would display its contents, edit its contents and so forth, with no fear of that spreading to other parts of the site. Only once the entire editing process is complete would I update the SESSION.LOGINUSER instance with the new data.

That's why I am not a huge fan of shadow properties even though they do provide a lot of elegance in certain cases - there's too much duplication. It *almost* makes more sense to compose a USER object within itself - one clean and one dirty. 

Caveat: I understand this wouldn't work out as nicely with transfer for reasons mentioned before.

What do you think?

Baz

Brian Kotek

unread,
Oct 12, 2008, 4:06:40 PM10/12/08
to cfc...@googlegroups.com
Right, this is what I typically do, so that I end up with an error object that contains not only type cast failures but validation rule failures as well. The only catch here is that for things that outright fail validation checks, the cloned object that repopulates the form doesn't have a value to redisplay to the user. Since typically cast failures are limited to things like dates or numbers, I check these using JavaScript or have the user make the choice from a select box or something like that. If you don't perform client-side checks, or the user disables JavaScript, it does mean that on rare occasions the redisplayed for will not be showing what the user entered for certain fields (i.e. if they enter letters for a date, that field is just empty when the form redisplays. The error message is there to explain the problem but the value they entered isn't.) Some people will say this is unacceptable for usability reasons but since the alternative is shadowing the properties with untyped functions, or manually building a separate object, I'm willing to accept this small issue. If it's really, really a problem you can disable type checking in the CF admin and let the validation catch the error instead.

As far as I'm concerned, if the user disables JavaScript and enters "aaa" into a date field, it's just too bad that the redisplayed form doesn't show "aaa" in that date field. The validation error message tells them about the problem, and to me having "aaa" show up in the form again is just an invitation for the user to submit the bad data again.

Dan O'Keefe

unread,
Oct 13, 2008, 8:35:36 AM10/13/08
to cfc...@googlegroups.com
> As far as I'm concerned, if the user disables JavaScript and enters "aaa"
> into a date field, it's just too bad that the redisplayed form doesn't show
> "aaa" in that date field. The validation error message tells them about the
> problem, and to me having "aaa" show up in the form again is just an
> invitation for the user to submit the bad data again.
>


Could not agree more. I am all for usability but at some point, have
to put some ownership on the user to have a clue. Thanks for the
verification. I am going to model out a test case and try it.

Dan

Jean Moniatte

unread,
Oct 14, 2008, 2:20:31 AM10/14/08
to cfc...@googlegroups.com
I disagree.

Whatever solution we decide to implement (server or client) should never affect the user experience, and data that does not validate should always be displayed in its original field. In the context of your example, a European user might very well enter 31/12/2008 as a date. That would not be clueless.

Jean

What if a European user enters 31/12/2008 for a date?

Mark Mandel

unread,
Oct 14, 2008, 2:51:37 AM10/14/08
to cfc...@googlegroups.com
what's wrong with having a date picker?

Barry Beattie

unread,
Oct 14, 2008, 3:21:11 AM10/14/08
to cfc...@googlegroups.com
you've never come across former terminal/green screen users, then.

data entry operators who can fly through dozens of form elements using
taborder, <enter> and shortcut keys to do so.

you want horror stories of 50-70 form fields on one screen,
micro-business logic modifying the taborder and/or disabling form
elements and/or kicking in custom validation?

where entering "12102008" in a text box will automatically render it
as "12/10/2008" as a date in October?

thank small miracles for CF custom tags as tag libs to reuse controls.
Pity it all happened pre Flex2.

Kevan Stannard

unread,
Oct 14, 2008, 6:56:52 AM10/14/08
to cfc...@googlegroups.com
We are using the jQuery UI date picker. Just checked now - it does prevent
alpha characters from being entered, but not prevent invalid dates from
being entered by hand, e.g. 123/45/67.

We also had a client who requested an application to function without JS
enabled. It was not practical in that particular case, but I can see that
these scenarios do come up.


-----Original Message-----
From: cfc...@googlegroups.com [mailto:cfc...@googlegroups.com] On Behalf Of

Barry Beattie
Sent: Tuesday, 14 October 2008 6:21 PM
To: cfc...@googlegroups.com

Dan O'Keefe

unread,
Oct 14, 2008, 9:47:49 AM10/14/08
to cfc...@googlegroups.com
> what's wrong with having a date picker?
>
> On 10/14/08, Jean Moniatte <je...@ugal.com> wrote:
>> I disagree.
>>
>> Whatever solution we decide to implement (server or client) should never
>> affect the user experience, and data that does not validate should always be
>> displayed in its original field. In the context of your example, a European
>> user might very well enter 31/12/2008 as a date. That would not be clueless.
>>
>> Jean
>>
>> What if a European user enters 31/12/2008 for a date?
>>

Perfect solution, but that was just one example of not repopulating a
field that fails the property type set.

@Jean - good point - but not big enough of an issue for me to worry
about now. Maybe as I flush out my process I will find a solution for
it. But still, if I prompt them for age and they enter aa, and enter
aa again, and they have JS turned off, I am OK with passing on that
user.

Dan

Brian Kotek

unread,
Oct 14, 2008, 10:20:08 AM10/14/08
to cfc...@googlegroups.com
That would not fail validation (on the JS side or the CF side) if the validation was properly written. They'd have to enter something outright invalid, like letters, for it to fail. That's what I'm talking about, and I don't see this having an effect on the user experience since it is very rare and means that they've disabled JavaScript. You can disagree with me which is fine, but it doesn't change my view of things, which is: if the user disables JavaScript and then enters totally invalid values into date fields or number fields, they don't get those populated with their bad data when the form redisplays.
Reply all
Reply to author
Forward
0 new messages