You are viewing a plain text version of this content. The canonical link for it is here.
Posted to users@tapestry.apache.org by Geoff Callender <ge...@gmail.com> on 2008/01/28 13:46:10 UTC

Re: T5: Edit page best practice - Mk III - now Mk V

Here's Mark V of the single page edit.  In previous attempts I was  
clinging onto client-side persistence of the entity to ensure  
optimistic locking, and then "the penny finally dropped" - all you  
need to ensure optimistic locking is to ensure the entity's version is  
retained - and you can do that with a hidden field.  So client-side  
persistence really isn't necessary.  Hallelujah!

	private Long _personId;

	private Person _person;

	void onActivate(Long id) throws Exception {
		_personId = id;
		_person = getPersonService().findPerson(_personId);
	}

	Long onPassivate() {
		return _personId;
	}

	void onValidateFromForm() {
		if (...a bit of validation logic detects an error...) {
			_form.recordError(...);
			return;
		}
		try {
			// move this back to onSuccess() once TAPESTRY-1972 has been  
resolved.
			getPersonService().changePerson(_person);
		}
		catch (Exception e) {
			_form.recordError(ExceptionUtil.getRootCause(e));
		}
	}

	Object onSuccess() {
		_nextPage.onActivate(_personId);
		return _nextPage;
	}

	void cleanupRender() {
		_form.clearErrors();
	}

and in the html form, use the Hidden component that's been discussed  
in this mailing list:

  		<t:hidden t:id="version" value="person.version"/>

or if you're using BeanEditForm:

	<t:beaneditform t:id="form" object="person" submitLabel="Save">
		<t:parameter name="version">
			<t:hidden t:id="version" value="person.version"/>
		</t:parameter>
	</t:beaneditform>

Yes, onActivate(...) is called twice if there's an error detected  
server-side, but that's due to the redirect-after-post paradigm, so  
it's a cost with a pretty big benefit.

One last note, the cleanupRender() method can be removed if T5 moves  
to "flash" persistence on the form's ValidationTracker.

Anything I've missed?

Cheers,

Geoff

On 12/12/2007, at 7:39 AM, Geoff Callender wrote:

> I'm planning on doing one, but not today.  If you know your way  
> around T4 then the web flow example in JumpStart Max 2.0.0 may help  
> you. It's at http://files.doublenegative.com.au/jumpstart/ .
>
> Here's Mk IV of the single page edit.  It seems that recording  
> errors in onSuccess() can cause fields to revert when you redisplay  
> with error, so I've moved some of it to onValidate().   The notes  
> from before are still relevant.
>
> 	private Long _personId;
> 	
> 	// "client" is used so that  even if you use the Back button
> 	// to get to me, I will have the right Person object.
> 	@Persist("client")
> 	private Person _person;
> 	
> 	void onActivate(Long id) throws Exception { _personId = id; }
>
> 	Long onPassivate() { return _person.getId(); }
>
> 	void setupRender() throws Exception {
> 		if (!_form.getHasErrors()) {
> 			_person = getPersonService().findPerson(_personId);
> 		}
> 	}
>
> 	void onValidate() throws Exception {
> 		if (...a bit of validation logic detects an error...) {
> 			_form.recordError(...);
> 			return;
> 		}
> 		try {
> 			getPersonService().changePerson(_person);
> 		}
> 		catch (Exception e) {
> 			_form.recordError(ExceptionUtil.getRootCause(e));
> 			return;
> 		}
> 	}
> 	
> 	Object onSuccess() {
> 		_nextPage.onActivate(_personId);
> 		return _nextPage;
> 	}
>
> 	void cleanupRender() {
> 		   _form.clearErrors();
> 	}
>
> Cheers,
>
> Geoff
>
> On 12/12/2007, at 4:53 AM, jeffrey ai wrote:
>
>>
>> Geoff, I think your code is great for **ONE** edit-page.
>> Our project is looking for a web flow from T5, like the Spring one.
>> Do you have any idea about it?
>> Howard mentioned he may add this feature in the next release.
>> Might be a little late to us, but I am expecting to see it.
>>
>> Cheers,
>> Jeffrey Ai
>>
>>
>> Geoff Callender-2 wrote:
>>>
>>> Hi,
>>>
>>> In search of best practice for an "edit" page, here's my 3rd  
>>> attempt.
>>> It's looking pretty clean-cut to me, but I'm looking for suggestions
>>> on how to improve it further.
>>>
>>> 	private Long _personId;
>>> 	
>>> 	@Persist("client")
>>> 	// Persistence is needed here because this is a detached Entity
>>> Bean.  When we call the service to
>>> 	// accept our changes, it will need its id and version fields  
>>> intact
>>> to be able to do optimistic
>>> 	// locking check and a successful merge. "client" is used so that
>>> even if you use the Back button
>>> 	// to get to this page, we will have the right Person object.
>>> 	private Person _person;
>>> 	
>>> 	void onActivate(Long id) throws Exception { _personId = id; }
>>>
>>> 	Long onPassivate() { return _person.getId(); }
>>>
>>> 	void setupRender() throws Exception {
>>> 		if (!_form.getHasErrors()) {
>>> 			_person = getPersonService().findPerson(_personId);
>>> 		}
>>> 	}
>>>
>>> 	void onValidate() throws Exception {
>>> 		if (...a bit of validation logic detects an error...) {
>>> 			_form.recordError(...);
>>> 		}
>>> 	}
>>> 	
>>> 	Object onSuccess() {
>>> 		try {
>>> 			getPersonService().changePerson(_person);
>>> 			_nextPage.onActivate(_personId);
>>> 			return _nextPage;
>>> 		}
>>> 		catch (Exception e) {
>>> 			_form.recordError(ExceptionUtil.getRootCause(e));
>>> 			return null;
>>> 		}
>>> 	}
>>>
>>> 	void cleanupRender() {
>>> 		   _form.clearErrors();
>>> 	}
>>> 	
>>> Some notes:
>>>
>>> 1. Detached object - Person is a detached entity.  I am deliberately
>>> avoiding refreshing it every time in setupRender() because a) it  
>>> would
>>> overwrite the user's changes, and b) it would defeat optimistic
>>> locking: if someone else has changed the object then I DO want
>>> getPersonService().changePerson(_person) to reject the transaction
>>> when Save is pressed.
>>>
>>> 2. Thread-safety - I'm using "client" persistence to avoid the whole
>>> thread-safety issue caused by the user opening a new window or  
>>> tabs in
>>> same browser (T5 can't tell them apart so they share the same
>>> HttpSession).
>>>
>>> 3. onPrepareFromForm() - I'm avoiding it because it gets called too
>>> often (something to do with "rewind"?).  setupRender() seems better
>>> for the job.  Any downside t this?
>>>
>>> 4. cleanupRender() - if/when 5.0.7 uses flash persistence on the
>>> form's ValidationTracker then I'll ditch this method.
>>>
>>> Suggestions please!
>>>
>>> Thanks,
>>>
>>> Geoff
>>>
>>>
>>>
>>
>> -- 
>> View this message in context: http://www.nabble.com/T5%3A-Edit-page-best-practice---Mk-III-tp14249141p14279495.html
>> Sent from the Tapestry - User mailing list archive at Nabble.com.
>>
>>
>> ---------------------------------------------------------------------
>> To unsubscribe, e-mail: users-unsubscribe@tapestry.apache.org
>> For additional commands, e-mail: users-help@tapestry.apache.org
>>
>


Re: T5: Edit page best practice - Mk VI

Posted by Martin Grotzke <ma...@javakaffee.de>.
On Wed, 2008-01-30 at 21:38 +1100, Geoff Callender wrote:
> * Perhaps what we really need is @Persist("redirection")?!?!?!
+1, this would make things more easy!

Cheers,
Martin


