A bunch of hacks for smartly managing a bootstrap modal life cycle

I might call this the negative side of perfectionism: wanting everything to be as efficient as possible even if it doesn’t matter. Oh well, I’m stubborn sometimes.

I took it as a personal challenge to be able to open a bootstrap modal window and…

  • on close partially refresh the modal body in case an error occurred in order to show it
  • on save partially refresh a different block and close the modal in case of no raised error

All of the above without wasting more than a server trip.

The Issues

Having to deal with the current IBM XPages framework limitations the challenge presents three distinct issues:

  1. there’s no out-of-the-box way to let the client side know whether any ValidationException was raised
  2. same is true, say, if you want to add any FacesMessage error during an event handler action evaluation
  3. there’s no out-of-the-box way to choose what block id to refresh as a consequence of the evaluation of point 1 and 2.

The fact is that points 1 and 2 deal with recoverable error situations. They are not throwing haltering exceptions and therefore they don’t trigger an eventual event handler onError JavaScript method. So, errors can be raised but the framework can’t be bothered with, for it the ajax request has completed just fine and therefore only an eventual event handler onComplete JavaScript method is invoked.

The Workaround

But there is something to hack. Each ajax request comes with a dojo.xhr object: request is performed and response is returned, it’s the same JavaScript object that will be worked on by the framework. However there’s nothing you can do with the response data itself; it’s kind of “sealed”. You can’t tamper with it in order to add some sort of flag in case of error somewhere. But fortunately enough response headers are totally hackable. So, the first puzzle piece is to create a method that will write a custom response header to return with the ajax response in case of induced error.

I have a non-instantiable class (enum) with static methods I keep around for things like this. So the method looks like the following:

public enum Helper {
	;
	
	...
	
	public static void setErrorHeader(HttpServletResponse response, PhaseId phaseId) {
		response.setHeader("Application-Error", phaseId.toString());
	}
	
	...

The method is just a personal interpretation of what header name and value I want to see. Application-Error will be the header looked up by the JavaScript code to determine whether any soft-error occurred. PhaseId for when it occurred; the second parameter choice is actually either overkill or too generic. Anyway, you can tweak the method parameter to be whatever thing you want it to be.

However, in case of validation exceptions in order to set the header I can’t rely on an eventual handler action: the application doesn’t get to it, so I don’t have any chance to set it from there. In order to fix this shortcoming I am going to write a small PhaseListener class.

public class ValidationPhaseListener implements PhaseListener {

	private static final long serialVersionUID = 1L;

	@Override
	public PhaseId getPhaseId() {
		return PhaseId.PROCESS_VALIDATIONS;
	}

	@Override
	public void beforePhase(PhaseEvent phaseEvent) {

	}

	@Override
	public void afterPhase(PhaseEvent phaseEvent) {
		FacesContext facesContext = phaseEvent.getFacesContext();

		if (facesContext.getMessages().hasNext()) {
			Helper.setErrorHeader((HttpServletResponse) facesContext.getExternalContext().getResponse(), getPhaseId());
		}
	}

}

In the after phase I check for the presence of any FacesMessage. If any is found I set the error header.

Similarly, if an error is to be shown to the user as a consequence of the action method evaluation I will set the header from there. However, in the action method I’m going to do a little more than that. As I said before I want to refresh a different block in case the action performs successfully. All I need to know is which id to refresh; yes, I could hard code it in the method but I don’t like to surrender that to the method and lose flexibility. But first things first: I create an additional method – applySuccessRefreshId – to my static class to quickly change the refresh id on the action side of things.

public enum Helper {
	;
	
	...
	
	@SuppressWarnings("unchecked")
	public static Map<String, String> getParameterScope(FacesContext facesContext) {
		return (Map<String, String>) facesContext.getExternalContext().getRequestParameterMap();
	}
	
	public static void applySuccessRefreshId(FacesContext facesContext) {
		if (!AjaxUtil.isAjaxPartialRefresh(facesContext)) {
			throw new UnsupportedOperationException();
		}
		
		String refreshId = getParameterScope(facesContext).get("onSuccessId");
		
		if (refreshId != null) {
			((FacesContextEx) facesContext).setPartialRefreshId(refreshId);
		}
	}
	
	...

What happens here are 2 things: with the first I make sure the method can be called only during an ajax call otherwise an exception will be thrown and with the second I look for the optional arbitrarily named onSuccessId parameter that contains the block id to refresh in case of clear success.

Now, a hypothetical action should look like this:

public String myAction() {
		// Business logic
		
		FacesContext facesContext = FacesContext.getCurrentInstance();

		if (iLikeWhatISee) {
			Helper.applySuccessRefreshId(facesContext);
		} else {
			HttpServletResponse response = (HttpServletResponse) facesContext.getExternalContext().getResponse();

			Helper.setErrorHeader(response, PhaseId.INVOKE_APPLICATION);
		}

		return null;
	}

Now I need to stich up server side and client side. How do I do that? How do I even send the onSuccessId parameter previously mentioned? By taking advantage of the XSP.partialRefreshPost JavaScript method.

Again, a hypothetical modal save button should look like this:

<xp:button id="buttonModalSave" value="Save" styleClass="btn-primary">
    <xp:eventHandler
        id="myEventHandlerId"
        event="onclick"
        submit="false"
        action="#{ctrl.myAction}"
        script="XSP.partialRefreshPost('#{id:myModalBody}', {
          execId: '#{id:whateverIdIKnowItsRight}',
          params: {
            '$$xspsubmitid' : '#{id:myEventHandlerId}',
            'onSuccessId' : '#{id:whateverIdIKnowItsRight}'
          },
          onComplete : 'if (!isBadRequest(arguments[1].xhr)) { logicToCloseTheDialog() }',
          onError : 'console.log(arguments[0])'
        })"/>
</xp:button>

Points of attention:

  • The eventHandler is given an id: in this case myEventHandlerId. That very same id is sent with the XSP.partialRefreshPost $$xspsubmitid parameter. It’s important because the request will know it will have to invoke the bound action method.
  • '#{id:myModalBody}' refers to the block id that will be refreshed once the response is received
  • 'onSuccessId' : '#{id:whateverIdIKnowItsRight}' should contain the actual id you want to refresh in case the action has performed successfully
  • onComplete contains the JavaScript method that will be called at the end. The method must be defined as string. That’s because it will be evaluated and inject two arguments: [0] the new HTML content that will be placed in the block id, [1] the dojo.xhr object that took care of everything which I’m greatly interested in for further inspection.

The onComplete is where I will close the dialog. Granted, no soft-error must have occurred. The last missing piece is actually what the isBadRequest does:

function isBadRequest(xhr) {
		return xhr.getResponseHeader("Application-Error") !== null;
	}

So, if there’s no error header the code goes ahead with closing the modal, otherwise it will stay open.

I put together a small video to show it’s working and achieving the goal with a single server trip:

2 thoughts to “A bunch of hacks for smartly managing a bootstrap modal life cycle”

  1. It’s a very interesting way of handling errors. I would use this method if there’s a need for server-side error checking. However, for client-side error checks (like the one you show in the video: password matching), I would use client side error check without any trip to the server.

    1. Granted on the client side validation. Actually it’s kind of hypocritical to want to save server trips and then do form validation server side. What generally holds me back is that, although remotely possible, client side validation can be fooled and therefore server side validation must be enforced anyway. At that point I’m asking myself: why do double work? But I know it’s just a weak and lazy excuse.

Leave a Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.