JSF Access viewParams from within a component

385 Views Asked by At

I have a component to display a entry. This component can appear multiple times within the same page, and therefore just takes an entity, passes it to the component's backing bean where all the magic happens.

The component's appearence can be affected by various parameters.

Now, it is a requirement, that no matter if I have 1 or 10 of those components, the "same" view-state should be recoverable using a deeplink.

Let's say my component looks like this:

<composite:interface componentType="itemView" >
  <composite:attribute name="item" required="true" shortDescription="The item to display."/>
  <composite:attribute name="urlPrefix" required="true" />
</composite:interface>

<composite:implementation>
    <f:event type="preRenderComponent" listener="#{cc.init}" />

   ....     
</composite:implementation>

it has a corresponding backing Bean, I can access. (leaving this out, as its not required)

Now, these components are embedded on a page, that looks like this:

   <f:metadata>
        <f:viewParam name="itemType"
            value="#{listController.itemType}"></f:viewParam>

    </f:metadata>

    <ui:define name="content">

    <!-- multiple occurences, ui repeat over list of items -->
    <my:itemView item="#{currentItem}" urlPrefix="#{listController.nextLetter}">

As already mentioned, the state of every item can be affect, and the changed state musst be accessible by a deeplink. So, my idea was, to use <h:link> inside the component, prefix every attribute and use includeViewParams="true", so that every (not defaulted) state is part of the url.

<composite:interface componentType="itemView" >
  <composite:attribute name="item" required="true" shortDescription="The item to display."/>
  <composite:attribute name="urlPrefix" required="true" />
</composite:interface>

<composite:implementation>
    <f:event type="preRenderComponent" listener="#{cc.init}" />

 <h:link includeViewParams="true">
<f:param name="#{cc.attrs.urlPrefix}size" value="#{cc.largerSize}" />
    larger 
  </h:link>

   ....     
</composite:implementation>

Just as for the "larger" link, there are upto 10 links, providing different options.

The problem: include-view-params does include all the view-params from within the same component, and its parent (itemType), but NOT from other components. So, basically changing the style of "one" component, will drop the parameters of another component.

Expected: The url already looks like ?itemType=something&asize=5&abackground=green Clicking the "larger" link on the b component will give me : ?itemType=something&asize=5&abackground=green&bsize=5

Whats happening: After clicking the link, the url looks like: ?itemType=something&bsize=5 so the attributes for "a" are dropped.

I noticed, that if i extend the <f:metadata> object on the listing-page with something like <f:viewParam name="asize" /> the aSize parameter will be preserved, when clicking something from within "b" - but of course if i need to list all parameters like THAT, i dont need components, because i can not predict the number of items.

So, i tried to add this just right inside the component (like the f:event): <f:viewParam name="#{cc.urlPrefix}size" /> - but that did not work.

Any Ideas on that (or alternative solutions? Note: Saving the view States in the database, and simple using a "single" id to reference that state is basically bad, because that would cause a big amount of datapolution. The View-State needs to stay transient, but recoverable :0) )

1

There are 1 best solutions below

0
On

Finally i found a solution. It's not the best, but it works at least ;)

I created a method on the component's bean, that will return a query string, containing all values ecxcept the one(s) passed as a paremeter.

So, whenever I want to change the parameter asize=5, i call this function with param size and append the new parameter (Function will take care of excluding this parameter only, when the prefi matches, so no other components style would be affected.):

public String buildUrlWithout(String keyString) {
        List<String> keys = ConversionHelper.Explode(keyString, ",");

        // get all parameters from the url.
        String prefix = this.getAttributes().get("urlPrefix").toString();
        ExternalContext externalContext = FacesContext.getCurrentInstance().getExternalContext();

        Map<String, String> params = externalContext.getRequestParameterMap();
        List<KeyValuePair<String, String>> result = new LinkedList<KeyValuePair<String, String>>();

        outerfor: for (Map.Entry<String, String> kvp : params.entrySet()) {
            for (String s : keys) {
                if (kvp.getKey().equals(prefix + s)) {
                    continue outerfor;
                }
            }

            // keep param
            result.add(new KeyValuePair<String, String>(kvp.getKey(), kvp.getValue()));
        }

        String qStr = KeyValuePair.toQueryString(result);
        return qStr;
    }

KeyValuePair ist just a Helper-Class that behaves like a Map<S,T> but offers several functions like join(), split() etc.. (in this case toQueryString() is used)

Finally, from within a component, I use this like this:

<h:outputLink
  value="#{cc.buildUrlWithout('size')}&amp;#{cc.attrs.urlPrefix}size={cc.size +1}">
    larger
</h:outputLink>

<h:outputLink
  value="#{cc.buildUrlWithout('size')}&amp;#{cc.attrs.urlPrefix}size={cc.size -1}">
    smaller
</h:outputLink>

<h:outputLink
  value="#{cc.buildUrlWithout('background,border')}&amp;#{cc.attrs.urlPrefix}background=red&amp;#{cc.attrs.urlPrefix}border=green">
    red background + green border
</h:outputLink>