On Wed, 2008-01-30 at 21:38 +1100, Geoff Callender wrote:
> Good point, Davor.  The way I've used flash persistence it could
> just  
> as well have been session persistence. The effect would be identical.
> 
> * Perhaps what we really need is @Persist("redirection")?!?!?!
> 
> It's a serious suggestion.  After all, what we're aiming for is  
> persistence during a redirection and only during a redirection.  We  
> don't want persistence after arriving from PageLink or browser Reload/ 
> Refresh because it causes anomalies (and that's why Mk VI uses  
> cleanupRender()).  Even better, redirection-persistence could be keyed  
> by a temporary conversation id behind the scenes.
> 
> What does everyone think?
> 
> * Mk VI might be unacceptable in clustered environments, because it  
> uses server-side persistence, requiring session replication traffic.   
> In that situation I guess the options are either the Mk V solution or  
> some sort of client-side persistence - hidden fields,  
> @Persist("client"). or something new.
> 
> As for your generic editing, I like it.  It's exactly where I wanted  
> to take this once we have the possible solutions codified.  Perhaps an  
> @EditPage annotation rather than a superclass would be even nicer?
> 
> Cheers,
> 
> Geoff
> 
> On 30/01/2008, at 1:15 AM, Davor Hrg wrote:
> 
> > cleanupRender is not a fix for flash persistence, it's more like
> > replacement for it.
> > flash persistence is made for the same reason (only persisting until
> > next request)
> >
> > you can take your example even further and make it generic
> > thus allowing actual edit page to be as simple as:
> >
> > public class EditCompany extends EntityEditPage<Company, Long>{
> >
> >    @Override
> >    protected Object getNextPage() {
> >        return ListCompany.class;
> >    }
> >
> > }
> >
> >
> > the generic edit page I'm working on
> > gets entity class like this:
> >
> >    @SuppressWarnings("unchecked")
> >    public AbstractEntityEdit() {
> >        Type genericSuperclassType = getClass().getGenericSuperclass();
> >        if((genericSuperclassType instanceof ParameterizedType)){
> >            ParameterizedType genericSuperclass = (ParameterizedType)
> > genericSuperclassType;
> >            if(genericSuperclass.getActualTypeArguments()[0]  
> > instanceof Class){
> >                this.persistentClass = (Class<T>)
> > genericSuperclass.getActualTypeArguments()[0];
> >                this.idClass = (Class<T_ID>)
> > genericSuperclass.getActualTypeArguments()[1];
> >            }
> >        }
> >    }
> >
> > uses Session to load entity when needed,
> > persists only entityId as string
> > uses TypeCoercer to convert string to "idClass" before calling  
> > session.get()
> >
> > resets the form if cancel button is pressed or entityId changes
> >
> > Davor Hrg
> >
> >
> > On Jan 29, 2008 2:48 PM, Geoff Callender
> > <ge...@gmail.com> wrote:
> >> Thanks for all the comments.  They've been invaluable.  Below is the
> >> result, Mark Vi.
> >>
> >> Kalle, I'm using T5.0.9, but I think 5.0.7 would behave similarly.
> >> Yes, I'm trying frantically to keep up with the releases!
> >>
> >> As Davor pointed out, if validation has failed then getting the  
> >> entity
> >> again doesn't overwrite the form in recent releases.  I believe,
> >> however, that this only works if you keep everything that can fail in
> >> the onValidate... methods, eg. changePerson is called in
> >> onValidateFromForm instead of in onSuccess for this very reason.
> >>
> >> As Martin pointed out, I can avoid getting the entity every time in
> >> onActivate if I use @Persist("flash"), test for null entity in
> >> onActivate, and nullify "flash" fields in cleanupRender, ie. right
> >> before the page is displayed.   The reason for the last bit is that
> >> sometimes there's only one onActivate before a page is displayed, so
> >> an attempt to refresh the page won't work because the entity is still
> >> in flash-persistence. A second attempt would work, which is very
> >> disconcerting.
> >>
> >> Davor, I like Martin's cleanupRender effect, so I don't think the  
> >> "new
> >> window" problems you describe occur, and therefore I don't think I
> >> need the @Meta form annotation.  Have I missed a corner-case here?
> >>
> >> If you're using BeanEditForm, just replace "Form" with  
> >> "BeanEditForm".
> >>
> >>        private Long _personId;
> >>
> >>        @Persist("flash")
> >>        private Person _person;
> >>
> >>        @Component(id = "form")
> >>        private Form _form;
> >>
> >>        @InjectPage
> >>        private NextPage _nextPage;
> >>
> >>        void onActivate(Long id) throws Exception {
> >>                _personId = id;
> >>                if (_person == null) {
> >>                        _person =  
> >> getPersonService().findPerson(_personId);
> >>                }
> >>        }
> >>
> >>        Long onPassivate() {
> >>                return _personId;
> >>        }
> >>
> >>        void onValidateFromForm() {
> >>                if (...a bit of validation logic detects an  
> >> error...) {
> >>                        _form.recordError(...);
> >>                        return;
> >>                }
> >>                // todo: move this next block back to onSuccess()  
> >> once TAPESTRY-1972
> >> has been resolved.
> >>                try {
> >>                        getPersonService().changePerson(_person);
> >>                }
> >>                catch (Exception e) {
> >>                         
> >> _form.recordError(ExceptionUtil.getRootCause(e));
> >>                }
> >>        }
> >>
> >>        Object onSuccess() {
> >>                _nextPage.onActivate(_personId);
> >>                return _nextPage;
> >>        }
> >>
> >>        void cleanupRender() {
> >>                _form.clearErrors();
> >>                // Clear the flash-persisted fields to prevent  
> >> anomalies in
> >> onActivate when we hit refresh on page or browser button
> >>                _person = null;
> >>        }
> >>
> >> and of course the hidden version field stuff still applies (it  
> >> ensures
> >> optimistic locking works, preventing us saving changes to a Person
> >> that has since been updated or deleted by someone else or by us in
> >> another of our windows or tabs):
> >>
> >>                <t:hidden t:id="version" value="person.version"/>
> >>
> >> or if you're using BeanEditForm:
> >>
> >>        <t:beaneditform t:id="form" object="person"  
> >> submitLabel="Save">
> >>                <t:parameter name="version">
> >>                        <t:hidden t:id="version"  
> >> value="person.version"/>
> >>                </t:parameter>
> >>        </t:beaneditform>
> >>
> >> Any more thoughts?
> >>
> >> Geoff
> >>
> >>
> >
> > ---------------------------------------------------------------------
> > To unsubscribe, e-mail: users-unsubscribe@tapestry.apache.org
> > For additional commands, e-mail: users-help@tapestry.apache.org
> >
> 


---------------------------------------------------------------------
To unsubscribe, e-mail: users-unsubscribe@tapestry.apache.org
For additional commands, e-mail: users-help@tapestry.apache.org


Re: T5: Edit page best practice - Mk VI

Posted by Davor Hrg <hr...@gmail.com>.
I was trying out few options, but subclassing
is the simplest.

When subclassing generic data is preserved,
and prop: binding can resolve to actual return type of a method.


generic data is preserved for:
 - fields : List<String> strings;
 - methods  List<Strings> getNames(List<User> users)
 - subclasses : class UserEdit extends GenericEdit<User, Long>

tapestry currently only handles the third case, and only for getters,
when for example:
class GenericEdit<T, T_ID>{
   public T getEntity(){...}
}


Davor Hrg


On Jan 30, 2008 11:38 AM, Geoff Callender
<ge...@gmail.com> wrote:
> Good point, Davor.  The way I've used flash persistence it could just
> as well have been session persistence. The effect would be identical.
>
> * Perhaps what we really need is @Persist("redirection")?!?!?!
>
> It's a serious suggestion.  After all, what we're aiming for is
> persistence during a redirection and only during a redirection.  We
> don't want persistence after arriving from PageLink or browser Reload/
> Refresh because it causes anomalies (and that's why Mk VI uses
> cleanupRender()).  Even better, redirection-persistence could be keyed
> by a temporary conversation id behind the scenes.
>
> What does everyone think?
>
> * Mk VI might be unacceptable in clustered environments, because it
> uses server-side persistence, requiring session replication traffic.
> In that situation I guess the options are either the Mk V solution or
> some sort of client-side persistence - hidden fields,
> @Persist("client"). or something new.
>
> As for your generic editing, I like it.  It's exactly where I wanted
> to take this once we have the possible solutions codified.  Perhaps an
> @EditPage annotation rather than a superclass would be even nicer?
>
> Cheers,
>
> Geoff
>
>
> On 30/01/2008, at 1:15 AM, Davor Hrg wrote:
>
> > cleanupRender is not a fix for flash persistence, it's more like
> > replacement for it.
> > flash persistence is made for the same reason (only persisting until
> > next request)
> >
> > you can take your example even further and make it generic
> > thus allowing actual edit page to be as simple as:
> >
> > public class EditCompany extends EntityEditPage<Company, Long>{
> >
> >    @Override
> >    protected Object getNextPage() {
> >        return ListCompany.class;
> >    }
> >
> > }
> >
> >
> > the generic edit page I'm working on
> > gets entity class like this:
> >
> >    @SuppressWarnings("unchecked")
> >    public AbstractEntityEdit() {
> >        Type genericSuperclassType = getClass().getGenericSuperclass();
> >        if((genericSuperclassType instanceof ParameterizedType)){
> >            ParameterizedType genericSuperclass = (ParameterizedType)
> > genericSuperclassType;
> >            if(genericSuperclass.getActualTypeArguments()[0]
> > instanceof Class){
> >                this.persistentClass = (Class<T>)
> > genericSuperclass.getActualTypeArguments()[0];
> >                this.idClass = (Class<T_ID>)
> > genericSuperclass.getActualTypeArguments()[1];
> >            }
> >        }
> >    }
> >
> > uses Session to load entity when needed,
> > persists only entityId as string
> > uses TypeCoercer to convert string to "idClass" before calling
> > session.get()
> >
> > resets the form if cancel button is pressed or entityId changes
> >
> > Davor Hrg
> >
> >
> > On Jan 29, 2008 2:48 PM, Geoff Callender
> > <ge...@gmail.com> wrote:
> >> Thanks for all the comments.  They've been invaluable.  Below is the
> >> result, Mark Vi.
> >>
> >> Kalle, I'm using T5.0.9, but I think 5.0.7 would behave similarly.
> >> Yes, I'm trying frantically to keep up with the releases!
> >>
> >> As Davor pointed out, if validation has failed then getting the
> >> entity
> >> again doesn't overwrite the form in recent releases.  I believe,
> >> however, that this only works if you keep everything that can fail in
> >> the onValidate... methods, eg. changePerson is called in
> >> onValidateFromForm instead of in onSuccess for this very reason.
> >>
> >> As Martin pointed out, I can avoid getting the entity every time in
> >> onActivate if I use @Persist("flash"), test for null entity in
> >> onActivate, and nullify "flash" fields in cleanupRender, ie. right
> >> before the page is displayed.   The reason for the last bit is that
> >> sometimes there's only one onActivate before a page is displayed, so
> >> an attempt to refresh the page won't work because the entity is still
> >> in flash-persistence. A second attempt would work, which is very
> >> disconcerting.
> >>
> >> Davor, I like Martin's cleanupRender effect, so I don't think the
> >> "new
> >> window" problems you describe occur, and therefore I don't think I
> >> need the @Meta form annotation.  Have I missed a corner-case here?
> >>
> >> If you're using BeanEditForm, just replace "Form" with
> >> "BeanEditForm".
> >>
> >>        private Long _personId;
> >>
> >>        @Persist("flash")
> >>        private Person _person;
> >>
> >>        @Component(id = "form")
> >>        private Form _form;
> >>
> >>        @InjectPage
> >>        private NextPage _nextPage;
> >>
> >>        void onActivate(Long id) throws Exception {
> >>                _personId = id;
> >>                if (_person == null) {
> >>                        _person =
> >> getPersonService().findPerson(_personId);
> >>                }
> >>        }
> >>
> >>        Long onPassivate() {
> >>                return _personId;
> >>        }
> >>
> >>        void onValidateFromForm() {
> >>                if (...a bit of validation logic detects an
> >> error...) {
> >>                        _form.recordError(...);
> >>                        return;
> >>                }
> >>                // todo: move this next block back to onSuccess()
> >> once TAPESTRY-1972
> >> has been resolved.
> >>                try {
> >>                        getPersonService().changePerson(_person);
> >>                }
> >>                catch (Exception e) {
> >>
> >> _form.recordError(ExceptionUtil.getRootCause(e));
> >>                }
> >>        }
> >>
> >>        Object onSuccess() {
> >>                _nextPage.onActivate(_personId);
> >>                return _nextPage;
> >>        }
> >>
> >>        void cleanupRender() {
> >>                _form.clearErrors();
> >>                // Clear the flash-persisted fields to prevent
> >> anomalies in
> >> onActivate when we hit refresh on page or browser button
> >>                _person = null;
> >>        }
> >>
> >> and of course the hidden version field stuff still applies (it
> >> ensures
> >> optimistic locking works, preventing us saving changes to a Person
> >> that has since been updated or deleted by someone else or by us in
> >> another of our windows or tabs):
> >>
> >>                <t:hidden t:id="version" value="person.version"/>
> >>
> >> or if you're using BeanEditForm:
> >>
> >>        <t:beaneditform t:id="form" object="person"
> >> submitLabel="Save">
> >>                <t:parameter name="version">
> >>                        <t:hidden t:id="version"
> >> value="person.version"/>
> >>                </t:parameter>
> >>        </t:beaneditform>
> >>
> >> Any more thoughts?
> >>
> >> Geoff
> >>
> >>
> >
> > ---------------------------------------------------------------------
> > To unsubscribe, e-mail: users-unsubscribe@tapestry.apache.org
> > For additional commands, e-mail: users-help@tapestry.apache.org
> >
>
>

