Skip to content

Instantly share code, notes, and snippets.

@edm00se
Last active September 24, 2015 14:12
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save edm00se/684539960bc23e7b447b to your computer and use it in GitHub Desktop.
Save edm00se/684539960bc23e7b447b to your computer and use it in GitHub Desktop.
Custom runtime error XPage with Google Code Prettify.

Custom XPages Error Page

Based on the XSnippet by Tony McGuckin, this version includes some place holders for custom theming (e.g.- logo, styling) and most importantly, code syntax highlighting!

For more, read my blog post on using this custom error XPage.

This is the result of some hair brained ideas of mine and the help of both Marky Roden and Sven Hasselbach via a rather messy Stack Overflow Question. Kudos to both of them for helping me get the perspective and solution I was looking or.

Google Code Prettify

The Google Code Prettify script is used (you could host it from non-CDN source) to generate the appropriate code highlighting with the error and stack trace code blocks being wrapped in a <pre> tag with the prettyprint class. When the JS script is invoked, it runs some JS to determine keywords, transforms some HTML accordingly, and then loads CSS to style those keywords.

Methodology

In an NSF which implements a custom error XPage, as mine do (mostly to take advantage of the OpenLog Logger for XPages OSGi plugin), when an XPage encounters a runtime error on a full refresh it loads the error page. This is different from a partial refresh, which loads the error page content via a dojo xhr, which is subject to the rules of innerHTML and nonevaluating JS script blocks. In other words, a <script> tag, while present and working for a full refresh, doesn't trigger by being loaded as content from an AJAX load.

Solution

I pointed out in one of my updates to my question that the images still loaded fine, which they do (as does a <style> tag), making this specific to the <script> blocks. As Sven's (third!) answer demonstrated, we can achieve JS execution within the onload attribute of an <img> tag. This simplifies things for me considerably and yields the solution now in my Error page.

1. Normal Loading with Full Refresh

When the page loads via a full refresh, it does its normal behavior via a <script> tag. Nothing special here.

2. AJAX Loading via 'onload' Attribute

When the partial refresh retrieves the custom error XPage, it computes an xp:text's rendered property and loads, with a src of a 1px x 1px image (base-64 encoded, guarantees that an image loads with something) and fires an onload event of creating a <script> tag and appending it to the <head> of the DOM.

Beauty is in the Eye of the Beholder

This requires no external dependencies or XHR hijacks (which I thought I would need for all pages, to be added in a JS file via a theme). To me, the simplicity here is sheer elegance.

<?xml version="1.0" encoding="UTF-8"?>
<xp:view
xmlns:xp="http://www.ibm.com/xsp/core"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.ibm.com/xsp/core xsdxp://localhost/xsp~core.xsd"
pageTitle="${javascript:database.getTitle() + ' | Error'}">
<style
type="text/css"><![CDATA[
body {
background-color: lightblue;
}
form {
width: 1000px !important;
margin: 0 auto !important;
background-color: white !important;
margin-top: 2rem !important;
padding: 0.5rem !important;
height: auto;
}
.xspTextLabel {
font-weight: bold !important;
}
pre {
overflow-x: auto;
}
]]></style>
<img
class="logo-simple"
src="//placehold.it/124x32" />
<xp:panel>
<xp:br />
<xp:br />
<xp:label
style="font-weight:bold;font-size:12pt"
value="An Unexpected Error Has Occurred:">
</xp:label>
<xp:br />
<xp:br />
<xp:label
value="Error:"></xp:label>
<xp:br />
<xp:text
escape="false">
<xp:this.value><![CDATA[#{javascript:var output = (requestScope.error.toString() || null)+"<br /><br />";
if(requestScope.error instanceof com.ibm.xsp.exception.XSPExceptionInfo){
var codeSnippet = requestScope.error.getErrorText();
var control = requestScope.error.getErrorComponentId();
var cause = requestScope.error.getCause();
output += "In the control : " + control + "<br /><br />";
if(cause instanceof com.ibm.jscript.InterpretException){
var errorLine = cause.getErrorLine();
var errorColumn = cause.getErrorCol();
output += "At line " + errorLine;
output += ", column " + errorColumn + " of:<br />";
}else{
output += "In the script:<br />";
}
if( @Contains(codeSnippet,"#{javascript:") ){
var snipAr = codeSnippet.split("#{javascript:");
var tmpSnip = snipAr[1];
var nwSnip = tmpSnip.substring(0, tmpSnip.length - 1);
output += "#{javascript:<br /><pre class=\"prettyprint\">"+nwSnip+"</pre>}"
}else{
output += "<pre class=\"prettyprint\">"+codeSnippet+"</pre>";
}
}
return output;}]]></xp:this.value>
</xp:text>
<xp:br />
<xp:br />
<xp:label
value="Stack Trace:" />
<xp:br />
<xp:text
escape="false"
style="font-size:10pt">
<xp:this.value><![CDATA[#{javascript:if( !!requestScope.error ){
var stackTrace = "";
var trace = (requestScope.error.getStackTrace() || null);
if(trace != null){
for(var i = 0; i < trace.length; i++){
stackTrace += trace[i] + "<br/>";
}
return "<pre class=\"prettyprint\">"+stackTrace+"</pre>";
}else{
return "nothing";
}
}else{
return "";
}}]]></xp:this.value>
</xp:text>
</xp:panel>
<script
type="text/javascript"
src="//cdn.rawgit.com/google/code-prettify/master/loader/run_prettify.js?skin=desert"></script>
<xp:text
escape="true"
id="executeOnAjax"
tagName="img">
<xp:this.attrs>
<xp:attr
name="src"
value="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" />
<xp:attr
name="onload"
value="var h=document.getElementsByTagName('head')[0],s=document.createElement('script');h.src=''//cdn.rawgit.com/google/code-prettify/master/loader/run_prettify.js?skin=desert',h.appendChild(s);this.parentNode.removeChild(this);" />
</xp:this.attrs>
<xp:this.rendered>
<![CDATA[#{javascript:var ex = facesContext.getExternalContext();
var pMap = com.ibm.xsp.util.TypedUtil().getRequestParameterMap(ex);
var refreshId = pMap.get("$$ajaxid");
refreshId?true:false;}]]>
</xp:this.rendered>
</xp:text>
</xp:view>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment