in my preRender code for a page i add faces message then make navigation to another page as follows:
if(error){
addMessageToComponent(null,"AN ERROR HAS OCCURRED");
FacesContext.getCurrentInstance().getExternalContext().getFlash()
.setKeepMessages(true);
navigateActionListener("myoutcome");
}
and the util methods for adding message and navigation are:
public static String getClientId(String componentId)
{
FacesContext context = FacesContext.getCurrentInstance();
UIViewRoot root = context.getViewRoot();
UIComponent c = findComponent(root, componentId);
return c.getClientId(context);
}
public static UIComponent findComponent(UIComponent c, String id)
{
if (id.equals(c.getId())) { return c; }
Iterator<UIComponent> kids = c.getFacetsAndChildren();
while (kids.hasNext())
{
UIComponent found = findComponent(kids.next(), id);
if (found != null) { return found; }
}
return null;
}
/**
* #param componentId
* : the id for the jsf/primefaces component without formId:
* prefix. <br>
* if you use null then the message will be added to the
* h:messages component.
**/
public static void addMessageToComponent(String componentId, String message)
{
if (componentId != null)
componentId = GeneralUtils.getClientId(componentId);
FacesContext.getCurrentInstance().addMessage(componentId,
new FacesMessage(message));
}
public static void navigateActionListener(String outcome)
{
FacesContext context = FacesContext.getCurrentInstance();
NavigationHandler navigator = context.getApplication()
.getNavigationHandler();
navigator.handleNavigation(context, null, outcome);
}
but messages are not saved and so it doesn't appear after redirect.
please advise how to fix that.
The preRenderView event runs in the very beginning of the RENDER_RESPONSE phase. It's too late to instruct the Flash scope to keep the messages. You can do this at the latest during the INVOKE_APPLICATION phase.
Since there's no standard JSF component system event for this, you'd need to homebrew one:
#NamedEvent(shortName="postInvokeAction")
public class PostInvokeActionEvent extends ComponentSystemEvent {
public PostInvokeActionEvent(UIComponent component) {
super(component);
}
}
To publish this, you need a PhaseListener:
public class PostInvokeActionListener implements PhaseListener {
#Override
public PhaseId getPhaseId() {
return PhaseId.INVOKE_APPLICATION;
}
#Override
public void beforePhase(PhaseEvent event) {
// NOOP.
}
#Override
public void afterPhase(PhaseEvent event) {
FacesContext context = FacesContext.getCurrentInstance();
context.getApplication().publishEvent(context, PostInvokeActionEvent.class, context.getViewRoot());
}
}
After registering it as follows in faces-config.xml:
<lifecycle>
<phase-listener>com.example.PostInvokeActionListener</phase-listener>
</lifecycle>
You'll be able to use the new event as follows:
<f:event type="postInvokeAction" listener="#{bean.init}" />
You only need to make sure that you've at least a <f:viewParam>, otherwise JSF won't enter the invoked phase at all.
The JSF utility library OmniFaces already supports this event and the preInvokeAction event out the box. See also the showcase page which also demonstrates setting a facesmessage for redirect.
Related
I have set up few things quite useful for our app. Useful in development mode, but not in production. For exemple, I registered this phaselistener
<lifecycle>
<phase-listener>org.primefaces.component.lifecycle.LifecyclePhaseListener</phase-listener>
</lifecycle>
In production mode, there is no need to have this, so I was wondering how you guys with JSF app are you disabling things by taking account the PROJECT_STAGE state in production mode?
PS: In xhtml pages, I'm aware it is easy as I can decide to not render component by using #{facesContext.application.projectStage eq 'Development'}
You can register the PhaseListener programatically instead of via faces-config.xml, for example:
#ManagedBean(eager = true)
#ApplicationScoped
public class ApplicationBean {
#PostConstruct
private void initialize() {
LifecycleFactory factory = (LifecycleFactory) FactoryFinder.getFactory(FactoryFinder.LIFECYCLE_FACTORY);
Lifecycle lifecycle = factory.getLifecycle(LifecycleFactory.DEFAULT_LIFECYCLE);
if (ProjectStage.Development.equals(getProjectStage())) {
lifecycle.addPhaseListener(new PhaseListenerImpl());
}
}
private ProjectStage getProjectStage() {
FacesContext fc = FacesContext.getCurrentInstance();
Application appl = fc.getApplication();
return appl.getProjectStage();
}
}
(cf. http://javaevangelist.blogspot.de/2012/05/jsf-2-tip-of-day-programmatic.html)
Using system event listener (See https://www.tutorialspoint.com/jsf/jsf_applicationevents_tag.htm):
public class PostConstructApplicationListener implements SystemEventListener {
#Override
public void processEvent(SystemEvent event) {
if (event instanceof PostConstructApplicationEvent) {
setupLifeCycleListener();
}
}
/**
* Add <code>org.primefaces.component.lifecycle.LifecyclePhaseListener</code> in case PROJECT_STAGE is not set to "Production"
*/
private void setupLifeCycleListener() {
if (Faces.getApplication().getProjectStage() != ProjectStage.Production) {
LifecycleFactory factory = (LifecycleFactory) FactoryFinder.getFactory(FactoryFinder.LIFECYCLE_FACTORY);
Lifecycle lifecycle = factory.getLifecycle(LifecycleFactory.DEFAULT_LIFECYCLE);
lifecycle.addPhaseListener(new LifecyclePhaseListener());
}
}
#Override
public boolean isListenerForSource(Object source) {
return source instanceof Application;
}
}
You can do so by extending the Primefaces LifecyclePhaseListener and returning null from getPhaseId() method if not in development mode.
public class LifecycleForDevelopmentPhaseListener extends LifecyclePhaseListener {
#Override
public PhaseId getPhaseId() {
if (!FacesContext.getCurrentInstance().isProjectStage(ProjectStage.Development)) {
return null;
}
return super.getPhaseId();
}
}
This will bypass the phase listener
I'm using JSF 2.2 (Mojarra) and Faclets in a web application. I use a custom ExceptionHandler to handle exceptions. I leverage the JSF implicit navigation system and cause the server to navigate to the 'error.xhtml' page.
public class FrontEndExceptionHandler extends ExceptionHandlerWrapper {
private ExceptionHandler wrapped;
FrontEndExceptionHandler(ExceptionHandler exception) {
this.wrapped = exception;
}
#Override
public ExceptionHandler getWrapped() {
return wrapped;
}
#Override
public void handle() throws FacesException {
final Iterator<ExceptionQueuedEvent> iter = getUnhandledExceptionQueuedEvents().iterator();
while (iter.hasNext()) {
ExceptionQueuedEvent event = iter.next();
ExceptionQueuedEventContext context = (ExceptionQueuedEventContext) event.getSource();
// get the exception from context
Throwable t = context.getException();
final FacesContext fc = FacesContext.getCurrentInstance();
final Flash flash = fc.getExternalContext().getFlash();
final NavigationHandler nav = fc.getApplication().getNavigationHandler();
try {
// redirect error page
flash.put("erorrDetails", t.getMessage());
nav.handleNavigation(fc, null, "/errors/error.xhtml");
fc.renderResponse();
} finally {
// remove it from queue
iter.remove();
}
}
// parent handle
getWrapped().handle();
}
}
This assumes that the exception to be handling is not happening during the Render Response phase. But the exception occurs within an Facelets page also during the Render Response phase.Therefore following code doesn't work correct:
nav.handleNavigation(fc, null, "/errors/error.xhtml");
Does anybody has a Idea how to convey the desired information? Is there a way to navigate to the error.xhtml without using NavigationHandler?
When I dispatch an ajax event from the Composite Component by using <cc:clientBehavior name="chartUpdated" event="change" targets="chartdata"/> I catch it in Facelet page by using <f:ajax event="chartUpdated" listener="#{bean.updateListener}">. And In backing bean I capture event of type AjaxBehaviorEvent.
public void updateListener(AjaxBehaviorEvent event){
...
}
I undertand that I can extend AjaxBehaviorEvent and pass within it object which has been changed. For example, Primefaces's Scheduler uses this approach:
<p:ajax event="eventMove" listener="#{scheduleView.onEventMove}" update="messages" />
And backing bean:
public void onEventMove(ScheduleEntryMoveEvent event) {
FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_INFO, "Event moved", "Day delta:" + event.getDayDelta() + ", Minute delta:" + event.getMinuteDelta());
addMessage(message);
}
Is it possible to achieve the same functionality by using Composite Component together with the #FacesComponent ?
Thank you in advance!
Nice to meet you, again :)
continuing from your previous question:
Override queueEvent() to filter interesting events (changes from specific components) and postpone their enqueue to validation phase to be able to fetch converted & validated values:
#FacesComponent("rangeComponent")
public class RangeComponent extends UIInput implements NamingContainer
{
private final List<AjaxBehaviorEvent> customEvents = new ArrayList<>();
...
#Override
public void queueEvent(FacesEvent event)
{
FacesContext context = getFacesContext();
if(event instanceof AjaxBehaviorEvent)
{
Map<String, String> params = context.getExternalContext().getRequestParameterMap();
String eventName = params.get("javax.faces.behavior.event");
Object eventSource = event.getSource();
if("change".equals(eventName) && (from.equals(eventSource) || to.equals(eventSource)))
{
customEvents.add((AjaxBehaviorEvent) event);
return;
}
}
super.queueEvent(event);
}
#Override
public void validate(FacesContext context)
{
super.validate(context);
if(from.isValid() && to.isValid())
{
for(AjaxBehaviorEvent event : customEvents)
{
SelectEvent selectEvent = new SelectEvent(this, event.getBehavior(), this.getValue());
if(event.getPhaseId().equals(PhaseId.APPLY_REQUEST_VALUES))
{
selectEvent.setPhaseId(PhaseId.PROCESS_VALIDATIONS);
}
else
{
selectEvent.setPhaseId(PhaseId.INVOKE_APPLICATION);
}
super.queueEvent(selectEvent);
}
}
}
...
}
then add the specific event listener to your managed bean:
#ManagedBean
#ViewScoped
public class RangeBean implements Serializable
{
private static final long serialVersionUID = 1L;
private String range = "01/01/2015-31/12/2015";
public void onSelect(SelectEvent event)
{
Messages.addGlobalInfo("[{0}] selected: [{1}]", event.getComponent().getId(), event.getObject());
}
public String getRange()
{
return range;
}
public void setRange(String range)
{
this.range = range;
}
}
I am migrating an old JSF application from WebSphere to JBoss: the old version uses an IBM implementation of JSF. My question concerns the following component:
<hx:scriptCollector id="aScriptCollector"
preRender="#{aBean.onPageLoadBegin}" postRender="#{aBean.onPageLoadEnd}">
To reproduce the preRender behavior in JSF 2 I use a binding for the form (s. here). My questions:
1) Do you know a trick for simulating postRender in JSF 2?
2) Do you think is the trick I am using for preRender "clean"?
Thanks a lot for your help!
Bye
Closest what you can get to achieve exactly the same hooks is
<f:view beforePhase="#{bean.beforePhase}" afterPhase="#{bean.afterPhase}">
with
public void beforePhase(PhaseEvent event) {
if (event.getPhaseId == PhaseId. RENDER_RESPONSE) {
// ...
}
}
public void afterPhase(PhaseEvent event) {
if (event.getPhaseId == PhaseId. RENDER_RESPONSE) {
// ...
}
}
The preRender can be achieved in an easier manner, put this anywhere in your view:
<f:event type="preRenderView" listener="#{bean.preRenderView}" />
with
public void preRenderView(ComponentSystemEvent event) {
// ...
}
(the argument is optional, you can omit it if never used)
There's no such thing as postRenderView, but you can easily create custom events. E.g.
#NamedEvent(shortName="postRenderView")
public class PostRenderViewEvent extends ComponentSystemEvent {
public PostRenderViewEvent(UIComponent component) {
super(component);
}
}
and
public class PostRenderViewListener implements PhaseListener {
#Override
public PhaseId getPhaseId() {
return PhaseId.RENDER_RESPONSE;
}
#Override
public void beforePhase(PhaseEvent event) {
// NOOP.
}
#Override
public void afterPhase(PhaseEvent event) {
FacesContext context = FacesContext.getCurrentInstance();
context.getApplication().publishEvent(context, PostRenderViewEvent.class, context.getViewRoot());
}
}
which you register in faces-config.xml as
<lifecycle>
<phase-listener>com.example.PostRenderViewListener</phase-listener>
</lifecycle>
then you can finally use
<f:event type="postRenderView" listener="#{bean.postRenderView}" />
with
public void postRenderView(ComponentSystemEvent event) {
// ...
}
I wrote myself a custom NavigationHandler very similar to following one but just using a stack to save the history:
http://jsfatwork.irian.at/book_de/custom_component.html#!idx:/custom_component.html:fig:backnavigationhandler-code
public class HistoryNavigationHandler extends NavigationHandler
{
private final NavigationHandler navigationHandler;
private final Stack<String> outcomes;
public HistoryNavigationHandler(final NavigationHandler navigationHandler)
{
this.navigationHandler = navigationHandler;
this.outcomes = new Stack<String>();
}
#Override
public void handleNavigation(final FacesContext context, final String fromAction, final String outcome)
{
if (outcome != null)
{
if (outcome.equals("back"))
{
final String lastViewId = this.outcomes.pop();
final ViewHandler viewHandler = context.getApplication().getViewHandler();
final UIViewRoot viewRoot = viewHandler.createView(context, lastViewId);
context.setViewRoot(viewRoot);
context.renderResponse();
return;
}
else
{
this.outcomes.push(context.getViewRoot().getViewId());
}
}
this.navigationHandler.handleNavigation(context, fromAction, outcome);
}
}
Registering this one in the faces-config.xml:
<navigation-handler>
package.HistoryNavigationHandler
</navigation-handler>
Results in following log warning and a message where a previously working link was present:
Warning: jsf.outcome.target.invalid.navigationhandler.type
Something like: this link is disabled because a navigation case could not be matched
What is the problem?
Since JSF 2, the NavigationHandler has been replaced by ConfigurableNavigationHandler. All JSF 2 specific tags/components like <h:link> and so on are relying on it. The NavigationHandler is kept for backwards compatibility.
Here's a kickoff example how to properly extend ConfigurableNavigationHandler:
public class HistoryNavigationHandler extends ConfigurableNavigationHandler {
private NavigationHandler wrapped;
public HistoryNavigationHandler(NavigationHandler wrapped) {
this.wrapped = wrapped;
}
#Override
public void handleNavigation(FacesContext context, String from, String outcome) {
// TODO: Do your job here.
wrapped.handleNavigation(context, from, outcome);
}
#Override
public NavigationCase getNavigationCase(FacesContext context, String fromAction, String outcome) {
return (wrapped instanceof ConfigurableNavigationHandler)
? ((ConfigurableNavigationHandler) wrapped).getNavigationCase(context, fromAction, outcome)
: null;
}
#Override
public Map<String, Set<NavigationCase>> getNavigationCases() {
return (wrapped instanceof ConfigurableNavigationHandler)
? ((ConfigurableNavigationHandler) wrapped).getNavigationCases()
: null;
}
}