---------------------------------------------------------------------
To unsubscribe, e-mail: users-unsubscribe@tapestry.apache.org
For additional commands, e-mail: users-help@tapestry.apache.org


Re: T5: Edit page best practice - Mk VI

Posted by Geoff Callender <ge...@gmail.com>.
Good point, Davor.  The way I've used flash persistence it could just  
as well have been session persistence. The effect would be identical.

* Perhaps what we really need is @Persist("redirection")?!?!?!

It's a serious suggestion.  After all, what we're aiming for is  
persistence during a redirection and only during a redirection.  We  
don't want persistence after arriving from PageLink or browser Reload/ 
Refresh because it causes anomalies (and that's why Mk VI uses  
cleanupRender()).  Even better, redirection-persistence could be keyed  
by a temporary conversation id behind the scenes.

What does everyone think?

* Mk VI might be unacceptable in clustered environments, because it  
uses server-side persistence, requiring session replication traffic.   
In that situation I guess the options are either the Mk V solution or  
some sort of client-side persistence - hidden fields,  
@Persist("client"). or something new.

As for your generic editing, I like it.  It's exactly where I wanted  
to take this once we have the possible solutions codified.  Perhaps an  
@EditPage annotation rather than a superclass would be even nicer?

Cheers,

Geoff

On 30/01/2008, at 1:15 AM, Davor Hrg wrote:

> cleanupRender is not a fix for flash persistence, it's more like
> replacement for it.
> flash persistence is made for the same reason (only persisting until
> next request)
>
> you can take your example even further and make it generic
> thus allowing actual edit page to be as simple as:
>
> public class EditCompany extends EntityEditPage<Company, Long>{
>
>    @Override
>    protected Object getNextPage() {
>        return ListCompany.class;
>    }
>
> }
>
>
> the generic edit page I'm working on
> gets entity class like this:
>
>    @SuppressWarnings("unchecked")
>    public AbstractEntityEdit() {
>        Type genericSuperclassType = getClass().getGenericSuperclass();
>        if((genericSuperclassType instanceof ParameterizedType)){
>            ParameterizedType genericSuperclass = (ParameterizedType)
> genericSuperclassType;
>            if(genericSuperclass.getActualTypeArguments()[0]  
> instanceof Class){
>                this.persistentClass = (Class<T>)
> genericSuperclass.getActualTypeArguments()[0];
>                this.idClass = (Class<T_ID>)
> genericSuperclass.getActualTypeArguments()[1];
>            }
>        }
>    }
>
> uses Session to load entity when needed,
> persists only entityId as string
> uses TypeCoercer to convert string to "idClass" before calling  
> session.get()
>
> resets the form if cancel button is pressed or entityId changes
>
> Davor Hrg
>
>
> On Jan 29, 2008 2:48 PM, Geoff Callender
> <ge...@gmail.com> wrote:
>> Thanks for all the comments.  They've been invaluable.  Below is the
>> result, Mark Vi.
>>
>> Kalle, I'm using T5.0.9, but I think 5.0.7 would behave similarly.
>> Yes, I'm trying frantically to keep up with the releases!
>>
>> As Davor pointed out, if validation has failed then getting the  
>> entity
>> again doesn't overwrite the form in recent releases.  I believe,
>> however, that this only works if you keep everything that can fail in
>> the onValidate... methods, eg. changePerson is called in
>> onValidateFromForm instead of in onSuccess for this very reason.
>>
>> As Martin pointed out, I can avoid getting the entity every time in
>> onActivate if I use @Persist("flash"), test for null entity in
>> onActivate, and nullify "flash" fields in cleanupRender, ie. right
>> before the page is displayed.   The reason for the last bit is that
>> sometimes there's only one onActivate before a page is displayed, so
>> an attempt to refresh the page won't work because the entity is still
>> in flash-persistence. A second attempt would work, which is very
>> disconcerting.
>>
>> Davor, I like Martin's cleanupRender effect, so I don't think the  
>> "new
>> window" problems you describe occur, and therefore I don't think I
>> need the @Meta form annotation.  Have I missed a corner-case here?
>>
>> If you're using BeanEditForm, just replace "Form" with  
>> "BeanEditForm".
>>
>>        private Long _personId;
>>
>>        @Persist("flash")
>>        private Person _person;
>>
>>        @Component(id = "form")
>>        private Form _form;
>>
>>        @InjectPage
>>        private NextPage _nextPage;
>>
>>        void onActivate(Long id) throws Exception {
>>                _personId = id;
>>                if (_person == null) {
>>                        _person =  
>> getPersonService().findPerson(_personId);
>>                }
>>        }
>>
>>        Long onPassivate() {
>>                return _personId;
>>        }
>>
>>        void onValidateFromForm() {
>>                if (...a bit of validation logic detects an  
>> error...) {
>>                        _form.recordError(...);
>>                        return;
>>                }
>>                // todo: move this next block back to onSuccess()  
>> once TAPESTRY-1972
>> has been resolved.
>>                try {
>>                        getPersonService().changePerson(_person);
>>                }
>>                catch (Exception e) {
>>                         
>> _form.recordError(ExceptionUtil.getRootCause(e));
>>                }
>>        }
>>
>>        Object onSuccess() {
>>                _nextPage.onActivate(_personId);
>>                return _nextPage;
>>        }
>>
>>        void cleanupRender() {
>>                _form.clearErrors();
>>                // Clear the flash-persisted fields to prevent  
>> anomalies in
>> onActivate when we hit refresh on page or browser button
>>                _person = null;
>>        }
>>
>> and of course the hidden version field stuff still applies (it  
>> ensures
>> optimistic locking works, preventing us saving changes to a Person
>> that has since been updated or deleted by someone else or by us in
>> another of our windows or tabs):
>>
>>                <t:hidden t:id="version" value="person.version"/>
>>
>> or if you're using BeanEditForm:
>>
>>        <t:beaneditform t:id="form" object="person"  
>> submitLabel="Save">
>>                <t:parameter name="version">
>>                        <t:hidden t:id="version"  
>> value="person.version"/>
>>                </t:parameter>
>>        </t:beaneditform>
>>
>> Any more thoughts?
>>
>> Geoff
>>
>>
>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: users-unsubscribe@tapestry.apache.org
> For additional commands, e-mail: users-help@tapestry.apache.org
>


Re: T5: Edit page best practice - Mk VI

Posted by Davor Hrg <hr...@gmail.com>.
oh,
generics support is added in T5.0.10
and is a limited first impl...
(as generics are pretty hard to handle at runtime)

Davor Hrg

On Jan 29, 2008 3:15 PM, Davor Hrg <hr...@gmail.com> wrote:
> cleanupRender is not a fix for flash persistence, it's more like
> replacement for it.
> flash persistence is made for the same reason (only persisting until
> next request)
>
> you can take your example even further and make it generic
> thus allowing actual edit page to be as simple as:
>
> public class EditCompany extends EntityEditPage<Company, Long>{
>
>     @Override
>     protected Object getNextPage() {
>         return ListCompany.class;
>     }
>
> }
>
>
> the generic edit page I'm working on
> gets entity class like this:
>
>     @SuppressWarnings("unchecked")
>     public AbstractEntityEdit() {
>         Type genericSuperclassType = getClass().getGenericSuperclass();
>         if((genericSuperclassType instanceof ParameterizedType)){
>             ParameterizedType genericSuperclass = (ParameterizedType)
> genericSuperclassType;
>             if(genericSuperclass.getActualTypeArguments()[0] instanceof Class){
>                 this.persistentClass = (Class<T>)
> genericSuperclass.getActualTypeArguments()[0];
>                 this.idClass = (Class<T_ID>)
> genericSuperclass.getActualTypeArguments()[1];
>             }
>         }
>     }
>
> uses Session to load entity when needed,
> persists only entityId as string
> uses TypeCoercer to convert string to "idClass" before calling session.get()
>
> resets the form if cancel button is pressed or entityId changes
>
> Davor Hrg
>
>
> On Jan 29, 2008 2:48 PM, Geoff Callender
>
> <ge...@gmail.com> wrote:
> > Thanks for all the comments.  They've been invaluable.  Below is the
> > result, Mark Vi.
> >
> > Kalle, I'm using T5.0.9, but I think 5.0.7 would behave similarly.
> > Yes, I'm trying frantically to keep up with the releases!
> >
> > As Davor pointed out, if validation has failed then getting the entity
> > again doesn't overwrite the form in recent releases.  I believe,
> > however, that this only works if you keep everything that can fail in
> > the onValidate... methods, eg. changePerson is called in
> > onValidateFromForm instead of in onSuccess for this very reason.
> >
> > As Martin pointed out, I can avoid getting the entity every time in
> > onActivate if I use @Persist("flash"), test for null entity in
> > onActivate, and nullify "flash" fields in cleanupRender, ie. right
> > before the page is displayed.   The reason for the last bit is that
> > sometimes there's only one onActivate before a page is displayed, so
> > an attempt to refresh the page won't work because the entity is still
> > in flash-persistence. A second attempt would work, which is very
> > disconcerting.
> >
> > Davor, I like Martin's cleanupRender effect, so I don't think the "new
> > window" problems you describe occur, and therefore I don't think I
> > need the @Meta form annotation.  Have I missed a corner-case here?
> >
> > If you're using BeanEditForm, just replace "Form" with "BeanEditForm".
> >
> >         private Long _personId;
> >
> >         @Persist("flash")
> >         private Person _person;
> >
> >         @Component(id = "form")
> >         private Form _form;
> >
> >         @InjectPage
> >         private NextPage _nextPage;
> >
> >         void onActivate(Long id) throws Exception {
> >                 _personId = id;
> >                 if (_person == null) {
> >                         _person = getPersonService().findPerson(_personId);
> >                 }
> >         }
> >
> >         Long onPassivate() {
> >                 return _personId;
> >         }
> >
> >         void onValidateFromForm() {
> >                 if (...a bit of validation logic detects an error...) {
> >                         _form.recordError(...);
> >                         return;
> >                 }
> >                 // todo: move this next block back to onSuccess() once TAPESTRY-1972
> > has been resolved.
> >                 try {
> >                         getPersonService().changePerson(_person);
> >                 }
> >                 catch (Exception e) {
> >                         _form.recordError(ExceptionUtil.getRootCause(e));
> >                 }
> >         }
> >
> >         Object onSuccess() {
> >                 _nextPage.onActivate(_personId);
> >                 return _nextPage;
> >         }
> >
> >         void cleanupRender() {
> >                 _form.clearErrors();
> >                 // Clear the flash-persisted fields to prevent anomalies in
> > onActivate when we hit refresh on page or browser button
> >                 _person = null;
> >         }
> >
> > and of course the hidden version field stuff still applies (it ensures
> > optimistic locking works, preventing us saving changes to a Person
> > that has since been updated or deleted by someone else or by us in
> > another of our windows or tabs):
> >
> >                 <t:hidden t:id="version" value="person.version"/>
> >
> > or if you're using BeanEditForm:
> >
> >         <t:beaneditform t:id="form" object="person" submitLabel="Save">
> >                 <t:parameter name="version">
> >                         <t:hidden t:id="version" value="person.version"/>
> >                 </t:parameter>
> >         </t:beaneditform>
> >
> > Any more thoughts?
> >
> > Geoff
> >
> >
>

