views:

62

answers:

3

A pretty normal case:

We have a 'portlet' composite component that has two states: expanded and collapsed. Portlets start as expanded, but user can collapse them by clicking on them. This state should be saved to the session, so that once the user refreshes the page or navigates to a new, the portlets remember if they are expanded/collapsed.

This is the core of the problem: How would you actually implement such state-saving into a component, that can be inserted on page multiple times?

Some background / ideas:

Implementing state-saving for one portlet would be easily achieved by using a session-scoped bean. Also fixed number of portlets could be supported by creating a fixed number of session-scoped beans or declaring different properties into one bean. However, I don't want to even mention why those approaches is bad.

My initial idea for this was to create a Map (or some object structure) under single session bean to hold the states for all of the portlets. However, I cannot directly refer to this data from JSF (so that JSF would also update them). I considered writing a specific getter/setter to fetch/update the right value in the map, but that's not possible as there's no identifying data during the execution of getters and setters.

However, it is probably clear that I need to save the states into a session bean. I can do the state-saving pretty easily by posting a form with f:ajax and executing a special method using listener and the submitted data. In order to support multiple instances of component, I could use (multiple instances of) a request-scoped bean to handle each expand/collapse. However, in order to actually post the data that identifies the portlet and its' state, I need to first insert it on the form fields in render-time.

So, how to actually provide each portlet with right data on render-time (actually just state boolean/enum in this case, but consider a case where more data should be handled)?

It seems that:

  • h:inputHidden and h:inputText do not support setting initial value other than than the value-attribute (which would point to #{portletBean.portletState}).
  • I cannot auto-load the request beans with right initial values on their creation time, as there's no identifying information available.

Once again it feels like I'm missing something... pointers?

I suppose at least one of the answers would be to use UIComponent rather than composite components, but I would like to keep this as composite component as it contains lots of raw HTML elements.

Update:

  • There is a rather similar question, with suggestion to use view-scoped bean. I don't think this is viable here, however.
A: 

I got a few possible hacks for this. I will post them here but I don't think these are really the way to go:

Use javascript to set form fields with the right data directly after rendering:

window.addEvent('domready', function() {
    var portletState = '#{portletBean.getPortletState(cc.attrs.clientId)}';
    // find the field and set the data...
});

Feels veery hacky. I don't want to take this route.

Use component's attribute map to hold the data:

<cc:interface>
    <cc:attribute name="portletState" default="#{portletBean.getPortletState(cc.attrs.clientId)}" />
</cc:interface>

Access that from code:

public void stateChangedCallback(String clientId) {
    UIComponent component = FacesContext.getCurrentInstance().getViewRoot().findComponent(clientId);
    String state = (String) component.getAttributes().get("portletState");
}

Also doesn't feel right. Also I am not sure if it works (can you actually use EL in default-attribute).

Use component's attribute map to hold the data by providing it when calling the component:

<portlet:portlet id="specificPortlet">
    <f:attribute name="portletState" value="#{portletBean.getPortletState(component.clientId)}" />
</portlet:portlet>

Again, very clumsy and duplicates your code.

Tuukka Mustonen
A: 

You can do this using taglibs, you can pass parameters into tags:

Add the following to your taglib:

<tag>
    <tag-name>date</tag-name>
    <source>components/date.xhtml</source>
</tag>

Then here is the contents of components/date.xhtml

<ui:composition name="date_template">
    <h:panelGrid id="#{id}Panel" columns="1" border="0" cellpadding="0" cellspacing="0">
        <rich:calendar immediate="true" id="#{id}" popup="true" datePattern="dd.MM.yyyy"
                        enableManualInput="true" showApplyButton="true" cellWidth="12px" cellHeight="11px" style="width:100px"
                        value="#{dateForm.date}" required="#{required}" readonly="#{readonly}" validator="#{dateForm.validateDate}">
            <a4j:support event="onchanged" reRender="#{id}Panel,#{reRenderDate}"/>
            <ui:insert />
        </rich:calendar>

        <rich:message for="#{id}" errorClass="error" />
    </h:panelGrid>
</ui:composition>

And then you use this like:

<adse:date id="dateTransfert" required="true"
           dateForm="#{dossierBean.dateTransfert}"
           readonly="false" reRenderDate="montantAncien,montantNouveau,montantEmolument">
     <a4j:support event="oninputblur" reRender="dateTransfertPanel,montantAncien,montantNouveau,montantEmolument"/>
</adse:date>

In this case, dateForm is passed in, this is an object, and in the taglib we use properties of that object (dateForm.date). Also note the <ui:insert /> which can be used to include other elements in the XHTML.

So, you could pass in context for each portlet. You could even make this standard by having portletBean as a superclass which defines the getPortletState() method.

MatthieuF
So consider you had multiple instances, each having different properties. How would you actually store and update these? Also, this just passes in parameters, but I need to store and alter them in the server. I don't want to verbosely give portlet's status to it - this is something that portlet-component should be able to self-manage.
Tuukka Mustonen
I don't knnow exactly how your component works, but in the above example, you set immediate to true, and it gets updated as soon as it changes, it updates the value in the bean. so in the rich:calendar the value is updated in the bean as soon as the user changes the value on the browser.
MatthieuF
I think what you are suggesting here is a conveniency wrapper for calling the actual component. Inside that wrapper, I could fetch the data for that specific component (using passed ID, for example) and pass that data into the actual portlet-component by attributes. That data would then be accessible in component's attribute map. However, I still don't see how you actually update this data on the fly? In this case, I think the answer lies deep inside the implementation of `rich:calendar`. Do you agree or did I miss something?
Tuukka Mustonen
A: 

I used a previously solved hack to call a bean method from JS:

Created an empty, invisible form, with h:commandButton and f:ajax inside it calling listener.

Created a separate button for launching the expand/collapse event. From that button initiate javascript method, that animates the expansion/collapse and at the same time clicks the hidden button inside the form. All needed data is sent using the listener attribute. Listener targets a session bean, which checks/updates a map of states, identified by component's client ids.

This is pretty simple, wonder why I didn't realize it eariler...

However, it's still far from perfect and it doesn't answer how the request-scoped beans could be initiated with default values to use them as storage/transport form.

Tuukka Mustonen