-
Available since OmniFaces 1.0
The <o:viewParam>
is a component that extends the standard <f:viewParam>
and provides a stateless mode of operation and fixes the issue wherein null model values are converted to empty string parameters in query string (e.g. when includeViewParams=true
) and the (bean) validation never being triggered when the parameter is completely absent in query string, causing e.g. @NotNull
to fail.
Stateless mode to avoid unnecessary conversion, validation and model updating on postbacks
The standard UIViewParameter
implementation calls the model setter again after postback. This is not always desired when being bound to a view scoped bean and can lead to performance problems when combined with an expensive converter. To solve this, this component by default stores the submitted value as a component property instead of in the model (and thus in the view state in case the binding is to a view scoped bean).
The standard UIViewParameter
implementation calls the converter and validators again on postbacks. This is not always desired when you have e.g. a required="true"
, but the parameter is not retained on form submit. You would need to retain it on every single command link/button by <f:param>
. To solve this, this component doesn't call the converter and validators again on postbacks.
Using name as default for label
The <o:viewParam>
also provides a default for the label
atrribute. When the label
attribute is omitted, the name
attribute will be used as label.
Avoid unnecessary empty parameter in query string
The standard UIViewParameter
implementation calls the converter regardless of whether the evaluated model value is null
or not. As converters by specification return an empty string in case of null
value, this is being added to the query string as an empty parameter when e.g. includeViewParams=true
is used. This is not desired. The workaround was added in OmniFaces 1.8.
Support bean validation and triggering validate events on null value
The standard UIViewParameter
implementation uses in JSF 2.0-2.2 an internal "is required" check when the submitted value is null
, hereby completely bypassing the standard UIInput
validation, including any bean validation annotations and even the PreValidateEvent
and PostValidateEvent
events. This is not desired. The workaround was added in OmniFaces 2.0. In JSF 2.3, this has been fixed and has only effect when jakarta.faces.INTERPRET_EMPTY_STRING_SUBMITTED_VALUES_AS_NULL
context param is set to true
.
Default value when no parameter is set
The <o:viewParam>
also supports providing a default value via the default
attribute. When the parameter is not available, then the value specified in default
attribute will be set in the model instead. The support was added in OmniFaces 2.2.
Usage
You can use it the same way as <f:viewParam>
, you only need to change f:
to o:
.
<o:viewParam name="foo" value="#{bean.foo}" />
If this page is requested by a request without foo
parameter which is set by <o:viewParam>
,
it displays the message with the name
of the <o:viewParam>
as label instead of its client ID.
Clicking this GET link
should cause a validation error on the bar
parameter which is set by <f:viewParam>
,
it displays the message with the client ID of the <f:viewParam>
as label.
Clicking this GET link initiates a new GET request with a test
parameter.
Every time this link is clicked, a converter runs that attaches an ever increasing number to the displayed value:
test-0-0-0
When starting off with URL without view parameters,
clicking the GET link with includeViewParams=true
would add the <f:viewParam name="bar">
with an empty value to the request URL.
This doesn't happen for <o:viewParam name="foo">
.
<f:metadata>
<o:viewParam name="foo" value="#{viewParamBean.foo}" required="true" converter="testConverter" />
<f:viewParam name="bar" value="#{viewParamBean.bar}" converter="jakarta.faces.Long" />
</f:metadata>
<h:messages styleClass="messages" errorClass="error" />
<p>
If this page is requested by a request without <code>foo</code> parameter which is set by <code><o:viewParam></code>,
it displays the message with the <code>name</code> of the <code><o:viewParam></code> as label instead of its client ID.
</p>
<p>
Clicking <h:link><f:param name="foo" value="test"/><f:param name="bar" value="test" />this GET link</h:link>
should cause a validation error on the <code>bar</code> parameter which is set by <code><f:viewParam></code>,
it displays the message with the client ID of the <code><f:viewParam></code> as label.
</p>
<p>
Clicking <h:link><f:param name="foo" value="test"/>this GET link</h:link> initiates a new GET request with a <code>test</code> parameter.
Every time this link is clicked, a converter runs that attaches an ever increasing number to the displayed value:
<b>#{viewParamBean.foo}</b>
</p>
<h:form>
<p>
Clicking <h:commandLink value="this POST link" /> initiates a POST request.
This should not convert the value again and thus the above number should not change.
This should also not trigger the <code>required="true"</code> validator of the <code><o:viewParam></code>
and thus no validation error should show up, on contrary to when you would use the <code><f:viewParam></code>.
</p>
</h:form>
<p>
When starting off with <h:link>URL without view parameters</h:link>,
clicking <h:link includeViewParams="true">the GET link with <code>includeViewParams=true</code></h:link>
would add the <code><f:viewParam name="bar"></code> with an empty value to the request URL.
This doesn't happen for <code><o:viewParam name="foo"></code>.
</p>
package org.omnifaces.showcase.components;
import java.io.Serializable;
import jakarta.inject.Named;
import org.omnifaces.cdi.ViewScoped;
@Named
@ViewScoped
public class ViewParamBean implements Serializable {
private static final long serialVersionUID = 1L;
private String foo;
private Long bar;
public String getFoo() {
return foo;
}
public void setFoo(String foo) {
this.foo = foo;
}
public Long getBar() {
return bar;
}
public void setBar(Long bar) {
this.bar = bar;
}
}
package org.omnifaces.showcase.components;
import java.util.concurrent.atomic.AtomicInteger;
import jakarta.faces.component.UIComponent;
import jakarta.faces.context.FacesContext;
import jakarta.faces.convert.Converter;
import jakarta.faces.convert.FacesConverter;
import org.omnifaces.util.Faces;
@FacesConverter("testConverter")
public class TestConverter implements Converter {
@Override
public Object getAsObject(FacesContext context, UIComponent component, String value) {
if (value == null || value.isEmpty()) {
return null;
}
return value + "-" + getCounter().getAndIncrement();
}
@Override
public String getAsString(FacesContext context, UIComponent component, Object value) {
return value != null ? value.toString() : "";
}
private static AtomicInteger getCounter() {
AtomicInteger counter = Faces.getSessionAttribute(TestConverter.class.getName());
if (counter == null) {
counter = new AtomicInteger();
Faces.setSessionAttribute(TestConverter.class.getName(), counter);
}
return counter;
}
}