---------------------------------------------------------------------
To unsubscribe, e-mail: users-unsubscribe@tapestry.apache.org
For additional commands, e-mail: users-help@tapestry.apache.org


Re: T5: Edit page best practice - Mk VI

Posted by Davor Hrg <hr...@gmail.com>.
cleanupRender is not a fix for flash persistence, it's more like
replacement for it.
flash persistence is made for the same reason (only persisting until
next request)

you can take your example even further and make it generic
thus allowing actual edit page to be as simple as:

public class EditCompany extends EntityEditPage<Company, Long>{

    @Override
    protected Object getNextPage() {
        return ListCompany.class;
    }

}


the generic edit page I'm working on
gets entity class like this:

    @SuppressWarnings("unchecked")
    public AbstractEntityEdit() {
        Type genericSuperclassType = getClass().getGenericSuperclass();
        if((genericSuperclassType instanceof ParameterizedType)){
            ParameterizedType genericSuperclass = (ParameterizedType)
genericSuperclassType;
            if(genericSuperclass.getActualTypeArguments()[0] instanceof Class){
                this.persistentClass = (Class<T>)
genericSuperclass.getActualTypeArguments()[0];
                this.idClass = (Class<T_ID>)
genericSuperclass.getActualTypeArguments()[1];
            }
        }
    }

uses Session to load entity when needed,
persists only entityId as string
uses TypeCoercer to convert string to "idClass" before calling session.get()

resets the form if cancel button is pressed or entityId changes

Davor Hrg


On Jan 29, 2008 2:48 PM, Geoff Callender
<ge...@gmail.com> wrote:
> Thanks for all the comments.  They've been invaluable.  Below is the
> result, Mark Vi.
>
> Kalle, I'm using T5.0.9, but I think 5.0.7 would behave similarly.
> Yes, I'm trying frantically to keep up with the releases!
>
> As Davor pointed out, if validation has failed then getting the entity
> again doesn't overwrite the form in recent releases.  I believe,
> however, that this only works if you keep everything that can fail in
> the onValidate... methods, eg. changePerson is called in
> onValidateFromForm instead of in onSuccess for this very reason.
>
> As Martin pointed out, I can avoid getting the entity every time in
> onActivate if I use @Persist("flash"), test for null entity in
> onActivate, and nullify "flash" fields in cleanupRender, ie. right
> before the page is displayed.   The reason for the last bit is that
> sometimes there's only one onActivate before a page is displayed, so
> an attempt to refresh the page won't work because the entity is still
> in flash-persistence. A second attempt would work, which is very
> disconcerting.
>
> Davor, I like Martin's cleanupRender effect, so I don't think the "new
> window" problems you describe occur, and therefore I don't think I
> need the @Meta form annotation.  Have I missed a corner-case here?
>
> If you're using BeanEditForm, just replace "Form" with "BeanEditForm".
>
>         private Long _personId;
>
>         @Persist("flash")
>         private Person _person;
>
>         @Component(id = "form")
>         private Form _form;
>
>         @InjectPage
>         private NextPage _nextPage;
>
>         void onActivate(Long id) throws Exception {
>                 _personId = id;
>                 if (_person == null) {
>                         _person = getPersonService().findPerson(_personId);
>                 }
>         }
>
>         Long onPassivate() {
>                 return _personId;
>         }
>
>         void onValidateFromForm() {
>                 if (...a bit of validation logic detects an error...) {
>                         _form.recordError(...);
>                         return;
>                 }
>                 // todo: move this next block back to onSuccess() once TAPESTRY-1972
> has been resolved.
>                 try {
>                         getPersonService().changePerson(_person);
>                 }
>                 catch (Exception e) {
>                         _form.recordError(ExceptionUtil.getRootCause(e));
>                 }
>         }
>
>         Object onSuccess() {
>                 _nextPage.onActivate(_personId);
>                 return _nextPage;
>         }
>
>         void cleanupRender() {
>                 _form.clearErrors();
>                 // Clear the flash-persisted fields to prevent anomalies in
> onActivate when we hit refresh on page or browser button
>                 _person = null;
>         }
>
> and of course the hidden version field stuff still applies (it ensures
> optimistic locking works, preventing us saving changes to a Person
> that has since been updated or deleted by someone else or by us in
> another of our windows or tabs):
>
>                 <t:hidden t:id="version" value="person.version"/>
>
> or if you're using BeanEditForm:
>
>         <t:beaneditform t:id="form" object="person" submitLabel="Save">
>                 <t:parameter name="version">
>                         <t:hidden t:id="version" value="person.version"/>
>                 </t:parameter>
>         </t:beaneditform>
>
> Any more thoughts?
>
> Geoff
>
>

---------------------------------------------------------------------
To unsubscribe, e-mail: users-unsubscribe@tapestry.apache.org
For additional commands, e-mail: users-help@tapestry.apache.org


T5: Edit page best practice - Mk VI

Posted by Geoff Callender <ge...@gmail.com>.
Thanks for all the comments.  They've been invaluable.  Below is the  
result, Mark Vi.

Kalle, I'm using T5.0.9, but I think 5.0.7 would behave similarly.   
Yes, I'm trying frantically to keep up with the releases!

As Davor pointed out, if validation has failed then getting the entity  
again doesn't overwrite the form in recent releases.  I believe,  
however, that this only works if you keep everything that can fail in  
the onValidate... methods, eg. changePerson is called in  
onValidateFromForm instead of in onSuccess for this very reason.

As Martin pointed out, I can avoid getting the entity every time in  
onActivate if I use @Persist("flash"), test for null entity in  
onActivate, and nullify "flash" fields in cleanupRender, ie. right  
before the page is displayed.   The reason for the last bit is that  
sometimes there's only one onActivate before a page is displayed, so  
an attempt to refresh the page won't work because the entity is still  
in flash-persistence. A second attempt would work, which is very  
disconcerting.

Davor, I like Martin's cleanupRender effect, so I don't think the "new  
window" problems you describe occur, and therefore I don't think I  
need the @Meta form annotation.  Have I missed a corner-case here?

If you're using BeanEditForm, just replace "Form" with "BeanEditForm".

	private Long _personId;

	@Persist("flash")
	private Person _person;

	@Component(id = "form")
	private Form _form;

	@InjectPage
	private NextPage _nextPage;

	void onActivate(Long id) throws Exception {
		_personId = id;
		if (_person == null) {
			_person = getPersonService().findPerson(_personId);
		}
	}

	Long onPassivate() {
		return _personId;
	}

	void onValidateFromForm() {
		if (...a bit of validation logic detects an error...) {
			_form.recordError(...);
			return;
		}
		// todo: move this next block back to onSuccess() once TAPESTRY-1972  
has been resolved.
		try {
			getPersonService().changePerson(_person);
		}
		catch (Exception e) {
			_form.recordError(ExceptionUtil.getRootCause(e));
		}
	}

	Object onSuccess() {
		_nextPage.onActivate(_personId);
		return _nextPage;
	}

	void cleanupRender() {
		_form.clearErrors();
		// Clear the flash-persisted fields to prevent anomalies in  
onActivate when we hit refresh on page or browser button
		_person = null;
	}

and of course the hidden version field stuff still applies (it ensures  
optimistic locking works, preventing us saving changes to a Person  
that has since been updated or deleted by someone else or by us in  
another of our windows or tabs):

  		<t:hidden t:id="version" value="person.version"/>

or if you're using BeanEditForm:

	<t:beaneditform t:id="form" object="person" submitLabel="Save">
		<t:parameter name="version">
			<t:hidden t:id="version" value="person.version"/>
		</t:parameter>
	</t:beaneditform>

Any more thoughts?

Geoff


Re: T5: Edit page best practice - Mk III - now Mk V

Posted by Davor Hrg <hr...@gmail.com>.
invalidated form is persisted using @Persist without explicitly
defining strategy
"session" is default strategy, so yes: if you use the same
invalidated form in different windows, expect problems.
When "conversation" strategy is implemented this will work just fine :)

you can however use @Meta annotation to change the persistence
strategy for the form.

Davor Hrg

