{"id":2070,"date":"2015-06-21T11:27:22","date_gmt":"2015-06-21T09:27:22","guid":{"rendered":"http:\/\/hasselba.ch\/blog\/?p=2070"},"modified":"2015-06-21T11:27:22","modified_gmt":"2015-06-21T09:27:22","slug":"xpages-an-optimized-javascript-resource-renderer","status":"publish","type":"post","link":"https:\/\/hasselba.ch\/blog\/?p=2070","title":{"rendered":"XPages: An optimized JavaScript Resource Renderer"},"content":{"rendered":"<p>Ferry Kranenburg created <a href=\"http:\/\/openntf.org\/xsnippets.nsf\/snippet.xsp?id=hack-to-use-jquery-amd-widgets-and-dojo-together\" target=\"_blank\">a nice hack<\/a> to solve the AMD loader problem with XPages and Dojo, and because of the missing ability to add a resource to the bottom of an XPage by a property, I have created a new JavaScriptRenderer which allows to control where a CSJS script will be rendered.<\/p>\n<p>The renderer has multiple options:<\/p>\n<ul>\n<li><strong>NORMAL<\/strong> &#8211; handles the CSJS resource as always<\/li>\n<li><strong>ASYNC<\/strong> &#8211; loads the script in an asynchronous way (with an own script tag)<\/li>\n<li><strong>NOAMD<\/strong> &#8211; adds the no amd scripts around the resource<\/li>\n<li><strong>NORMAL_BOTTOM<\/strong> &#8211; adds the script at the bottom of the &lt;body&gt; tag<\/li>\n<li><strong>ASYNC_BOTTOM<\/strong> &#8211; async, but at the end of the generated HTML page<\/li>\n<li><strong>NOAMD_BOTTOM<\/strong> &#8211; at the end, with the surrounding no amd scripts<\/li>\n<\/ul>\n<p>To use the normal mode, you don&#8217;t have to change you resource definition. If you want to use the other modes, you have to change the content type of the resource with one of the entries in the list above. This for example would add a script block to the end of the page, including the non amd script blocks around it:<\/p>\n<pre><code>&lt;?xml version=\"1.0\" encoding=\"UTF-8\"?&gt;\r\n&lt;xp:view xmlns:xp=\"http:\/\/www.ibm.com\/xsp\/core\"&gt;\r\n\u00a0\u00a0 \u00a0&lt;xp:this.resources&gt;\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0&lt;xp:script clientSide=\"true\" type=\"NOAMD_BOTTOM\"&gt;\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0&lt;xp:this.contents&gt;&lt;![CDATA[alert(\"Hello World!\");]]&gt;&lt;\/xp:this.contents&gt;\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0&lt;\/xp:script&gt;\r\n\u00a0\u00a0 \u00a0&lt;\/xp:this.resources&gt;\r\n&lt;\/xp:view&gt;\r\n<\/code><\/pre>\n<p><a href=\"https:\/\/hasselba.ch\/blog\/wp-content\/uploads\/2015\/06\/2015-06-21-11_04_09.png\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-2071 size-large\" src=\"https:\/\/hasselba.ch\/blog\/wp-content\/uploads\/2015\/06\/2015-06-21-11_04_09-1024x263.png\" alt=\"2015-06-21 11_04_09\" width=\"640\" height=\"164\" srcset=\"https:\/\/hasselba.ch\/blog\/wp-content\/uploads\/2015\/06\/2015-06-21-11_04_09-1024x263.png 1024w, https:\/\/hasselba.ch\/blog\/wp-content\/uploads\/2015\/06\/2015-06-21-11_04_09-300x77.png 300w, https:\/\/hasselba.ch\/blog\/wp-content\/uploads\/2015\/06\/2015-06-21-11_04_09.png 1319w\" sizes=\"auto, (max-width: 640px) 100vw, 640px\" \/><\/a><\/p>\n<p>Here is the code for the resource renderer:<\/p>\n<pre><code>package ch.hasselba.xpages;\r\n\r\nimport java.io.IOException;\r\nimport java.util.Iterator;\r\nimport java.util.Map;\r\n\r\nimport javax.faces.component.UIComponent;\r\nimport javax.faces.context.FacesContext;\r\nimport javax.faces.context.ResponseWriter;\r\n\r\nimport com.ibm.xsp.component.UIViewRootEx;\r\nimport com.ibm.xsp.renderkit.html_basic.ScriptResourceRenderer;\r\nimport com.ibm.xsp.resource.Resource;\r\nimport com.ibm.xsp.resource.ScriptResource;\r\nimport com.ibm.xsp.util.JSUtil;\r\n\r\npublic class OptimizedScriptResourceRenderer extends ScriptResourceRenderer {\r\n\u00a0\u00a0 \u00a0private static final String TYPE = \"type\";\r\n\u00a0\u00a0 \u00a0private static final String SCRIPT = \"script\";\r\n\u00a0\u00a0 \u00a0private static final String CSJSTYPE = \"text\/javascript\";\r\n\u00a0\u00a0 \u00a0private boolean isBottom = false;\r\n\u00a0\u00a0 \u00a0\r\n\u00a0\u00a0 \u00a0private static enum Mode {\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0NORMAL, ASYNC, NOAMD, ASYNC_BOTTOM, NOAMD_BOTTOM, NORMAL_BOTTOM\r\n\u00a0\u00a0 \u00a0}\r\n\r\n\u00a0\u00a0 \u00a0public void encodeResourceAtBottom(FacesContext fc,\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0UIComponent uiComponent, Resource resource) throws IOException {\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0isBottom = true;\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0encodeResource(fc, uiComponent, resource);\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0isBottom = false;\r\n\u00a0\u00a0 \u00a0}\r\n\r\n\u00a0\u00a0 \u00a0@Override\r\n\u00a0\u00a0 \u00a0public void encodeResource(FacesContext fc, UIComponent uiComponent,\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0Resource resource) throws IOException {\r\n\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0ScriptResource scriptResource = (ScriptResource) resource;\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0ResponseWriter rw = fc.getResponseWriter();\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0String type = scriptResource.getType();\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0String charset = scriptResource.getCharset();\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0String src = scriptResource.getSrc();\r\n\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0Mode mode = Mode.NORMAL;\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0try{\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0mode = Mode.valueOf( type );\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0}catch(Exception e){};\r\n\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0if (mode == Mode.NORMAL || mode == Mode.NORMAL_BOTTOM ) {\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0normalBottomJSRenderer( fc, uiComponent, scriptResource, (mode == Mode.NORMAL_BOTTOM), type );\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0} else {\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0if (mode == Mode.ASYNC || mode == Mode.ASYNC_BOTTOM) {\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0asyncJSRenderer(fc, uiComponent, scriptResource, (mode == Mode.ASYNC_BOTTOM), rw, type,\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0charset, src );\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0}else if (mode == Mode.NOAMD || mode == Mode.NOAMD_BOTTOM ) {\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0noAMDJSRenderer(fc, uiComponent, scriptResource, (mode == Mode.NOAMD_BOTTOM) , rw, \r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0type, charset, src);\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0}\r\n\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0}\r\n\r\n\u00a0\u00a0 \u00a0}\r\n\r\n\u00a0\u00a0 \u00a0private void normalBottomJSRenderer(FacesContext fc,UIComponent uiComponent,\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0ScriptResource scriptResource, final boolean addToBottom, final String type ) throws IOException {\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0if( addToBottom &amp;&amp; !isBottom )\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0return;\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0scriptResource.setType(null);\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0super.encodeResource(fc, uiComponent, scriptResource);\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0scriptResource.setType(type);\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\r\n\u00a0\u00a0 \u00a0}\r\n\u00a0\u00a0 \u00a0private void asyncJSRenderer(FacesContext fc,\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0UIComponent uiComponent, ScriptResource scriptResource, \r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0 final boolean addToBottom, ResponseWriter rw, final String type, final String charset,\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0final String src) throws IOException {\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0if( addToBottom &amp;&amp; !isBottom )\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0return;\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0Map&lt;String, String&gt; attrs = null;\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0String key = null;\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0String value = null;\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0String id = \"\";\r\n\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0if (scriptResource.getContents() == null) {\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0attrs = scriptResource.getAttributes();\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0if (!attrs.isEmpty()) {\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0StringBuilder strBuilder = new StringBuilder(124);\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0for (Iterator&lt;String&gt; it = attrs.keySet().iterator(); it\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0.hasNext();) {\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0key = it.next();\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0value = attrs.get(key);\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0strBuilder.append(key).append('(').append(value)\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0.append(')');\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0}\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0id = strBuilder.toString();\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0}\r\n\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\/\/ check if already added\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0UIViewRootEx view = (UIViewRootEx) fc.getViewRoot();\r\n\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0String resId = \"resource_\" + ScriptResource.class.getName() + src\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0+ '|' + type + '|' + charset + id;\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0if (view.hasEncodeProperty(resId)) {\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0return;\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0}\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0view.putEncodeProperty(resId, Boolean.TRUE);\r\n\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0}\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0if (!scriptResource.isClientSide()) {\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0return;\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0}\r\n\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0rw.startElement(SCRIPT, uiComponent);\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0JSUtil.writeln(rw);\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0rw.write(\"var s = document.createElement('\" + SCRIPT + \"');\");\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0JSUtil.writeln(rw);\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0rw.write(\"s.src = '\" + src + \"';\");\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0JSUtil.writeln(rw);\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0rw.write(\"s.async = true;\");\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0JSUtil.writeln(rw);\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0rw.write(\"document.getElementsByTagName('head')[0].appendChild(s);\");\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0JSUtil.writeln(rw);\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0rw.endElement(SCRIPT);\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0JSUtil.writeln(rw);\r\n\u00a0\u00a0 \u00a0}\r\n\r\n\u00a0\u00a0 \u00a0\r\n\u00a0\u00a0 \u00a0private void noAMDJSRenderer(FacesContext fc,\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0 UIComponent uiComponent,ScriptResource scriptResource,\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0final boolean addToBottom, ResponseWriter rw, final String type, final String charset,\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0final String src ) throws IOException {\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0if( addToBottom &amp;&amp; !isBottom )\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0return;\r\n\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\/\/ write the \"disable AMD\" script\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0rw.startElement(SCRIPT, uiComponent);\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0rw.writeAttribute(TYPE, CSJSTYPE, TYPE);\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0rw.writeText(\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\"'function'==typeof define&amp;&amp;define.amd&amp;&amp;'dojotoolkit.org'==define.amd.vendor&amp;&amp;(define._amd=define.amd,delete define.amd);\",\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0null);\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0rw.endElement(SCRIPT);\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0JSUtil.writeln(rw);\r\n\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\/\/ write the normal CSJS\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0scriptResource.setType(null);\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0super.encodeResource(fc, uiComponent, scriptResource);\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0scriptResource.setType(type);\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\/\/ write the \"reenable AMD\" script\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0rw.startElement(SCRIPT, uiComponent);\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0rw.writeAttribute(TYPE, CSJSTYPE, TYPE);\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0rw\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0.writeText(\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\"'function'==typeof define&amp;&amp;define._amd&amp;&amp;(define.amd=define._amd,delete define._amd);\",\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0null);\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0rw.endElement(SCRIPT);\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0JSUtil.writeln(rw);\r\n\r\n\u00a0\u00a0 \u00a0}\r\n}<\/code><\/pre>\n<p>The ViewRenderer must also be modified, otherwise it is not possible to add the resources at the bottom of the &lt;body&gt; tag:<\/p>\n<pre><code>package ch.hasselba.xpages;\r\n\r\nimport java.io.IOException;\r\nimport java.util.List;\r\n\r\nimport javax.faces.context.FacesContext;\r\nimport javax.faces.context.ResponseWriter;\r\nimport javax.faces.render.Renderer;\r\n\r\nimport com.ibm.xsp.component.UIViewRootEx;\r\nimport com.ibm.xsp.render.ResourceRenderer;\r\nimport com.ibm.xsp.renderkit.html_basic.ViewRootRendererEx2;\r\nimport com.ibm.xsp.resource.Resource;\r\nimport com.ibm.xsp.resource.ScriptResource;\r\nimport com.ibm.xsp.util.FacesUtil;\r\n\r\npublic class ViewRootRendererEx3 extends ViewRootRendererEx2 {\r\n\r\n\u00a0\u00a0 \u00a0protected void encodeHtmlEnd(UIViewRootEx uiRoot, ResponseWriter rw)\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0throws IOException {\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0FacesContext fc = FacesContext.getCurrentInstance();\r\n\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0List&lt;Resource&gt; resources = uiRoot.getResources();\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0for (Resource r : resources) {\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0if (r instanceof ScriptResource) {\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0ScriptResource scriptRes = (ScriptResource) r;\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0if (scriptRes.isRendered()) {\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0Renderer renderer = FacesUtil.getRenderer(fc, scriptRes.getFamily(), scriptRes.getRendererType());\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0ResourceRenderer resRenderer = (ResourceRenderer) FacesUtil.getRendererAs(renderer, ResourceRenderer.class);\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0if( resRenderer instanceof OptimizedScriptResourceRenderer ){\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0((OptimizedScriptResourceRenderer) resRenderer).encodeResourceAtBottom(fc, uiRoot, r);\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0}\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0}\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0}\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0}\r\n\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0rw.endElement(\"body\");\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0writeln(rw);\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0rw.endElement(\"html\");\r\n\u00a0\u00a0 \u00a0}\r\n\r\n}<\/code><\/pre>\n<p>To activate the new renderes, you have to add them to the <em>faces-config.xml<\/em>:<\/p>\n<pre><code>&lt;?xml version=\"1.0\" encoding=\"UTF-8\"?&gt;\r\n&lt;faces-config&gt;\r\n\u00a0 &lt;render-kit&gt;\r\n\u00a0\u00a0\u00a0 &lt;renderer&gt;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 &lt;component-family&gt;com.ibm.xsp.resource.Resource&lt;\/component-family&gt;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 &lt;renderer-type&gt;com.ibm.xsp.resource.Script&lt;\/renderer-type&gt;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 &lt;renderer-class&gt;ch.hasselba.xpages.OptimizedScriptResourceRenderer&lt;\/renderer-class&gt;\r\n\u00a0\u00a0\u00a0 &lt;\/renderer&gt;\r\n\u00a0\u00a0\u00a0 &lt;renderer&gt;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 &lt;component-family&gt;javax.faces.ViewRoot&lt;\/component-family&gt;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 &lt;renderer-type&gt;com.ibm.xsp.ViewRootEx&lt;\/renderer-type&gt;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 &lt;renderer-class&gt;ch.hasselba.xpages.ViewRootRendererEx3&lt;\/renderer-class&gt;\r\n\u00a0\u00a0\u00a0 &lt;\/renderer&gt;\r\n\u00a0 &lt;\/render-kit&gt;\r\n&lt;\/faces-config&gt;<\/code><\/pre>\n<p>Needless to say that this works in Themes too.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Ferry Kranenburg created a nice hack to solve the AMD loader problem with XPages and Dojo, and because of the missing ability to add a resource to the bottom of an XPage by a property, I have created a new &hellip; <a href=\"https:\/\/hasselba.ch\/blog\/?p=2070\">Weiterlesen <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[89,26,74],"tags":[47,31,4,86,3],"class_list":["post-2070","post","type-post","status-publish","format-standard","hentry","category-java","category-jsf","category-xpages","tag-9-0","tag-java","tag-js","tag-jsf","tag-xpages"],"_links":{"self":[{"href":"https:\/\/hasselba.ch\/blog\/index.php?rest_route=\/wp\/v2\/posts\/2070","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/hasselba.ch\/blog\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/hasselba.ch\/blog\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/hasselba.ch\/blog\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/hasselba.ch\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=2070"}],"version-history":[{"count":4,"href":"https:\/\/hasselba.ch\/blog\/index.php?rest_route=\/wp\/v2\/posts\/2070\/revisions"}],"predecessor-version":[{"id":2075,"href":"https:\/\/hasselba.ch\/blog\/index.php?rest_route=\/wp\/v2\/posts\/2070\/revisions\/2075"}],"wp:attachment":[{"href":"https:\/\/hasselba.ch\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=2070"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/hasselba.ch\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=2070"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/hasselba.ch\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=2070"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}