Today we have a question from Bruce Grant, a regular anwsers visitor and active community member. Thanks for that. The question is: How to add a ‘Loading, Please Wait… on the preview panel. This has been asked many times. It is true that while you load a sizable pdf or office file, you won’t get any feedback on the screen telling you the server is working hard to convert it to HTML. This is really frustrating. So I’m going to add a small waiter gif when the preview page is loading, and remove it when the page is done loading.

Let’s start with a simple case, the preview without annotation. There is no GWT for now. It’s really just an iFrame showing the HTML preview. I’m just going to execute some JavaScript code when the iFrame is done loading.

<div xmlns="" xmlns:h="" xmlns:c="" xmlns:nxh="" xmlns:nxl="" xmlns:nxu="">

 <h:form id="document_view"><div id="waiter">![](#{contextPath}/img/big_loading.gif)</div>

<iframe onload="var waiter = document.getElementById('waiter'); waiter.parentNode.removeChild(waiter);" src="#{previewActions.previewURL}" frameborder="1" height="600px" width="100%" scrolling="auto" id="preview-frame"></iframe>

<script type="text/javascript">jQuery(document).ready(function() { var fancyBox = jQuery('#fancybox-content', window.parent.document); if (fancyBox.length > 0 &amp;&amp; fancyBox.height() > 0) { // we know we are in a fancybox jQuery('#preview-frame').height(fancyBox.height() - 20); } });</script></h:form> </div>

And this is all you have to do. But this is the simple case. If you are using the Document Management module, your preview can be annotated using GWT. And the iFrame I’ve just modified is also handled by GWT, which makes it more difficult to modify. Actually I tried, but could not manage to get the appropriate event I wanted. I need to wait until the frame is fully loaded. So what we’re going to do is add the JavaScript at the end of every HTML file generated for the preview. This way we are sure that when it’s executed, the DOM is loaded.

The good news is that we have an extension point for that. It’s called BlobPostProcessor and is already used by the annotation framework to inject the JavaScript annotation module. To contribute to this you only need a Class implementing the _interface: BlobPostProcessor_


Of course the JavaScript will have to be adapted because I will then be inside an iFrame.

javascript <script type="text/javascript">var waiter = parent.document.getElementById('waiter'); waiter.parentNode.removeChild(waiter);</script>

My processor will retrieve the HTML file as a String and will match it to the following regex: (.)()(.) This way I can add my code between the first and the second group, in this case right before the tag. If for some reason, I cannot find a closed body tag, I’ll look for the starting one. This happens, for instance, while previewing small, simple images that don’t require tilling.

`java package org.nuxeo.ecm.platform.annotation.preview;

import; import; import; import java.util.regex.Matcher; import java.util.regex.Pattern;

import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.nuxeo.ecm.core.api.Blob; import org.nuxeo.ecm.core.api.impl.blob.ByteArrayBlob; import org.nuxeo.ecm.platform.preview.adapter.BlobPostProcessor;

/* @author Laurent Doguin / public class WaiterBlobPostProcessor implements BlobPostProcessor {

private static final Log log = LogFactory.getLog(WaiterBlobPostProcessor.class);

protected static final int BUFFER_SIZE = 4096 * 16;

protected static final String REMOVE_WAITER_JS = ““;

protected Pattern bodyPattern = Pattern.compile(“(.)()(.)”, Pattern.CASE_INSENSITIVE | Pattern.DOTALL);

protected Pattern endBodyPattern = Pattern.compile(“(.)()(.)”, Pattern.CASE_INSENSITIVE | Pattern.DOTALL);

protected Pattern charsetPattern = Pattern.compile( “(.) charset=(.?)”(.*)”, Pattern.CASE_INSENSITIVE | Pattern.DOTALL);

@Override public Blob process(Blob blob) { String mimetype = blob.getMimeType(); if (mimetype == null || !mimetype.startsWith(“text/“)) { // blob does not carry HTML payload hence there is no need to try to // inject HTML metadata return blob; } try { String encoding = null; if (blob.getEncoding() == null) { Matcher m = charsetPattern.matcher(blob.getString()); if (m.matches()) { encoding =; } } else { encoding = blob.getEncoding(); }

String blobAsString = getBlobAsString(blob, encoding); String processedBlob = addRemoveWaiterScript(blobAsString);

byte[] bytes = encoding == null ? processedBlob.getBytes() : processedBlob.getBytes(encoding); blob = new ByteArrayBlob(bytes, blob.getMimeType(), encoding); } catch (IOException e) { log.debug(“Unable to process Blob”, e); } return blob; }

protected String getBlobAsString(Blob blob, String encoding) throws IOException { if (encoding == null) { return blob.getString(); } Reader reader = new InputStreamReader(blob.getStream(), encoding); return readString(reader); }

protected String addRemoveWaiterScript(String blob) { StringBuilder sb = new StringBuilder(); Matcher m = endBodyPattern.matcher(blob); if (m.matches()) { sb.append(; sb.append(REMOVE_WAITER_JS); sb.append(; sb.append(; } else { m = bodyPattern.matcher(blob); if (m.matches()) { sb.append(; sb.append(; sb.append(REMOVE_WAITER_JS); sb.append(; } else { log.debug(“Unable to inject Annotation module javascript”); sb.append(blob); } } return sb.toString(); }

public static String readString(Reader reader) throws IOException { StringBuilder sb = new StringBuilder(BUFFER_SIZE); try { char[] buffer = new char[BUFFER_SIZE]; int read; while ((read =, 0, BUFFER_SIZE)) != -1) { sb.append(buffer, 0, read); } } finally { if (reader != null) { reader.close(); } } return sb.toString(); }

} `

Don’t forget to add the waiter div in the preview.xhtml file used by the annotation module:

<div id="waiter">![](#{contextPath}/img/big_loading.gif)</div>

And that’s it - now we have a waiter for the preview tab :D

Note that this still won’t work for tiled pictures since they are handled by GWT. But I’m not sure this is mandatory here, as we can see images being loaded one after the other. If you still want to add some user feedback, you’re in for some GWT coding.

That’s it for today. See ya’ Monday.