On Jan 28, 2008 6:09 PM, Kalle Korhonen <ka...@gmail.com> wrote:
> Really important work Geoff, thanks. Which version are you using? I hope you
> go through the extra work to keep up with the versions.
>
> Davor, you say the invalidated form is persisted in session by default. Does
> multi-window support work in this case, i.e. if the same user opens another
> edit page for the same object, changes somethings and submits the first one,
> what would happen?
>
> Kalle
>
>
>
> On 1/28/08, Davor Hrg <hr...@gmail.com> wrote:
> >
> > things are a bit different in newer versions,
> >
> > for one, form persists submited data if validation fails,
> > and does not update data on your entity.
> >
> > the invalidated form is persisted default (session if without @Meta)
> > and suits my use fine, I reset form only if new entityid shows up,
> > if you go to: "edit/3" and have validation errors ,and go to another page,
> > then return to "edit/3" validation errors are stil there, and last
> > submited data,
> > if you go to edit/4 errors are reset and new data from db loaded...
> >
> > not that this suits everybody else, but is fine for me..
> >
> > Davor Hrg
> >
> > On Jan 28, 2008 3:01 PM, Martin Grotzke <ma...@javakaffee.de>
> > wrote:
> > > Hi Geoff,
> > >
> > > if validation fails the page is displayed again, and the person will be
> > > loaded again in the according GET request - so all input from the user
> > > will be lost.
> > >
> > > We're using flash persistence for this: the person would get annotated
> > > with @Persist("flash"), would be reset in cleanupRender and loaded in
> > > onActivate only if the entity (field) is null.
> > >
> > > With this two subsequent request like
> > >
> > > /person/edit/1
> > > and
> > > /person/edit/2
> > >
> > > display the correct data, as the person is not persisted after
> > > cleanupRender has been invoked.
> > >
> > > When first page is submitted, the person with id 1 is loaded from the
> > > backend in onActivate, in populated by tapestry, and validated by
> > > yourself. If then some validation error occurs and the page is displayed
> > > again (by using redirect after POST), the person is still existing in
> > > persist/flash with the populated values.
> > >
> > > When redirect is done in onSuccessFromYourForm the entity should be also
> > > reset / removed from persist/flash...
> > >
> > > Btw: our experiences are based on T5.0.5, but I suppose these still
> > > apply to later versions of T5...
> > >
> > > Cheers,
> > > Martin
> > >
> > >
> > >
> > > On Mon, 2008-01-28 at 23:46 +1100, Geoff Callender wrote:
> > > > Here's Mark V of the single page edit.  In previous attempts I was
> > > > clinging onto client-side persistence of the entity to ensure
> > > > optimistic locking, and then "the penny finally dropped" - all you
> > > > need to ensure optimistic locking is to ensure the entity's version is
> > > > retained - and you can do that with a hidden field.  So client-side
> > > > persistence really isn't necessary.  Hallelujah!
> > > >
> > > >       private Long _personId;
> > > >
> > > >       private Person _person;
> > > >
> > > >       void onActivate(Long id) throws Exception {
> > > >               _personId = id;
> > > >               _person = getPersonService().findPerson(_personId);
> > > >       }
> > > >
> > > >       Long onPassivate() {
> > > >               return _personId;
> > > >       }
> > > >
> > > >       void onValidateFromForm() {
> > > >               if (...a bit of validation logic detects an error...) {
> > > >                       _form.recordError(...);
> > > >                       return;
> > > >               }
> > > >               try {
> > > >                       // move this back to onSuccess() once
> > TAPESTRY-1972 has been
> > > > resolved.
> > > >                       getPersonService().changePerson(_person);
> > > >               }
> > > >               catch (Exception e) {
> > > >                       _form.recordError(ExceptionUtil.getRootCause
> > (e));
> > > >               }
> > > >       }
> > > >
> > > >       Object onSuccess() {
> > > >               _nextPage.onActivate(_personId);
> > > >               return _nextPage;
> > > >       }
> > > >
> > > >       void cleanupRender() {
> > > >               _form.clearErrors();
> > > >       }
> > > >
> > > > and in the html form, use the Hidden component that's been discussed
> > > > in this mailing list:
> > > >
> > > >               <t:hidden t:id="version" value="person.version"/>
> > > >
> > > > or if you're using BeanEditForm:
> > > >
> > > >       <t:beaneditform t:id="form" object="person" submitLabel="Save">
> > > >               <t:parameter name="version">
> > > >                       <t:hidden t:id="version" value="person.version
> > "/>
> > > >               </t:parameter>
> > > >       </t:beaneditform>
> > > >
> > > > Yes, onActivate(...) is called twice if there's an error detected
> > > > server-side, but that's due to the redirect-after-post paradigm, so
> > > > it's a cost with a pretty big benefit.
> > > >
> > > > One last note, the cleanupRender() method can be removed if T5 moves
> > > > to "flash" persistence on the form's ValidationTracker.
> > > >
> > > > Anything I've missed?
> > > >
> > > > Cheers,
> > > >
> > > > Geoff
> > > >
> > > > On 12/12/2007, at 7:39 AM, Geoff Callender wrote:
> > > >
> > > > > I'm planning on doing one, but not today.  If you know your way
> > > > > around T4 then the web flow example in JumpStart Max 2.0.0 may help
> > > > > you. It's at http://files.doublenegative.com.au/jumpstart/ .
> > > > >
> > > > > Here's Mk IV of the single page edit.  It seems that recording
> > > > > errors in onSuccess() can cause fields to revert when you redisplay
> > > > > with error, so I've moved some of it to onValidate().   The notes
> > > > > from before are still relevant.
> > > > >
> > > > >     private Long _personId;
> > > > >
> > > > >     // "client" is used so that  even if you use the Back button
> > > > >     // to get to me, I will have the right Person object.
> > > > >     @Persist("client")
> > > > >     private Person _person;
> > > > >
> > > > >     void onActivate(Long id) throws Exception { _personId = id; }
> > > > >
> > > > >     Long onPassivate() { return _person.getId(); }
> > > > >
> > > > >     void setupRender() throws Exception {
> > > > >             if (!_form.getHasErrors()) {
> > > > >                     _person =
> > getPersonService().findPerson(_personId);
> > > > >             }
> > > > >     }
> > > > >
> > > > >     void onValidate() throws Exception {
> > > > >             if (...a bit of validation logic detects an error...) {
> > > > >                     _form.recordError(...);
> > > > >                     return;
> > > > >             }
> > > > >             try {
> > > > >                     getPersonService().changePerson(_person);
> > > > >             }
> > > > >             catch (Exception e) {
> > > > >                     _form.recordError(ExceptionUtil.getRootCause
> > (e));
> > > > >                     return;
> > > > >             }
> > > > >     }
> > > > >
> > > > >     Object onSuccess() {
> > > > >             _nextPage.onActivate(_personId);
> > > > >             return _nextPage;
> > > > >     }
> > > > >
> > > > >     void cleanupRender() {
> > > > >                _form.clearErrors();
> > > > >     }
> > > > >
> > > > > Cheers,
> > > > >
> > > > > Geoff
> > > > >
> > > > > On 12/12/2007, at 4:53 AM, jeffrey ai wrote:
> > > > >
> > > > >>
> > > > >> Geoff, I think your code is great for **ONE** edit-page.
> > > > >> Our project is looking for a web flow from T5, like the Spring one.
> > > > >> Do you have any idea about it?
> > > > >> Howard mentioned he may add this feature in the next release.
> > > > >> Might be a little late to us, but I am expecting to see it.
> > > > >>
> > > > >> Cheers,
> > > > >> Jeffrey Ai
> > > > >>
> > > > >>
> > > > >> Geoff Callender-2 wrote:
> > > > >>>
> > > > >>> Hi,
> > > > >>>
> > > > >>> In search of best practice for an "edit" page, here's my 3rd
> > > > >>> attempt.
> > > > >>> It's looking pretty clean-cut to me, but I'm looking for
> > suggestions
> > > > >>> on how to improve it further.
> > > > >>>
> > > > >>>   private Long _personId;
> > > > >>>
> > > > >>>   @Persist("client")
> > > > >>>   // Persistence is needed here because this is a detached Entity
> > > > >>> Bean.  When we call the service to
> > > > >>>   // accept our changes, it will need its id and version fields
> > > > >>> intact
> > > > >>> to be able to do optimistic
> > > > >>>   // locking check and a successful merge. "client" is used so
> > that
> > > > >>> even if you use the Back button
> > > > >>>   // to get to this page, we will have the right Person object.
> > > > >>>   private Person _person;
> > > > >>>
> > > > >>>   void onActivate(Long id) throws Exception { _personId = id; }
> > > > >>>
> > > > >>>   Long onPassivate() { return _person.getId(); }
> > > > >>>
> > > > >>>   void setupRender() throws Exception {
> > > > >>>           if (!_form.getHasErrors()) {
> > > > >>>                   _person =
> > getPersonService().findPerson(_personId);
> > > > >>>           }
> > > > >>>   }
> > > > >>>
> > > > >>>   void onValidate() throws Exception {
> > > > >>>           if (...a bit of validation logic detects an error...) {
> > > > >>>                   _form.recordError(...);
> > > > >>>           }
> > > > >>>   }
> > > > >>>
> > > > >>>   Object onSuccess() {
> > > > >>>           try {
> > > > >>>                   getPersonService().changePerson(_person);
> > > > >>>                   _nextPage.onActivate(_personId);
> > > > >>>                   return _nextPage;
> > > > >>>           }
> > > > >>>           catch (Exception e) {
> > > > >>>                   _form.recordError(ExceptionUtil.getRootCause
> > (e));
> > > > >>>                   return null;
> > > > >>>           }
> > > > >>>   }
> > > > >>>
> > > > >>>   void cleanupRender() {
> > > > >>>              _form.clearErrors();
> > > > >>>   }
> > > > >>>
> > > > >>> Some notes:
> > > > >>>
> > > > >>> 1. Detached object - Person is a detached entity.  I am
> > deliberately
> > > > >>> avoiding refreshing it every time in setupRender() because a) it
> > > > >>> would
> > > > >>> overwrite the user's changes, and b) it would defeat optimistic
> > > > >>> locking: if someone else has changed the object then I DO want
> > > > >>> getPersonService().changePerson(_person) to reject the transaction
> > > > >>> when Save is pressed.
> > > > >>>
> > > > >>> 2. Thread-safety - I'm using "client" persistence to avoid the
> > whole
> > > > >>> thread-safety issue caused by the user opening a new window or
> > > > >>> tabs in
> > > > >>> same browser (T5 can't tell them apart so they share the same
> > > > >>> HttpSession).
> > > > >>>
> > > > >>> 3. onPrepareFromForm() - I'm avoiding it because it gets called
> > too
> > > > >>> often (something to do with "rewind"?).  setupRender() seems
> > better
> > > > >>> for the job.  Any downside t this?
> > > > >>>
> > > > >>> 4. cleanupRender() - if/when 5.0.7 uses flash persistence on the
> > > > >>> form's ValidationTracker then I'll ditch this method.
> > > > >>>
> > > > >>> Suggestions please!
> > > > >>>
> > > > >>> Thanks,
> > > > >>>
> > > > >>> Geoff
> > > > >>>
> > > > >>>
> > > > >>>
> > > > >>
> > > > >> --
> > > > >> View this message in context:
> > http://www.nabble.com/T5%3A-Edit-page-best-practice---Mk-III-tp14249141p14279495.html
> > > > >> Sent from the Tapestry - User mailing list archive at Nabble.com.
> > > > >>
> > > > >>
> > > > >>
> > ---------------------------------------------------------------------
> > > > >> To unsubscribe, e-mail: users-unsubscribe@tapestry.apache.org
> > > > >> For additional commands, e-mail: users-help@tapestry.apache.org
> > > > >>
> > > > >
> > > >
> > > --
> > > Martin Grotzke
> > > http://www.javakaffee.de/blog/
> > >
> > >
> > >
> > > ---------------------------------------------------------------------
> > > To unsubscribe, e-mail: users-unsubscribe@tapestry.apache.org
> > > For additional commands, e-mail: users-help@tapestry.apache.org
> > >
> > >
> >
> > ---------------------------------------------------------------------
> > To unsubscribe, e-mail: users-unsubscribe@tapestry.apache.org
> > For additional commands, e-mail: users-help@tapestry.apache.org
> >
> >
>

---------------------------------------------------------------------
To unsubscribe, e-mail: users-unsubscribe@tapestry.apache.org
For additional commands, e-mail: users-help@tapestry.apache.org


Re: T5: Edit page best practice - Mk III - now Mk V

Posted by Kalle Korhonen <ka...@gmail.com>.
Really important work Geoff, thanks. Which version are you using? I hope you
go through the extra work to keep up with the versions.

Davor, you say the invalidated form is persisted in session by default. Does
multi-window support work in this case, i.e. if the same user opens another
edit page for the same object, changes somethings and submits the first one,
what would happen?

Kalle


On 1/28/08, Davor Hrg <hr...@gmail.com> wrote:
>
> things are a bit different in newer versions,
>
> for one, form persists submited data if validation fails,
> and does not update data on your entity.
>
> the invalidated form is persisted default (session if without @Meta)
> and suits my use fine, I reset form only if new entityid shows up,
> if you go to: "edit/3" and have validation errors ,and go to another page,
> then return to "edit/3" validation errors are stil there, and last
> submited data,
> if you go to edit/4 errors are reset and new data from db loaded...
>
> not that this suits everybody else, but is fine for me..
>
> Davor Hrg
>
> On Jan 28, 2008 3:01 PM, Martin Grotzke <ma...@javakaffee.de>
> wrote:
> > Hi Geoff,
> >
> > if validation fails the page is displayed again, and the person will be
> > loaded again in the according GET request - so all input from the user
> > will be lost.
> >
> > We're using flash persistence for this: the person would get annotated
> > with @Persist("flash"), would be reset in cleanupRender and loaded in
> > onActivate only if the entity (field) is null.
> >
> > With this two subsequent request like
> >
> > /person/edit/1
> > and
> > /person/edit/2
> >
> > display the correct data, as the person is not persisted after
> > cleanupRender has been invoked.
> >
> > When first page is submitted, the person with id 1 is loaded from the
> > backend in onActivate, in populated by tapestry, and validated by
> > yourself. If then some validation error occurs and the page is displayed
> > again (by using redirect after POST), the person is still existing in
> > persist/flash with the populated values.
> >
> > When redirect is done in onSuccessFromYourForm the entity should be also
> > reset / removed from persist/flash...
> >
> > Btw: our experiences are based on T5.0.5, but I suppose these still
> > apply to later versions of T5...
> >
> > Cheers,
> > Martin
> >
> >
> >
> > On Mon, 2008-01-28 at 23:46 +1100, Geoff Callender wrote:
> > > Here's Mark V of the single page edit.  In previous attempts I was
> > > clinging onto client-side persistence of the entity to ensure
> > > optimistic locking, and then "the penny finally dropped" - all you
> > > need to ensure optimistic locking is to ensure the entity's version is
> > > retained - and you can do that with a hidden field.  So client-side
> > > persistence really isn't necessary.  Hallelujah!
> > >
> > >       private Long _personId;
> > >
> > >       private Person _person;
> > >
> > >       void onActivate(Long id) throws Exception {
> > >               _personId = id;
> > >               _person = getPersonService().findPerson(_personId);
> > >       }
> > >
> > >       Long onPassivate() {
> > >               return _personId;
> > >       }
> > >
> > >       void onValidateFromForm() {
> > >               if (...a bit of validation logic detects an error...) {
> > >                       _form.recordError(...);
> > >                       return;
> > >               }
> > >               try {
> > >                       // move this back to onSuccess() once
> TAPESTRY-1972 has been
> > > resolved.
> > >                       getPersonService().changePerson(_person);
> > >               }
> > >               catch (Exception e) {
> > >                       _form.recordError(ExceptionUtil.getRootCause
> (e));
> > >               }
> > >       }
> > >
> > >       Object onSuccess() {
> > >               _nextPage.onActivate(_personId);
> > >               return _nextPage;
> > >       }
> > >
> > >       void cleanupRender() {
> > >               _form.clearErrors();
> > >       }
> > >
> > > and in the html form, use the Hidden component that's been discussed
> > > in this mailing list:
> > >
> > >               <t:hidden t:id="version" value="person.version"/>
> > >
> > > or if you're using BeanEditForm:
> > >
> > >       <t:beaneditform t:id="form" object="person" submitLabel="Save">
> > >               <t:parameter name="version">
> > >                       <t:hidden t:id="version" value="person.version
> "/>
> > >               </t:parameter>
> > >       </t:beaneditform>
> > >
> > > Yes, onActivate(...) is called twice if there's an error detected
> > > server-side, but that's due to the redirect-after-post paradigm, so
> > > it's a cost with a pretty big benefit.
> > >
> > > One last note, the cleanupRender() method can be removed if T5 moves
> > > to "flash" persistence on the form's ValidationTracker.
> > >
> > > Anything I've missed?
> > >
> > > Cheers,
> > >
> > > Geoff
> > >
> > > On 12/12/2007, at 7:39 AM, Geoff Callender wrote:
> > >
> > > > I'm planning on doing one, but not today.  If you know your way
> > > > around T4 then the web flow example in JumpStart Max 2.0.0 may help
> > > > you. It's at http://files.doublenegative.com.au/jumpstart/ .
> > > >
> > > > Here's Mk IV of the single page edit.  It seems that recording
> > > > errors in onSuccess() can cause fields to revert when you redisplay
> > > > with error, so I've moved some of it to onValidate().   The notes
> > > > from before are still relevant.
> > > >
> > > >     private Long _personId;
> > > >
> > > >     // "client" is used so that  even if you use the Back button
> > > >     // to get to me, I will have the right Person object.
> > > >     @Persist("client")
> > > >     private Person _person;
> > > >
> > > >     void onActivate(Long id) throws Exception { _personId = id; }
> > > >
> > > >     Long onPassivate() { return _person.getId(); }
> > > >
> > > >     void setupRender() throws Exception {
> > > >             if (!_form.getHasErrors()) {
> > > >                     _person =
> getPersonService().findPerson(_personId);
> > > >             }
> > > >     }
> > > >
> > > >     void onValidate() throws Exception {
> > > >             if (...a bit of validation logic detects an error...) {
> > > >                     _form.recordError(...);
> > > >                     return;
> > > >             }
> > > >             try {
> > > >                     getPersonService().changePerson(_person);
> > > >             }
> > > >             catch (Exception e) {
> > > >                     _form.recordError(ExceptionUtil.getRootCause
> (e));
> > > >                     return;
> > > >             }
> > > >     }
> > > >
> > > >     Object onSuccess() {
> > > >             _nextPage.onActivate(_personId);
> > > >             return _nextPage;
> > > >     }
> > > >
> > > >     void cleanupRender() {
> > > >                _form.clearErrors();
> > > >     }
> > > >
> > > > Cheers,
> > > >
> > > > Geoff
> > > >
> > > > On 12/12/2007, at 4:53 AM, jeffrey ai wrote:
> > > >
> > > >>
> > > >> Geoff, I think your code is great for **ONE** edit-page.
> > > >> Our project is looking for a web flow from T5, like the Spring one.
> > > >> Do you have any idea about it?
> > > >> Howard mentioned he may add this feature in the next release.
> > > >> Might be a little late to us, but I am expecting to see it.
> > > >>
> > > >> Cheers,
> > > >> Jeffrey Ai
> > > >>
> > > >>
> > > >> Geoff Callender-2 wrote:
> > > >>>
> > > >>> Hi,
> > > >>>
> > > >>> In search of best practice for an "edit" page, here's my 3rd
> > > >>> attempt.
> > > >>> It's looking pretty clean-cut to me, but I'm looking for
> suggestions
> > > >>> on how to improve it further.
> > > >>>
> > > >>>   private Long _personId;
> > > >>>
> > > >>>   @Persist("client")
> > > >>>   // Persistence is needed here because this is a detached Entity
> > > >>> Bean.  When we call the service to
> > > >>>   // accept our changes, it will need its id and version fields
> > > >>> intact
> > > >>> to be able to do optimistic
> > > >>>   // locking check and a successful merge. "client" is used so
> that
> > > >>> even if you use the Back button
> > > >>>   // to get to this page, we will have the right Person object.
> > > >>>   private Person _person;
> > > >>>
> > > >>>   void onActivate(Long id) throws Exception { _personId = id; }
> > > >>>
> > > >>>   Long onPassivate() { return _person.getId(); }
> > > >>>
> > > >>>   void setupRender() throws Exception {
> > > >>>           if (!_form.getHasErrors()) {
> > > >>>                   _person =
> getPersonService().findPerson(_personId);
> > > >>>           }
> > > >>>   }
> > > >>>
> > > >>>   void onValidate() throws Exception {
> > > >>>           if (...a bit of validation logic detects an error...) {
> > > >>>                   _form.recordError(...);
> > > >>>           }
> > > >>>   }
> > > >>>
> > > >>>   Object onSuccess() {
> > > >>>           try {
> > > >>>                   getPersonService().changePerson(_person);
> > > >>>                   _nextPage.onActivate(_personId);
> > > >>>                   return _nextPage;
> > > >>>           }
> > > >>>           catch (Exception e) {
> > > >>>                   _form.recordError(ExceptionUtil.getRootCause
> (e));
> > > >>>                   return null;
> > > >>>           }
> > > >>>   }
> > > >>>
> > > >>>   void cleanupRender() {
> > > >>>              _form.clearErrors();
> > > >>>   }
> > > >>>
> > > >>> Some notes:
> > > >>>
> > > >>> 1. Detached object - Person is a detached entity.  I am
> deliberately
> > > >>> avoiding refreshing it every time in setupRender() because a) it
> > > >>> would
> > > >>> overwrite the user's changes, and b) it would defeat optimistic
> > > >>> locking: if someone else has changed the object then I DO want
> > > >>> getPersonService().changePerson(_person) to reject the transaction
> > > >>> when Save is pressed.
> > > >>>
> > > >>> 2. Thread-safety - I'm using "client" persistence to avoid the
> whole
> > > >>> thread-safety issue caused by the user opening a new window or
> > > >>> tabs in
> > > >>> same browser (T5 can't tell them apart so they share the same
> > > >>> HttpSession).
> > > >>>
> > > >>> 3. onPrepareFromForm() - I'm avoiding it because it gets called
> too
> > > >>> often (something to do with "rewind"?).  setupRender() seems
> better
> > > >>> for the job.  Any downside t this?
> > > >>>
> > > >>> 4. cleanupRender() - if/when 5.0.7 uses flash persistence on the
> > > >>> form's ValidationTracker then I'll ditch this method.
> > > >>>
> > > >>> Suggestions please!
> > > >>>
> > > >>> Thanks,
> > > >>>
> > > >>> Geoff
> > > >>>
> > > >>>
> > > >>>
> > > >>
> > > >> --
> > > >> View this message in context:
> http://www.nabble.com/T5%3A-Edit-page-best-practice---Mk-III-tp14249141p14279495.html
> > > >> Sent from the Tapestry - User mailing list archive at Nabble.com.
> > > >>
> > > >>
> > > >>
> ---------------------------------------------------------------------
> > > >> To unsubscribe, e-mail: users-unsubscribe@tapestry.apache.org
> > > >> For additional commands, e-mail: users-help@tapestry.apache.org
> > > >>
> > > >
> > >
> > --
> > Martin Grotzke
> > http://www.javakaffee.de/blog/
> >
> >
> >
> > ---------------------------------------------------------------------
> > To unsubscribe, e-mail: users-unsubscribe@tapestry.apache.org
> > For additional commands, e-mail: users-help@tapestry.apache.org
> >
> >
>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: users-unsubscribe@tapestry.apache.org
> For additional commands, e-mail: users-help@tapestry.apache.org
>
>

