You are viewing a plain text version of this content. The canonical link for it is here.
Posted to users@myfaces.apache.org by Jurgen Lust <ju...@gmail.com> on 2008/03/31 10:49:00 UTC
orchestra and richfaces table scroller
Hello,
I've successfully added orchestra to my Faces web applications, using
MyFaces 1.2.2, Facelets and JBoss RichFaces. Normal Ajax operations
with a4j:commandButton and the likes work as they should, but the
rich:datascroller does not.
I have created a custom ExtendedDataModel for a rich:datatable, using
Hibernate, based on what I found on the JBoss forums. I added this
datamodel as a bean in conversation scope. Now, when I open the page
in two browser windows it seems as if the datascroller is using the
model in session scope, because when scrolling in one window, the
selected range in the other window is also updated.
Here is the spring configuration for my datamodel bean:
<bean id="personDataModel"
class="org.edoframework.dev.orchestra.view.handler.PersonTableDataModel"
scope="conversation.access" orchestra:conversationName="personView">
<property name="repository" ref="personDao" />
</bean>
My xhtml page:
<h:form>
<rich:messages />
<a4j:outputPanel id="table">
<h:outputText value="#{personCrud.conversationName}" />
<rich:dataTable id="personTableModel" value="#{personDataModel}"
var="person" rows="10" width="100%">
<rich:column>
<f:facet name="header">
<h:outputText value="Name" />
</f:facet>
<h:outputText value="#{person.name}" />
</rich:column>
<rich:column>
<f:facet name="header">
<h:outputText value="First Name" />
</f:facet>
<h:outputText value="#{person.firstname}" />
</rich:column>
<f:facet name="footer">
<rich:datascroller />
</f:facet>
</rich:dataTable>
</a4j:outputPanel>
</h:form>
My datamodel bean:
package org.edoframework.dev.orchestra.view.handler;
import org.ajax4jsf.model.DataVisitor;
import org.ajax4jsf.model.ExtendedDataModel;
import org.ajax4jsf.model.Range;
import org.ajax4jsf.model.SequenceRange;
import org.edoframework.domainmodel.Identifiable;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.faces.context.FacesContext;
/**
* DataModel for displaying persistent objects in RichFaces datatables.
*
* @author Jurgen Lust
* @author $LastChangedBy$
*
* @version $LastChangedRevision$
*
* @param <T> The type of persistent object
* @param <ID> The type of the Id
*/
public abstract class GenericPersistentTableDataModel<T extends
Identifiable, ID extends Serializable>
extends ExtendedDataModel {
//~ Instance fields --------------------------------------------------------
public List<T> listRow;
int rowNum = -1;
private ID currentId;
private List<ID> wrappedKeys;
private Map<ID, T> wrappedData = new HashMap<ID, T>();
//~ Methods ----------------------------------------------------------------
public int getRowNum() {
return ++rowNum;
}
public abstract Long getCount();
public abstract List<T> getList(
final Integer firstRow,
final Integer maxResults
);
private List<T> loadList(
final Integer firstRow,
final Integer maxResults
) {
return listRow = getList(firstRow, maxResults);
}
public abstract T findById(final ID id);
@SuppressWarnings("unchecked")
public ID getId(final T row) {
ID id = (ID) row.getIdentity();
return id;
}
public void wrap(
final FacesContext context,
final DataVisitor visitor,
final Range range,
final Object argument,
final List<T> list
)
throws IOException {
wrappedKeys = new ArrayList<ID>();
wrappedData = new HashMap<ID, T>();
for (final T row : list) {
ID id = getId(row);
wrappedKeys.add(id);
wrappedData.put(id, row);
visitor.process(context, id, argument);
}
}
public boolean hasById(final ID id) {
for (final T row : listRow) {
ID rowId = getId(row);
if (rowId.equals(id)) {
return true;
}
}
return false;
}
@Override
public void walk(
final FacesContext context,
final DataVisitor visitor,
final Range range,
final Object argument
)
throws IOException {
int firstRow = ((SequenceRange) range).getFirstRow();
int maxResults = ((SequenceRange) range).getRows();
wrap(context, visitor, range, argument, loadList(firstRow, maxResults));
}
/*
* This method normally called by Visitor before request Data Row.
*/
@Override
@SuppressWarnings("unchecked")
public void setRowKey(final Object key) {
this.currentId = (ID) key;
}
@Override
public int getRowCount() {
return this.getCount().intValue();
}
@Override
public boolean isRowAvailable() {
if (currentId == null) {
return false;
} else {
return hasById(currentId);
}
}
/**
* This is main way to obtain data row. It is intensively used by framework.
* We strongly recommend use of local cache in that method.
*/
@Override
public Object getRowData() {
if (currentId == null) {
return null;
} else {
T ret = wrappedData.get(currentId);
if (ret == null) {
ret = this.findById(currentId);
wrappedData.put(currentId, ret);
return ret;
} else {
return ret;
}
}
}
// Unused rudiment from old JSF staff.
@Override
public int getRowIndex() {
return 0;
}
@Override
public void setRowIndex(final int rowIndex) {}
@Override
public Object getWrappedData() {
return this;
}
@Override
public void setWrappedData(final Object data) {
throw new UnsupportedOperationException();
}
@Override
public Object getRowKey() {
return currentId;
}
}
Jurgen
Re: orchestra and richfaces table scroller
Posted by Mario Ivankovits <ma...@ops.co.at>.
Hi
>> Now I am wondering, would it theoretically be possible for Orchestra
>> to also intervene in the server side state saving so that the
>> component tree is stored in a conversation context?
>>
> Theoretically yes. And this is also a wish others expressed already.
> We will investigate if it is possible to store the server-side state in
> the conversation context, but state-saving is not one of the "gloriest"
> in JSF.
>
By copying the MyFaces StateManager and replacing its session access by
conversationContext access it should easily possible to fix that problem.
We can look at it in the next few days .... or contribute :-)
Ciao,
Mario
Re: orchestra and richfaces table scroller
Posted by Mario Ivankovits <ma...@ops.co.at>.
Hi!
> Now I am wondering, would it theoretically be possible for Orchestra
> to also intervene in the server side state saving so that the
> component tree is stored in a conversation context?
Theoretically yes. And this is also a wish others expressed already.
We will investigate if it is possible to store the server-side state in
the conversation context, but state-saving is not one of the "gloriest"
in JSF.
Ciao,
Mario
Re: orchestra and richfaces table scroller
Posted by Jurgen Lust <ju...@gmail.com>.
Hi Simon,
You are right: the current page is stored in the component state. When
I put the backing bean in request scope, I get exactly the same
behaviour.
I don't use client side state saving by the way, the
javax.faces.ViewState fields are added by RichFaces I guess.
Now I am wondering, would it theoretically be possible for Orchestra
to also intervene in the server side state saving so that the
component tree is stored in a conversation context? The use of
different windows is something web users have gotten used to,
especially since tabs were introduced, and the fact that JSF does not
allow it is one of its main drawbacks I think.
Anyway, Orchestra is already a major step forward in this area. I was
planning on using Spring WebFlow to solve the conversation scope
problem, but that seemed a bit too heavyweight. Orchestra is
wonderfully light and easy to install!
Jurgen
2008/3/31, simon.kitching@chello.at <si...@chello.at>:
> simon.kitching@chello.at schrieb:
>
> > Jurgen Lust schrieb:
> >
> >> Hi Simon,
> >>
> >> I've setup a little test application here:
> >>
> >> https://ninja.ugent.be/orchestra-test/
> >>
> >> You should open this url in 2 different windows, so you have different
> >> conversationContexts. Then just follow the instructions on the page.
> >> You will see the a4j:commandButton updates the conversation scoped
> >> property, while the rich:datascroller updates the datatable in both
> >> conversations.
> >>
> >>
> >
> > Hmm. I've given it a try and that sure is odd behaviour.
> >
> > I've used the firefox "live http headers" plugin, and it does seem that
> > the requests are sending the right urls. With two windows:
> > * in window 1, the button and table-scroller both send
> > "?conversationContext=2"
> > * in window 2, the button and table-scroller both send
> > "?conversationContext=3"
> >
> > However as you say, the simple counter appears to work as expected
> > (separate count in each window) while the table-scroller page-number is
> > coupled between windows (next-page in one window, then next-page in the
> > other moves forward two pages).
> >
> > I wonder: is the "current page in table" held in a backing bean at all,
> > or is it held by the component itself? I don't know much about
> > richfaces, but in tomahawk I believe the datascroller's "current index"
> > is a property of the JSF component itself, not a property on a backing bean.
> >
> > If each RichFaces ajax postback is reusing the same component tree (ie
> > restoring the same view components) then that would explain things;
> > after render of window1 the "current page" is being saved in the
> > component's saved-state and then on postback from window2 the "current
> > page" is being restored from that same data.
> >
> > I can't remember for the moment what JSF does when two GET requests for
> > the same viewId are processed; does it create a new UIViewRoot on the
> > second request, or restore the first one? In the case of
> > client-side-state, a new view obviously *must* be created as there is no
> > posted state info. In the case of server-side-state I would have
> > expected the same behaviour for consistency.
> >
> > Looking at your page, I see hidden input fields with name
> > "javax.faces.ViewState", indicating that you have client-side-state
> > enabled. But these hidden fields are nested within <fieldset> tags, not
> > at the end of the <form> tag as I would normally expect with MyFaces. Is
> > this something that RichFaces changes in order to be able to do
> > partial-submits easier? In this case, maybe the RichFaces change to the
> > way state-saving is done means that the same component tree is getting
> > restored for posts by both windows..
> >
> > I don't like to point the finger at someone else, but at the moment I
> > cannot think of any way that Orchestra could be getting this wrong; the
> > conversationContext params are right, and that is just an index into a
> > map of conversations. The only possibility is that somehow the same
> > backing bean has been stored into both conversationContexts but it is
> > not likely. The above suggestion re a shared component instance seems
> > more probable than a shared backing bean.
> >
> > Maybe you should ask on the RichFaces list about this..
> >
>
>
> Hmm..actually, what happens if you just make the backing bean
> request-scoped?
>
> Can you still scroll through pages with the data scroller? And if so,
> what happens with two windows?
>
> Regards,
>
> Simon
>
>
Re: orchestra and richfaces table scroller
Posted by "simon.kitching@chello.at" <si...@chello.at>.
simon.kitching@chello.at schrieb:
> Jurgen Lust schrieb:
>
>> Hi Simon,
>>
>> I've setup a little test application here:
>>
>> https://ninja.ugent.be/orchestra-test/
>>
>> You should open this url in 2 different windows, so you have different
>> conversationContexts. Then just follow the instructions on the page.
>> You will see the a4j:commandButton updates the conversation scoped
>> property, while the rich:datascroller updates the datatable in both
>> conversations.
>>
>>
>
> Hmm. I've given it a try and that sure is odd behaviour.
>
> I've used the firefox "live http headers" plugin, and it does seem that
> the requests are sending the right urls. With two windows:
> * in window 1, the button and table-scroller both send
> "?conversationContext=2"
> * in window 2, the button and table-scroller both send
> "?conversationContext=3"
>
> However as you say, the simple counter appears to work as expected
> (separate count in each window) while the table-scroller page-number is
> coupled between windows (next-page in one window, then next-page in the
> other moves forward two pages).
>
> I wonder: is the "current page in table" held in a backing bean at all,
> or is it held by the component itself? I don't know much about
> richfaces, but in tomahawk I believe the datascroller's "current index"
> is a property of the JSF component itself, not a property on a backing bean.
>
> If each RichFaces ajax postback is reusing the same component tree (ie
> restoring the same view components) then that would explain things;
> after render of window1 the "current page" is being saved in the
> component's saved-state and then on postback from window2 the "current
> page" is being restored from that same data.
>
> I can't remember for the moment what JSF does when two GET requests for
> the same viewId are processed; does it create a new UIViewRoot on the
> second request, or restore the first one? In the case of
> client-side-state, a new view obviously *must* be created as there is no
> posted state info. In the case of server-side-state I would have
> expected the same behaviour for consistency.
>
> Looking at your page, I see hidden input fields with name
> "javax.faces.ViewState", indicating that you have client-side-state
> enabled. But these hidden fields are nested within <fieldset> tags, not
> at the end of the <form> tag as I would normally expect with MyFaces. Is
> this something that RichFaces changes in order to be able to do
> partial-submits easier? In this case, maybe the RichFaces change to the
> way state-saving is done means that the same component tree is getting
> restored for posts by both windows..
>
> I don't like to point the finger at someone else, but at the moment I
> cannot think of any way that Orchestra could be getting this wrong; the
> conversationContext params are right, and that is just an index into a
> map of conversations. The only possibility is that somehow the same
> backing bean has been stored into both conversationContexts but it is
> not likely. The above suggestion re a shared component instance seems
> more probable than a shared backing bean.
>
> Maybe you should ask on the RichFaces list about this..
>
Hmm..actually, what happens if you just make the backing bean
request-scoped?
Can you still scroll through pages with the data scroller? And if so,
what happens with two windows?
Regards,
Simon
Re: orchestra and richfaces table scroller
Posted by "simon.kitching@chello.at" <si...@chello.at>.
Jurgen Lust schrieb:
> Hi Simon,
>
> I've setup a little test application here:
>
> https://ninja.ugent.be/orchestra-test/
>
> You should open this url in 2 different windows, so you have different
> conversationContexts. Then just follow the instructions on the page.
> You will see the a4j:commandButton updates the conversation scoped
> property, while the rich:datascroller updates the datatable in both
> conversations.
>
Hmm. I've given it a try and that sure is odd behaviour.
I've used the firefox "live http headers" plugin, and it does seem that
the requests are sending the right urls. With two windows:
* in window 1, the button and table-scroller both send
"?conversationContext=2"
* in window 2, the button and table-scroller both send
"?conversationContext=3"
However as you say, the simple counter appears to work as expected
(separate count in each window) while the table-scroller page-number is
coupled between windows (next-page in one window, then next-page in the
other moves forward two pages).
I wonder: is the "current page in table" held in a backing bean at all,
or is it held by the component itself? I don't know much about
richfaces, but in tomahawk I believe the datascroller's "current index"
is a property of the JSF component itself, not a property on a backing bean.
If each RichFaces ajax postback is reusing the same component tree (ie
restoring the same view components) then that would explain things;
after render of window1 the "current page" is being saved in the
component's saved-state and then on postback from window2 the "current
page" is being restored from that same data.
I can't remember for the moment what JSF does when two GET requests for
the same viewId are processed; does it create a new UIViewRoot on the
second request, or restore the first one? In the case of
client-side-state, a new view obviously *must* be created as there is no
posted state info. In the case of server-side-state I would have
expected the same behaviour for consistency.
Looking at your page, I see hidden input fields with name
"javax.faces.ViewState", indicating that you have client-side-state
enabled. But these hidden fields are nested within <fieldset> tags, not
at the end of the <form> tag as I would normally expect with MyFaces. Is
this something that RichFaces changes in order to be able to do
partial-submits easier? In this case, maybe the RichFaces change to the
way state-saving is done means that the same component tree is getting
restored for posts by both windows..
I don't like to point the finger at someone else, but at the moment I
cannot think of any way that Orchestra could be getting this wrong; the
conversationContext params are right, and that is just an index into a
map of conversations. The only possibility is that somehow the same
backing bean has been stored into both conversationContexts but it is
not likely. The above suggestion re a shared component instance seems
more probable than a shared backing bean.
Maybe you should ask on the RichFaces list about this..
Regards,
Simon
Re: orchestra and richfaces table scroller
Posted by Jurgen Lust <ju...@gmail.com>.
Hi Simon,
I've setup a little test application here:
https://ninja.ugent.be/orchestra-test/
You should open this url in 2 different windows, so you have different
conversationContexts. Then just follow the instructions on the page.
You will see the a4j:commandButton updates the conversation scoped
property, while the rich:datascroller updates the datatable in both
conversations.
Jurgen
Re: orchestra and richfaces table scroller
Posted by "simon.kitching@chello.at" <si...@chello.at>.
Jurgen Lust schrieb:
> I have checked the conversation context feature in this case: the
> parameter is sent with every ajax request. For example, the
> 'last'-button in the scroller contains this javascript:
>
> onclick="A4J.AJAX.Submit('_viewRoot','j_id7',event,{'ignoreDupResponses':true,'parameters':{'j_id7:personTableModel:j_id19':'last'}
> ,'eventsQueue':'j_id7:personTableModel:j_id19','actionUrl':'/3column.jsf?javax.portlet.faces.DirectLink=true&conversationContext=2'}
> ); return false;
>
> But as I said in my first mail, it does not seem to work.
>
Isn't the problem that you have one window in which this URL is being
used (conversationContext=2), and then in a different window something
also uses (conversationContext=2) and then the two windows interact?
When the conversationContext is the same, then of course they will. The
windows will only be isolated if the conversationContext value is
*different* for each window.
> On the same page, there is an a4j:commandButton which contains a
> similar javascript, and that button does work correctly in 2 parallel
> windows, and 2 different conversations.
>
> onclick="A4J.AJAX.Submit('_viewRoot','j_id27',event,{'parameters':{'j_id27:j_id30':'j_id27:j_id30'}
> ,'actionUrl':'/3column.jsf?javax.portlet.faces.DirectLink=true&conversationContext=2'}
> );return false;"
>
Really?
So this button has some effect on the server that *would* be observable
in your second window if they were referencing the same bean instance on
the server, but you do NOT see any change in your second window after
the button is clicked?
I would be very surprised...
Regards, Simon
Re: orchestra and richfaces table scroller
Posted by Jurgen Lust <ju...@gmail.com>.
I have checked the conversation context feature in this case: the
parameter is sent with every ajax request. For example, the
'last'-button in the scroller contains this javascript:
onclick="A4J.AJAX.Submit('_viewRoot','j_id7',event,{'ignoreDupResponses':true,'parameters':{'j_id7:personTableModel:j_id19':'last'}
,'eventsQueue':'j_id7:personTableModel:j_id19','actionUrl':'/3column.jsf?javax.portlet.faces.DirectLink=true&conversationContext=2'}
); return false;
But as I said in my first mail, it does not seem to work.
On the same page, there is an a4j:commandButton which contains a
similar javascript, and that button does work correctly in 2 parallel
windows, and 2 different conversations.
onclick="A4J.AJAX.Submit('_viewRoot','j_id27',event,{'parameters':{'j_id27:j_id30':'j_id27:j_id30'}
,'actionUrl':'/3column.jsf?javax.portlet.faces.DirectLink=true&conversationContext=2'}
);return false;"
Jurgen
Re: orchestra and richfaces table scroller
Posted by "simon.kitching@chello.at" <si...@chello.at>.
Hi Jurgen,
Jurgen Lust schrieb:
> Hello,
>
> I've successfully added orchestra to my Faces web applications, using
> MyFaces 1.2.2, Facelets and JBoss RichFaces. Normal Ajax operations
> with a4j:commandButton and the likes work as they should, but the
> rich:datascroller does not.
>
> I have created a custom ExtendedDataModel for a rich:datatable, using
> Hibernate, based on what I found on the JBoss forums. I added this
> datamodel as a bean in conversation scope. Now, when I open the page
> in two browser windows it seems as if the datascroller is using the
> model in session scope, because when scrolling in one window, the
> selected range in the other window is also updated.
>
I don't think this problem is a "table scroller" problem; isn't it due
to having two concurrent open windows?
Handling multiple windows is always a difficult problem with webapps.
By default, orchestra does share conversations between multiple windows.
The problem is that it is almost impossible for a webapp to *know* that
they are different windows; all a webapp sees is two consecutive
requests with the same cookies (hence same http session). It would be
great if some future version of the http spec ensured a "window-id" http
header was included in each request....
Using request-scope obviously avoids this issue, because there is no
state to be kept.
Orchestra does provide support for multiple concurrent windows via its
"conversation context" feature, but it requires some cooperation from
the webapp and the user. By default, a parameter is added into the url
so that different windows can be associated with different conversation
instances on the server. You've probably seen the
"?conversationContext=n" added to the url when using orchestra. If a new
window is created *without* this parameter, then a new set of
conversations are associated with that new window. One way to do this is
to create a link with the "target" property set, and then to wrap that
link in the <o:separateConversationContext> tag (which just blocks the
appending of the special query param).
See:
http://myfaces.apache.org/orchestra/myfaces-orchestra-core/tlddoc/index.html
Regards,
Simon
Re: orchestra and richfaces table scroller
Posted by Mario Ivankovits <ma...@ops.co.at>.
Hi!
> Now, when I open the page
> in two browser windows it seems as if the datascroller is using the
> model in session scope, because when scrolling in one window, the
> selected range in the other window is also updated.
>
I've tried to implement a StateManager using the ConversationContext as
its place to store the state ... and it looks like it works. But the way
I choose was to copy over the whole StateManager impl from myfaces. We
are not very happy adding this "hack" to Orchestra ... Probably another
hack will come ;-)
Anyway, could you please try the latest myfaces 1.2.3 snapshot. I've
fixed something in the state saving which might have been the real problem.
Ciao,
Mario