Re: T5: Edit page best practice - Mk III - now Mk V

Posted by Davor Hrg <hr...@gmail.com>.
things are a bit different in newer versions,

for one, form persists submited data if validation fails,
and does not update data on your entity.

the invalidated form is persisted default (session if without @Meta)
and suits my use fine, I reset form only if new entityid shows up,
if you go to: "edit/3" and have validation errors ,and go to another page,
then return to "edit/3" validation errors are stil there, and last
submited data,
if you go to edit/4 errors are reset and new data from db loaded...

not that this suits everybody else, but is fine for me..

Davor Hrg

On Jan 28, 2008 3:01 PM, Martin Grotzke <ma...@javakaffee.de> wrote:
> Hi Geoff,
>
> if validation fails the page is displayed again, and the person will be
> loaded again in the according GET request - so all input from the user
> will be lost.
>
> We're using flash persistence for this: the person would get annotated
> with @Persist("flash"), would be reset in cleanupRender and loaded in
> onActivate only if the entity (field) is null.
>
> With this two subsequent request like
>
> /person/edit/1
> and
> /person/edit/2
>
> display the correct data, as the person is not persisted after
> cleanupRender has been invoked.
>
> When first page is submitted, the person with id 1 is loaded from the
> backend in onActivate, in populated by tapestry, and validated by
> yourself. If then some validation error occurs and the page is displayed
> again (by using redirect after POST), the person is still existing in
> persist/flash with the populated values.
>
> When redirect is done in onSuccessFromYourForm the entity should be also
> reset / removed from persist/flash...
>
> Btw: our experiences are based on T5.0.5, but I suppose these still
> apply to later versions of T5...
>
> Cheers,
> Martin
>
>
>
> On Mon, 2008-01-28 at 23:46 +1100, Geoff Callender wrote:
> > Here's Mark V of the single page edit.  In previous attempts I was
> > clinging onto client-side persistence of the entity to ensure
> > optimistic locking, and then "the penny finally dropped" - all you
> > need to ensure optimistic locking is to ensure the entity's version is
> > retained - and you can do that with a hidden field.  So client-side
> > persistence really isn't necessary.  Hallelujah!
> >
> >       private Long _personId;
> >
> >       private Person _person;
> >
> >       void onActivate(Long id) throws Exception {
> >               _personId = id;
> >               _person = getPersonService().findPerson(_personId);
> >       }
> >
> >       Long onPassivate() {
> >               return _personId;
> >       }
> >
> >       void onValidateFromForm() {
> >               if (...a bit of validation logic detects an error...) {
> >                       _form.recordError(...);
> >                       return;
> >               }
> >               try {
> >                       // move this back to onSuccess() once TAPESTRY-1972 has been
> > resolved.
> >                       getPersonService().changePerson(_person);
> >               }
> >               catch (Exception e) {
> >                       _form.recordError(ExceptionUtil.getRootCause(e));
> >               }
> >       }
> >
> >       Object onSuccess() {
> >               _nextPage.onActivate(_personId);
> >               return _nextPage;
> >       }
> >
> >       void cleanupRender() {
> >               _form.clearErrors();
> >       }
> >
> > and in the html form, use the Hidden component that's been discussed
> > in this mailing list:
> >
> >               <t:hidden t:id="version" value="person.version"/>
> >
> > or if you're using BeanEditForm:
> >
> >       <t:beaneditform t:id="form" object="person" submitLabel="Save">
> >               <t:parameter name="version">
> >                       <t:hidden t:id="version" value="person.version"/>
> >               </t:parameter>
> >       </t:beaneditform>
> >
> > Yes, onActivate(...) is called twice if there's an error detected
> > server-side, but that's due to the redirect-after-post paradigm, so
> > it's a cost with a pretty big benefit.
> >
> > One last note, the cleanupRender() method can be removed if T5 moves
> > to "flash" persistence on the form's ValidationTracker.
> >
> > Anything I've missed?
> >
> > Cheers,
> >
> > Geoff
> >
> > On 12/12/2007, at 7:39 AM, Geoff Callender wrote:
> >
> > > I'm planning on doing one, but not today.  If you know your way
> > > around T4 then the web flow example in JumpStart Max 2.0.0 may help
> > > you. It's at http://files.doublenegative.com.au/jumpstart/ .
> > >
> > > Here's Mk IV of the single page edit.  It seems that recording
> > > errors in onSuccess() can cause fields to revert when you redisplay
> > > with error, so I've moved some of it to onValidate().   The notes
> > > from before are still relevant.
> > >
> > >     private Long _personId;
> > >
> > >     // "client" is used so that  even if you use the Back button
> > >     // to get to me, I will have the right Person object.
> > >     @Persist("client")
> > >     private Person _person;
> > >
> > >     void onActivate(Long id) throws Exception { _personId = id; }
> > >
> > >     Long onPassivate() { return _person.getId(); }
> > >
> > >     void setupRender() throws Exception {
> > >             if (!_form.getHasErrors()) {
> > >                     _person = getPersonService().findPerson(_personId);
> > >             }
> > >     }
> > >
> > >     void onValidate() throws Exception {
> > >             if (...a bit of validation logic detects an error...) {
> > >                     _form.recordError(...);
> > >                     return;
> > >             }
> > >             try {
> > >                     getPersonService().changePerson(_person);
> > >             }
> > >             catch (Exception e) {
> > >                     _form.recordError(ExceptionUtil.getRootCause(e));
> > >                     return;
> > >             }
> > >     }
> > >
> > >     Object onSuccess() {
> > >             _nextPage.onActivate(_personId);
> > >             return _nextPage;
> > >     }
> > >
> > >     void cleanupRender() {
> > >                _form.clearErrors();
> > >     }
> > >
> > > Cheers,
> > >
> > > Geoff
> > >
> > > On 12/12/2007, at 4:53 AM, jeffrey ai wrote:
> > >
> > >>
> > >> Geoff, I think your code is great for **ONE** edit-page.
> > >> Our project is looking for a web flow from T5, like the Spring one.
> > >> Do you have any idea about it?
> > >> Howard mentioned he may add this feature in the next release.
> > >> Might be a little late to us, but I am expecting to see it.
> > >>
> > >> Cheers,
> > >> Jeffrey Ai
> > >>
> > >>
> > >> Geoff Callender-2 wrote:
> > >>>
> > >>> Hi,
> > >>>
> > >>> In search of best practice for an "edit" page, here's my 3rd
> > >>> attempt.
> > >>> It's looking pretty clean-cut to me, but I'm looking for suggestions
> > >>> on how to improve it further.
> > >>>
> > >>>   private Long _personId;
> > >>>
> > >>>   @Persist("client")
> > >>>   // Persistence is needed here because this is a detached Entity
> > >>> Bean.  When we call the service to
> > >>>   // accept our changes, it will need its id and version fields
> > >>> intact
> > >>> to be able to do optimistic
> > >>>   // locking check and a successful merge. "client" is used so that
> > >>> even if you use the Back button
> > >>>   // to get to this page, we will have the right Person object.
> > >>>   private Person _person;
> > >>>
> > >>>   void onActivate(Long id) throws Exception { _personId = id; }
> > >>>
> > >>>   Long onPassivate() { return _person.getId(); }
> > >>>
> > >>>   void setupRender() throws Exception {
> > >>>           if (!_form.getHasErrors()) {
> > >>>                   _person = getPersonService().findPerson(_personId);
> > >>>           }
> > >>>   }
> > >>>
> > >>>   void onValidate() throws Exception {
> > >>>           if (...a bit of validation logic detects an error...) {
> > >>>                   _form.recordError(...);
> > >>>           }
> > >>>   }
> > >>>
> > >>>   Object onSuccess() {
> > >>>           try {
> > >>>                   getPersonService().changePerson(_person);
> > >>>                   _nextPage.onActivate(_personId);
> > >>>                   return _nextPage;
> > >>>           }
> > >>>           catch (Exception e) {
> > >>>                   _form.recordError(ExceptionUtil.getRootCause(e));
> > >>>                   return null;
> > >>>           }
> > >>>   }
> > >>>
> > >>>   void cleanupRender() {
> > >>>              _form.clearErrors();
> > >>>   }
> > >>>
> > >>> Some notes:
> > >>>
> > >>> 1. Detached object - Person is a detached entity.  I am deliberately
> > >>> avoiding refreshing it every time in setupRender() because a) it
> > >>> would
> > >>> overwrite the user's changes, and b) it would defeat optimistic
> > >>> locking: if someone else has changed the object then I DO want
> > >>> getPersonService().changePerson(_person) to reject the transaction
> > >>> when Save is pressed.
> > >>>
> > >>> 2. Thread-safety - I'm using "client" persistence to avoid the whole
> > >>> thread-safety issue caused by the user opening a new window or
> > >>> tabs in
> > >>> same browser (T5 can't tell them apart so they share the same
> > >>> HttpSession).
> > >>>
> > >>> 3. onPrepareFromForm() - I'm avoiding it because it gets called too
> > >>> often (something to do with "rewind"?).  setupRender() seems better
> > >>> for the job.  Any downside t this?
> > >>>
> > >>> 4. cleanupRender() - if/when 5.0.7 uses flash persistence on the
> > >>> form's ValidationTracker then I'll ditch this method.
> > >>>
> > >>> Suggestions please!
> > >>>
> > >>> Thanks,
> > >>>
> > >>> Geoff
> > >>>
> > >>>
> > >>>
> > >>
> > >> --
> > >> View this message in context: http://www.nabble.com/T5%3A-Edit-page-best-practice---Mk-III-tp14249141p14279495.html
> > >> Sent from the Tapestry - User mailing list archive at Nabble.com.
> > >>
> > >>
> > >> ---------------------------------------------------------------------
> > >> To unsubscribe, e-mail: users-unsubscribe@tapestry.apache.org
> > >> For additional commands, e-mail: users-help@tapestry.apache.org
> > >>
> > >
> >
> --
> Martin Grotzke
> http://www.javakaffee.de/blog/
>
>
>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: users-unsubscribe@tapestry.apache.org
> For additional commands, e-mail: users-help@tapestry.apache.org
>
>

---------------------------------------------------------------------
To unsubscribe, e-mail: users-unsubscribe@tapestry.apache.org
For additional commands, e-mail: users-help@tapestry.apache.org


Re: T5: Edit page best practice - Mk III - now Mk V

Posted by Martin Grotzke <ma...@javakaffee.de>.
Hi Geoff,

if validation fails the page is displayed again, and the person will be
loaded again in the according GET request - so all input from the user
will be lost.

We're using flash persistence for this: the person would get annotated
with @Persist("flash"), would be reset in cleanupRender and loaded in
onActivate only if the entity (field) is null.

With this two subsequent request like

/person/edit/1
and 
/person/edit/2

display the correct data, as the person is not persisted after
cleanupRender has been invoked.

When first page is submitted, the person with id 1 is loaded from the
backend in onActivate, in populated by tapestry, and validated by
yourself. If then some validation error occurs and the page is displayed
again (by using redirect after POST), the person is still existing in
persist/flash with the populated values.

When redirect is done in onSuccessFromYourForm the entity should be also
reset / removed from persist/flash...

Btw: our experiences are based on T5.0.5, but I suppose these still
apply to later versions of T5...

Cheers,
Martin


On Mon, 2008-01-28 at 23:46 +1100, Geoff Callender wrote:
> Here's Mark V of the single page edit.  In previous attempts I was  
> clinging onto client-side persistence of the entity to ensure  
> optimistic locking, and then "the penny finally dropped" - all you  
> need to ensure optimistic locking is to ensure the entity's version is  
> retained - and you can do that with a hidden field.  So client-side  
> persistence really isn't necessary.  Hallelujah!
> 
> 	private Long _personId;
> 
> 	private Person _person;
> 
> 	void onActivate(Long id) throws Exception {
> 		_personId = id;
> 		_person = getPersonService().findPerson(_personId);
> 	}
> 
> 	Long onPassivate() {
> 		return _personId;
> 	}
> 
> 	void onValidateFromForm() {
> 		if (...a bit of validation logic detects an error...) {
> 			_form.recordError(...);
> 			return;
> 		}
> 		try {
> 			// move this back to onSuccess() once TAPESTRY-1972 has been  
> resolved.
> 			getPersonService().changePerson(_person);
> 		}
> 		catch (Exception e) {
> 			_form.recordError(ExceptionUtil.getRootCause(e));
> 		}
> 	}
> 
> 	Object onSuccess() {
> 		_nextPage.onActivate(_personId);
> 		return _nextPage;
> 	}
> 
> 	void cleanupRender() {
> 		_form.clearErrors();
> 	}
> 
> and in the html form, use the Hidden component that's been discussed  
> in this mailing list:
> 
>   		<t:hidden t:id="version" value="person.version"/>
> 
> or if you're using BeanEditForm:
> 
> 	<t:beaneditform t:id="form" object="person" submitLabel="Save">
> 		<t:parameter name="version">
> 			<t:hidden t:id="version" value="person.version"/>
> 		</t:parameter>
> 	</t:beaneditform>
> 
> Yes, onActivate(...) is called twice if there's an error detected  
> server-side, but that's due to the redirect-after-post paradigm, so  
> it's a cost with a pretty big benefit.
> 
> One last note, the cleanupRender() method can be removed if T5 moves  
> to "flash" persistence on the form's ValidationTracker.
> 
> Anything I've missed?
> 
> Cheers,
> 
> Geoff
> 
> On 12/12/2007, at 7:39 AM, Geoff Callender wrote:
> 
> > I'm planning on doing one, but not today.  If you know your way  
> > around T4 then the web flow example in JumpStart Max 2.0.0 may help  
> > you. It's at http://files.doublenegative.com.au/jumpstart/ .
> >
> > Here's Mk IV of the single page edit.  It seems that recording  
> > errors in onSuccess() can cause fields to revert when you redisplay  
> > with error, so I've moved some of it to onValidate().   The notes  
> > from before are still relevant.
> >
> > 	private Long _personId;
> > 	
> > 	// "client" is used so that  even if you use the Back button
> > 	// to get to me, I will have the right Person object.
> > 	@Persist("client")
> > 	private Person _person;
> > 	
> > 	void onActivate(Long id) throws Exception { _personId = id; }
> >
> > 	Long onPassivate() { return _person.getId(); }
> >
> > 	void setupRender() throws Exception {
> > 		if (!_form.getHasErrors()) {
> > 			_person = getPersonService().findPerson(_personId);
> > 		}
> > 	}
> >
> > 	void onValidate() throws Exception {
> > 		if (...a bit of validation logic detects an error...) {
> > 			_form.recordError(...);
> > 			return;
> > 		}
> > 		try {
> > 			getPersonService().changePerson(_person);
> > 		}
> > 		catch (Exception e) {
> > 			_form.recordError(ExceptionUtil.getRootCause(e));
> > 			return;
> > 		}
> > 	}
> > 	
> > 	Object onSuccess() {
> > 		_nextPage.onActivate(_personId);
> > 		return _nextPage;
> > 	}
> >
> > 	void cleanupRender() {
> > 		   _form.clearErrors();
> > 	}
> >
> > Cheers,
> >
> > Geoff
> >
> > On 12/12/2007, at 4:53 AM, jeffrey ai wrote:
> >
> >>
> >> Geoff, I think your code is great for **ONE** edit-page.
> >> Our project is looking for a web flow from T5, like the Spring one.
> >> Do you have any idea about it?
> >> Howard mentioned he may add this feature in the next release.
> >> Might be a little late to us, but I am expecting to see it.
> >>
> >> Cheers,
> >> Jeffrey Ai
> >>
> >>
> >> Geoff Callender-2 wrote:
> >>>
> >>> Hi,
> >>>
> >>> In search of best practice for an "edit" page, here's my 3rd  
> >>> attempt.
> >>> It's looking pretty clean-cut to me, but I'm looking for suggestions
> >>> on how to improve it further.
> >>>
> >>> 	private Long _personId;
> >>> 	
> >>> 	@Persist("client")
> >>> 	// Persistence is needed here because this is a detached Entity
> >>> Bean.  When we call the service to
> >>> 	// accept our changes, it will need its id and version fields  
> >>> intact
> >>> to be able to do optimistic
> >>> 	// locking check and a successful merge. "client" is used so that
> >>> even if you use the Back button
> >>> 	// to get to this page, we will have the right Person object.
> >>> 	private Person _person;
> >>> 	
> >>> 	void onActivate(Long id) throws Exception { _personId = id; }
> >>>
> >>> 	Long onPassivate() { return _person.getId(); }
> >>>
> >>> 	void setupRender() throws Exception {
> >>> 		if (!_form.getHasErrors()) {
> >>> 			_person = getPersonService().findPerson(_personId);
> >>> 		}
> >>> 	}
> >>>
> >>> 	void onValidate() throws Exception {
> >>> 		if (...a bit of validation logic detects an error...) {
> >>> 			_form.recordError(...);
> >>> 		}
> >>> 	}
> >>> 	
> >>> 	Object onSuccess() {
> >>> 		try {
> >>> 			getPersonService().changePerson(_person);
> >>> 			_nextPage.onActivate(_personId);
> >>> 			return _nextPage;
> >>> 		}
> >>> 		catch (Exception e) {
> >>> 			_form.recordError(ExceptionUtil.getRootCause(e));
> >>> 			return null;
> >>> 		}
> >>> 	}
> >>>
> >>> 	void cleanupRender() {
> >>> 		   _form.clearErrors();
> >>> 	}
> >>> 	
> >>> Some notes:
> >>>
> >>> 1. Detached object - Person is a detached entity.  I am deliberately
> >>> avoiding refreshing it every time in setupRender() because a) it  
> >>> would
> >>> overwrite the user's changes, and b) it would defeat optimistic
> >>> locking: if someone else has changed the object then I DO want
> >>> getPersonService().changePerson(_person) to reject the transaction
> >>> when Save is pressed.
> >>>
> >>> 2. Thread-safety - I'm using "client" persistence to avoid the whole
> >>> thread-safety issue caused by the user opening a new window or  
> >>> tabs in
> >>> same browser (T5 can't tell them apart so they share the same
> >>> HttpSession).
> >>>
> >>> 3. onPrepareFromForm() - I'm avoiding it because it gets called too
> >>> often (something to do with "rewind"?).  setupRender() seems better
> >>> for the job.  Any downside t this?
> >>>
> >>> 4. cleanupRender() - if/when 5.0.7 uses flash persistence on the
> >>> form's ValidationTracker then I'll ditch this method.
> >>>
> >>> Suggestions please!
> >>>
> >>> Thanks,
> >>>
> >>> Geoff
> >>>
> >>>
> >>>
> >>
> >> -- 
> >> View this message in context: http://www.nabble.com/T5%3A-Edit-page-best-practice---Mk-III-tp14249141p14279495.html
> >> Sent from the Tapestry - User mailing list archive at Nabble.com.
> >>
> >>
> >> ---------------------------------------------------------------------
> >> To unsubscribe, e-mail: users-unsubscribe@tapestry.apache.org
> >> For additional commands, e-mail: users-help@tapestry.apache.org
> >>
> >
> 
-- 
Martin Grotzke
http://www.javakaffee.de/blog/


---------------------------------------------------------------------
To unsubscribe, e-mail: users-unsubscribe@tapestry.apache.org
For additional commands, e-mail: users-help@tapestry.apache.org